Искусственный интеллект (AI) в играх занимает скромное место в приоритетах развития уже в течение длительного времени по многим причинам, но будущие игры, безусловно, будут с гораздо более подробным иговым ИИ. Если ИИ вашей игры не дотягивает до уровня, которого ожидает от него игрок, то мнение о ней будет незавидное.

Игровые ИИ — это не просто нейронные сети, системы обучения и сложные математические структуры, хотя и это в них может быть, в первую очередь игровой ИИ воссоздаёт окружающий мир и проявление игровыми единицами разума. Игровой ИИ — в первую очередь поведенческий, а не научный.

Ключ к пониманию, как создать игровой ИИ — понимание того, какими вы хотите видеть конкретные результаты его проявления, а затем построение системы для представления этих результатов. Все это сводится к тому, что может увидеть игрок. Если игрок не видит, что что-то происходит, с тем же результатом этого можно было и не делать.

Примеры и обсуждение будет дано на основе формата Real-Time Strategy (RTS), однако некоторые из этих концепций могут также подойти и для других жанров. Все приведённые примеры выполнены в формате standart C.

Автоматы состоний (state machines)

Конечный автомат (Finite State Machine)

Конечный автомат (FSM) является системой, которая имеет ограниченное число состояний операции. Реальным примером может быть выключатель, который может быть включен или выключен, или будильник, который либо ждет заданное время, либо звонит, либо не заведён. Любая система, которая имеет ограниченное число состояний и даже их комбинаций, может быть определена как конечный автомат (КА).

Конечные автоматы являются естественными для любого типа компьютерной программы и понимание того, как эффективно их использовать для создания игрового ИИ ровно настолько же сложно, насколько и понимание системы, которую вы пытаетесь получить в итоге, которая может быть настолько простой или наоборот детализированной, какой вы сами её сделаете.

Использование конечных автоматов

Есть много целей использования КА в играх, но одна из наиболее сложных из них, с которой вам придется иметь дело — попытки задать модель поведения юнита, симулирующую поведение человека. Хоть успешная имитация поведения человека гарантированно трудна, было уже достаточно много историй о детализированных игровых ИИ, которые были ошибочно приняты за живых игроков, особенно некоторые подробные системы КА.

Хотя есть и некоторые другие системы для более точного моделирования пути человеческого мышления и обучения, иногда ИИ просто не способен совершить тот же выбор, который совершил бы человек, предложи вы этот выбор ему. При чтении о принятии AI решений и их системах обучения всегда имейте это в виду, так как зачастую лучшей системой на практике является простая и не самая научно обоснованная.

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

Автомат игровых состояний

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

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

Вторая машина состояний будет иметь дело с тем, что на самом деле происходит в игре — текущее состояние мира, объектов на уровне, завершены ли задачи миссии и выполнены цели, поставленные перед игроком.

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

struct GameLevelState {
int alert;            // тревожный статус врагов //
struct Positionshot;  // место последнего прозвучавшего выстрела //
int shotTime;         // игровой цикл, когда последний раз звучал выстрел //
int hostage;          // заложник спасен //
int explosives;       // взрывчатка установлена или нет //
int tank;             // танк уничтожен //
int dialogue;         // переменная диалога //
int complete;         // миссия завершена //
};

 

Гибкость

Поддержание вашего ИИ гибким является чрезвычайно важной задачей. Чем более модульным вы сделаете ваши процедуры, тем сильнее вы сможете расширить их по мере развития. Очень важно понимать, что разработка игрового AI очень итерационный процесс, вы должны попробовать что-то новое и переделывать старое.

Цель создания хорошего ИИ — иметь юниты, реалистично реагирующие в ситуациях, формирующихся в среде, с которой они взаимодействуют. Если вы намертво зашьете действия, которые ваши юниты будут в состоянии произвести, слишком рано, будет трудно расширить их действя в дальнейшем, когда вы решите увеличить игровой мир для создания его более полным или интерактивным.

Анатомия 101

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

struct Character {
struct Positionpos;               // позиция на карте //
int screenX, screenY;             // позиция на экране //
int animDir, animAction, animNum; // информация об анимации — действии и кадре //
int rank;                         // ранг //
int health;                       // здоровье //
int num;                          // номер юнита //
int group;                        // номер группы //
int style;                        // стиль юнита (elf, human) //
struct AnimationObject animObj;   // Объект анимации для комплексных анимаций //
};

 

Теперь некоторые определения переменных:

Переменная pos определяет позицию юнита в игровом мире, а переменные screenX, Screeny полезны для легкого вывода на экран информации вокруг юнита на экране, такой как рамка выбора или информация о здоровье персонажа.

AnimDir, animAction и animNum — все они относятся к текущей анимации действий персонажа, которая будет отображена на экране.

Переменные rank и health представляют информацию, о которой легко догадаться из их названий.

Переменная num — порядковый номер юнита в массиве всех юнитов. Позже, при вызове информации о юните из всей группы юнитов иногда полезно передать номер юнита вместо указания фактического адреса структуры в памяти.

Переменная group определяет, к какой группе принадлежит юнит, т.к. юнит практически всегда должен принадлежать какой-то группе. Единственный случай, когда юнит не должен быть в группе — если он мертв.

Переменные style и animObj обе содержат дополнительную информацию о том, как будет отображено графическое представление юнита.

Строительство прошлых основ

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

Вам нужно будет подумать о том, какие действия и реакции вы хотите увидеть от них. Хотите ли вы, чтобы юнит руководствовался эмоциями? Чтобы он имел возможность замерзнуть? Бежать? Прыгать как сумасшедший?

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

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

Любой из этих признаков может убить хорошую во всех других отношениях игру, поэтому хорошо обдумайте свой ИИ, и главное, играйте с ним до смерти, чтобы убедиться, что он работает!

Группировка

Группировать или не группировать?

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

Нужно ли вашим юнитам действовать скоординированно?

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

Преимущества группировки

1. Юниты могут двигаться в формации, используя всего один общий маршрут движения. Преимущество тут в том, что вам не нужно распространять информацию о движении на каждую единицу в группе, все юниты в ней могут получать информацию о движении от негого группового источника.

2. Скоординированными действиями группы юнитов, такие, как окружение здания, можно управлять из одного центра вместо управления каждой отдельной единицей, пытаясь каждый раз вычислить её расположение по отношению к другим и постоянно передвигая вперед-назад в поисках верной позиции.

3. Группы могут хранить свою структуру таким образом, что отдача группе новых приказов займет столько же времени и усилий, как и отдача приказов одному юниту. Если попробовать заставить 25 юнитов передать приказ друг другу, это может выйти довольно муторно.

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

Большая Картинка

Организация вашей группы, как и все остальное в создании ИИ, требует для начала полностью понять, каким вы хотите видеть управление юнитами. Идея заключается в создании некоего места — центрального хранилища информации, которая может быть быстро доступна группе единиц. Идея так же заключается в том, чтобы не дублировать никакие данные, которые уже содержатся в некоем источнике, и этот источник должен быть самым логичным местом хранения информации с тем, чтобы доступ к ней был легок и быстр для всех юнитов в группе.

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

Это означает, что единицы сами по себе не будут иметь информации о цели своего движения или действия, всё, что они делают сами по себе — занимают положение в мире и отображают свой кадр анимации. В итоге я пришел к выводу, что юнит ВСЕГДА должен принадлежать какой-либо группе, пока способен совершать движение или действие. Если юнит один — это просто группа, состоящая из одного юнита.

Один из многих

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

struct GroupUnit {
int unitNum;                  // Номер юнита //
struct Unit *unit;            // данные юнита //
struct Positionwaypoint[50];  // путь для юнита в вейпойнтах //
int action[50];               // действия на вейпойнтах //
int stepX, stepY;             // шаг для индивидуальных юнитов //
int run, walk, sneak, fire, hurt, sprint, crawl;          // действия //
int target;                   // цели для юнитов //
struct Position targetPos;    // позиция цели //
};

 

Переменные:

unitNum — номер юнита в группе, а unit — структуры с информацией о юнитах. Для данной конкретной группы максимальное число юнитов было жестко задано как 4.

Флаг formation — определяет тип формации для группы. Это может быть колонна, клин или ромб, для изменения построения достаточно изменить этот флаг и дать юнитам переместиться соответствующим образом.

destPos, destPX и destPY — информация о пункте назначения группы. Вейпойнты и шаги работают таким же образом, как и в индивидуальном случае за исключением того, что в формации все должны поддерживать одинаковую скорость, чтобы не нарушить строй.

Переменная formed — одна из самых важных, она определяет, действует ли юнит в формации или индивидуально. Суть в том, что если группа сформирована, все её юниты будут выполнять одинаковые операции в каждом игровом цикле. Самостоятельно они двигаются только в случае непосредственной драки с врагом либо при обходе препятствия.

Действия — те же, что и в индивидуальном случае, и вы можете заметить, что есть и переменная для снайпера, который по сути не групповой юнит, так как нет причин делать одного из юнитов в группе снайпером, а остальных оставлять бегать вокруг него. Логично будет выделить этого юнита в его собственную группу и контролировать его действия на групповом уровне. Это та часть планирования, которую вы должны тщательно продумать заранее.

strategyMode — небольшая переменная, определяющая, как юниты реагируют на врага. Например, она может быть установлена в значения «агрессивно», «нейтрально», «трусливо». Намного удобнее оперировать юнитами, когда эта переменная вынесена на груповой уровень и не нужно контролировать поведение каждой отдельной единицы. Кроме того, это удобнее и для игрока — он может назначить разным группам юнитов разную стратегию поведения и точно знать, как они будут вести себя при встрече врага.

Массивы orders и goals указывают на приказы и цели, изложенные в глобальной базе данных приказов; таким образом приказ может легко быть отдан нескольким группам сразу, и каждая группа получит одинаковую информацию.

Обобщая сказанное

Ну вот, теперь у нас есть некоторые структуры для наших групп, что дальше? Следующим шагом будет выяснение того, как использовать эту информацию — процедуры вашей игры.

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

Советы

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

Ваша забота — это получение лучших результатов, которые не дают модные ныне шаблоны и функции. Если что-то работает как вы хотите — используйте это, не усложняя. Мантра разработчиков игр была раньше и должна быть всегда «Если это выглядит правильно — это правильно». Не позволяйте людям, которым интересна разработка реального мира с идеальной физикой заставить вас недооценивать свою простую позиционную систему, где вы прибавляете X к позиции в каждом кадре. Если это работает в вашей конкретной ситуации, то этого достаточно. Правильная физика может и нужна в некоторых играх, но точно не во всех. Есть и другие пути достижения почти аналогичных результатов.

Вы никогда не сможете построить точную копию реальности. Это просто факт. Поэтому вам нужно задать собственную планку «достаточно хорошо» и создавать достаточно хорошую реальность.