Лки своими руками сделать

Формирование стратегии
нельзя доверять дилетантам:
их планы могут неожиданно сработать,
а к этому никто не готов.

(А.Канингем)

Карта прямоугольная, изометрическая, поворот 45 градусов, без границ между клетками (tileStraight).

В двух предыдущих выпусках мы научились делать несложные двумерные игры, управлять спрайтами, прокручивать игровой экран, отслеживать столкновения игровых объектов, строить интерфейс (кнопки, мышь, клавиатура, текстовые области) и работать в полноэкранном и оконном режимах. Все это делалось на примере аркадной игры.

На этот раз мы перейдем от аркад к более «серьезному» жанру - стратегиям. Здесь нам придется освоить целую серию новых механизмов, но ничего сложного не будет и здесь. В этой статье мы изучим устройство пошаговой стратегии (а также и стратегии реального времени - ее с ЛКИ-Creator'ом делать даже проще) и сделаем для примера игру, рассчитанную, однако, только на многопользовательский режим (а также редактор карт для нее). Одиночным же режимом мы займемся в следующем выпуске нашей рубрики - посвященном основам искусственного интеллекта.

Поскольку это уже третье занятие, мы не будем в подробностях разбирать весь код примера - благо многое сделано точно так же, как в два предыдущих раза. Для справок есть программа-пример (в ней немало комментариев) и предыдущие статьи.

Ну, а материалы наших прошлых занятий вы можете найти на нашем компакт-диске, в специально созданном для этой цели разделе «Игра своими руками».

Постановка задачи

Напишем стратегическую игру, состоящую из сражения двух фэнтези-армий. Цель битвы - захват нескольких обелисков, расставленных по карте. Перед сражением мы расставляем свои войска, состоящие из 6 мечников, 4 лучников, 2 рыцарей, 2 магов и 1 призрака, в пределах отведенной нам территории. Кроме них, на карте бывают нейтральные драконы.

Далее с каждым ходом игрок имеет право сделать по одному действию каждой фигуркой. Действие состоит из движения и атаки - в произвольном порядке.

Характеристики бойцов
БоецПередвижениеХитыДальнобойностьПоврежденияЗащитаСпособности
Мечник48172-
Лучник45751-
Рыцарь315194Лечение, рыцарский удар
Маг312560Огненный шар
Привидение47255Регенерация
Дракон6302125Полет

Характеристики бойцов представлены в таблице. Лечение - это право раз за бой вылечить соседнего воина (кроме призрака) до полного здоровья. Рыцарский удар - право раз за игру нанести тройные повреждения. Огненный шар - атака мага снимает хиты не только с непосредственной цели, но и с окружающих квадратов. Регенерация - восстановление по 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 используются для проверки всех клеток, соседних с данной. Первая из них указывает на какую-то клетку-соседа, а вторую можно вызвать только после вызова первой, и она выдает следующих соседей, по одному.

Перебор соседей

Пример - на врезке «Перебор соседей». Эта процедура обсчитывает удар огненным шаром по клетке и всем ее соседям.

Это интересно: для ее работы совершенно неважно, шестиугольная у нас решетка или квадратная.

Часто нам требуются еще какие-то параметры, и обычно класс плиток, из которых состоит карта - потомок TLKITile. Так и в примере - TObeliskTile унаследовано от TLKITile.

Это важно: если мы вносим на наш игровой экран плиточную карту, координаты, а также методы TLKIGameObject, связанные с дистанцией, по умолчаниюначинают измерять расстояние в плитках, а не в точках. Координаты же кнопок, значков и т.п. продолжают мериться в пикселах! Но этот режим можно и отключить - это бывает полезно для стратегий реального времени.

Выбор карты

Итак, возьмем для начала прямоугольную решетку (TLKIRectMap), изометрическое отображение (угловые параметры 0, 1.5). Пусть сетка у нас рисуется (стиль tileBorder). Укажем движку, что именно эту карту надо отображать. Пока что все нужные действия совершены без написания единой строки кода.

Однако надо еще определить возможные рисунки плиток. Для этого служит процедура InitTile. Ее параметры - код, имя файла с картинкой, проходимость:

Эти операции надо сделать до инициализации движка, как и объявление шрифтов.

Фигурки объявим, как и раньше - спрайтами.

Редактор карты

Лоскутная карта, изометрическая.

Трудностей тут довольно немного. Такой же точно движок, такие же объявления плиток… Интерфейс, вроде выбора плитки, загрузки/сохранения и т.п., можно запросто сделать стандартными средствами Delphi: никто же не заставляет нас переводить его в полноэкранный режим. Разбирать это здесь мы не будем - все есть в файле с примером. В коде примера сознательно использован простейший способ; при желании можно, например, палитру объектов и палитру плиток делать графическими.

В редакторе есть всего две незнакомых нам особенности. Первая довольно проста: это новая функция мыши, предназначенная специально для «плиточных» карт. Функция TLKIRectMap.SelectTile возвращает нам указатель на именно ту плитку, по которой щелкнули мышью, так что мы легко можем обработать нажатие.

А вот вторая новинка заслуживает более тщательного рассмотрения.

Вообще-то существует множество способов сохранять в файл и считывать из него данные. Мы выбрали способ, закодированный в файле CannonBase. Cannon - это средство для считки и записи объектов-потомков TCannonObject с контролем типов и некоторыми другими особенностями.

Посмотрим на код («Запись карты»).

Запись карты

Вот как это работает. Сперва нужно открыть файл специальной процедурой InitSave, у которой единственный параметр - имя файла. Затем сохраняем заголовок для контроля типов - специальной процедурой WriteHeader. Потом записываем все, что нам нужно, используя процедуру WriteStr для строк, а для всех остальных полей - Write (ее второй параметр - размер записываемых данных в байтах). По мере необходимости для полей-объектов можно написать собственные процедуры Save c записью заголовка. Наконец, закрываем файл процедурой FinSave.

Загрузка идет примерно так же - только вместо Write везде Read, а вместо Save - Load.

Все объекты, у которых есть свой заголовок, надо отдельно объявить. В разделе Initialization модуля (необязательный раздел, идущий после Implementation, в котором находятся команды, которые надо выполнить в самом начале, при запуске программы) вы должны написать такую, например, строчку:

TpMap - это константа, которую вы тоже должны объявить. Приравняйте ее, скажем, 1. А в конструкторе объекта TObeliskMap присвойте значение этой константы параметру TypeID.

Зачем такая возня? Помимо проверки соответствия типов, вы получаете одно очень важное преимущество.

Если формат файла изменится, скажем, из-за добавления новых полей - вам не понадобится писать никаких «конвертеров», преобразующих старые файлы в новые. Ваш код автоматически прочитает их.

Предположим, мы добавили к карте информацию об ее названии. Для ее сохранения и загрузки нужно будет сделать следующее: измените значение константы Version (она определена в модуле CannonBase), увеличив его на 1, и внедрите в процедуру загрузки дополнительный код (см. «Добавление новой информации»).

Добавление новой информации

Такой код автоматически инициализирует новое поле как пустое, если в файле оно не сохранено. А записывать файл можно, просто добавив строку WriteStr(Name) в самый конец.

Замечание: если вам все равно пока непонятно, для чего такой процесс, не беспокойтесь. Можно пользоваться более привычными методами записи-сохранения. Но в действительно масштабных игровых проектах этот путь дает изрядные преимущества.

Играем

Первым делом нам нужно создать новый класс, порожденный от TLKIGameObject. Свойств старого нам будет не хватать. В новом классе нужно добавить поля для основных характеристик: дальнобойности, передвижения и так далее.

Это важно: наш старый параметр скорости остается с нами, но он обозначает скорость движения фишки по экрану, а не то расстояние, которое она пройдет за ход. Если бы мы делали стратегию реального времени, нам бы не потребовалось нового параметра, а так - придется его вводить.

На наш экран нанесем кнопки TLKIButton в форме лучников, мечников, мага, призрака, рыцарей.

Сначала у нас идет расстановка. Определим зону расстановки для одной стороны как верхние три «строки» карты, для другой - как нижние три «строки».

Код устроен так. При нажатии на любую из кнопок активизируется установка соответствующего бойца; щелчок по незанятой клетке в области расстановки помещает фигурку туда и отключает кнопку. Как только все кнопки отключены - ход передается противнику.

В начале каждого нового хода все кнопки включаются снова: это делается затем, чтобы человеку было проще заметить, кем он еще не походил. Соответственно, щелчок по кнопке выделяет фигурку, а как только ход сделан - кнопка пропадает. Еще одна кнопка - «Конец хода» - появляется только после фазы расстановки.

Мы уже делали в прошлый раз операции по включению и отключению интерфейсных элементов, так что подробно разбирать эту операцию не будем - посмотрите в коде примера.

Ход фигуры

Ход обрабатывается так (см. «Ход фигуры»). Находится клетка, по которой щелкнули. Если на ней враг, и они в пределах дальнобойности - ему наносится вред, если она пустая и в пределах дальности хода - фигура перемещается (если позволяют препятствия), если же она занята, но не врагом - нажатие игнорируется.

Когда походили обе стороны, действуют драконы. Они действуют очень просто: выбирают ближайшего не-дракона, который находится в пределах 7 клеток от них, и атакуют. См. код «Действия дракона».

Действия дракона

Наконец, осталось только проверить, не занято ли больше половины обелисков войсками одной стороны - и если занято, то остановить игру и объявить победителя!


Итак, у нас получилась стратегическая игра. Однако для полного счастья не хватает, в первую очередь, искусственного интеллекта, который позволит придать игре однопользовательский режим (простейшую процедуру управления драконами не считаем). Им-то мы и займемся в следующий раз. До встречи через месяц!

В будущих номерах

В следующих номерах мы поговорим о:

  • системах «частиц» (particles) для отображения дыма, искр и т.п.;
  • работе с прозрачностью;
  • трехмерных движках;
  • основах AI;
  • отладке программы;
  • создании замысла и сценария игры,
  • написании дизайн-документа;
  • игровом балансе;
  • продумывании игровых персонажей и их реплик;
  • работе с Photoshop и трехмерными пакетами;
  • анимации;
  • музыке и озвучке;
  • и многом другом.

Все это вполне реально научиться делать своими руками. Вы скоро в этом убедитесь.

Пишите нам…

Все, кто хочет поделиться своими соображениями о пакете ЛКИ-Creator и этом цикле статей, сообщить о найденной ошибке, спросить совета или предложить какое-то усовершенствование - милости просим писать по адресу . Кто-нибудь из авторов пакета постарается ответить вам.

Тем, кто считает, что пакет можно чем-то дополнить: во-первых, не забудьте, что на нашем диске сегодня еще не финальная версия пакета, а только та, в которой реализованы описанные в наших статьях функции. Возможно, что-то из ваших идей уже реализовано и ждет своей очереди (см. врезку «В будущих номерах»). И в любом случае: предлагая нам какую-то идею, попытайтесь обосновать, почему ваше предложение полезно сразу для многих игр, а не только для вашей конкретной.

Для самостоятельной работы

В ожидании следующего номера можно заняться своим собственным проектом, а можно попробовать усовершенствовать этот. Вот несколько идей для самостоятельной реализации:

  • разделить объекты-препятствия на уничтожимые (деревья и кусты) и неуничтожимые (камни), и сделать так, чтобы огненные шары и дыхание дракона сжигали растительность;
  • организовать на месте, где сработала огненная атака, ямы (бурая клетка) либо полыхающий несколько ходов пожар (красная клетка);
  • разрешить мечникам и рыцарям прикрывать соседей, давая им +1 к защите;
  • сделать передвижение фигурок по экрану плавным.

А если в реальном времени?

Сделать стратегию в реальном времени ничуть не сложнее, если только предоставить игрокам разные средства ввода. Проще всего сделать это по сети - об этом мы поговорим в одном из ближайших номеров. Еще понадобятся такие изменения:

  • не понадобится поле GameSpeed у класса TObeliskObject - пользуемся скоростью Speed из базового движка (cкорость перемещения по экрану равна игровой скорости);
  • отключается целочисленный обсчет расстояний;
  • переписывается код движения фигуры - с учетом того, что надо отрисовать траекторию в обход препятствий;
  • убирается кнопка "конец хода".

Вот и все. Попробуете сделать сами?

Источник: http://www.lki.ru/text.php?id=30

Предыдущая статья: украшения своими руками для прически

Следущая статья: принтер canon ремонт своими руками

Лучшие статьи: