В статье речь пойдет об одном очень не новом проекте, который создавался совсем в другое время и совсем в других условиях. Это моя старенькая RTS под названием Земля онимодов (Onimod land). Чтобы было сразу понятно, что она собой представляет, можно посмотреть коротенькое видео:
Пожалуй, будет правильным сразу же уточнить следующее: Данная игра была запрограммирована когда-то лично мною с нуля без движков, конструкторов и прочих современных инструментов. Проект достаточно большой и делался на чистом энтузиазме несколько лет. Вопреки всем законам экономики мне-таки удалось довести эту работу до конца, правда, к тому моменту численность команды разработчиков уже уменьшилась до одного человека. К сожалению, на момент своего выхода эта игра не могла по многим причинам оказаться востребованной широкими массами игроков — рынок игр поменялся сначала в сторону 3D, потом в сторону казуальных игр. Долгое время эта игра лежала у меня, так сказать, «на полке», да и сам я давно перестал быть разработчиком игр.
Тем не менее, вспомнить о том, как всё это делалось, мне достаточно интересно. Тем более, что лично я играю в собственную игру с большим удовольствием. Я совершенно не уверен, что всем известный Blizzard делал свой Starcraft по той же схеме, что и я, но я точно помню, что по ходу разработки я периодически натыкался на те же самые проблемы, на которые в своё время натыкались разработчики Blizzard-а. Это можно было понять по тем решениям, которые и им, и мне пришлось использовать, чтобы выпутаться из одинаковой «ситуации». Свой вариант о том, как я всё это делал, я и предлагаю вашему вниманию. В конце я коротко опишу сами условия, в которых происходила разработка, а также те «правила игры», которые на тот момент устанавливали российские издатели.
В статье делается попытка описать общее устройство классической RTS. Естественно, что описание будет не особенно подробным, так как нет никакой возможности описать подробно такой объем кода (да и, честно говоря, даже для автора удержать в голове тонкости реализации нереально, особенно учитывая, сколько уже лет минуло). Однако, хочется надеяться, что какое-то пусть и поверхностное представление о том, как всё это делается с нуля, мне осветить удастся. Кое-где я буду вставлять небольшие куски исходного кода, но делать это буду, скорее, для демонстрации принципа, чем для чего большего.
Будут рассмотрены следующие механизмы, которые, на мой взгляд, являются фундаментальными:
- Графика (в каком виде, что и как).
- Игровые ресурсы / редактор ресурсов — подготовка спрайтов для игры, создание игровых объектов (юнитов и зданий).
- Управление объектами — сказ о том, как заставить юнитов что-то делать.
- Алгоритм поиска пути.
- AI или Искусственный интеллект — раздел сначала разбирает действия Инстинктов или врожденную сообразительность юнитов, а потом уже пытается пояснить процесс глобального управления командой.
- Сеть.
- Коротко об авторах и о проделанной работе.
- Стартовые условия и их последствия — это лирический раздел, не имеющий отношения к сути статьи. Я решил, что он не будет лишним, так как наши представления о том времени могут быть различными. Поэтому я кратенько освещу свой вариант ситуации, в которой начинался этот проект.
RTS
В качестве игрового жанра была однозначно выбрана RTS с типом графики 2D в изометрии. Такой выбор был обоснован исключительно нашими личными предпочтениями. Основным языком программирования я выбрал для себя C++, который после длительного программирования на ассемблере на процессоре типа Z80 (ZX-Spectrum) показался мне очень простым, так как по своей сути сильно напоминал очень высокоуровневый ассемблер. Хотя должен признать, что я долго не мог привыкнуть использовать виртуальные функции, а без этого C++ сильно похож на обычный C.
На мой взгляд, если вы делаете игру с нуля, то первое, что нужно сделать — это получить хоть какое-то изображение на экране. Имеется в виду, конечно, что изображение должно быть осмысленным, т.е. своим появлением тестировать игровые механизмы. Основной момент здесь — это способ попадания графики в игру и её последующая отрисовка. Именно с этих двух моментов нужно всё начинать.
Графика
В тот момент подавляющее большинство игр использовало 8-битное цветовое разрешение, и мы логично решили, что мы пойдем дальше и наша игра будет использовать 16-битные цвета. Почему не 24-битные? На тот момент на видеокартах в качестве нормы присутствовал 1 Мб памяти. Количество памяти, которое потребуется под одну экранную поверхность 800x600, соответственно, составит 800 x 600 x 2 = 960000 байт или 937,5 КБайт. Т.е. под одну экранную поверхность задействовалась почти вся видеопамять тогдашних видеокарт. Изначально наша игра должна была уметь работать в режиме экрана 640x480, но в дальнейшем этот вопрос был пересмотрен в настоящий момент игра работает с любым разрешением, которое позволяет монитор.
Для хранения графики требуется память. Понятно, что хранить что-то на видеокарте в моем случае не получится, да это и невозможно с учетом предъявляемых требований. Поэтому спрайты должны лежать в ОЗУ.
К самому спрайту предъявлялись следующие требования:
- Спрайт должен обладать именем.
- Сжатый спрайт может иметь любую форму, а не только прямоугольную. Пиксели, которые на изначальной прямоугольной форме полностью прозрачны, обозначаются специальным цветом, чтобы их можно было отбросить.
- Полупрозрачные пикселы не используются.
- Спрайт может содержать идентификационный цвет, который можно менять во время рисования. Это требуется, чтобы «отмечать» юниты/здания, принадлежащие к одной команде, своим цветом.
- Вместо 16-битного цвета закодированный спрайт должен содержать индекс цвета в палитре, который кодируется 1 байтом. Палитры будут строится отдельно для большого количества спрайтов. Такой подход позволял сильно экономить на хранении самого цвета, но требовал возможности переключать текущую палитру во время визуализации. Однако чаще всего спрайту вполне хватало одной палитры.
- Спрайт должен содержать, так называемый, pivot (координату начала спрайта), относительно которой производится рисование.
- Спрайт может обладать размерами в «клетках». Игровое поле — это клеточный мир, поэтому иногда полезно знать то пространство, которое занимает спрайт на игровом поле.
На картинке показаны размеры спрайтов в «клетках» и положение спрайта относительно клеток, т.е. pivot.
В результате по спрайтам я принял такое решение: хранить графику в памяти в сжатом виде и раскодировать её в момент вывода на экран. Главным плюсом этого варианта является малое количество памяти, которое требуется спрайт, главным минусом, естественно, будет постоянное раскодирование процессором каждого спрайта в момент рисования. Что для этого требуется? Нужен формат сжатия и алгоритм, который будет сжимать и разжимать картинку.
Для сжатия был выбран следующий способ. Алгоритм проходил по прямоугольной области спрайта по вертикали, начиная от левого верхнего угла. Основной для рисования служили вертикальные неразрывные полоски пикселей (вертикаль была выбрана из того простого соображения, что юниты в основном имеют больший размер по высоте). Каждая такая полоска запоминалась как массив, о котором известно количество пикселей и цвета этих пикселей. Когда полоска прерывалась «пустым» пикселем, то происходил поиск следующей вертикальной полоски. Между окончание одной полоски и началом следующей сохранялось смещение координат, что позволяло избежать хранения и, самое главное, рисования «пустых» пикселей. Ко всему этому добавлялись трюки с палитрами, которые могли переключаться на ходу. В упрощенном виде формат сжатия представлял из себя следующие команды:
- Смещение координат относительно предыдущей порции пикселей
- Массив цветов пикселей по вертикали (количество, затем цвета)
- Установка новой палитры
Всё это неплохо, но возникает вопрос, а как быть с цветом, который идентифицирует команду, т.е. постоянно изменяется? Решение в данном случае достаточно простое. Этот идентификационный цвет можно всегда кодировать 0-ым индексом в палитре. Тогда нулевой цвет в палитре будет как бы зарезервирован под изменчивость. Зато перед рисованием, спрайта достаточно в используемых палитрах заменить самый первый цвет на цвет команды, и вопрос решен.
В качестве дополнительной оптимизации был использован такой прием. Многие здания содержали анимацию, которая занимала куда больше места, чем занимал бы просто статический спрайт здания. Но так как само здание всегда отображалось статическим спрайтом, а анимация накладывалась поверх спрайта, то из анимации можно было выбросить те пиксели, которые по цвету совпадали с цветом статического спрайта здания. Обычно это удаляло большую часть пикселей из спрайта с анимацией.
Например, в игре Ферма выглядела так:
В реальности же графика хранилась так:
Теперь надо решить вопрос с раскодировкой сжатого изображения. А эта задача оказывается значительно сложнее, чем само сжатие. Принципиальное отличие состоит в том, что сжимать изображение можно заранее, т.е. не заботясь о скорости этого процесса. Раскодирование же должно выполняться «на лету» причем постоянно и для каждого спрайта, выводимого на экран. И именно в этом моменте и заключалась главная трудность. Вторая трудность, вытекающая из первой состояла в том, что спрайты должны раскодироваться разными способами. Например, представим, что какой-то юнит включил себе режим «маскировки», который визуально выглядит так, что он начинает быстро становиться прозрачным почти до полного растворения. Или, допустим, у летающих юнитов должна присутствовать тень на поверхности ландшафта, причем тень на деле являлась отдельным спрайтом, чтобы летающий юнит мог изменять высоту, а тень при этом оставалась на земле. Тень всегда черная и всегда обладает прозрачностью, а значит её можно раскодировать по собственному алгоритму.
Или еще один пример, направленный на оптимизацию — если заранее известно, что спрайт использует только одну палитру, то достаточно выставить её один раз и при раскодировании вообще не проверять на изменение палитры. В общем, вариантов по улучшению алгоритма раскодирования для частных случаев вполне хватает. Присутствовал даже такой экзотический вариант раскодировки спрайта, который проверял на попадание в видимую область указанных координат, что требовалось для проверки на точное попадание мышью.
Кроме того, все варианты раскодировки дублировались для следующих случаев:
- Зеркальное отражение спрайта по горизонтали.
- Спрайт частично оказывается за пределами окна. Т.е. было необходимо ручками срезать кусок, который не попадал в видимую область экрана.
Для раскодирования было решено применять ассемблер, так как данный алгоритм являлся самым узким местом во всей программе. Ассемблер же позволяет программисту эффективно использовать регистры процессора, а не размещать переменные в стеке, как это обычно делает компилятор. В результате на ассемблере было написано несколько десятков функций, которые, по сути, делали одно и то же раскодирование, но с разными нюансами. Нужная функция вызывалась из C++ получая в качестве аргументов информацию о сжатом спрайте и тонкостях его визуализации.
В качестве ассемблера использовался tasm32.exe. Командная строка для сборки ассемблера выглядела так:
tasm32 /l /ml /zi modul_s.asm, где modul_s.asm исходный код на ассемблере. В результате образовывался объектный файл modul_s.obj, который уже можно было линковать к проекту на C++.
Само обращение к функциям из C++ выглядело примерно так:
extern «C» bool AsmSprite(параметры);
Ресурсы
Вопрос хранения и редактирования ресурсов — это, пожалуй, самый важный вопрос, который определяет в дальнейшем всё остальное. Поэтому, на мой взгляд, создание любой не самой маленькой игры нужно начинать с создания редактора ресурсов. В идеале у вас должен быть визуальный редактор, который позволит вносить почти любые изменения в игру без помощи программиста. Т.е. при создании игры заниматься самой игрой нужно, как ни странно, в последнюю очередь, а первый ваш штурм должен быть направлен на создание такого редактора. Редактор ресурсов не нужно путать с редактором карт — у них принципиально разные назначения. Редактор карт — это задача, наоборот, уже «под занавес», так как чтобы рисовать карты вам, практически, требуется, чтобы сама игра уже функционировала.
Итак, вернемся к ресурсам…
В моем случае в игре должны были обрабатываться ресурсы следующих видов:
- Спрайты (служат изображением для всех остальных видов ресурсов)
- Неживые объекты (деревья, камни, минералы, разные украшательства)
- Живые объекты: юниты и здания
- Снаряды (стрелы, ядра, ракеты, магия)
- Апгрейды, т.е. исследования, которые можно произвести во время игры с целью улучшения характеристик юнита или здания.
Со спрайтами всё было более-менее понятно. Их надо загружать, сжимать и сохранять большими порциями в отдельный файл, который потом игра сможет открывать уже самостоятельно. Так как спрайт обладает именем, то в некоторых случаях вполне можно идентифицировать по этому имени принадлежность спрайта к Неживым объектам.
Как я уже говорил ранее, на начальной стадии очень важно получить изображение, поэтому редактор ресурсов можно начинать с возможности загружать и сжимать спрайты.
Далее необходимо было подумать о том, чтобы научить редактор ресурсов создавать Живые объекты. Для этого недостаточно просто иметь набор картинок, необходимо объединить эти картинки в анимации и объяснить игре, для чего именно используется та или иная анимация.
Давайте разберемся, какие обычно действия может выполнять боевой юнит в RTS. Очевидно, что юнит должен уметь находится в состоянии «ничегонеделания» — назовем его Ожидание. Далее юнит должен уметь перемещаться — назовем это действие Перемещение. Юнит должен уметь сражаться — назовем это действие Воздействие (вообще воздействие может быть и положительное, например, Излечивание). И, наконец, юнит, которому не повезло с командиром, должен иметь действие Смерть. Эти действия представляют собой базовый набор истинного воина.
На самом деле ситуация с Воздействием была гораздо сложнее, так как существовали и дополнительные стадии. Полный цикл Воздействия мог выглядеть примерно так:
- Подготовка в Воздействию — это, практически, подготовка к стрельбе (Арбалетчик присел для выстрела).
- Воздействие — собственно, сам выстрел.
- Перезарядка оружия — в настройках юнита можно указать количество выстрелов без перезарядки, затем оружие нужно было перезарядить (Арбалетчик закладывает новую стрелу).
- Пауза между Воздействиями — здесь могла быть любая анимация, но самое главное, что данную стадию можно ускорять за счет Апгрейдов, тогда юнит начинает стрелять чаще.
- Выход из Воздействия — цель уничтожена и новая цель отсутствует, значит можно войти в обычный режим (Арбалетчик, который присел для стрельбы, выпрямляется).
Дополнительно вся эта схема сильно осложняется тем, что у нас не 3D, а стало быть нельзя просто крутить модель, чтобы показать её с разных сторон. Поэтому у анимации обязательно должно быть еще и Направление (вверх, вниз, вправо, влево и т.д.). Направления эти, естественно, должны быть фиксированными, причем надо учитывать, что каждое Направление, практически, требует дублирования всех спрайтов юнита. Исходя из этого, было выбрано 8 направлений движения для юнитов, причем в реальности их получалось сократить до 5-и, так как не зеркализуются в противоположные стороны только направления «вверх» и вниз".
Для юнитов, которые слишком резко меняли своё направление, было добавлено действие Поворот, например, Танк поворачивался на месте для смены направления движения.
Свойства, которыми обладает анимация:
- Действие — показывает игре возможности объекта (Ожидание, Перемещение, Воздействие и т.д.).
- Направление движения (5 шт) — требуется, чтобы объект визуально мог отображаться в разных направлениях. Обычно для каждого направления движения потребуется дублирование анимации со всеми её свойствами, кроме, собственно, спрайтов.
- Завершение анимации:
- Окончание — анимация завершается (если делать больше нечего, то обычно запускается анимация на Ожидание)
- Зацикливание — анимация начинается с начала (полезно для Зданий, на которых может бесконечно воспроизводиться анимация Ожидание)
- Переход — запуск другой анимации, дополнительно требуется выбрать анимацию (позволяет комбинировать анимации одну с другой, можно использовать случайную анимацию из нескольких вариантов).
- Новый объект — в момент окончания текущий объект превращается в новый, который должен быть указан (полезная штука для случая, когда, например, Шаман оживляет Статую, т.е. требуется превратить здание в юнита).
- Смерть — текущий объект уничтожается в памяти игры.
- Прерывание анимации — признак того, что выполнение анимации может быть прервано действиями пользователя (ткнул куда-то мышкой). Дополнительно можно указать, анимацию, которая будет выполнена, если пользователь попытался прервать текущую анимацию. (Например, Солдат в состоянии отдыха держит автомат перед собой, а когда шагает, то автомат у него за спиной — мгновенное переключение между этими состояниями смотрелось бы не очень хорошо, поэтому есть промежуточная анимация, которая визуально убирает автомат за спину, именно она и сработает прежде чем Солдат сдвинется с места).
- Скорость — позволяет указать скорость выполнения анимации в процентах.
- Иконка — изображение для панели управления, куда щелкает пользователь для выполнения данного действия.
- Стоимость — некоторые действия не бесплатны, например, чтобы маг мог колдовать, ему нужна энергия.
- Кадры — анимация состоит из спрайтов или кадров. Это самое важное свойство анимации. Чаще всего кадр лишь отображает соответствующий спрайт, но есть и большой дополнительный функционал:
- Пауза — определяет задержку анимации на данном кадре. Есть возможность использовать случайные задержки. Паузы хорошо помогают изобразить неторопливое разложение убитого воина, так как обычно труп присутствует на карте еще очень долго, после его смерти.
- Звук — можно проиграть какой-нибудь звуковой файл. Данное свойство хорошо себя зарекомендовало в анимациях стрельбы из автомата или долбания противника мечом.
- Функция — это конкретное действие, которое выполняется на данном кадре анимации. Можно было одновременно задано несколько функций на одном кадре (например, выпустить ракеты из двух стволов сразу). Перечислим основные функции (хотя их немного больше):
- Воздействие — это и есть тот самый момент, когда враг получает топориком по голове. Это, конечно, самое простое использование функции Воздействия, на деле же дополнительные параметры позволяют указать тип этого воздействия. А здесь в списке есть не только Физический удар, но и Лечение, и Воровство магии, и еще куча всего. Также в тип воздействия входят такие обыденные дела, как Добыча ресурса (Работник стукает топориком по ёлочке и на нужном кадре анимации функция Добыча ресурса вытряхивает ему из дерева дровишки), а еще здесь же банальный Ремонт поломанного Здания, когда Работник возвращает Зданию жизнь. Любое Воздействие всегда направлено на текущую цель, над которой юнит выполняет действие.
- Взрыв — по координатам текущего объекта выполняется взрыв, у которого регулируется сила и радиус ударной волны. Для взрыва также можно выбрать тип воздействия, но обычно это просто Физический удар. Логично предположить, что функцию Взрыва обычно используют не Живые юниты, а Снаряды, которые взрываются достигая цели.
- Зарождение объекта — функция для создания другого объекта. Например, Лучник с помощью этой функции должен создать Стрелу на данном кадре анимации. Дополнительно для Стрелы надо будет указать координаты, по которым она будет создана, т.е., практически, придется еще таскать спрайт Стрелы по экрану, чтобы правильно расположить его относительно Лучника. Естественно, что позиционировать создаваемый объект относительно создающего необходимо для всех направлений движения, т.е. практически, эта операция выполнялась 5 раз. В каждом случае подбирался такой спрайт Стрелы, который больше соответствовал бы текущему направлению юнита. Стрела в игре начинала своё существование именно с этого спрайта, а далее могла самостоятельно разворачиваться, в зависимости от направления к цели.
На деле возможностей, конечно, было гораздо больше, но нет особого смысла в перечислении всего — главное, пожалуй, принцип.
Естественно, что юниты должны иметь характеристики, такие как сила, защита, количество жизни, скорость перемещения. Для них должно быть определено Здание, в котором они будут производиться, скорость производства, стоимость и т.д. Есть юниты, которые могут перевозить других юнитов, т.е. Транспорты. Есть вариант, когда юниты не просто перевозятся Транспортом, а еще и стреляют из него. Некоторые юниты могут летать, соответственно, для них нужно определить высоту полёта «от» и «до», в этих пределах летающий юнит может изменять свою высоту случайным образом. Некоторые Здания умеют действовать по аналогии с транспортами, т.е. юниты могут попадать внутрь, например, Лучники могут быть помещены в Защитную Башню, чтобы стрелять из неё.
Отдельного стоит кратко упомянуть об объектах Снарядах. Снаряд может поражать наземные или воздушные цели, быть автонаводящимся, т.е. следовать за целью как ракета или вести себя как катапультное ядро, т.е. двигаться по параболической траектории. Снаряд умеет выполнять только два типа анимаций Полёт и Смерть. Анимация Полёт позволяет указать Снаряду множество направлений, а не фиксированные 8 как у обычного юнита, что обеспечивает плавное изменение траектории Снаряда. В момент достижения цели Снаряд выполняет анимацию Смерть, которую можно завершить функцией Взрыв. Кроме того есть возможность дополнительно указать, что снаряд может поджигать деревья. Создаваемый снаряд всегда получает какую-то Цель в виде координат либо игрового объекта, чтобы двигаться к ней, иначе само существования Снаряда теряет смысл.
Апгрейды, в моем случае, это такие же объекты, которые создаются с помощью редактора ресурсов. Апгрейд в игре может существовать только в одном экземпляре. Апгрейд не имеет анимаций, но имеет иконку для панели управления и список производимых действий. Также апгрейд может иметь конкретный список объектов, на которые он действует. В момент, когда апгрейд произведен, в памяти игры просматриваются все объекты и для тех, на кого действует данный Апгрейд, выполняется изменение характеристик. Помимо обычных Апгрейдов существуют и очень фундаментальные. В частности, для расы Онимодов есть возможность выбрать Путь развития, коих у него ровно 3:
- Путь Нападения
- Путь Защиты
- Путь Неизвестного
История их появления весьма необычна, и связана с тем, что одному из художников очень нравилось рисовать технику для расы Онимодов. В результате для Онимодов у меня были следующие боевые единицы тяжелой техники:
- Дрон
- Танк
- Броневик
- Камикадзе
- Когг
Выбрасывать столько готовых юнитов с моей стороны было бы непростительным расточительством, особенно учитывая, что для второй расы Ботсвана нужных юнитов, наоборот, не хватало. Поэтому для Онимодов я придумал такую штуку как Путь развития. Выбирая один из путей, игрок, практически, делает доступными одних юнитов и запрещает других. Например, первый путь, именуемый Путь Нападения позволял использовать юнитов Дрон, Танк и Камикадзе. Во втором пути, который назывался Путь Защиты, были доступны Дрон, Танк и Броневик, причем Танк умел трансформироваться в очень сильную дальнобойную пушку, а Броневик, практически, являлся ходячим дотом, так как может возить Солдат, которая находясь внутри расстреливает противника. Третий путь, именуемый Путь Неизвестного, позволял использовать юнитов Дрон, Камикадзе и Когг. Дополнительно этот путь обладал достаточно интересной возможностью — он позволял присылать воинов в Капсуле (выполняет у Онимодов роль Дома) прямо с орбитальной станции, которая по сценарию висит на орбите планеты. Визуально это выглядит так, что при посадке здания из него вываливаются юниты.
Влияние пути развития не ограничивалось только набором доступных юнитов — некоторые ключевые Апгрейды были доступны только в одном из путей. Например, Путь Нападения, позволял сделать Апгрейд, который давал возможность сажать в Вертолёт до 3-х Штурмовиков, ведущих огонь прямо из Вертолета. В результате выбор Пути развития сильно влиял на общую стратегию ведения боевых действий.
Управление объектами
Юниты и Здания должны выполнять те действия, которые им поручены игроком или AI. Для выполнения этих действий каждый объект имеет в своем составе массив команд. Чаще всего в массиве будет одна команда, которая и будет текущей, но, например, игрок может используя клавишу SHIFT добавить на выполнение сразу несколько команд. Завершив выполнение очередной команды, объект приступает к выполнению следующей. Если все команды выполнены, то объект автоматически добавляет в массив команду COM_WAIT или Ожидание, что в результате запускает анимацию с действием Ожидание.
Также юниты обладают возможностью самостоятельно добавлять к себе в массив команды. Эта ситуация рассмотрена в разделе AI где описываются Инстинкты юнитов. Простой пример: воины получили от игрока команду «Патрулирование» и курсируют между двумя точками. Если обнаруживается неприятель, то воины самостоятельно добавляют себе в начало массива команду Уничтожение или COM_DESTROY и атакуют цель. После того, как COM_DESTROY будет выполнена, она будет удалена из массива команд и управление опять вернется к команде Патрулирование, что приведет к тому, что воины продолжат патрулирование.
Для начала нужно разобраться, что же представляется из себя команда. Простейший случай команды — это COM_GO или Перемещение. Игрок выделил юнита и щелкнул мышкой в произвольное место карты. Это приведет к тому, что в массив команд выделенного юнита попадет COM_GO, которая незамедлительно начнет осуществляться. Эта команда очень проста, так как в качестве аргумента ей нужны лишь координаты точки на карте, к которому должен подойти юнит. Однако обратите внимание, что данная команда не имеет продолжения, т.е. она завершится после того, как юнит достигает цели. На деле же существуют куда более сложные команды, например, Патрулирование, Строительство здания или Добыча ресурсов. Все эти команды имеют несколько фаз, к примеру, Добыча ресурсов подразумевает после того, как текущий ресурс полностью добыт, найти новый ресурс и приступить к его добыче, т.е. команда Добычи ресурса, по сути, не заканчивается до тех пор, пока поблизости есть что добывать.
Или другой пример. Работнику дана команда Строить здание:
- Работник закладывает фундамент. Если на месте закладки фундамента находятся юниты, то они отойдут в сторону, освобождая место.
- Работник осуществляет строительство (оно же ремонт) здания.
- Работник ищет рядом другое недостроенное или поломанное здание. Если такое здание не найдено — завершаем выполнение команды, иначе нужно выполнить Ремонт найденного Здания.
Обработчик команд выполняет команды не постоянно, а только в тот момент, когда завершена очередная анимация. Т.е. шагнул юнит на одну клетку в сторону — теперь можно обрабатывать команды или стукнул Работник киркой по камушку — опять можно обрабатывать команды. Но в середине шага или на половине замаха киркой команды не обрабатываются. Практически, после выполнения очередной команды становится известна следующая анимация.
Чтобы была возможность выполнять команды любой сложности, необходимо разделить их на фазы. Значит любая команда состоит из фаз, и этих фаз может быть N штук. Сначала всегда выполняется первая фаза, но далее фазы могут выполняться в любой последовательности, так как любая фаза может передать управление на любую другую фазу. Каждая фаза может прервать команду, если нет возможности для её выполнения.
Любая фаза, в моем случае, должна уметь выполнять следующие функции:
- INITIALIZAION — Инициализация перед началом выполнения фазы.
- TEST — Проверка цели на пригодность выполнения над ней действия (цель может умереть или уже не нуждаться в выполнении над ней указанной команды). Чаще всего эта функция не требуется, так как большинство команд направлены на другие игровые объекты, а проверка по умолчанию типа «живой / не живой» выполняется автоматически. Команды, не требующие цель, и вовсе не нуждаются ни в каких проверках на её пригодность. Если проверка всё же присутствует, то она вызывается постоянно по ходу выполнения фазы в момент, когда обработчик команд получает управление.
- ACTION — Основное действие фазы, которое является целевым. Выполняется, когда цель достигнута (например, оказалась в пределах зоны поражения). Выполняется один раз, как и Инициализация. Может вместо чего-то конкретного просто передать управление на другую фазу.
- FIND — Выбор новой цели для данной команды. Обычно эта функция не определена. Выполняется один раз в случае, если цель стала недоступна для выполнения над ней действия (здание было полностью отремонтировано — нужно поискать другое поломанное здание).
Большинство функций в фазе могут быть лишними. В этом случае их можно опустить.
Команда Добывать ресурс является одной из самых сложных. Поэтому хотелось бы разобрать именно её более подробно. Визуально добыча ресурсов осуществляется для Онимодов и Ботсваны принципиально иначе, но в основе всего этого лежит один и тот же алгоритм.
Добытчик Онимодов называется ИТР и визуально выглядит как достаточно крупный робот, который закрепляется рядом с ресурсом и осуществляет добычу. Добытые ресурсы он отправляет в Хранилище не двигаясь с места. Визуально это выглядит так, что он погружает в землю «трубу», а на Хранилище анимация этой трубы появляется на одной из шести технических площадок, что визуально демонстрирует, что ресурс доставлен.
Добытчик Ботсваны называется Работник и является как бы «классикой жанра». Он добывает ресурсы при помощи кирки и носит их в Хранилище своими ножками.
Специально в игру для обоих команд было добавлено условие, что добытчики у обоих рас могут одновременно добывать один и тот же ресурс только одним добытчиком.
Итак, игрок выделил мышкой несколько добытчиков и щелкнул правой кнопкой по минералу. Что происходит дальше? Так как я сам ввел условие «каждому ресурсу по одному добытчику», то первым делом я выбираю для каждого добытчика свой персональный минерал, чтобы каждый добытчик сразу направился к своему личному ресурсу. В качестве команды каждый добытчик получит COM_EXTRACT, которая принимает координаты ресурса и его тип как аргумент.
Очень важно то, что какая бы команда не была текущей, алгоритм обработки команд всегда проверяет, присутствует ли у команды целевой объект. Например, команды типа Маскировка (включить невидимость) не имеют целевого объекта, так как они выполняются «на себе» и при этом совершенно точно никуда не нужно двигаться. Но многие команды типа Атака или Добыча ресурса выполняют действие над целью. Это означает, что цель должна быть в пределах досягаемости. Действие над целью всегда выполняется через анимацию Воздействие, которая может создавать для этой цели объект типа Снаряд, либо наносить удар самостоятельно (киркой по минералу). В первом случае достаточно приблизиться на расстояние выстрела (distance = радиус видимости). Во втором случае, очевидно, что объект должен подойти к цели вплотную (distance = 1). Действия по командам обрабатываются только после завершения очередной анимации — анимации не должны резко прерываться. Обработчик команд всегда проверяет, находится ли цель в пределах расстояния distance. Если нет, то обработчик команд будет всегда выполнять движение к цели, т.е. юнит потихоньку пошагает в нужном направлении. Вызов обработчика команд будет происходить с периодичностью в один шаг (имеется в виду переход с одной клетки карты на другую). Опять же уточняю, что движение к цели не входит в саму команду Добыча ресурса, а является частью алгоритма по обработке любой команды.
Команда COM_EXTRACT состоит из 4-х фаз:
- Фаза START_EXTRACTION.
Фаза начинается с INITIALIZATION. Инициализация фазы всегда вызывается до того, как обработчик команд начинает движение к цели — это позволяет подменить цель. Первым делом проверяем, есть ли уже в руках какой-то ресурс. Если есть, то переходим на фазу DELIVER_RESOURCE, т.е. ресурс сначала необходимо отнести в Хранилище.
Если ресурса в руках нет, то можно добывать. Для этого нужно проверить аргументы команды. Если по указанным координатам ресурс отсутствует, то это говорит о том, что он уже добыт (возможно, что другим добытчиком). В этом случае, требуется произвести поиск другого ресурса, преимущественно того же типа, который указан в аргументах команды. Такой поиск не просто просматривает сами минералы, но и определяет факт того, что какой-то добытчик уже трудится над данным ресурсом. Т.е. выбирается именно свободный для добычи ресурс. Кроме того выполняется тестовое построение пути до выбранного ресурса с учетом того, что другие добытчики также являются препятствием на пути. Результатом выполнения инициализации фазы START_EXTRACTION является выбор ресурса, который можно добывать в настоящий момент.
Обычно добытчик находится на некотором расстоянии от ресурса, поэтому нужно подойти вплотную. Обработчик команд автоматически проверяет расстояние до цели и начинает движение, если это требуется. При каждом изменении координат юнита для фазы START_EXTRACTION вызывается функция TEST, которая проверяет на то, что ресурс остается доступным для добычи. Если TEST вернула отрицательный ответ, то для фазы запускается функция FIND, которая ищет новый доступный ресурс и вся команда Добывать ресурс перезапускается с самого начала, но с новыми координатами ресурса.
Если добытчик сумел добраться до ресурса, то для фазы START_EXTRACTION выполняется функция ACTION, которая, правда, ничего не делает, а лишь передает управление на следующую фазу.
- Фаза WAIT_EXTRACTION.
Задача этой фазы — это дожидаться, когда ресурс станет доступен для добычи. Особенно эта фаза актуальна для Работников, так как они постоянно носят ресурсы в Хранилище, позволяя другим Работникам занять их место у ресурса.
Фаза имеет функцию ACTION, которая и осуществляет все эти проверки. Если ресурс пока занят для добычи, то функция включит ожидание на пару секунд, после чего будет выполнена повторная проверка на доступность ресурса к добыче. Если после ожидания добывать по прежнему нельзя, то осуществляется поиск другого доступного ресурса на максимально близком расстоянии и управление передается фазе START_EXTRACTION. Если ресурс освободился или изначально не был занят, то управление сразу передается следующей фазе, которая уже и осуществляет добычу. В случае, если ресурс за время ожидания оказался полностью добытым, то вызывается поиск нового ресурса и управление опять передается на START_EXTRACTION.
- Фаза EXTRACT.
Представляет из себя непосредственную добычу ресурса.
Сначала выполняется INITIALIZATION, которая делает для добытчика текущим Воздействие типа Добыча ресурса.
Функция ACTION осуществляет процесс добычи, которая вызывает на выполнение действие Добыча ресурса. Все типы Воздействий обрабатываются одним и тем же алгоритмом, поэтому само осуществление Воздействия не имеет никакого отношения к команде Добыча ресурса. Однако мне кажется, что дополнительные объяснения по этому поводу позволят связать то, что я говорил уже про стадии Воздействия (Подготовка в Воздействию, Воздействие, Перезарядка оружия, Пауза между Воздействиями, Выход из Воздействия) с логикой выполнения команд.
Выполнение любого Воздействия требует разворота к цели лицом. Для Работника тут всё достаточно просто — у него Воздействие осуществляется при помощи одной анимации (машем киркой). Однако ИТР или добытчик Онимодов имеет несколько дополнительных анимаций для выполнения Воздействия.
Для начала ИТР должен закрепиться около ресурса — визуально он как бы приседает на землю для чего используется анимация типа Подготовка к воздействию. И только после этого включается анимация основного Воздействия, которая визуально выглядит как тыканье механической клешней в ресурс. Обе эти анимации (присесть и добывать) в редакторе ресурсов настраиваются так, что их прерывание игроком без выполнения переходной анимации невозможно, т.е. если игрок захочет управлять ИТР-ом, то тот сначала запустит анимацию, которая визуально выглядит как «открепление от ресурса», т.е. ИТР сначала выйдет из присевшего состояния.
Функция TEST позволяет определить 2 момента:
- Не добыт ли еще ресурс полностью ?
- Может хватит добывать и пора отнести добычу в Хранилище ?
В случае положительного ответа управление передается на фазу DELIVER_EXTRACTION, иначе продолжает выполняться Воздействие по Добыче ресурса.
- Фаза DELIVER_RESOURCE.
Задача этой фазы — доставить собранные добытчиком ресурсы в Хранилище. Как это не странно, данная фаза во многом аналогична фазе EXTRACT, так как, практически, эта фаза вместо Воздействия типа Добыча ресурса использует Воздействие под названием Доставка ресурса. Как я уже говорил в начале, типов Воздействий в редакторе ресурсов достаточно много и каждое уникальное действие над другим объектом требует собственный тип Воздействия. Сюда же, естественно, относятся и все виды магии, которые существуют в игре.
Опять же сначала выполняется инициализация фазы через функцию INITIALIZATION, которая делает для добытчика текущим Воздействие типа Доставка ресурса. Далее необходимо определиться с Хранилищем, куда рациональнее всего доставить ресурс. Хранилищ на карте может быть много, поэтому надо выбрать самое ближайшее. Однако производить этот поиск каждый раз нерационально, так как Хранилища не перемещаются по карте и, стало быть, достаточно выбрать Хранилище один раз, запомнить его и далее носить ресурс именно туда. Собственно, я почти так и делаю, только если вдруг появляется еще одно Хранилище, то я сбрасываю найденное ранее Хранилище у всех добытчиков на карте, чтобы поиск был произведен повторно. Для большей интеллектуальности можно выполнять новый поиск еще и периодически, так как в теории добытчик может очень сильно удалиться от первоначального места добычи.Функция ACTION выполняет действие Доставка ресурса. Для Работника здесь опять же всё относительно просто, так как в качестве цели выступает Хранилище, а у Работника анимация Доставки ресурса не создает никаких Снарядов, то он должен подойти вплотную к Хранилищу, поэтому он топает до него ножками. У Онимодов ИТР поступает значительно более интересно. Его анимация по доставке ресурса содержит создание Снаряда, но этот снаряд не разрушает цель, а обладает Воздействием типа Доставка ресурса, т.е. ударив в Хранилище он перебросит ресурс своей команде. Это избавляет от необходимости каждый раз бегать до Хранилища и обратно. Само Воздействие имеет несколько стадий. Сначала происходит подготовка к Воздействию (ИТР погружает в землю трубу), затем выполняется анимация Воздействия, которая кидает в Хранилище невидимый снаряд, летящий с очень высокой скоростью. Снаряд же обладает интересной функцией, которую я ранее не упоминал, он прикрепляется к цели, практически, происходит что-то типа посадки в Транспорт, т.е. Снаряд как бы попадает внутрь Хранилища, как будто в дот. У Хранилища заранее определены 6 посадочных мест, куда могу прикрепляться «пассажиры», и Снаряд прикрепляется к свободному месту и тут же запускает анимацию, которая на Хранилище наблюдается в виде вылезшей из земли трубы. Визуально вся схема выглядит так, что ИТР запустил трубу под землю, а она вылезла на Хранилище. Труба на Хранилище выполнив анимацию просто умирает, так как её анимация заканчивается действием Смерть. Затем ИТР выполняет анимацию Выход из воздействия, т.е. затягивает обратно трубу. Всё, ресурс доставлен. Естественно, что все анимации ИТР на стадии доставки настроены так, что игрок не сможет мгновенно получить контроль над юнитом — ИТР сначала вытащит трубу из земли, а потом уже сможет куда-то поехать.
Далее управление опять передается фазе START_EXTRACTION, но так как ИТР уже готов к добыче, т.е. находится рядом с ресурсом и уже в закопанном состоянии, то фазы START_EXTRACTION и WAIT_EXTRACTION в 99,99% случае будут пропущены и сразу начнется фаза EXTRACT. Исключение произойдет в случае, когда Хранилище находится уже слишком далеко и Снаряд просто не долетает до цели. В этом случае анимации отработают свою задачу следующим образом:
Так как расстояние слишком далеко, то необходимо подойти к цели поближе, а ИТР в этот момент находится визуально в «присевшем» состоянии. Обработчик команд велит ему шевелить колёсами в сторону Хранилища, но из анимации добычи нужно сначала выйти, поэтому сначала выполниться анимация, которая визуально открепляет ИТР от ресурса. Теперь можно ехать и ИТР направится к Хранилищу. Подъехав на достаточное расстояние ему нужно будет выполнить Воздействие типа Доставка ресурса, но у неё есть подготовка, поэтому он сначала выпустит трубу. После того, как ресурс будет доставлен, управление передастся на фазу START_EXTRACTION, но так как до ресурса теперь далеко, то опять придется к нему ехать.
Приведенный пример имел целью продемонстрировать механизмы лежащие в основе игровой механики буквально «в двух словах». Мне хотелось, чтобы та информация, которую я приводил насчет возможностей редактора ресурсов была как-то связана с конкретными действиями юнитов. Обратите внимание, что команда COM_EXTRACT и для Работника, и для ИТР является полностью одинаковой с точки зрения кода. Но настройки анимаций юнитов в редакторе ресурсов делают процесс добычи ресурса абсолютно разным. Именно поэтому важно иметь широкие возможности по настройке анимаций.
Под каждое действие, которое можно выполнить должна существовать своя команда. При этом, например, команды COM_GO (идти в указанную точку), COM_DESTROY (уничтожить конкретную цель), COM_ATTACK (идти в указанную точку, уничтожая всех на своем пути), COM_RUN (отбежать от атакующего врага, так как нет возможности защищаться) и COM_LEAVE (отойти с места, где будет построено здание) являются, по сути, разными. В моем случае в коде я наблюдаю около 50 команд, которые и обеспечивают функционирование всей игры.
Также обратите внимание, что есть даже такая милая команда как COM_CANCEL, которая просто отменяет все другие команды. Зачем она нужна? Дело в том, что в конце статьи я кратко опишу сеть, а по сети должны уметь передаваться любые действия, которые выполняет игрок, так как эти действия должны быть повторены один в один на другом компьютере.
Поиск пути
Юниты должны уметь перемещаться по карте и находить эффективный маршрут в сложных случаях. Этому вопросу должно быть уделено достаточное внимание и время. На начальной стадии я бы рекомендовал сделать любое простейшее решение, лишь бы можно было тестировать создаваемых художниками юнитов. Реальный же алгоритм поиска пути понадобится в момент создания AI, когда компьютер должен будет иметь возможность построить путь в любой самой витиеватой ситуации. Без нормального алгоритма поиска пути AI не сможет действовать эффективно, кроме каких-то исключительных случаев, когда игровое поле представляет из себя «голую степь».
Итак, у нас более менее отлажена игровая механика, юниты умеют выполнять предписанные им действия, которые они получают при помощи мыши, при этом программа не вываливается через каждые 2 минуты. Следующая стадия — это создание эффективного алгоритма поиска пути. В настоящий момент в Интернете присутствует немало советов на эту тему, я не могу сказать, насколько хороши или плохи эти методы, так как в своё время изобрел для себя собственное и, на мой взгляд, очень эффективное решение по этому поводу. Вполне возможно, что оно примерно повторяет какой-то опубликованный впоследствии алгоритм, так как решение действительно достаточно изящное. Но, как я уже сказал, все решения, которые я показываю в данной статье на тот момент я генерировал своей головой самостоятельно.
Подробная статья по моему методу поиска пути в классических RTS имеется здесь. Помимо, собственно, самого алгоритма поиска, там описан процесс управления этим алгоритмом. Этот раздел касается таких проблем, как столкновение юнитов между собой во время движения, следованию за другим юнитом (цель постоянно меняет своё положение) и прочих не таких уж и очевидных моментов. Статья писалась лет 10 назад и лично для меня имела целью «не забыть» о собственном решении. Дублировать эту информацию здесь особого смысла я не вижу, особенно учитывая, что этой информации и так достаточно много.
AI (он же Искусственный интеллект)
Давайте сначала разберемся с тем, что же такое AI. Лично я для себя определяю AI как алгоритм, который заменяет собою действия живого игрока, которые тот выполняет при помощи мыши и клавиатуры. Теперь обратите внимание вот на что… если в вашей игре юниты не совсем тупые, то они могут самостоятельно принимать некоторые решения и выполнять их без помощи игрока. К таким решениям в моем случае относятся следующие действия:
- Воины, замечая врага в пределах видимости, должны самостоятельно вступать в бой. Более того, если какой-то воин из отряда ввязался в драку, то «коллеги по оружию» не должны стоят и равнодушно «курить» чуть в сторонке от места действия — они обязаны тоже вступить в бой.
- Если юнит умеет лечить, то он должен автоматически вылечивать раненных воинов. Это особенно эффективно, если отдать приказ лекарю следовать за каким-нибудь воином из отряда — слоняясь вслед за отрядом Лекарь будет лечить каждого, кто в этом нуждается.
- Работник, стоящий без дела, будет автоматически чинить раненное здание или помогать строить новое строение. В моем случае, Работник даже анализирует «степень поломанности» зданий и может прервать текущий ремонт и начать чинить здание, которое больше в этом нуждается. Это качество может быть очень полезно, когда Работники «суетятся» между несколькими Защитными башнями, которые одновременно находятся под обстрелом. Усиленный ремонт должен производиться у той башни, которая вот-вот готова рухнуть. Дополнительно алгоритм следит за тем, чтобы распределение Работников по «строительным объектам» было разумным, а не по принципу «все силы на одно здание».
- Добытчик ресурсов должен автоматически находить рядом следующий ресурс, если текущий закончился. Если такого вида ресурса рядом нет, но есть другие виды ресурсов, то нужно посмотреть каких ресурсов у команды меньше и добывать именно этот вид ресурса.
- Юниты, которые обладают магией должны самостоятельно следить за тем, чтобы не помереть понапрасну. Практически, когда у мага вычитается жизнь, то он проверяет количество оставшейся жизни и, если её уже критически мало, то логично рассуждает, что игрок, скорее всего, не успеет никак отреагировать. В этом случае «ничегонеделание» равносильно тому, что маг просто будет убит не нанеся противнику никакого урона, поэтому маг применяет магию самостоятельно.
- Юнит, который может включить режим маскировки (невидимость), автоматически включит его, если по нему нанесен удар, а он не находится в поле зрения Определителя (юнита, который размаскировывает невидимых врагов).
- Юниты умеют автоматически отходить в сторону, если они мешают возведению какого-то строения.
- С некоторым натягом сюда же можно отнести и умение юнита следовать за подвижной целью, т.е. если одному юниту приказать следовать за другим, то тот будет ходить за ним везде как привязанный. При этом «привязанный» юнит вполне может расстреливать врагов или осуществлять лечение дружественных юнитов, а закончив это дело снова бежать за «целью».
Как видно из приведенных примеров, все эти действия не требуют вмешательства игрока. Соответственно, моя собственная формулировка о том, что «AI — это алгоритм, который заменяет собою действия живого игрока, которые тот выполняет при помощи мыши и клавиатуры» не соответствует данным примерам. А значит, все эти примеры не имеют к AI никакого отношения. Лично для себя я в своё время дал этим действиям название «Инстинкты». Инстинкты юнитов — важнейшая часть игры, так как они снимают с игрока заботу о рутинных действия, за которыми часто просто невозможно уследить. И именно от качества имеющихся Инстинктов зависит общая комфортность игры. Инстинктом обладает конкретный юнит, AI же управляет группами юнитов и выполняет глобальные задачи типа «Основать новую базы для добычи ресурсов».
Причины проявления Инстинктов в игре человек часто видит с точностью до наоборот. Например, когда Работник бежит чинить здание, то создается впечатление, что это является инициативой самого Работника. На деле же горящее Здание «зовёт на помощь» в небольшой радиусе от себя всех юнитов, которые имеют в своем арсенале Воздействие типа Ремонт. То же самое касается и ситуации, когда к воюющему воину подбегают на помощь рядомстоящие воины — это реакция на призыв «Помоги-и-и-те! Убива-а-а-а-ют!».
Несмотря на то, что, по моему мнению, Инстинкты не имеют к AI никакого отношения, я поместил их небольшое описание в раздел с AI, так как для многих такое размещение покажется вполне ожидаемым. Но, на мой взгляд, Инстинкты относятся к общей игровой механике, т.е. по уму их надо было описывать немного выше по тексту. А игровая механика на момент написания AI должна уже полностью функционировать включая раздел об Инстинктах юнитов. Инстинкты должны работать одинаково как для команды, которой управляет живой игрок, так и для команды, которой управляет AI. В своих решениях AI не должен опускаться на уровень Инстинктов, его задачи должны звучать примерно так «сформировать отряд и отправить его в атаку по таким-то координатам», а то, что этот отряд будет уже самостоятельно крушить всех на своем пути — это уже задача Инстинктов.
Итак вернемся непосредственно к AI. В моем случае для каждой команды, которой управляет компьютер, обработчик AI вызывается примерно 1 раз в секунду. AI руководствуется в своих действиях следующими глобальными принципами:
- Есть команды врагов и есть команды союзников. Соответственно, врагов надо уничтожать, а союзникам приходить на помощь в трудную минуту.
- Какая-то из команд врагов назначается основной целью для нападения, но периодически этот враг может меняться.
- У AI есть понятие База, которое является ключевым. База является, по сути, скоплением из нескольких строений на карте. У каждой базы есть свои границы — прямоугольник, который занимает данная база. Если произошло взаимопроникновение двух баз, то AI производит их объединение. На каждой базе строится собственная оборона, а любой юнит является привязанным к какой-то базе, до момента, пока он не получил какое-то задание.
- Основные задачи, которые выполняет AI:
- Развитие
- Нападение
- Защита
- Поиск противника
- Определение невидимых воинов врага с помощью определителя
Если быть точным, то интеллект умеет выполнять 15 видов задач. Самое интересное — это конечно задачи, которые относятся к нападению. Всего интеллект умеет атаковать 6-ю разными способами:
- Простая атака. Отряд собирается рядом с целью атаки, но там где его не видно и затем атакует.
- Осадная атака. Чем-то напоминает простую, но отряд, расположившись рядом с целью не бежит сразу атаковать, а ждет пока будет накоплено достаточно сил. Силы постепенно подтягиваются и когда их достаточно, происходит атака. Побочный эффект от осадной атаки это то, что параллельно происходит блокада иногда единственного входа в лагерь противника. Также AI может во время осады выкатить вперед Катапульту и начать расстреливать издалека Защитные башни противника, основные силы при этом стоят позади Катапульты и ждут реакции осаждаемого.
- Уничтожение конкретной цели. Иногда некоторые юниты врага (обычно танки), расположены так, что они кромсают силы атакующих без каких-либо серьезных потерь со своей стороны. Этот момент отслеживается и периодически интеллект предпринимает карательные рейды конкретно против этих юнитов. Для этого используется летающий транспорт, с которого воины высаживаются прямо на целевого юнита.
- Диверсионная атака. Карательный рейд в сердце базы противника. Для этого применяются Транспорты, которые высаживают воинов за линией обороны врага. В основном, атакуются Хранилища с большим скоплением работников. Второй вариант этой атаки – это высадка воинов на ближайший склон, откуда можно хорошо постреливать вниз и куда враг не может подняться.
- Магическая диверсия. Может исполняться только либо Шаманом (от Ботсваны), либо Научным модулем (от Онимодов). Исполнители начинают просто околачиваться рядом с лагерем противника и иногда покидывают магию в зазевавшегося вражеского воина.
- Суператака. Это простая атака, которая делается совместно. Перед ней происходит небольшое затишье – накопление сил. Далее все AI, находящиеся в одном союзе совершают нападение одновременно.
Жанр: RTS — стратегия в реальном времени
Программирование: Алексей Седов (он же Odin_KG)
— Технологии программирования: C++, Assembler, DirectDraw
— Системные требования: Windows XP, Windows 7 (сеть в 7-ке не работает, но в остальном всё в порядке)
Графика: Роман Коваленко, Константин Иванов
— Стиль графики: 2D изометрия (используется примерно 12 тысяч спрайтов)
Музыка: Дмитрий Голов
Способ разработки: Энтузиазм
Время разработки: 1998 — 2005
Страница для скачивания: Земля онимодов ~ 53 Мб.
Способ распространения: бесплатно
Стартовые условия и их последствия (лирический раздел)
Итак, на дворе 1998 год. Окинем беглым взглядом окружающую действительность тогдашей IT-индустрии. Intel выпустил процессор, аж, на 233 МГц, Blizzard уже известен благодаря «Diablo 1» и «Warcraft 2», а Microsoft отличилась с помощью «Age of Empires 1». Почти на всех ПК установлен «Windows 95», который некоторые уже пытаются проапгрейдить до «Windows 98». На барахолках всей необъятной родины тоннами расходятся пиратские диски, очень популярные в народе из-за низкой стоимости. Лицензионный софт может где-то и существует (например, в Москве), но, как минимум, люди даже не понимают разницу между пираткой и лицензией. Еще один важный уточняющий момент — в стране сильнейший экономический кризис, так как доллар в течение недели вырос с 6-и до 30-и рублей, соответственно, стоимость всего, что относится к «железу» выросла пропорционально доллару. Экономика лежит…
Итак…
Группа отчаянных ребят численностью 3 человека, в этот непростой для родины час, решила попробовать себя в качестве разработчиков игр. Среди них 1 человек, который надеется, что он более-менее умеет программировать (это автор статьи) и 2 человека, которые искренне любят рисовать, в том числе и на компьютере.
У каждого из нас был собственный ПК, но характеристики соответствовали моменту. Например, моя конфигурация была такая: Intel 200-MMX, ОЗУ — 32 Мб, Видеокарта — 2 Мб, HDD — 4Гб. К слову говоря, такое чудо обошлось мне до кризиса примерно в 9000 рублей (при средней зарплате по стране в районе 700 рублей), что соответствовало 1500$. У художников ПК были куда более скромными.
В общем-то всё, что у нас было — это желание + «современная техника». Хотя я забыл, пожалуй, о самом важном — мы были хорошими друзьями, которые знали друг друга со школы и прошли вместе также и ВУЗ. И как оказалось этот фактор оказался в результате очень важным.
Пожалуй, стоит упомянуть о том, чего не было. А не было в то время достаточной информации. Именно той информации, от которой сейчас интернет просто захлебывается. Не было, кстати, и самого интернета, точнее он был, но у очень ограниченного числа людей, а скорость по Dual-Up модему максимально достигала 5 Кбайт/сек, к которым прилагались частые обрывы связи.
Всё это означает буквально следующее, что нет примеров программирования, нет алгоритмов, нет видеоуроков по обучению тому, как работать с 3D Studio Max, нет готовых движков, наконец. Не у кого спросить совета на форуме. Готовых ответов на вопросы, которые сейчас сходу «гуглятся», тем более нет. Т.е. есть только разработчик и его задача, которую необходимо решать самостоятельно и рассчитывая только на свои силы.
Вообще стартанули мы достаточно неплохо. Я потихоньку реализовывал то, о чем написано в статье, а художники предоставляли мне графику. Основная часть графики делалась в 3D Studio Max, для анимации юнитов использовался плагин Character Studio. К счастью, удалось каким-то чудом раздобыть книжку по этому плагину и более-менее разобраться. Существовала такая проблема, что 3D Max не мог толком работать на таком слабом железе, которое еще и управлялось с помощью Windows 95. При старте 3D Max возмущался, что ОЗУ должно быть минимум 48 Мб, а на компьютере его было только 16. Надо ли объяснять, с какой скоростью всё это функционировало. Исправление любой мелочи могло занять много часов. Дополнительно 3D Max очень любил схлопываться без видимых причин.
Однако художникам очень понравился мой редактор ресурсов, хотя он и выглядел как «инструмент для внутреннего пользования». Сначала они долго не понимали, что собирать готовых юнитов можно и без меня, а после произошедшего «озарения» на этот счёт у них появился новый виток энтузиазма. Константин стал периодически притаскивать мне не просто набор спрайтов, а уже вполне «живого» юнита. На тот момент сама возможность вставлять в игру объекты без участия программиста казалась чем-то фантастическим.
Ситуация резко ухудшилась, когда мы решили, что уже есть что показать издателю. Издатели были в Москве, и мы просто купили билеты на поезд и отправились показывать свою работу. На тот момент прошло уже года полтора, с начала всего этого действа. AI, конечно, не было еще и в помине, не было хорошего алгоритма поиска пути, но очень многое уже работало. Нам надо было выяснить, хоть что-то конкретное по поводу дальнейших перспектив нашего проекта. И перспективы были выяснены… и они в общем-то, практически, похоронили данный проект. Юмор ситуации был в том, что вроде бы издатели хотели издавать, но, когда нам озвучили условия, стало ясно, что это всё очень напоминает рабство. После возвращения из Москвы, художники, практически, прекратили работу над игрой — и в общем-то это было во многом правильное решение. Периодически, правда, мне что-то дорисовывали, но все уже понимали, что этот проект не может принести каких-то денег, которые не выглядели бы унизительно на фоне потраченных на него усилий.
Твёрдое мнение о том, как в россии в то время производилось «издание» у меня появилось уже позже, когда мне более-менее удалось получить в голове целостную картину, которая, собственно, всё объясняла. Не могу настаивать на её абсолютной истинности, но после того, как у меня появилось это объяснение, все вопросы были сняты. Пожалуй, я потрачу еще минутку на расстановку всего по своим местам.
Как я уже сказал — страна была просто завалена пиратскими дисками. За этими дисками «челноки» ездили в Москву, где можно было приобрести диски оптом, чтобы потом реализовать их на барахолке в родном городе. Цены на диски не сильно отличались от цен на пустые болванки, и сама цена в пределах одного рынка всегда была одинакова для любого продукта, который содержался на этом диске. Теперь вернемся к изданию лицензионных продуктов. По моему мнению, оно выглядело примерно так: у издателя в Москве было несколько своих точек сбыта (ларьков), через которые издатель реализовывал лицензионные диски. При этом выставить какую-то реальную цену на лицензионный софт было навряд ли возможно, так как:
а) Процветают пиратские барахолки.
б) Население страны просто не может покупать софт за нормальную цену, особенно, когда можно купить пиратский диск задёшево.
Поэтому стоимость лицензионных дисков отличалась от пиратских далеко не в десятки раз. Причем издатель должен был сначала печатать диски, тем самым производя затраты.
Чтобы не тянуть время, я озвучу стандартные условия, которые предлагались разработчику:
Издатель всегда требовал эксклюзивные имущественные права. В случае, практически, законченной игры разработчику предлагался аванс 10 000 $ + роялти. Роялти (они же процент с продаж) по максимуму могли быть 25%, чаще предлагали от 15% до 20%. Проценты, в теории, должны были выплачиваться после того, как издатель погасит свои предварительные затраты, включая аванс. По поводу роялти, сразу хочется сказать, что, по имеющимся у меня данным, их никому не платили, так как для издателя в этом просто нет особого смысла. Надо понимать, что на тот момент идея о том, что издателя можно как-то проверить была полным мифом. Я сомневаюсь, что это легко сделать сейчас, но, по крайней мере, в настоящий момент об этом хотя бы можно всерьез мечтать. А так… в стране, где бизнес всячески укрывает налоги, рассчитывать на то, что удастся выявить какие-то записи о реальном состоянии продаж, на мой взгляд, наивно.
Итак, нам как и всем предложили эти 10 000 $. Уточню, что это не в день, а ВСЕГО, а то казуальщики, которые попадают в топ на AppStore могут неправильно понять. Т.е. разработчик ставился в ситуацию, когда он должен как-то выдавать достаточно серьезную по трудозатратам игру, но предполагаемая оплата никак не соответствует этим трудозатратам. В общем-то это напрямую связано с тем, что издатель, по сути, никаким издателем не являлся, т.е. он не выполнял свою основную функцию — он не мог ничего в реальности продать, так как продажа по своим «пятнадцати ларькам» в одном городе (при наличии эксклюзивных прав) — это не издание. О каком-то продвижении вообще речи не было, хотя в общем-то это тоже обязанность издателя. Насчет продвижения в «Буке» нам сказали, что они своим подписчикам рассылают новость на e-mail — численность подписчиков на тот момент была 11 000 человек. С «Букой» вообще было достаточно весело, так как ихняя девочка-менеджер на полном серьезе поведала мне, что они Blizzard-у за издание тоже заплатили аванс 10 000 $. В общем вся эта ахинея, на мой взгляд, была направлена на то, чтобы убедить разработчика в том, что «иначе нельзя» и надо «пока потерпеть», а зато «потом» и им тоже «что-то начнет перепадать». Точных сроков на этот «потом» не называлось, но делался намёк на то, что, возможно, следующий проект, будет рассматриваться на других условиях.
Чуть позже мне случайно стала доступна очень занимательная информация. Оказывается, некоторые очень известные пиратские компании типа того же Fargus-а, на деле являлись дочерними компаниями российских издателей. И это простое объяснение кажется мне более чем логичным, так как лично для меня оно не оставляет больше никаких вопросов.
А еще в позапрошлом году познакомился я в Интернете с одним человеком, который, как потом выяснилось, в те самые времена занимался локализацией игр (говоря по простому, они брали заказы на перевод игры с английского языка на русский). Он мне пожаловался на то, что им за локализацию платили всего-то 5000 $. Т.е. перевести игру стоило всего лишь в 2 раза дешевле, чем её сделать. И сейчас я понимаю, почему было именно так — в россии было хорошо налажено только пиратство, но не было, по сути, никакого издания. И оно так и не появилось, а вместо него появился высокоскоростной Интернет, который открыл доступ к западным торговым площадкам.
Что же случилось с игрой дальше?
Последние годы я доделывал эту работу в гордом одиночестве. Почему я не бросил эту затею? Видимо потому что, я понимал, что второй раз я такое уже не вытяну, а вытянуть очень хотелось, тем более, что было уже очень много сделано. Да и, честно говоря, я терпеть не могу сдаваться и бросать начатое на полпути — это что-то вроде «дара» и «проклятия» в одном лице. Также у меня были серьезные просчеты в сроках, т.е. я предполагал, что удастся закончить гораздо быстрее, но на деле мелочи сжирали всё время. А чем дальше я продвигался вперед, тем обиднее было бы признаться себе, что «я не смог». Вот так я и дошел до конца. Под конец вынужден был кое-что дорисовывать самостоятельно — меню, например. Рисовать я, в общем-то, не умею и не особо стремлюсь научиться, но деваться было некуда.
Музыку к игре взял у своего приятеля, который жил в соседнем подъезде — он мне дал штук 15 мелодий на выбор. Также как и многие творческие люди, он не имел никакой возможности продавать своё творчество, поэтому его мелодии были, естественно, никому неизвестны. По-моему, он их набирал нотами в какой-то музыкальном редакторе, но так как у него имелось музыкальное образование и абсолютный слух, то, на мой взгляд, произведения вполне достойны внимания. Чувствуется симпатия автора к произведениям Жан-Мишеля Жарра, а мне «космическая» тематика очень даже подходила.
Остальная озвучка была сделана, прямо скажем, не очень профессионально, так как уже не было никаких сил этим заниматься — надо было заканчивать эту эпопею и начинать жить дальше. В основном все фразы проговорили в обычный микрофон мои знакомые, а «вопли смерти» я озвучивал сам. В результате эта озвучка многим не понравилась, хотя лично меня она совершенно не раздражала.
Чем всё это закончилось… да в общем-то ничем. Я бросил делать игры, так как понял, что рассчитывать на кого-то кроме самого себя я уже не смогу, а игры, как минимум, требуют оформления. Примерно год после завершения не мог себя заставить ничего делать вообще, так как просто не видел смысла что-то начинать. Потом всё же пришёл в себя и решил сделать небольшой редактор спецэффектов, скорее, из «спортивного интереса», чем ради какой-то выгоды. Впоследствии этот редактор получил название «Magic Particles». После того, как я показал редактор игроделам, мне стали предлагать добавить в него API, чтобы спецэффекты можно было воспроизводить из собственных программ. Двигалось всё это очень сложно, но в результате у меня появились продажи. Игроделы периодически покупали мою технологию в основном для казуальных игр.
В настоящий момент я продолжаю заниматься своим Magic Particles, и к моему удивлению обнаружил, что он стал уже крупнее, чем проект Земля онимодов, который, как я думал, является моим теоретическим пределом.
Игру я недавно решил выложить на Steam и в настоящий момент разместил её в Greenlight. Я рассудил так, что в любом случае, я ничего не теряю, так как работа-то в общем уже сделана. Классических RTS вроде бы сейчас почти нет, так что мало ли… вдруг кому-то это будет интересно. По крайней мере, сами авторы по выходным частенько собираются, чтобы поиграть в собственную игру по сети.
Если удастся выйти на Steam, то кое-что мне придется доработать, но на общем фоне проделанной работы — это уже мелочи.
Благодарю за проявленное упорство в чтении данной статьи, а, также, за терпение к моему «литературному таланту».
Автор: Odin_KG