главная страница статьи файлы о сайте ссылки |
Георгий Мошкин Часть 1 - Часть 2 - Часть 3 - Часть 4 - Часть 5 - Часть 6 - Часть 7 Перед тем, как мы начнём двигать игрока, нам необходимо выбрать его мышью. Но мы сразу пойдём по пути унификации, и обеспечим возможность выбора не только одного юнита, но и группы юнитов. Как вы помните, в играх типа WarCraft существует выбрать сразу нескольких персонажей. Например, пять воинов и одного крестьянина. И всех их куда-нибудь послать. В принципе, это неплохая идея: найти новый золотник, крестьянин будет строить базу, воины охранять неокрепшую базу. Как только база готова - строим вышки, стреляющие стрелами, покупаем новых крекстьян и развитие идёт полным ходом! Я предлагаю такой подход, при котором уже сейчас мы одновременно воссоздадим искуственный интеллект. Основную идею создания искуственного интеллекта я изложил в статье "Как сделать 2d игру: уровни и монстры, а также скрипты, интерпретаторы, искуственный интелект и создание стратегических игр". Я сделаю всё таким образом, что попросту объединю код искуственного интеллекта с кодом передвижения игрока. Смотрите: вместо того, чтобы плодить лишние переменные типа "пункт назначения" и т.п., мы будем пользоваться интерпретатором мыслей. Причём если рассой управляет компьютер, то он и задаёт мысли. А вот если рассой управляет человек, то мысли будут задаваться при нажатии на мышку или на какие-нибудь элементы управления. Но это не означает, что расса, которой управляют с помощью мышки или клавиатуры, будет полностью лишена искуственного интеллекта: например, если к воину подойдёт какой-нибудь вражеский громила и начнёт его колотить, то мы в любом случае зададим мысль "дать сдачи", чтобы наш персонаж не стоял на месте, а автоматичски начинал сопротивляться. Подобное логичное поведение вы без труда найдёте в играх типа WarCraft. Сначала я прокомментирую приготовленый мною исходный код dev001.rar (85kb). Процедуру загрузчика ZagruzOpisan(fname:string;var fRassa:TRassa) описаний юнитов я дополнил двумя обработчиками:
и
В файле описания крестьянина есть соответствующие разделы:
Вы без труда сможете разобраться в содержиомом блоков IF, предназначенных для парсинга данных текстовых строк. В блоке парсинга индексов присутствуют следующий код:
Рассмотрим как это работает на примере чтения строки индексов "0 1 2 3 2 1 4 5 4". Формат хранения индексов следующий: сначала идёт номер текстуры, а затем несколько номеров строк. В данном случае номер текстуры нулевой. Число ноль запишется в texturnum: сначала функция StringWordGet() выдаст первое слово из строки, а затем мы преобразуем данный текст в число с помощью функции StrToInt. Происходит следующе:
Далее в строке идут числа, описывающие номера строк. Для их обработки используется цикл
Как раз и получается, что сначала мы первое слово (0) записали в texturnum, а все последующие слова (1 2 3 2 1 4 5 4) мы в цикле записываем в kadr[0], kadr[1], kadr[2] и т.д.. Новый раздел параметры предназначен для повышения уровня универсальности движка. Вместо того, чтобы жестко задавать некоторые числа в исходном коде, мы обеспечим возможность чтения их из файла. Таким образом, даже если игра будет уже откомпилирована, будет очень просто поменять нужные параметры.
Здесь RADIUS - это радиус, описанный вокруг юнита. Данный параметр предназначен для проверки столкновений. XSize, YSize - это размеры текстуры. sprX, sprY - это размеры спрайта. Теперь я покажу, как процедура StringWordGet будет учавствовать в чтении данного раздела:
Это всё равно, что:
Всё очень просто. С помощью процедуры StringWordGet мы взяли нужные слова из строки. Словами в данном случае являются цифры. Но так как мы прочитали текстовую строку, и разбили её на текстовые слова, то нам необходимо применить функцию StrToInt, которая преобразует STRING в INTEGER. В переменных появился следующий тип:
Здесь texturnum - это номер текстуры, из которой следует брать спрайты, а kadr - это массив с номерами строк (как вы помните, в одной текстуре у нас несколько строк со спрайтами). У каждого юнита есть несколько анимационных последовательностей, которые хранятся в массиве posledovatelnost:
Для анимирования персонажа нам необходимо знать номер текущего кадра (TekushKadr) и номер последовательности (TekushPosl):
Также введены две переменные MISL1 и MISL2, которые будут использоваться в интерпретаторе мыслей. Мысли мы храним в текстовом виде. Приведу пример мысли:
Эта мысль говорит, что объект должен идти в точку с координатами x=150, y=775. Как только объект достигнет этой точки, мы обнулим его мысль:
Передвежение игрока обеспечится интерпретатором мыслей, о котором речь будет идти ниже. Но смысл такой: мы с помощью функции StringWordGet разделим строку "idti 150,775" на строку "idti" и строку "150,775". Дальше в интерпретаторе будет присутствовать блок IF:
Внутри begin-end мы превратим строку "150,775" в числа x=150, y=150 и будем двигать игрока. Как только игрок окажется в нужной точке, мы сделаем удаление данной мысли:
Поэтому при следующем проходе интерпретатор мыслей не выполнит блок if MISL1='idti' then. Зачем нужна вторая мысль MISL2 - я расскажу позже. Но в принципе сейчас вы уже должны видеть мощь данной простой системы. Для облегчения процесса создания новых юнитов удобно ввести какую-нибудь процедуру. Подробное описание будет дано в комментариях к главному релизу исходников. А сейчас лишь поясню смысл процедуры
Данная процедура в каком-то смысле и занимается клонированием типов. Всё, что она делает - это создаёт новый объект (юнит) нужной нам рассы nomRas, нужного типа nomTip, с определённым номером дружбы nomDru. Причём из описания типа берутся максимальные уровни энергий, соответствующих данному юниту, и заносятся в соответствующие записи. Вывод данных с учётом анимации осуществляет процедура SuperKvadrat:
Данная процедура анализирует параметры объекта под номером nomerObj и проигрывает анимационную последовательность под номером nomerPosl. Процедура SUPERkvadrat в каком-то смысле является надстройкой над написанной ранее процедурой Kvadrat. Вывод всех юнитов на экран производит процедура RenderObjects, которая в цикле вызывает процедуру SuperKvadrat для всех юнитов уровня:
Перейдём к процессу выбора игрока. Для начала сделаем выбор хотя бы одного игрока, а не целой группы. Введём переменную Vibran:
При нажатии правой конпки мышки мы проверям, нет ли в близлежаших точках какого-нибудь юнита. Если юнит есть, то заносим его номер в переменную Vibran.
Выше мы находим вектор с координатами (a,b). Данный вектор проведён между точкой, в которой расположен объект (юнит) и другой точкой, в которой была нажата мышь. Координаты i-того объекта: Obyekti[i].x, Obyekti[i].y. Координаты мыши: MyMouse.X, MyMouse.Y. А переменные movex и movey - это смещение карты относительно экрана. Положение мышки в координатах карты с учётом смещения карты: (MyMouse.X-movex, MyMouse.Y-moveY). Затем подсчитали длину fff вектора (a,b). Иными словами, мы узнали расстояние fff между юнитом и курсором мышки с учётом смещения карты. В переменную ppp мы занесли радиус окружности, описанной вокруг юнита. Потом идёт проверка: если расстояние fff меньше радиуса ppp, и при этом нажата левая кнопка мыши, то выбран данный объект (номер i). На самом деле это простая проверка: находится ли курсор мыши внутри окружности радиуса Rassa[Obyekti[i].nomRas].Opisan[Obyekti[i].nomTip].radius. Для красоты и повышения удобства игры добавляем подсветку игрока крутящимя кружком. Для этого при выполнении условия fff<ppp вызываем процедуру Kursor(0,ppp). Итак, допустим мы выбрали игрока. Теперь мы знаем его номер (Vibran:integer). Последовательность дальнейших действий следующая:
Рассмотрим соответствующий код:
А так как в главном цикле игры каждый раз вызывается интерпретатор мыслей, то юнит немедленно начнёт передвигаться. Исходный код процедуры интерпретатора мыслей:
А теперь вернёмся к выводу анимационных спрайтов. Как вы помните, столбцы соответствуют различным углам поворота, а строки - различным кадрам анимации. Процедура вывода спрайта:
Итак, мы создали заготовку интерпретатора мыслей и автоматическую систему выбора нужных кадров анимации. Теперь нам достаточно развить интерпретатор мыслей и создать искуственный интеллект на его основе. Поимо этого необходимо реализовать вывод карты (трава, вода, камни и т.д.). Файлы к статье: dev002src.rar - исходники (144kb), dev002exe.rar - исходники + exe (329kb). Часть 1 - Часть 2 - Часть 3 - Часть 4 - Часть 5 - Часть 6 - Часть 7 |