Последние записи
- TChromium (CEF3), сохранение изображений
- Как в Delphi XE обнулить таймер?
- Изменить цвет шрифта TextBox на форме
- Ресайз PNG без потери прозрачности
- Вывод на печать графического файла
- Взаимодействие через командную строку
- Перенести программу из Delphi в Lazarus
- Определить текущую ОС
- Автоматическая смена языка (раскладки клавиатуры)
- Сравнение языков на массивах. Часть 2
Интенсив по Python: Работа с API и фреймворками 24-26 ИЮНЯ 2022. Знаете Python, но хотите расширить свои навыки?
Slurm подготовили для вас особенный продукт! Оставить заявку по ссылке - https://slurm.club/3MeqNEk
Online-курс Java с оплатой после трудоустройства. Каждый выпускник получает предложение о работе
И зарплату на 30% выше ожидаемой, подробнее на сайте академии, ссылка - ttps://clck.ru/fCrQw
6th
Сен
Дневник разработчика игр
Виталий Желтяков:
В данном дневнике я буду описывать процесс разработки своей BMMORPG (браузерной многопользовательской РПГ). Я планирую записывать: во-первых, ход разработки; во-вторых, описывать важные моменты, на которые стоит обратить внимание читателю. Цель данного произведения обмен опыта. (читать всё…)
4th
Сен
Создание игры. Особенности коллективной разработки флеш приложений
Столкнулся не столько с программной, сколько с идеологической трудностью и спешу обратиться к вам за помощью.
Суть состоит вот в чём: сейчас, как известно, очень популярны приложения для соц.сетей. На работе меня озадачили сделать промо-игру. Не буду вдаваться в подробности, но должен отметить, что идея игры, в некоторой степени, уникальна. (читать всё…)
9th
Май
Игра танчики на delphi
я хочу написать игру танчики! Мне не понятно как сделать чтобы из танчика вылетали пули! У меня есть изображение танчика помещенный в image , я хочу чтобы при нажатии на пробел из image вылетали маленькие шары (shape) до конца формы, и так каждый раз как я нажимаю пробелю (читать всё…)
27th
Авг
Искусство изменеия GTA
Здравствуйте, любители гейминга. В данной статье, я хочу показать, как делаются плагины для всем известной GTA. Начнем мы самого простого – это программирование плагинов на Delphi для Grand theft Auto ViceCity. А поняв принцип их работы, никакого труда не составит написать плагин и для других серий GTA…
Виталий Иванов
by VintProg vintprog@gmail.com
Пишем простой плагин для GTA – VC*
Итак, Для работы нам понадобится следующее:
- IDE среда Delphi [1]
- знание языка
- утилита ArtMoney** [2]
И немного теории, что же такое плагины. Плагины – это те же динамические подключаемые библиотеки DLL. Однако часто бывает, что им изменяют расширение.
Возможно, вы спросите: «…как же это работает все?», А работает оно следующим образом… При запуске <gta-sa.exe> загружаются комплектные библиотеки от разработчиков. В одной из этихDLL, в частности — vorbisFile.dll имеется функция загрузки библиотек *.asi, И пожелавшие остаться неизвестными, программисты написали <cleo.asi> и набили ее весьма и весьма полезными функциями, такими как: новые опкоды, загрузка плагинов *.cleo и.т.п. Когда загрузилась библиотека <cleo.asi>, ее код выполняет нужные функции в памяти игры.
* Комментарий автора.
Вы наверняка встречали Cleo на GTA-SA,и видели, что там существует такая библиотека cleo.asi, Так вот она и загружает из папки Cleo – скрипты и сами плагины .cleo.
Именно благодаря этому и появляются новые возможности в игре. А что касается GTA Vice City, то в ней тот же процесс, только библиотеки *.asi загружаются из библиотеки <Mss32.dll>. Отсюда понятно, что для того чтобы писать плагины – необходимо хорошо уметь работать с памятью игры и знать что за значения находятся в игровой памяти в определенном адресе.
** Комментарий редакции.
ArtMoney – программа, предназначенная для редактирования параметров в кмпьютерных играх, для получения бесконечных денег, жизней, патронов и т.п. Она умеет сканировать память или файлы игры для поиска каких-то определенных значений (деньги, ресурсы). Официальный сайт www.artmoney.ru
Приступим… Для начала запустим Borland Delphi, после чего кликаем на «File -> New -> other… » и перед нами откроется вот такое окно (см. рисунок 2):
Рис. 2. Выбор DLL Wizard
Далее выделяем DLL Wizard и жмем OK. Сразу возьмем и сохраним наш проект «File->Save Project As» и под именем ShowMessage, чтобы получилось как показано на рисунке 3:
Рис. 3. Заготовка плагина
Также можно удалить дерективу {$R *.res} , потому-что для данного плагина мы не будем использовать ресурсы. Из дополнительных модулей оставим лишь – Windows, а остальные удалим. Теперь напишем следующий код:
код:
uses
Windows;
var HWND : THandle;
begin
// Получить хендл окна GTA: Vice City
HWND := FindWindow(nil,’GTA: Vice City’);
if HWND <> 0 then // Проверка если окно GTA: Vice City
// существует, то тогда выполнить MessageBox
MessageBoxA(HWND,’Плагин загружен’,’Сообщение’,0)
end.
И скомпилируем его. Прокомментирую работу данного кода… Когда библиотека загружается, между Begin end начинает выполнятся код, Тут сразу появляется окошко с сообщением. Чтобы это заработало, переименуйте расширение *.DLL на *.ASI или воспользуйтесь директивой {$E.ASI}. После чего, скопируйте библиотеку в каталог GTA и запустите игру GTA-VC.exe, Далее мы увидим окошко, когда загрузится <mss32.dll>. Поздравляю, вы написали свой первый плагин для GTA***!
Пишем простой плагин-трейнер для GTA – VC
Настала пора сделать что-то полезное. Итак, сперва подумаем, что нам еще нужно? А нужно нам сделать плагин-трейнер. Для чего он предназначен? К примеру, мы можем сделать так, чтобы при нажатии определенной кнопки в игре – появлялось окно, в котором можно прибавить деньги игроку. Для этого, запускаем Gta-VC, сворачиваем ее и запускаем ArtMoney. В этой утилите выбираем «Искать -> Объект -> Процесс» и в выпадающем списке, где написано «Выбери процесс», выберем нашу GTA Vice City (см. рисунок 4). Теперь зайдем в GTA-VC и соберем небольшое количество денежных средств (к примеру, как я набрал $18). Судя по представлению «денег» в игре, видно, что они целого числа и можно набрать их в игре большое количество. Отсюда, мы уже знаем тип представления данных «денег».
Это 4-байтные целые числа, которые нам и нужно отыскать (см. рисунок 5).
Рис. 4. Выбор GTA-VC в ArtMoney
Обратите внимание! Вам необходимо повторить те же действия, как показано на рисунке, только у вас будет свое число. По завершению поиска у вас появится длиннющий список адресов (см. рисунок 6). Но не переживайте по этому поводу. Ведь адрес «денег» мы будем искать более легким методом, На то она и ArtMoney. Следующий шаг будет таков: снова заходим в GTA и либо тратим, либо добавляем деньги к игроку Tommy, запоминаем измененное количество «денег» и сворачиваем GTA. Вписываем новое значение (см. рисунок 7):
*** Комментарий автора.
Если что то не получается то пример расположен в каталоге “Examples\Plugin1”.
Рис. 5. Поиск значений по заданному типу данных
Рис. 6. Выборка адресов в ArtMoney
Рис. 7. Повторный поиск значений по заданному типу данных
Теперь нажимаем «отсеять». Как видите, длина списка адресов значительно уменьшится. И так продолжаем до тех пор, пока не останется один адрес. Если-же все равно остается насколько адресов, то меняем в них значения и проверяем изменилось-ли количество «денег» в GTA (см. рисунок 8). Если все нормально, то нормально. Однако, еще осталось сделать одну проверку на указатель. ArtMoney не закрываем, так все и оставляем. Вырубаем GTA-VC и заново запускаем GTA-VC. В ArtMoney, в выпадающем списке, где написано «Выбери процесс», заново выбираем GTA и повторим операции по изменению значений по адресу местонахождения «денег». Если все работает нормально, то записываем этот адрес в текстовой блокнот и вырубаем GTAVC и выключаем ArtMoney. Теперь он нам не понадобится.
Итак, первый шаг сделан. Мы нашли адрес «денег» и остается лишь написать плагин.
Распишем особенность работы плагина, то есть то – как он будет работать: так, при нажатии кнопки <M> добавляется 1000 долларов, а значит нам нужен обработчик нажатия кнопки. Воспользуемся таймером. Теперь, точно так же как и в первом примере, созадим новый проект DLL и назовем его «MoneyAdds». И напишем следующий код:
**** Комментарий автора.
Хочу напомнить, что не на всех версиях GTA-VC могут быть одинаковые адреса. Так что имейте это ввиду, при написании плагинов.
код:
{ GTA-VC 1.1 Плагин для добовления денег }
uses Windows;
type // определяем свой тип (указатель целых чисел)
P_Integer = ^Integer;
var
GTA_VC_Handle : THandle;
CurrentMoney : Integer;
keyUp : boolean;
const // тут твой найденный адрес «денег»
Address_Money = $0094ADD0;
{$E .asi}
//—- Эта процедура будет вызываться таймером —
procedure Timer_begin;
begin
// Нажатие и отпуск «M»
if not GetKeyState($4D) < 0 then keyUp := true;
if (GetKeyState($4D) < 0) and (keyUp = true) then
begin
// Читаем деньги из GTA-VC и присваиваем в CurrentMoney
CurrentMoney := P_Integer(Address_Money)^;
// Записываем 1000 + текущие деньги
P_Integer(Address_Money)^ := CurrentMoney + 1000;
keyUp := false;
end
end;
//————————————————————
begin
GTA_VC_Handle := FindWindow(nil,’GTA: Vice City’);
if GTA_VC_Handle <> 0 then
begin
SetTimer(GTA_VC_Handle,0,25,@Timer_begin);
end;
end.
Исходный код этого плагина находится в «Examples\Trainer1» [3]. Вот мы и реализовали плагин-трейнер добавления «денег» по нажатию клавиши «M», Только скажу – не выгодно на каждый плагин делать один таймер, Поэтому имейте ввиду, что таймер нагружает процессор. Для решений данной проблемы можно воспользоваться функциями DirectX для обработки нажатий клавиатуры.
Пишем свой собственный менеджер-загрузчик плагинов
Вы наверняка заметили одну вещь: когда создаешь новый плагин, его приходиться бросать рядом с GTA-VC.exe, А представьте себе, если таких плагинов будет больше десятка? Это не наш метод, поэтому мы напишем свой загрузчик плагинов из отдельно созданного каталога под наши плагины, скажем <bin>. И пускай там будет их хоть 1000!
Итак, запускаем среду Delphi и по аналогии создадим проект DLL-ки. Внутри напишем следующий код:
код:
{ Даная библиотека нужна для загрузки плагинов в GTA-VC }
{$E .ASI}
uses SysUtils, Windows;
var
SearchRec : TSearchRec;
filename : pAnsiChar;
const
dir_bin = ‘Bin\*.bin’;
dir_dll = ‘Bin\*.dll’;
//— Процедура отыскивает все плагины из папки Bin и
// подгружает их —
procedure Load_libs(FilesName : string);
begin
if FindFirst(FilesName, faAnyFile, SearchRec) = 0 then
repeat
filename := pAnsiChar(‘Bin\’ + SearchRec.name);
LoadLibrary(filename);
until FindNext(SearchRec) <> 0;
end;
//————————————————————
begin
Load_libs(dir_dll);
Load_libs(dir_bin);
end.
Исходный код этого менеджера-загрузчика находится в «Examples\Loader_VC» [3].
Заключение
Вот теперь готов загрузчик плагинов bin и dll, Теперь достаточно бросить его в корневую папку GTA, создать там каталог <BIN> со всеми нашими плагинами, запустить игру и полюбоваться результатом наших трудов.
Рассматриваемые в данной статье исходники плагина добавления «денег», плагина-трейнера и менеджера-загрузчика полностью приведены в виде ресурсов в теме «Журнал клуба программистов. Пятый выпуск» или непосредственно в архиве с журналом.
Продолжение смотрите в следующем выпуске нашего журнала…
Ресурсы
- Бесплатный TurboDelphi-Lite (over BDS-2006) http://www.andyaska.com/?act=download&mode=get&id=34
- Скачать ArtMoney http://www.artmoney.ru/r_download_se.htm
- Учебник. Искусство изменения GTA http://programmersup.3dn.ru/load/skachat_vse_na_gta/uchebniki_po_gta/10
- Модули и проекты, использованные в статье http://programmersclub.ru/pro/pro5.pdf
Статья из пятого выпуска журнала «ПРОграммист».
Скачать этот номер можно по ссылке.
Ознакомиться со всеми номерами журнала.
2nd
Июл
Игра Fortress 2. Создание лучшего бота – призовой фонд 5000 рублей!
Здравствуйте читатели нашего журнала. Сегодня мы хотим напомнить вам, что продолжается прием заявок на участие в конкурсе по созданию лучшего бота для игры в Fortress2 с денежным призом. Организатор конкурса – Форум программистов www.programmersforum.ru.
Игра Fortress 2. Создание лучшего бота – призовой фонд 5000 рублей!
Аблязов Руслан
by rpy3uH http://www.programmersforum.ru/member.php?u=11
* Комментарий автора
Для тех, кто не в курсе: бот представляет собой DLL с тремя экспортируемыми функциями. Документация по созданию бота находится в файле Fortress 2 Bot Specification
Скачать Fortress 2 build 2025 beta + Документация + исходник SimpleBot v1.0 http://programmersforum.ru/attachment.php?attachmentid=23688&d=1270876754
Скачать исходники SimpleBot v1.0 на С++ (CodeBlocks+MinGW) http://programmersforum.ru/attachment.php?attachmentid=23689&d=1270876964
Скачать документацию по созданию ботов http://pkonkurs.ru/wp-content/uploads/2010/06/Fortress-2-Bot-Specification.zip
Скачать исходник бота на С++ (CodeBlocks+MinGW) http://pkonkurs.ru/wp-content/uploads/2010/06/SimpleBotCpp.zip
Исходник бота на Delphi поставляется в комплекте с игрой.
Почему надо участвовать в этом конкурсе?
Во-первых, это интересно, вы можете поучаствовать в конкурсе, где не нужно загружать данные из текстового файла и сохранять их туда! Во-вторых, можно получить денежный приз, толстовку или футболку от клуба. В-третьих, вы получите опыт в создании ИИ для игры, и сможете сказать «Я разрабатывал бота для игры!»
Призы как денежные – 3000 рублей, так и сувениры от клуба на 2000 рублей. Конкурс расчитан на 2-4 месяца. Первая битва ботов состоится 15 июля 2010 года.
Ключевые понятия игры
Итак, вы хотите написать бота для этой игры, но не знаете как. Что пригодится для написания бота для игры:
1. Компилятор, который позволяет компилировать DLL файлы и разумеется знание языка этого компилятора
2. Знание правил игры
3. Знание, какие функции должны присутствовать в DLL, для того чтобы бот смог работать
С компилятором я думаю, проблем не будет он может быть любой, главное условие, чтобы он смог компилировать функции по соглашению stdcall. Сначала поясню как вообще происходит игра. играют двое, у каждого игрока есть база, есть три типа ресурсов, есть щит, есть проекты. Проекты бывают разные: атака вражеской базы, развитие своей базы, ремонт базы, шпионаж. Всего проектов 50. В начале игры игрок выбирает 15 проектов, которыми он будет играть. Потом игроки по очереди выбирают проекты, игрок может выбрать только тот проект, на который хватает ресурсов. Проигрывает то
т игрок, броня базы которого станет равной нулю.
Какова ее физика с точки зрения ботописателя? Читаем внимательно!
Мы выбираем двух ботов и нажимаем начать игру, ядро игры вызывает функции StartGame ботов участвующих в игре. Задача функции StartGame это выбор набора проектов, которыми будет играть бот, набор проектов надо сохранить в массиве, указатель на который будет передан функции StartGame. Проекты задаются числами (полный набор проектов указан в документации). После выбора проектов начинается игра. В ходе игры по очереди вызываются функции GetTurn каждого бота. В случае, если боту недоступен ни один проект, то функция GetTurn вызывается с нулевым указателем на массив доступных пр
оектов. В конце игры еще раз вызывается функция StartGame с нулевым указателем на массив проектов. Нулевой указатель на массив свидетельствует о том, что происходит уведомление бота о конце игры.
Количество ресурсов (энергия, металл, электроэлементы) зависит от количества объектов их добывающих (батареи, рудники, лаборатории). Количество батарей и рудников у врага можно уменьшить используя специальные шпионские-проекты. Внимание, нововведение в Fortress 2 : введено ограничение на количество лабораторий. По-умолчанию оно равно 5. Как только броня базы становится больше 50, к лимиту прибавляется одна лаборатория за каждые 5 единиц брони свыше 50. Например:
Base = 30, LabLimit = 5;
Base = 54, LabLimit = 5;
Base = 56, LabLimit = 6;
Base = 63, LabLimit = 7;
Base = 75, LabLimit = 8;
Base = 81, LabLimit = 9;
И т.д.
В случае использования проектов, которые увеличивают количество лабораторий и при этом уже достигнут лимит количества лабораторий, проект считается использованным (ресурсы расходуются), а количество лабораторий не изменяется.
Отличия от первой версии игры
1. Добавлены уведомления о пропуске хода и о конце игры и ее результатах
2. Бот может узнать имя противника (см. структуру TAdditionalGameInfo)
Возможности улучшения бота
Покажем, как можно изменить игру бота SimpleBot v1.1 в лучшую сторону, всего лишь добавив несколько строчек кода. Итак, приступим. Для разнообразия будем писать на C++. Что мы сделаем в первую очередь? Изменим набор проектов. Смотрим строку в коде, которая содержит список проектов [1, 2]:
const int Projects[MaxProjectsToPlayer] = {5, 6, 11, 13, 16, 18, 20, 28, 33, 34, 35, 44, 45, 46, 50};
Что тут можно изменить? В принципе можно тут изменить все, но заморачиваться мы не будем, просто удалим проект номер 50 и вставим (29) СуперАтака 2. En 16, Me 14, El 6 : Base-20, Shield-15. В итоге получаем такой список проектов:
const int Projects[MaxProjectsToPlayer] = {5, 6, 11, 13, 16, 18, 20, 28, 29, 33, 34, 35, 44, 45, 46};
Теперь смотрим функцию хода бота:
if (!aAvailProjects) return 0;
int OPP = GetOtherPlayer(aPlayerNumber);
if ((IsProjectAvail(20,aAvailProjects)) and
(aGame[aPlayerNumber].Base<25))
return 20;
if ((IsProjectAvail(18,aAvailProjects)) and
(aGame[aPlayerNumber].Base<20))
return 18;
if ((IsProjectAvail(16,aAvailProjects)) and
(aGame[aPlayerNumber].Shield<5))
return 16;
Первые две строки это проверка на уведомление о пропуске хода и получение индекса противника. Потом идет проверка доступности проектов 20, 18, 16:
(16) Ремонт 2. En 9, El 2 : SS+10
(18) Ремонт 4. Me 7, El 2 : SB+9
(20) Ремонт 6. Me 8, El 5 : SB+12
т.е. у бота приоритет : сначала проверить состояние базы, если состояние плохое выбираем ремонтный проект. изменять здесь ничего не будем. Смотрим далее. Далее идут при проверки и использование проектов увеличения количества батарей, рудников и лабораторий:
if (IsProjectAvail(34,aAvailProjects) and
(aGame[aPlayerNumber].Mines<3) and
(aGame[aPlayerNumber].Base>35) and
(aGame[aPlayerNumber].Shield>2))
return 34;
if (IsProjectAvail(33,aAvailProjects) and
(aGame[aPlayerNumber].Battery<4) and
(aGame[aPlayerNumber].Base>35) and
(aGame[aPlayerNumber].Shield>2))
return 33;
if (IsProjectAvail(35,aAvailProjects) and
(aGame[aPlayerNumber].Labs<3) and
(aGame[aPlayerNumber].Base>35) and
(aGame[aPlayerNumber].Shield>2))
return 35;
т.е. если состояние базы нормальное то можно увеличить количество батарей, рудников и лабораторий. Но как видно что количество батарей, рудников и лабораторий будет не больше 4,3,3 соответственно. Эти строчки мы менять не будем, добавим дополнительные строчки отвечающие за усиленное развитие базы
if (IsProjectAvail(34,aAvailProjects) and
(aGame[aPlayerNumber].Mines<6) and
(aGame[aPlayerNumber].Base>45) and
(aGame[aPlayerNumber].Shield>10))
return 34;
if (IsProjectAvail(33,aAvailProjects) and
(aGame[aPlayerNumber].Battery<7) and
(aGame[aPlayerNumber].Base>45) and
(aGame[aPlayerNumber].Shield>10))
return 33;
if (IsProjectAvail(35,aAvailProjects) and
(aGame[aPlayerNumber].Labs<5) and
(aGame[aPlayerNumber].Base>45) and
(aGame[aPlayerNumber].Shield>5))
return 35;
Теперь количество батарей, рудников и лабораторий будет увеличиваться до 7,6,5 соответственно. Развитие базы будет осуществляться только в том случае если состояние базы очень хорошее.
В набор проектов мы добавили проект номер 29, вопрос куда вставить его обработку чтобы не ухудшить игру бота. После проверок на развитие базы есть такие строки
if (IsProjectAvail(46,aAvailProjects)) return 46;
if (IsProjectAvail(28,aAvailProjects)) return 28;
в первую очередь обрабатывается наличие проекта номер 46 а потом 28. Вставим проверку проекта номер 29 между ними.
if (IsProjectAvail(46,aAvailProjects)) return 46;
if (IsProjectAvail(29,aAvailProjects)) return 29;
if (IsProjectAvail(28,aAvailProjects)) return 28;
Компилируем бота, запускаем игру, ставим 500 игр и смотрим результат (см. рисунок 1):
Результат очевиден, выигрыш в 84% игр. Но нет предела совершенству. Рассмотрим поведение бота… В начале, бот проверяет состояние базы и если оно плохое, то выбирает проект ремонта базы. Далее он проверяет условия начального развития базы (когда батарей, рудников и лабораторий совсем мало). Потом проверяет условия усиленного развития базы, и только потом атакующие проекты, и в самом конце если ни одно условие не выполнилось, то случайно выбирает проект.
Что можно изменить в первую очередь? Условия начального развития базы слишком жесткие: база должна быть не менее 35 единиц и щит должен быть не нулевой. Также слишком жесткие условия усиленного развития базы. Вот их нам и надо изменить. Вот что у меня получилось (в терминах Delphi):
if IsProjectAvail(BuildMineProject,aAvailProjects) and
(aGame[aPlayerNumber].Mines<3) and
(aGame[aPlayerNumber].Base>25) then
begin
Result:=BuildMineProject;
exit;
end;
if IsProjectAvail(BuildBatteryProject,aAvailProjects) and
(aGame[aPlayerNumber].Battery<4) and
(aGame[aPlayerNumber].Base>25) then
begin
Result:=BuildBatteryProject;
exit;
end;
if IsProjectAvail(BuildLabProject,aAvailProjects) and
(aGame[aPlayerNumber].Labs<3) and
(aGame[aPlayerNumber].Base>25) then
begin
Result:=BuildLabProject;
exit;
end;
if IsProjectAvail(BuildBatteryProject,aAvailProjects) and
(aGame[aPlayerNumber].Battery<7) and
(aGame[aPlayerNumber].Base>40) and
(aGame[aPlayerNumber].Shield>10) then
begin
Result:=BuildBatteryProject;
exit;
end;
if IsProjectAvail(BuildMineProject,aAvailProjects) and
(aGame[aPlayerNumber].Mines<6) and
(aGame[aPlayerNumber].Base>40) and
(aGame[aPlayerNumber].Shield>10) then
begin
Result:=BuildMineProject;
exit;
end;
if IsProjectAvail(BuildLabProject,aAvailProjects) and
(aGame[aPlayerNumber].Labs<5) and
(aGame[aPlayerNumber].Base>40) then
begin
Result:=BuildLabProject;
exit;
end;
Немного хотелось бы сказать про количество лабораторий. Их количество увеличивается только до 5, так как строительство больше пяти лабораторий требует дополнительного строительства базы. Вообще проекты спроектированы так, что на шпионские проекты надо много электроэллементов и если у вас мало электроэлементов вы не сможете использовать сильные шпионские проекты. Правильное развитие на лаборатории и использование шпионских проектов может стать решающим фактором в победе вашего бота.
Теперь посмотрим список проектов выбираемых ботом. Удалим 28 проект и добавим (31) СуперАтака 4. En 26, Me 16, El 10 : ->49 в итоге получаем такой список проектов:
const
STRATEGY:array[0..MaxProjectsToPLayer-1] of integer =
(
5, 6, 11, 13, 16, 18, 20, 28, 31, 33, 34, 35, 44, 45, 46
);
И вставим проверку использования 31 проекта перед всеми атакующими проектами, т.е. его наличие будет обрабатываться в первую очередь:
if IsProjectAvail(31,aAvailProjects) then
begin
Result:=31;
exit;
end;
if IsProjectAvail(46,aAvailProjects) then
begin
Result:=46;
exit;
end;
if IsProjectAvail(29,aAvailProjects) then
begin
Result:=29;
exit;
end;
Проверку наличия проектов 45 и 44 оставим без изменения, а вот в конце при случайном выборе проекта изменим число 15 на 8, таким образом, случайно будут выбираться только первые 8 проектов из списка доступных проектов, а именно проекты мелких атак и ремонта базы:
repeat
Result:=aAvailProjects^[random(8)];
until Result<>0;
Компилируем и смотрим результаты (см. рисунок 2):
Результат – выигрыш более 60% игр.
Ресурсы
. Скачать исходники SimpleBot v1.1. (C++) http://pkonkurs.ru/wp-content/uploads/2010/06/SimpleBotCpp_v11.zip
. Скачать Fortress 2 build 2026 + FortUI build 1004 http://pkonkurs.ru/wp-content/uploads/2010/06/Fortress-2-2026-+-FortUI-1004.zip
. Скачать Fortress 2 build 2026 beta + SimpleBot v1.2 http://pkonkurs.ru/wp-content/uploads/2010/06/Fortress-2-2026-+-FortUI-1004-+-SimpleBot-v1.2.zip
Статья из четвертого выпуска журнала “ПРОграммист”.
Скачать этот номер можно по ссылке.
Ознакомиться со всеми номерами журнала.
Под редакцией Сергея Бадло…
Обсудить на форуме — Игра Fortress 2. Создание лучшего бота – призовой фонд 5000 рублей!
23rd
Июн
GAMEDEV на Delphi. Делаем змейку
Вэтой статье я подробно рассмотрю создание игры «Змейка» с использованием ООП (объектно-ориентированного программирования). Делать игру мы будем средствами GDI на Delphi 7 (хотя должна подойти и любая другая версия). Для полного осознания того, о чем говорится в статье желательно иметь хоть какой-нибудь опыт программирования, знать что такое Сanvas и TBitmap, что такое классы и в чем заключаются их особенности.
Вадим Буренков
Вообще, при создании любой игры можно выделить три момента:
- Создание идеи, примерный план реализации. При желании разработчиком пишется «диздок» (дизайн-документ — это документ, который описывает то, что вы хотите создать).
- Алгоритмизация. То есть мы представляем план строения игры, какие типы данных будут использоваться. Обдумываются алгоритмы работы и взаимодействия элементов игры.
- Все выше мы записываем в виде кода, то есть программируем.
В принципе, игра не использует наследования, поэтому можно было обойтись и без классов, но мне захотелось сделать так.
Краткий экскурс…
При создании любой игры нужно придерживаться определенного строения кода.
Каждая игра имеет следующие секции:
- инициализация (тут происходит настройка окна для рендера (вывода изображения), загрузка всех игровых ресурсов и уровня)
- обработка (всех игровых объектов, их взаимодействие друг с другом. Эта секция в отличие от инициализации и деинициализации выполняется постоянно, причем скорость выполнения измеряется в миллисекундах и обычно** равна 10-100 мс)
- деинициализация (выполняется при завершении работы приложения и служит для очищения занятой приложением памяти)
Обратите внимание! Компонент TTimer имеет низкую точность. Ставить на нем низкие значения (менее 30 мс) не имеет смысла.
Сделаем основу для игры
Откройте Delphi, создайте новое приложение. Сразу хочу заметить, что именам всех переменных, окон, файлов, и.т.п. надо давать осмысленные имена. Конечно, кто-то скажет что и так запомнит что такое Form1 и Label2, но когда пишется проект не на 10 а на 10000 строк и используется не одна, а пятнадцать форм… Также не надо мелочиться на комментарии, поскольку смысл определенного кода через месяц простоя не поймет и тот, кто его написал, не говоря уже о том – что вам может понадобиться сторонняя помощь.
Назовем форму MainForm, модуль GameMain, а проект SnakeProject. Поскольку эта игра простая и небольшая, то я буду использовать один модуль, но в остальных случаях рекомендуется использовать свой модуль для отдельной игровой части. Создадим два события формы: OnCreate и OnDestroy которые и будут инициализатором и деинициализатором игры. За обработку игры будет отвечать компонент-таймер TTimer (поместите его на форму из вкладки System, назовем его MainTimer), а именно его событие – OnTimer.
Я не буду делать игру очень гибкой в плане игрового поля (его размеры нельзя будет менять. Поле будет 16х12 клеток, но о них будет написано ниже, основные параметры будут константами. Игровое же поле 640х480 по центру экрана без возможности перемещения (без синей рамки). Объявим константы:
SCR_WIDTH=640;
SCR_HEIGHT=480;
Можно не выставлять параметры окна в среде разработки, а написать их в коде (событие OnCreate):
MainForm.Width := SCR_WIDTH;
MainForm.Height:= SCR_HEIGHT;
// параметры формы
MainForm.BorderStyle:= bsNone; // без рамки
MainForm.Position := poDesktopCenter; // по центру экрана
Теперь заставим работать таймер (изначально параметр таймера должен быть enable = false):
MainTimer.Enabled:= true;
Создадим и добавим в событие OnTimer две процедуры (пока пустые) – Main_Update() и Main_Draw(). Для чего это? Мы ведь можем писать весь код напрямую в таймер. Поясню. Например, если мы захотим сделать в игре меню, то должны быть отдельные процедуры обработки/рисования для меню и игры и выполняться они будут в зависимости от того, где игрок находится:
if Scene = sMenu then Menu_Draw;
if Scene = sGame then Main_Draw;
Отрисовка
В этой статье я не буду делать меню, но все же. Итак, мы получили некую заготовку игры. Теперь надо правильно настроить рендер. Canvas отличается простотой, но его недостаток – медленная скорость. Если просто выводить спрайты (изображения) на форму, то будет видно – что они выводятся не сразу, а один за другим и появляется эффект мерцания изображения. Чтобы избежать этого – нужно выводить спрайты не на экран, а в буфер памяти. После того как финальное изображение будет построено, можно вывести его на форму. После вывода буфер можно очистить и он будет готов для построения следующего кадра.
Теперь реализуем данную систему. Объявим переменную – буфер scr_Buffer как Tbitmap. Перед использованием, буфер нужно проинициализировать и установить размеры:
scr_Buffer:= TBitmap.Create;
scr_Buffer.Width := SCR_WIDTH;
scr_buffer.Height:= SCR_HEIGHT;
И в событии Main_Draw() напишем код отрисовки буфера (все рисование должно происходить до этих строчек):
scr_Buffer.Canvas.Rectangle(0, 0, SCR_WIDTH, SCR_HEIGHT); // очищаем буфер ***
Тут же можем проверить, как наша заготовка справляется с рисованием. Загрузим фон для игры в переменную BackImage:
BackImage:= Tbitmap.Create;
BackImage.LoadFromFile(‘fon.bmp’);
// отрисовка
scr_Buffer.Canvas.Draw(0 ,0, BackImage);
// очищение
BackImage.Free;
Нажимаем клавишу <F9> и любуемся на результат (см. рис.1):
Рис. 1. Вывод фоновой картинки игры
Концепция игры. Долгосрочная стратегия
Итак, мы сделали «скелет» игры, на который будем насаживать игровой код. Обдумаем, что же будет представлять из себя игра? Есть змейка, она ползет по уровню, маневрирует между препятствиями и ест все съедобное на ее пути. Но змея не должна есть стены и себя, иначе она погибнет. Из выше написанного подобия «диздока» можно выделить два класса, которые мы будем использовать в игре: змея (назовем ее TSnake) и остальные объекты (TGameObject). Объекты будут иметь переменную, которая определяет их тип: либо это еда, либо препятствие. Каждый объект имеет координату. Поле будет поделено на клетки, это позволит легко определять столкновения (если координаты двух объектов совпадают, то они столкнулись). Размер каждой клетки будет определяться константой CAGE_SIZE = 40. Зная координату клетки можно получить координату на экране, умножив ее на размер клетки. Это нам понадобится при рисовании****. Эти вспомогательные функции делают следующие:
begin
result:= x*CAGE_SIZE
end;
Function G_Y(Y:integer):integer;
begin
result:= Y*CAGE_SIZE
end;
Программируем поведение змейки
Обдумаем, как же будет работать змейка. Каждая деталь змейки – это отдельный элемент со своими координатами. Когда змейка движется – первый элемент (голова) занимает новую позицию, а второй элемент занимает позицию предыдущего до перемещения (там, где раньше была голова), третий позицию второго и.т.д. Это самый логичный вариант, который приходит в голову. Но тогда надо сохранять координаты, где было тело змейки. Для упрощения кода есть более легкий способ – двигать змейку с хвоста. Перейдем к созданию класса змейки:
TSnake=class
Sprite:TBitmap; // спрайт составной части змейки
len:integer; // длина змейки (из какого кол-ва частей состоит змейка)
BodyPos: array of vect2D; // динамический (!) массив с координатами частей змейки
Speed:vect2D; // скорость змейки (сколько клеток она проходит за ход)
MoveTo:(left,right,up,down); // показатель направления движения
Constructor Create(spr_name:string;pos:vect2D;size:integer); // создание змейки со спрайтом,
// позицией и длиной
Procedure SetSize(size:integer);// изменение размера
Procedure Drive; // управление змейкой
Procedure Update; // один ход змейки
Procedure Draw; // отрисовка змейки
Destructor Free; // удаление змейки
end;
Как и в любой игре, в этой нельзя обойтись без математики. Тип «Vect2D» облегчает нам задачу хранения координат и взаимодействия с ним:
vect2D = record
X,Y: integer;
end;
Function v2(x,y:integer):vect2D; // создание
begin
result.X:= X;
result.Y:= Y
end;
Function v2ident(v1,v2:vect2D):boolean; // проверка одинаковых координат
begin
result:= false;
if (v1.x=v2.x) and (v1.y=v2.y) then result:=true
end;
Function v2add(v1,v2:vect2D):vect2D; // сложение координат
begin
result.X:= v1.x+v2.x;
result.Y:= v1.y+v2.y
end;
И вот код всех процедур класса:
Constructor TSnake.Create(spr_name:string;pos:vect2D;size:integer);
Begin
Sprite:= TBitmap.create; // загружаем спрайт
Sprite.LoadFromFile(spr_name);
Sprite.Transparent:= true;
SetSize(size); // установка длины
BodyPos[0]:= pos; // установка положения
MoveTo:= left; // изначальное направление
end;
// установка длины массива частей змейки:
Procedure TSnake.SetSize(size:integer);
begin
len:=size;
SetLength(BodyPos, len)
end;
// вот тут самое сложенное и интересное. Относительно MoveTo устанавливается скорость змейки
// и происходит сдвиг змейки в сторону движения
procedure TSnake.Update;
var i: integer;
begin
if MoveTo=Up then Speed:=v2( 0,-1);
if MoveTo=Down then Speed:=v2( 0, 1);
if MoveTo=Left then Speed:=v2(-1, 0);
if MoveTo=right then Speed:=v2( 1, 0);
// двигаем хвост (downto означает, что мы идет от большего к меньшему)
for i:= len-1 downto 1 do
BodyPos[i]:= BodyPos[i-1];
// двигаем первый элемент (голову) прибавляя скорость
BodyPos[0]:= v2add(BodyPos[0], Speed);
// проверка столкновения головы змейки с хвостом
for i:=1 to len-1 do if v2Ident(BodyPos[0],BodyPos[i]) then TryAgain;
end;
TryAgain() – процедура вызываемая при проигрыше. К ней вернемся позже. В следующей процедуре мы управляем змейкой, задавая ее направление движения через MoveTo(). Это отдельная от Tsnake.Update процедура, поскольку Update() будет вызываться реже таймера, чтобы контролировать скорость движения змейки. При этом, ловить нажатые клавиши управления надо постоянно. Определение нажатых клавиш происходит через функцию Key_Press():
var
keys: TKeyboardState;
begin
result:=false;
GetKeyboardState(keys);
If (keys[key] = 128) or (keys[key] = 129) then result:= true
end;
Коды клавиш можно определить специальной программой [2]. Те, что нам понадобятся, я занес в константы:
KEY_UP= 38;
KEY_DOWN= 40;
KEY_LEFT= 37;
KEY_RIGHT= 39;
KEY_ESC= 27;
Поскольку змейка может только поворачивать влево и вправо, а не может изменять направления движения мгновенно на 180 градусов, введены проверки типа v2Ident(v2(BodyPos[0].x,BodyPos[0].y-1),BodyPos[1])=false, которые не позволяют ей этого сделать:
begin
if Key_Press(KEY_UP) then if v2Ident(v2(BodyPos[0].x,BodyPos[0].y-1),BodyPos[1])=false then MoveTo:=Up;
if Key_Press(KEY_DOWN) then if v2Ident(v2(BodyPos[0].x,BodyPos[0].y+1),BodyPos[1])=false then MoveTo:=Down;
if Key_Press(KEY_LEFT) then if v2Ident(v2(BodyPos[0].x-1,BodyPos[0].y),BodyPos[1])=false then MoveTo:=Left;
if Key_Press(KEY_RIGHT) then if v2Ident(v2(BodyPos[0].x+1,BodyPos[0].y),BodyPos[1])=false then MoveTo:=Right;
end;
И две последние процедуры. Отрисовка отобразит все части змейки по координатам (голова отрисовывается два раза для наглядности):
var i: integer;
begin
for i:=0 to len-1 do if not v2Ident(BodyPos[i],v2(0,0)) then begin
// не отрисовываются части с нулевыми координатами,
// так как их имеют новые части змейки до движения
if i=0 then scr_Buffer.Canvas.draw(G_X(BodyPos[i].x)+Speed.x*5,G_Y(BodyPos[i].y)+Speed.y*5,sprite);
scr_Buffer.Canvas.draw(G_X(BodyPos[i].x),G_Y(BodyPos[i].y),sprite);
end
end;
// при удалении змейки очищается спрайт
destructor TSnake.Free;
begin
Sprite.Free;
end;
Рис. 2. Отображение нашей змейки
Объекты в игре. Не дадим змейке «помереть с голоду»
Теперь необходимо добавить объекты. Они будут двух типов:
TObjType=(oWall,oFood); // стены и еда
type
TgameObject = class
ObjType:TObjType; // тип объекта
Sprite:TBitmap; // спрайт
Pos:vect2D; // положение
Constructor Create(spr_name:string;oType:TObjType;p:vect2D); // создание
procedure Update(Snake:TSnake); // обновление (проверка столкновения со змеей)
procedure Draw; // отрисовка
destructor Free; // удаление
end;
Тут ничего сложного нет. Многие процедуры аналогичны змейке:
begin
Sprite:=TBitmap.create;
Sprite.LoadFromFile(spr_name);
Sprite.Transparent:=true;
pos:= p;
ObjType:= oType
end;
Procedure TGameObject.Update(Snake:TSnake);
begin
if v2Ident(Snake.BodyPos[0],pos) then begin // если змея столкнулась с объектом
if ObjType = oWall then TryAgain;
if ObjType = oFood then begin Snake.SetSize(Snake.len+1);
pos:= RandomFieldPos
end;
// увеличиваем размер змеи, перемещаем еду в другое место
// что такое RandomFieldPos см. ниже
end;
end;
Procedure TGameObject.Draw;
begin
scr_Buffer.Canvas.Draw(G_X(pos.x),G_Y(pos.y),sprite)
end;
Destructor TGameObject.Free;
begin
Sprite.Free
end;
Используем классы в игре
Итак, компоненты игры написаны. Теперь их надо создать, добавить обработку и отрисовку в Main_Update() и Main_Draw(). Вот полный код инициализации змейки и стен. Объявим следующие переменные:
Wall: array of TGameObject; // все стены игры
WallNum:integer; // их кол-во
Food:TGameObject; // еда для змейки
И добавим в инициализацию следующий код:
MySnake:= TSnake.Create(‘snake.bmp’,v2(7,7),2);
// теперь создам еду. А на ужин у нас аппетитненький пингвинчик
Food:=TGameObject.Create(‘food.bmp’,oFood,RandomFieldPos);
Чтобы не вводить координату каждой стенки уровня, так как загрузчика уровней нет, я просчитал создание всех стен в операторе for:
SetLength(Wall,WallNum);
for i:=0 to 15 do Wall[i] := TGameObject.Create(‘wall.bmp’, oWall, v2(i,0)); // верхняя
for i:=16 to 31 do Wall[i]:= TGameObject.Create(‘wall.bmp’, oWall, v2(i-16,11)); // нижняя
for i:=32 to 42 do Wall[i]:= TGameObject.Create(‘wall.bmp’, oWall, v2(0,i-32)); // левая
for i:=43 to 53 do Wall[i]:= TGameObject.Create(‘wall.bmp’, oWall, v2(15,i-43)); // правая
Выше я писал о функции RandomFieldPos(), которая возвращает случайную координату на поле, где нет стен. В OnCreate() надо поставить randomize() для инициализации генератора случайных чисел:
begin
result:=v2(random(13)+1,random(9)+1); // я просчитал допустимые значения
// по X от 1 до 14, по Y от 1 до 10
// тут ничего сложного нет.
end;
Собираем запчасти
Теперь надо добавить нашу змейку, стены и пингвинчика в обработку и отрисовку. Поскольку скорость движения змейки надо ограничить, мы заводим переменную-счетчик WaitTime. Она считает до 5 и выполняет процедуру движения и сбрасывает себя на 0. В итоге, MySnake.Update() срабатывает в 5 раз реже таймера. Ошибкой многих начинающих разработчиков является использование большого количества таймеров, что сильно усложняет код. Чтобы из игры можно было нормально выйти, сделаем условие нажатия на клавишу <ESCAPE>:
var i: integer;
begin
MySnake.Drive;
for i:=0 to WallNum-1 do wall[i].Update(MySnake);
Food.Update(MySnake);
if WaitTime>5 then begin MySnake.Update; WaitTime:= 0; end else inc(WaitTime);
if Key_Press(KEY_ESC) then Application.Terminate
end;
procedure Main_Draw;
var i: integer;
begin
scr_Buffer.Canvas.Draw(0, 0, BackImage);
MySnake.Draw;
for i:=0 to WallNum-1 do Wall[i].Draw;
Food.Draw;
MainForm.Canvas.Draw(0, 0, scr_Buffer);
scr_Buffer.Canvas.Rectangle(0, 0, SCR_WIDTH, SCR_HEIGHT);
end;
Напишем «правильное очищение» всех ресурсов игры:
var i: integer;
begin
scr_Buffer.Free;
BackImage.Free;
MySnake.Free;
for i:=0 to wallnum-1 do wall[i].Free
end;
После чего остается процедура «проигрыша» TryAgain(). В ней сбрасывается длина змейки, а сама она перемещается на стартовую позицию:
begin
MySnake.SetSize(0);
MySnake.SetSize(2);
MySnake.BodyPos[0]:= v2(7,7);
MySnake.MoveTo:= left;
Food.Pos:=RandomFieldPos
end;
Размер устанавливается два раза: первый для того, чтобы сбросить координаты частей змейки на нулевые значения, второй для установки начальной длины. И вот результат (см. рис.3):
Рис. 3. Результат наших трудов. Любуемся игрой
Заключение
Теперь можно запустить игру и наслаждаться результатом. Посмотрев на финальный код можно увидеть, что разработка простой игры не требует каких-либо особых усилий и знаний. Мной эта игра для статьи была написана за 2-3 часа. Весь исходный код проекта, а также текстуры, используемые для создания игры можно найти на http://www.programmersforum.ru в разделе «Журнал клуба программистов. Второй выпуск».
Обсудить на форуме – GAMEDEV на Delphi. Делаем змейку
Статья из второго выпуска журнала «ПРОграммист».
19th
Июн
Правильное определение порядка точек в прорисовке тени в 2d-игре
Давно тут не постил уже, прям забыл уже как тут хорошо было
Вот заинтересовал вопрос о прорисовке тени. Допустим есть некий игрок и препятствия (см. вложение) и он имеет угол обзора 90 градусов и все, что выпадает за этот угол или закрывается препятствием не должно быть отображено. Допустим неким образом я сумел просчитать координаты этих точек, где кривая тени ломается. Определяю методом перебора:
1. Проверяю доступность для “обзора” каждой из четырех точек препятствия (все объекты – прямоугольники).
2. Если точка оказывается доступной(красные круги) то добавляем ее в список точек а также пробуем спроектировать луч из этой точки с углом из точки обзора, тем самим добавляя спроектированную точку в список(угол показан жёлтым, а спроектированная точка зелёным).
3. Дальше пытаюсь отсортировать эти точки так, чтобы их можно было бы прорисовать одним циклом (для начала просто соединить в ломанную(показано белым), а в дальнейшем хочу зарисовать все, не входящее в эту фигуру чёрным или серым). Но так как все препятствия расположены хаотически то может случится ситуация, когда одно препятствие попадает в список раньше положенного.
Прошу помощи в сортировке такого списке, чтобы можно было отобразить как на картинке Спасибо за внимание.
Интересная проблема и возможные варианты решения. Смотрим на форуме. Принимаем участие.
Облако меток
css реестр ассемблер timer SaveToFile ShellExecute программы массив советы word MySQL SQL ListView pos random компоненты дата LoadFromFile form база данных сеть html php RichEdit indy строки Win Api tstringlist Image мысли макросы Edit ListBox office C/C++ memo графика StringGrid canvas поиск файл Pascal форма Файлы интернет Microsoft Office Excel excel winapi журнал ПРОграммист DelphiКупить рекламу на сайте за 1000 руб
пишите сюда - alarforum@yandex.ru
Да и по любым другим вопросам пишите на почту
пеллетные котлы
Пеллетный котел Emtas
Наши форумы по программированию:
- Форум Web программирование (веб)
- Delphi форумы
- Форумы C (Си)
- Форум .NET Frameworks (точка нет фреймворки)
- Форум Java (джава)
- Форум низкоуровневое программирование
- Форум VBA (вба)
- Форум OpenGL
- Форум DirectX
- Форум CAD проектирование
- Форум по операционным системам
- Форум Software (Софт)
- Форум Hardware (Компьютерное железо)