![]() |
![]() |
![]() |
главная страница ![]() ![]() ![]() ![]() |
![]() |
![]() |
![]() |
![]() |
Георгий Мошкин Часть 1 - Часть 2 - Часть 3 - Часть 4 - Часть 5 - Часть 6 - Часть 7 Как вы помните, в прошлой части мы остановились на формате описания юнитов. Напомню вам, что для описания юнитов я решил использовать текстовые файлы. Каждому виду юнитов у нас соответствует отдельный текстовый файл. Например, это могут быть krestyanin.txt, katapulta.txt, ferma.txt, zolotnik.txt и т.д.. Для чтения данных из текстовых файлов мы объявили переменную f типа text:
А так как размеры различных массивов у нас имеют переменную длину, которую можно менять с помощью процедуры setlength(), то сразу же появилась возможность унифицировать все юниты. Суть состоит в следующем: допустим, в текстовом файле krestyanin.txt есть раздел "ремонт":
Это означает, что крестьянин может ремонтировать базу и ферму. Если вы уже в самой игре выберете крестьянина с помощью курсора, то в меню будет кнопка по ремонту, которую можно нажать (вспомните warcraft). Теперь посмотрим, что же находится в разделе "ремонт" у катапульты. И что же мы видим: у катапульты (katapulta.txt) вообще нет такого раздела! В этом и состоит унификация, позволяющая сделать огромное разнообразие юнитов без написания дополнительного исходного кода. Всё решается на этапе загрузки. Посмотрим исходный код rtsmain.pas, соответствующий анализу раздела "ремонт":
У катапульты (katapulta.txt) мы вообще никогда не сработает этот IF, соответствующий анализу раздела "ремонт". А это означает, что не будет запущена процедура SetLength, которую я использую для увеличения массива fRassa.Opisan[i].remont. Когда данные будут уже загружены, то в самой игре мы всегда можем проанализировать длину массива "ремонт". Если длина нулевая - не будем выводить кнопку ремонт, или выведем её затемнённым цветом, что она недоступна для нажатия. Теперь напомню вам, как я задал типы и переменные для хранения загруженных описаний различных расс:
Итак, допустим мы загрузили две рассы: люди и орки. Это означает, что массив Rassa будет состоять из двух элемнтов. У каждой рассы есть массив юнитов Opisan:array of TOpisan. Например, пусть 0 - индекс рассы людей, 1 - индекс рассы орков. Тогда к данным 14-того вида юнита у орков можно обратиться так:
Так как мы уже упомянали катапульту и размер массива переменной длины, то полезно будет показать как всё-таки узнать размер массива "ремонт". Представим, что 8-ой вид юнита - это и есть катапульта, тогда:
А вот если бы 8-мой вид юнита был крестьянином, то мы бы не получили сообщения 'Не может ничего ремонтировать!'. Казалось бы, можно было бы уже приступать вплотную к написанию искуственного интеллекта и отображения карты на экран. Это действительно так, но перед этим стоит вспомнить ещё одну деталь, о которой я упомянал в прошлой части. Представим, что у рассы людей есть 10 типов юнитов с учётом всех будущих апгрейдов. Но во время игры у нас может быть 5 юнитов первого типа, 17 юнитов второго типа, 300 юнитов третьего типа, 3 юнита четвёртого типа и т.д.. Поэтому в целях экономии памяти и создания более логичного и понятного исходного кода целесообразно описания юнитов хранить отдельно от изменяющихся параметров юнитов. Именно поэтому в прошлой части описания юнитов было решено хранить здесь:
А изменяющиеся параметры - здесь:
Хочу сказать вам, что в прошлой части была допущена ошибка. В типе TObyekt нам нужно задать переменные, с помощью которых мы бы могли узнать следующие параметры:
Вот теперь, после этих исправлений, у нас всё на месте. Подитожим что же здесь всё-таки было сделано. Во-первых, где хранятся юниты, которые будут бегать по экрану? Все бегающие, плавающие и неподвижные юниты, включая золотники и деревья, будут храниться в массиве var Obyekti:array of TObyekt. Причём обратите внимание, что в этом массиве хранятся абсолютно все герои игры, т.е. вперемешку разные рассы, и в довершение ко всему деревья, золотники и прочая нечисть. Второе - как мы узнаем о способностях юнита? Всё просто. У нас есть три переменные: номер рассы, номер типа юнита и номер дружбы. Тогда для юнита 578 мы можем узнать абсолютно всю необходимую информацию для "подстановки" её в движок и вывода на экран:
То есть мы как бы наделали клонов. У нас были изначальные типы с описаниями. А реальные юниты представляют из себя всего лишь координаты, угол поворота, уровни энергии и ссылки на индексы базовых описаний. А теперь о так называемом "номере дружбы". Допустим, у вас есть только две рассы: орки и люди. Но в игре вы хотите сделать вражду между орками и орками. Ну или между людьми и людьми. Так вот, если у юнитов не равен этот номер, то они являются врагами! Приведу пример двух врагов:
Кстати, в зависимости от этого номера можно несколько видоизменять цвета спрайтов. Ведь для орков спрайты у нас одинаковые. Тогда мы просто в зависимости от номера дружбы подкорректируем цвета: у всех спрайтов, для которых номер дружбы = 100, делаем зеленоватый оттенок. А у тех, что 200 - красноватый. Но тут возникает ещё одна интересная идея. В своей стратегии вы можете реализовать переход на чужую сторону! Да-да! Для этого достаточно заменить номер дружбы. Стоит только поменять номер дружбы, так сразу же управление юнитом возмёт на себя AI (искуственный интеллект) рассы другого цвета! Ну а вот теперь, когда мы более-менее вспомнили предыдущие наработки по формату файлов с описаниями юнитов, можно вплотную приступить к написанию исходников для реализации карты, реализации движения игрока и предварительному этапу создания искуственного интеллекта. Карта у нас будет следующая: все идут куда хотят, никаких сеток и т.п.. Деревья, золотники и дома можно выстраивать в любых точках, и опять же НИКАКИХ сеток. А вот потом, когда уже всё это заработает, можно будет сделать какую-нибудь надстройку для выравнивания по сетке. Очень вероятно, чтомы вообще никогда никаких сеток делать не будем. Сейчас же все юниты у нас будут перемещаться не между квадратами сетки, а между реальными любыми координатами. Например, между x=0.001, y=1 и x=-14.33312, y=303.5. То есть забудем о сетках. У кого-то может возникнуть вопрос: а как же воду описывать без сеток и т.д.. Ответ следующий: мы можем описывать области, и никаких сеток не понадопится. Например, треугольный пруд. Неподалёку - круглое болото. Деревья торчат в любых координатах. На данный момент карта у нас почти бесконечная. Ограничение состоит в предельных значениях для переменных X и Y типа SINGLE. Заглянем в помощь по Delphi и видим:
Итак, с картой мы разобрались. Теперь перейдём к реализации движении игрока и одновременно будем создавать зачатки искуственного интеллекта. Нам нужно реализовать следующее:
При этом нужно врубить у спрайта нужные кадры анимации, а также учесть угол поворота. Первое - это загрузка нужных спрайтов для каждого типа юнита. Идентификаторы загруженных OpenGL-евских текстур имеют тип GLUINT. Введём массив sprite переменной длины, в который можно загружать текстуры (спрайты) юнитов:
Добавим в файл krestyanin.txt раздел "спрайты":
И напишем обработчик, который сработает при загрузке раздела "спрайты":
Но нам ещё нужно знать из каких строк большой текстуры со спрайтами брать спрайты ходьбы, атаки, ходьбы с золотом для крестьянина. В связи с этим в файле krestyanin.txt добавим раздел с индексами строк:
Итак, читаем: для ходьбы берём нулевую текстуру (peasant.bmp) и меняем кадры, соответствующие строкам 1, 2, 3, 2, 1, 4, 5, 4. Для атаки или рубки деревьев берём нулевую текстуру и последовательно прокручиваем спрайты из строк 6, 7, 8, 9 и 10. Если данный юнит уничтожен - запускаем последовательность 11, 12, 13 из текстуры peasant.bmp (0 = peasant.bmp, 11 12 13 = индексы строк). Ну и наконец, ходьба с добытым золотом (мешок за плечами). Для этого берётся перавая текстура (1 1 2 3 2 1 4 5 4), которая хранится в peasant with gold.bmp. И далее периодически прокручиваем последовательность строк 1 2 3 2 1 4 5 4. То есть: 1 2 3 2 1 4 5 4 1 2 3 2 1 4 5 4 1 2 3 2 1 4 5 4 1 2 3 2 1 4 5 4 и т.д. до тех пор, пока крестьянин идёт. Как вы понимаете, крестьянин в нашей игре - это самый нужный и работящий юнит. Поэтому на него было нарисовано аж две текстуры со спрайами. Но никто не мешает сделать и больше текстур - больше разнообразий в действиях. Формат данных следующий:
А номера текстур идут по порядку загрузки:
Обращаю ваше внимание, что необходимость в таких дополнениях связана с тем, что мы делаем движок под готовые спрайты. А спрайты хранятся специфическим образом - по несколько спрайтов в одном BMP-файле. Для загрузки этих данных нужно добавить соответствующие переменные в тип type TOpisan=record ... end. Также необходимо написать обработчик, реагирующий на раздел "индексы".
Итак, эту процедуру мы создали как раз для того, чтобы из одной большой текстуры вывести на экран только один маленький спрайт. Например, мы загрузили текстуру peasant with gold.bmp. Мы знаем, что в этой большой текстуре содержится много маленьких спрайтов размером 72 на 72. Также нам известно количество строк и количество столбцов. Взглянем ещё раз на кусочек написанного ранее кода для этой процедуры:
Обратите внимание, что номера строк и столбцов мы решили считать, начиная с единицы. Итак, мы выводим спрайт. Например, спрайт из левого верхнего угла текстуры:
Эта процедура как раз и выведет маленький спрайт размером 72x72 из огромной текстуры 512x1024. Причём это будет спрайт из левого верхнего угла, так как мы задали в параметрах процедуры первую строку первый столбец. Ну а параметр false говорит о том, что нам не требуется зеркальное отражение спрайта. Зачем вообще понадобилось зеркальное отражение? Дело вот в чём: например, крестьянин стоит, повернувшись вправо. Это спрайт. Как получить спрайт, на котором тот же крестьянин стоит, но повернулся влево? Для этого достаточно сделать зеркальное отображение отосительно оси Y (вертикальной). Более подробно код рассмотрен в предыдущих частях. Но даже если вы и сами заглянете в файл rtsmain.pas, то без труда найдёте условия проверки параметра flip:boolean, где сразу видна суть зеркального отображения. Но вот если крестьянин смотрит вперёд или назад (при виде сверху), зеркальное отражение не поможет (почему - внимательно посмотрите, что случится со прайтом при отражении относительно горизонтальной оси X). И ещё раз проведём небольшой анализ проделанной нами ранее работы. Вначале мы получили спрайты от заброшенного проекта "Варяг". Возникла идея написать движок. Далее мы сделали всё необходимое для загрузки и отображения этих спрайтов на экран с помощью OpenGL. Потом был очень важный раздел по созданию формата файлов: мы создали формат файлов и написали код для загрузки и анализа этих данных, который выделяет нужные числовые параметры из текстового файла и заносит их в соответствующие переменные, элементы массивов и записи. Параллельно с этим мы задались такими вопросами, как: реализация карты и передвижение игрока по карте. В следующих частях вы увидите, как проделанная нами ранее работа по написанию исходного кода облегчит создание игрового движка и самой игры. Стоит отметить, что при написании этой статьи я решил учесть некоторые замечания в адрес "Самодельного WarCraft-а". Многим из вас известен форум сайта по созданию компьютерных игр - gamedev.ru. Благодаря ценным замечаниям посетителей этого форума был выделен ряд проблем "Самодельного WarCraft-а": отсутствие разнообразия юнитов, отсутствие навыков и умений, отсутствие апгрейдов. В то же время было отмечено, что сама по себе идея создания RTS стратегической игры в реальном времени актуальна, а результаты, полученные в процессе создания "Самодельного WarCraft-а" достаточно интересны. Итак, обозначенные выше замечания в большей или меньшей степени были учтены. При написании нового исходного кода я придерживался идей максимальной простоты и унификации. Хотя это и привело к небольшому усложнению, но всё-таки я уже дал простое решение в виде "Самодельного WarCraft-а". А в данной статье излагаю процесс более продвинутой (more advanced) версии стратегического движка. Теперь мы перейдём к более интересным и динамичным вещам - первые шаги игрока, создание искуственного интеллекта. Причём я хочу отметить, что, к примеру, при создании искуственного интеллекта, всё окажется очень просто. А в какой-то момент вы вообще увидите, что игра практически уже готова из самого движка: вам будет достаточно только дописывать текстовые файлы с описаниями юнитов и рисовать спрайты. Файлы к статье: dev002src.rar - исходники (144kb), dev002exe.rar - исходники + exe (329kb). Часть 1 - Часть 2 - Часть 3 - Часть 4 - Часть 5 - Часть 6 - Часть 7 |
![]() |
![]() |
![]() |