Формирование стратегии
нельзя доверять дилетантам:
их планы могут неожиданно сработать,
а к этому никто не готов.
(А.Канингем)
Карта прямоугольная, изометрическая, поворот 45 градусов, без границ между клетками (tileStraight). |
В двух предыдущих выпусках мы научились делать несложные двумерные игры, управлять спрайтами, прокручивать игровой экран, отслеживать столкновения игровых объектов, строить интерфейс (кнопки, мышь, клавиатура, текстовые области) и работать в полноэкранном и оконном режимах. Все это делалось на примере аркадной игры.
На этот раз мы перейдем от аркад к более «серьезному» жанру - стратегиям. Здесь нам придется освоить целую серию новых механизмов, но ничего сложного не будет и здесь. В этой статье мы изучим устройство пошаговой стратегии (а также и стратегии реального времени - ее с ЛКИ-Creator'ом делать даже проще) и сделаем для примера игру, рассчитанную, однако, только на многопользовательский режим (а также редактор карт для нее). Одиночным же режимом мы займемся в следующем выпуске нашей рубрики - посвященном основам искусственного интеллекта.
Поскольку это уже третье занятие, мы не будем в подробностях разбирать весь код примера - благо многое сделано точно так же, как в два предыдущих раза. Для справок есть программа-пример (в ней немало комментариев) и предыдущие статьи.
Ну, а материалы наших прошлых занятий вы можете найти на нашем компакт-диске, в специально созданном для этой цели разделе «Игра своими руками».
Постановка задачи
Напишем стратегическую игру, состоящую из сражения двух фэнтези-армий. Цель битвы - захват нескольких обелисков, расставленных по карте. Перед сражением мы расставляем свои войска, состоящие из 6 мечников, 4 лучников, 2 рыцарей, 2 магов и 1 призрака, в пределах отведенной нам территории. Кроме них, на карте бывают нейтральные драконы.
Далее с каждым ходом игрок имеет право сделать по одному действию каждой фигуркой. Действие состоит из движения и атаки - в произвольном порядке.
Характеристики бойцов | ||||||
Боец | Передвижение | Хиты | Дальнобойность | Повреждения | Защита | Способности |
Мечник | 4 | 8 | 1 | 7 | 2 | - |
Лучник | 4 | 5 | 7 | 5 | 1 | - |
Рыцарь | 3 | 15 | 1 | 9 | 4 | Лечение, рыцарский удар |
Маг | 3 | 12 | 5 | 6 | 0 | Огненный шар |
Привидение | 4 | 7 | 2 | 5 | 5 | Регенерация |
Дракон | 6 | 30 | 2 | 12 | 5 | Полет |
Характеристики бойцов представлены в таблице. Лечение - это право раз за бой вылечить соседнего воина (кроме призрака) до полного здоровья. Рыцарский удар - право раз за игру нанести тройные повреждения. Огненный шар - атака мага снимает хиты не только с непосредственной цели, но и с окружающих квадратов. Регенерация - восстановление по 1 хиту за ход. Полет - право перемещаться через препятствия.
Игра идет в многопользовательском режиме, в варианте Hot Seat (игра с одного компьютера, ходы по очереди). После хода игроков делают свой ход нейтральные драконы, атакуя любого противника в радиусе 7 клеток.
Партия заканчивается, когда одна из сторон либо захватывает более половины присутствующих на карте обелисков, либо гибнет полностью.
Карта задана изначально в редакторе карт. На ней расставлены обелиски, драконы и препятствия (объекты, через которые нельзя двигаться и атаковать).
Подготовка к работе
Перед началом работы нам потребуется переустановить пакет ЛКИ-Creator. Дело в том, что по сравнению с прошлым разом в него внесено немало изменений и дополнений.
(Надеюсь, что Delphi у вас уже установлен; если нет, то рекомендации на эту тему читайте в нашей предыдущей статье - в июньском номере журнала или на CD этого номера или на сайте.)
Карта шестиугольная, вид сверху, с границами (tileBorder). |
Это важно: в предыдущей версии ЛКИ-Creator были некоторые проблемы совместимости с новыми версиями Delphi. В этом варианте они устранены.
Возьмите с нашего компакт-диска (раздел «Игра своими руками») файл с текстами программ и картинками и распакуйте его в каталог проектов.
Теперь вы можете скачать нужные файлы отсюда.
У нас должно получиться три подкаталога. В одном - Units - хранятся библиотеки DirectX и модули пакета ЛКИ-Creator. В другом - Project - мы будем работать; туда заблаговременно положены картинки, которые нам понадобятся, и предыдущая версия нашей аркады. В третьем - Escort - готовая программа, которая должна у нас получиться.
Теперь установим (переустановим) ЛКИ-Creator. В меню Delphi откройте пункт Component, в нем выберите Install Component. Если у вас уже был установлен этот пакет, оставайтесь на закладке Into existing package, иначе перейдите на закладку Into new package и заполните пустые строчки, как показано на рисунке (в верхней строчке проще всего выбрать файл LKI2dEngine.pas с помощью кнопки Browse, а в нижней просто запишите LKI). После чего нажмите OK и выберите Install. В верхней панели Delphi у вас должна появиться закладка LKI.
Теперь осталось только загрузить наш проект. В меню File выбираем Open, открываем файл Project\Obelisk.dpr…
Где карта, Билли? Нам нужна карта!
Однако прежде, чем заняться «высокими материями», нам понадобится еще немного потрудиться над графическим движком.
В «Звездном эскорте», нашем предыдущем проекте, «карта» никакого значения не имела: звезды расставлялись случайно и не влияли ни на что, а положение остальных объектов либо задавалось прямо в коде, либо определялось случайно. Это годится далеко не для всякого проекта. А значит, нам пора добавить к нашему движку карту местности.
Как это будет выглядеть, вы, вероятно, уже догадываетесь - ставим на окно проекта объект-карту, а потом прописываем ее в свойстве Map нашего движка.
Так-то оно так… но у нас далеко не один класс карты. Посмотрим подробнее…
Типы карт
Графовая карта: значение имеют только координаты звезд. |
Карта состоит из некоего ландшафта и объектов, установленных на нем. Ландшафт чаще всего (но не всегда) разбит на клетки, которые называют tiles - плитками.
Как известно нам из школьного курса геометрии, плоскость можно без промежутков и наложений покрыть правильными многоугольниками трех типов: треугольник (равносторонний), квадрат, шестиугольник. Треугольные поля не особо удобны, поэтому чаще применяются квадратные клетки или шестиугольники-«гексы».
С квадратами, в некотором смысле, жить проще: если у нас есть двумерный массив клеток, сразу понятно, как найти соседние с заданной клетки. Это +1 и -1 по каждому из двух индексов. С шестиугольниками все несколько сложнее… но зато гексагональная доска обладает очень ценным свойством: все направления в ней одинаковы. У квадратной сетки это не так: диагонали существенно отличаются от горизонталей и вертикалей. Поэтому для серьезных стратегических расчетов шестиугольники могут быть лучше квадратов.
Бывают и неплиточные карты. ЛКИ-Creator поддерживает два их типа: графовые и лоскутные.
Графовая карта - это карта, на которой значение имеют только несколько ключевых точек, плюс, возможно, особые области (например, непроходимые), а остальное - просто узор, игрового эффекта не имеющий. Так часто делаются звездные карты, как, скажем, в Master of Orion: звезды и черные дыры - ключевые точки, остальное - фон. Еще в этом режиме иногда делают глобальные карты, например, к ролевой игре.
Лоскутная карта разбита на области, и внутри области все точки одинаковы, двигаться по «лоскутку» нельзя. Это хорошо для глобальных стратегий, где провинция - минимальная единица территории.
Примеры карт из разнообразных игр, с указанием типа - на рисунках.
Итак, большинство двумерных карт (трехмерные - особая статья) можно разделить на четыре класса:
Прямоугольная - TLKIRectMap. Это плиточная карта, клетки - квадраты. Такая карта, например, в Civilization III. Шестиугольная - TLKIHexMap. Плиточная карта с шестиугольными клетками. Используется во множестве wargames, и не только: так, например, традиционно делалась боевая карта Heroes of Might & Magic.Два этих типа карт - потомки общего класса TLKITileMap. Графовая - TLKIGraphMap. У этой карты есть фон (свойство Background) и выделенные на ней ключевые точки - статичные объекты. Положение других объектов на этой карте выражается либо обычными координатами (как у космического корабля в межзвездном пространстве), либо привязкой к объекту (тот же корабль - на орбите планеты). Таковы карты Master of Orion, Arcanum (глобальная) и так далее. Лоскутная - TLKIClusterMap. У нее есть свойство фона, как и у графовой, и второе свойство - маска (Mask), которое определяет, какая точка к какой области принадлежит, и свойство Borders, задающее связи между «лоскутками». Так устроены карты, например, в Medieval: Total War или Victoria.
Это важно: классы карт описаны не в модуле LKI2dEngine, а в LKI2dMap.
Углы наклона
Графовая карта: важны особые точки, остальное - случайные локации. |
Но если вы думаете, что этим и исчерпываются возможности ЛКИ-Creator'а по отображению карт, то вы сильно ошибаетесь.
Карта может быть представлена видом сверху или же изометрическим - взгляд под углом к вертикали. Например, карта Civilization III или Heroes of Might & Magic IV - изометрические, а в Civilization I принят вид сверху.
Обычно изометрия в ходу у плиточных карт, а графовые обходятся видом сверху, поскольку масштаб у графовых карт обычно мельче. Но бывают и исключения: например, в Medieval: Total War - лоскутная изометрическая карта.
За изометричность отвечает свойство карты IsIsometric и два параметра, задающие угол, под которым смотрит наша камера: Phi и Theta.
Первый отвечает за поворот карты относительно вертикальной оси: например, если задать его равным 45 градусов (он измеряется именно в градусах), то клетка прямоугольной решетки будет ориентирована углом вверх, как в Civilization. При Phi=0 одна из сторон клетки будет горизонтальной.
Второй заведует наклоном камеры по отношению к вертикали. Для удобства он задан как соотношение горизонтальных и вертикальных единиц длины. Скажем, если мы хотим, чтобы наша клетка рисовалась по высоте вдвое меньше, чем по ширине, нам надо присвоить Theta значение 2.
При плиточной карте выбирать эти углы произвольно нам не дано: все-таки у нас (пока) не 3D. Они напрямую зависят от параметров плиток. Например, если она у нас ромбообразная, углом вверх, и вертикальная ось вдвое меньше горазионтальной, то мы обязаны задать параметры 45 и 2.
А вот графовые и лоскутные карты дают право назначать эти параметры как угодно (и даже, при желании, менять их в процессе), но увлекаться этим не следует - помимо того, что такие повороты отнимают немало времени, они еще и не слишком здорово выглядят. И не забудьте, что, если карта у вас художественная, с картинками, надписями и т.п, то они повернутся вместе с нею… Вообще, лоскутную карту порой бывает проще отрисовать уже с учетом нужного поворота - благо расстояния там зачастую не играют никакой роли.
Стыки
Лоскутная карта, вид сверху. |
У плиточных карт есть еще одна проблема - стыковка плиток. Ею заведует параметр TileBorderStyle. Чаще всего это tileStraight, режим, в котором плитки просто прилегают друг к другу без каких-либо краевых эффектов, или tileBorder, в котором рисуются линии, отсекающие одну плитку от другой - границы клеток (в последнем случае не забудьте определить цвет решетки в параметре TileBorderColor).
Но есть и более хитрый вариант, когда одинаковые плитки прилегают друг к другу без изменений, а разные - с использованием специальной «переходной» плитки. Так делают обычно, если карта состоит в основном из широких пространств одного типа территории, скажем, больших зеленых площадей, а отдельная клетка не важна и не должна замечаться игроком. Такова карта Heroes of Might • Magic. А вот если каждая клетка обрабатывается отдельно, как в Civilization, тогда этот метод не годится, и лучше четко отделить клетки друг от друга. «Слитная» технология (ее еще называют масочной) задается значением TileBorderStyle, равным tileMasked. Об их устройстве мы поговорим в другой раз - это достаточно сложная тема.
Плитка
Элемент карты - объект класса TLKITile - обладает простой структурой. В нем изначально заложены: координаты, спрайт, который его рисует, код типа плитки (по которому определяется, что у нас тут - холм, пустыня, дорога, море?) и проходимость (это актуально в большинстве игр). Последняя - это число единиц хода, которые тратятся на передвижение через эту плитку сухопутным отрядом. Для непроходимых плиток здесь стоит отрицательное число.
Еще один параметр - Objects, список находящихся на этой плитке объектов (типа TLKIGameObject).
Чтобы узнать, по какой клетке щелкнули мышкой, у карты есть метод MouseTile(x,y), возвращающий выбранную плитку.
В числе методов плитки есть IsNeighbour(Tile, Distance). Эта функция возвращает истину, если плитка Tile отстоит от данной не более, чем на Distance клеток (по умолчанию этот параметр приравнивается единице, то есть, если вы напишете просто IsNeighbour(Tile), функция даст истину для непосредственно прилегающей к данной плитки. У квадратной решетки «соседями» считаются и те плитки, что граничат по диагонали.
Функции FirstNeighbour и NextNeighbour используются для проверки всех клеток, соседних с данной. Первая из них указывает на какую-то клетку-соседа, а вторую можно вызвать только после вызова первой, и она выдает следующих соседей, по одному.
Перебор соседей
// Нанесение повреждений по клетке
procedure TObeliskTile.Damage(dmg : integer);
begin
if (Objects.Count > 0) and // У нас может быть
// не больше одного объекта на клетке
(Objects[0].ID > 0) // Пассивные объекты
// не повреждаются
then
begin
Dec(Objects[0].Hits,
// Автоматически вычитаем из повреждений защиту
Max(0,dmg-(Objects[0] as TObeliskGameObject).Defense);
if Objects[0].Hits1)
// 0 - код препятствия, 1 - дракона
then begin
// Выбираем точку для перемещения
if x=i then ax := i
else if x>i then ax := i+2
else ax := i-2;
if y=j then ay := j
else if y>j then ay := j+2
else ay := j-2;
MoveObj(NO, ax, ay);
FinishMove(NO);
// Атакуем
Map.Tiles[i,j].Damage(12);
// Прерываем цикл: не больше одной атаки
// каждым драконом за раунд
break;
end;
Наконец, осталось только проверить, не занято ли больше половины обелисков войсками одной стороны - и если занято, то остановить игру и объявить победителя!
Итак, у нас получилась стратегическая игра. Однако для полного счастья не хватает, в первую очередь, искусственного интеллекта, который позволит придать игре однопользовательский режим (простейшую процедуру управления драконами не считаем). Им-то мы и займемся в следующий раз. До встречи через месяц!
В будущих номерах
В следующих номерах мы поговорим о:
системах «частиц» (particles) для отображения дыма, искр и т.п.; работе с прозрачностью; трехмерных движках; основах AI; отладке программы; создании замысла и сценария игры, написании дизайн-документа; игровом балансе; продумывании игровых персонажей и их реплик; работе с Photoshop и трехмерными пакетами; анимации; музыке и озвучке; и многом другом.Все это вполне реально научиться делать своими руками. Вы скоро в этом убедитесь.
Пишите нам…
Все, кто хочет поделиться своими соображениями о пакете ЛКИ-Creator и этом цикле статей, сообщить о найденной ошибке, спросить совета или предложить какое-то усовершенствование - милости просим писать по адресу . Кто-нибудь из авторов пакета постарается ответить вам.
Тем, кто считает, что пакет можно чем-то дополнить: во-первых, не забудьте, что на нашем диске сегодня еще не финальная версия пакета, а только та, в которой реализованы описанные в наших статьях функции. Возможно, что-то из ваших идей уже реализовано и ждет своей очереди (см. врезку «В будущих номерах»). И в любом случае: предлагая нам какую-то идею, попытайтесь обосновать, почему ваше предложение полезно сразу для многих игр, а не только для вашей конкретной.
Для самостоятельной работы
В ожидании следующего номера можно заняться своим собственным проектом, а можно попробовать усовершенствовать этот. Вот несколько идей для самостоятельной реализации:
разделить объекты-препятствия на уничтожимые (деревья и кусты) и неуничтожимые (камни), и сделать так, чтобы огненные шары и дыхание дракона сжигали растительность; организовать на месте, где сработала огненная атака, ямы (бурая клетка) либо полыхающий несколько ходов пожар (красная клетка); разрешить мечникам и рыцарям прикрывать соседей, давая им +1 к защите; сделать передвижение фигурок по экрану плавным.А если в реальном времени?
Сделать стратегию в реальном времени ничуть не сложнее, если только предоставить игрокам разные средства ввода. Проще всего сделать это по сети - об этом мы поговорим в одном из ближайших номеров. Еще понадобятся такие изменения:
не понадобится поле GameSpeed у класса TObeliskObject - пользуемся скоростью Speed из базового движка (cкорость перемещения по экрану равна игровой скорости); отключается целочисленный обсчет расстояний; переписывается код движения фигуры - с учетом того, что надо отрисовать траекторию в обход препятствий; убирается кнопка "конец хода".Вот и все. Попробуете сделать сами?