Назад в библиотеку

Разработка AI в стратегических компьютерных играх

Автор: Андрей Плахов

Источник: Разработка AI в стратегических компьютерных играх, часть 1 http://plakhov.livejournal.com/49077.html

Источник: Разработка AI в стратегических компьютерных играх, часть 2 http://plakhov.livejournal.com/49420.html

Введение

Привет, меня зовут Андрей Плахов, и я работаю в компании «Nival Interactive» ведущим программистом проекта «Silent Storm: Часовые», адд-она к проекту «Операция Silent Storm» (уже давно работаю не там и не над тем). Мой доклад называется «Организация разработки AI в стратегических компьютерных играх».

Есть некоторые разночтения в понимании аббревиатуры «AI» (искусственный интеллект), когда речь идет об играх. Так, на прошлой КРИ читался доклад, подготовленный Алексеем Осипенко из компании «Бука», в котором слово AI понималось скорее в том значении, в котором в компании «Нивал» понимаются словосочетания «игровая механика» или «игровая логика». Очень часто к AI относят также механизмы поиска пути и следования по найденному пути - pathfinding и pathtracking (и правильно относят, нечего их разделять).

В моем докладе я этого делать не буду. Произнося слово AI, я буду иметь в виду исключительно только механизм, при помощи которого формируются команды, отдаваемые компьютерным игроком - то есть то, на что заменяется input, если переходить от игрока-человека к компьютерному сопернику. И доклад посвящен исключительно только разработке этого механизма.

В докладе будут рассмотрены только такие стратегические игры (как пошаговые, так и в реальном времени), в которых каждый из игроков управляет несколькими или многими «боевыми единицами». Количество одновременно доступных действий в рассматриваемых играх слишком велико, и реализация дерева перебора невозможна. Для карточных игр (в том числе и игр серии Etherlords и Magic: The Gathering), шахмат, шашек и других подобных игр, в которых дерево перебора использовать можно, существуют специализированные архитектуры и методы оптимизации. О них я говорить не буду.

Довольно большая часть доклада будет посвящена опыту, полученному в ходе проектов Silent Storm и Silent Storm: Sentinels, поэтому я буду рад, если Вы играли в эти игры, или знаете, что они собой представляют. Если Вы не знаете, на что они похожи, возможно, Вам поможет перечисление аналогичных проектов: известная серия Jagged Alliance, не менее известная серия X-Com (UFO), игра Fallout Tactics, а также русская игра «Код доступа: Рай». Надо отметить, что игры серии Silent Storm отличаются от вышеперечисленных большим количеством возможностей игровой механики со сложно просчитываемыми результатами. Например, все здания и объекты в игре могут быть разрушены. Полет пуль, гранат, снарядов и то, видит ли один персонаж другого, просчитывается в 3d с учетом всех препятствий. Все это, естественно, приводит в том числе и к усложнению AI.

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

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

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

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

Теперь о том, чего не будет в докладе
Я не буду рассказывать о нейронных сетях, генетических алгоритмах, цепях Маркова и прочих принятых парадигмах самообучения. Это связано с тем, что, по нашим данным, в компьютерных играх данные техники не оправдывают себя - результаты, полученные с помощью них, малопредсказуемы, и не обладают какими-либо важными преимуществами. Примерно по тем же причинам речь не пойдет ни о языках Lisp, ПРОЛОГ (в 2007 году я совсем не так уверен насчет Lisp'а), ни о нечеткой логике.

Также я не буду рассказывать о каких-то специализированных алгоритмах и давать оценки быстродействия. Вряд ли многим из вас будут интересны технические подробности решения задач, которые в Вашем проекте, возможно, никогда не возникнут (сейчас я бы уже мог рассказать, как изложенные решения меняются с учетом жестко ограниченной памяти и быстродействия Nintendo DS или наоборот, с учетом нагрузки масштабной ММО - думаю, многих бы это заинтересовало).

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

Эволюция парадигм AI в проекте Silent Storm

Эволюция архитектуры и основных идей AI в Silent Storm изложу достаточно кратко и во многом упрощенно, иначе это займет слишком много времени

Просчет дерева позиций с минимаксной оценкой. Первоначально было принято решение реализовать перебор позиций «в глубину», как это делается в шахматных программах. От этого решения довольно быстро отказались, т.к. количество вариантов, подлежащих перебору, становилось недопустимо огромным уже на глубине 2. Дело в том, что количество возможных вариантов «ходов» в Silent Storm существенно выше, нежели в шахматах.

Просчет пар «позиция- действие». После этого мы перешли к просчету вариантов действий только на текущем ходу, при этом действия выбирались на основе весовых коэффициентов, определяемых сначала «нечеткой логикой», затем распознавателем паттернов действий игрока, а также при помощи прочих «продвинутых» методик. При выборе нужного действия определенный вес придавался каждой паре «позиция – действие», и из них выбиралась пара, отвечающая наибольшему коэффициенту, например, «бежать в точку (3:12) и кидать оттуда гранату в точку (3,22)». Основной проблемой при использовании такой архитектуры стала сложность реализации требований дизайнеров. Настройкой весовых коэффициентов сложно, а иногда и невозможно реализовать правила «общего характера». Например, для реализации правила «всегда, когда видишь группу близко стоящих персонажей, кидай в них гранату» пришлось бы менять коэффициенты «на лету» в зависимости от того, стоит ли часть видимых врагов группой. А ведь есть и такие логики, как «отступить» или «выйти из-за укрытия - выстрелить - зайти обратно», требующие определенных действий в течение нескольких последовательных ходов. При использовании просчета пар «позиция-действие» реализовать становилось почти невозможно (во всяком случае, громоздко и запутанно).

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

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

На этом этапе, в общем и целом, развитие AI в Silent Storm прекратилось. Мы получили несколько важных уроков.

Проблемы

Главные проблемы, возникшие при разработке и тестировании AI в Silent Storm и Silent Storm: Sentinels. Я расскажу о них потому, что они вовсе не специфичны для наших проектов. В той или иной степени они встречаются при разработке любой игры стратегического жанра. И следующие части доклада будут посвящены тому, как от них избавиться выбором правильной архитектуры AI и налаживанием правильной технологической цепочки между программистами, дизайнерами и тестерами.

Затрудненный tracing и bugfixing причин, приведших к явно неверному поведению AI. В ходе тестирования игры обнаруживается ситуация, в которой AI повел себя глупо. Но как узнать, где ошибка? В момент ее непосредственного проявления настоящая причина, как правило, давно осталась позади. Даже если сохранился save с подобной ситуацией, или она может быть воспроизведена, понять, в чем причина, бывает сложно и часто требует часов внимательного tracing'a исходного кода игры.

Ошибочное применение логики, пригодной только к «наиболее часто встречающимся случаям», и задуманной для них, в нетипичных ситуациях. Это одна из главных проблем. Новые «тактические приемы» или стратегии, придумываемые дизайнерами и программистами, как правило, задумываются и реализуются только для наиболее часто встречающихся ситуаций, в которых управляемый персонаж, например, не имеет критических ранений (т.е. ограничений на действия), вооружен типично действующим оружием, не имеет сценарных или скриптовых ограничений и т.п. Примеры для Silent Storm: у нас возникали «залегающие» персонажи, вооруженные ножами (если бы они убегали или пытались атаковать, это было бы намного эффективнее – «залегание» имеет смысл только для персонажей, вооруженных стрелковым оружием); пытались прятаться за препятствиями водители панцеркляйнов или стрелки из стационарных пулеметов (естественно, это у них не получалось, что вводило AI в ступор); бывали доблестные воины, которые шли окружать противника, не имея патронов или выжидали в засаде, несмотря на обильное кровотечение; встречались «испуганные» юниты, которые бегали друг к другу за помощью. В общем, значительная часть проблем с AI возникала потому, что мы забывали указать те или иные ограничения на использование сложных логик.

Отсутствие предсказуемости, затрудненная реализация сценария. При наличии разветвленной системы «реакция→логика→действие», в которой есть несколько типов реакций, пара десятков логик и множество типов действий, сложно предсказать, как поведут себя бойцы AI в том или ином случае. Это сильно ухудшает предсказуемость gameplay'a на различных зонах игры, усложняет скрипты, а также может повлиять на сценарий неожиданным (и чаще всего абсурдным) образом. Пара примеров:
Подобные проблемы непредсказуемости возникали и возникают постоянно.

«Зависания» AI. Эта проблема специфична для походовых игр (то есть, в RTS она не может возникнуть) и заключается в том, что AI в некоторых ситуациях никогда не отдавал команды «конец хода» (вечно удерживал ход). Типичный способ возникновения «зависания» таков: AI отдает команду, которую unit не может выполнить или на выполнение которой он тратит 0 action points (action points - это игровой механизм в turn-based играх, регулирующий максимальное количество действий, выполняемых персонажем за его ход). Игра запрашивает следующую команду для данног unit'a. Она оказывается той же, т.к. AI-состояние unit'a не изменилось. Так и продолжается в вечном цикле.

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

Проблемы, присущие многим другим реализациям AI в играх
Silent Storm явно не является единственной игрой, при реализации AI в которой были допущены ошибки. Достаточно вспомнить классические примеры неверного поведения AI в других играх, даже AAA-класса. Возьмем, например, StarCraft - возможно, лучшую игру жанра realtime strategy.

Пример повторения одних и тех же действий, причем явно глупых (с точки зрения человеческого здравого смысла). В Startcraft AI устраивал «беготню перед препятствием», если атакуемая цель была скрыта за ним (вместо того, чтобы атаковать препятствие). Эта ошибка приводила к тому, что хороший игрок-человек мог уничтожить бесконечно много наземных юнитов противника. Также можно было видеть бесконечную стрельба по unit'у, который сделан неуязвимым при помощи скрипта и явно не получает от этого никаких повреждений (кстати, это настолько распространенное поведение, что его даже не принято считать ошибочным).

Пример применения логики в неподходящей ситуации. Хороший пример того, что данная ошибка неспецифична для Silent Storm - погоня «крестьян», управляемых AI, за разведывательным юнитом вплоть до их бесславной кончины (в StarCraft это позволяло уничтожить 4 компьютерных соперников на маленькой карте, не потеряв ни одного своего бойца). Другой пример - строительство строения shipyard на картах, лишенных воды (эта нелогичность встречается во многих играх).

Анализ архитектурных ошибок

Допустим, мы можем классифицировать основные проблемы. Но почему они возникают вновь и вновь? Конечно, можно объяснить их банальным недостатком людей, времени или денег на разработку и тестирование AI (и игры в целом). Но это неконструктивная позиция, т.к. ресурсов не хватает никогда и никому. Вместо этого можно выявить явные архитектурные ошибки и ошибки, связанные с неверно организованной технологической цепочкой. Сначала поговорим об архитектурных ошибках и о том, как они влияют на качество итогового продукта.

1. Отношение между «состоянием», «реакцией», «логикой» etc. и игровым персонажем выбирается ошибочно.Например, в Silent Storm и в Blitzkrieg подобные сущности относились друг к другу как «один к одному», что кажется естественным: в каждый момент времени между персонажем (боевой единицей) и его AI-состоянием («логикой», «реакцией») существует взаимно-однозначное соответствие. На самом деле следует с самого начала реализовать это отношение как «многие к одному» (то есть, одна и та же логика или реакция может управлять несколькими персонажами). Тогда не может возникнуть проблем с реализацией совместных действий. 2007: На самом деле, с учетом того, что логики иерархические - «многие к многим». В Нивале игры делались под РС, и память управлялась автоматически, при помощи счетчиков ссылок. В проектах, в которых память управляется вручную, корректно реализовать отношение «многие к многим» - та еще задача. Хотя оно того стоит, но считайте, что я вас предупредил.

2. Неверно организуется иерархия «планов». Если назвать реакцию, логику и отдельное действие одним словом «план», то мы увидим, что в нашем случае дерево планов, управляющее персонажами, сначала имело глубину 0, затем глубину 1 и в конце разработки глубину 2. Естественно продолжить эту тенденцию: вообще не разделять реакции, логики и действия, и сделать структуру дерева планов гибкой. То есть, любой план может передать управление любому «младшему» плану, причем количество уровней вложенности заранее не ограничивается. Действительно, в адд-оне Silent Storm: Sentinels мы столкнулись с тем, что двух уровней вложенности уже не хватает для описания некоторых вариантов поведения.

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

3. При создании AI описываются его действия, а не его задачи. А все потому, что к такому программированию мы привыкли. Почти любой язык программирования и почти любая среда разработки предназначены для описания детерминированных последовательностей действий. (2007: ну это я, конечно, черезчур мощно обобщил)

Между тем, разработку AI гораздо правильнее представлять себе как описание последовательностей задач и подзадач. Пока что различие между этими двумя методиками может быть для Вас неясным (вплоть до «какая разница, какими словами это называть, все равно это одно и то же программирование!»). Тем не менее, если пользоваться идеологией «описывай задачи», можно построить AI, знающий и учитывающий смысл, который вкладывается в те или иные задуманные Вами действия.

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

Все эти понятия - производные от понятия «задача». Вот пример типичной задачи, и связанных с ее выполнением бессмысленных действий, ошибок и непредвиденных ситуаций.

Задача: сделай А, чтобы получить X. Когда условие Х выполняется, сделай В, чтобы получить Y
Бессмысленное действие: X или даже Y уже и так выполняется, но мы все-таки делаем А
Ошибка, непредвиденная ситуация: Действия А выполнены, но X не наступило; или: действия А выполнены, X наступило, мы выполняем B, но в этот момент X вновь перестает выполняться
В коде обычно пишут: Сделай А, затем сделай В.

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

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

Если не (часть условия Х), делай А. Затем делай В.

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

Если не (часть условия Х и еще одна часть условия Х), делай А. Затем делай В до тех пор, пока не (часть условия Y).

Итоговый код после нескольких таких итераций работает «почти всегда». Что в таком процессе плохого? Многое:

Как надо правильно описывать задачи в коде, мы и поговорим подробнее при разборе "идеальной архитектуры", к которому сейчас приступим.

Описание "идеальной" архитектуры


Слово "идеальная", конечно, является сильным преувеличением (2007: как и слово "архитектура" - это, скорее, набор принципов, на основе которых она должна быть построена). Все же в ней собраны идеи, проверенные на практике, и учтены многие известные ошибки.

Основные принципы.

А вот они же, подробнее:

Скрытие источников управления. Различия между человеком, удаленным входом (то есть, командами, приходящими от человека, играющего за другим компьютером), скриптом и AI должны быть локализованы на уровне небольшого общего интерфейса. Например, на уровне абстрактного класса "командир", или общей системы передачи команд. Команды пользователей, команды скрипта и команды AI должны передаваться в одинаковом формате, т.е. вид одной и той же команды не должен зависеть от того, кто ее отдает - input (игрок за данным компьютером), сеть (удаленный игрок), AI, скрипт. Так и было сделано в Silent Storm, и мы об этом ни разу не пожалели.

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

По ходу выполнения каждый план может выполнить одно или несколько из следующих действий:
- отдавать команды управляемым боевым единицам
- создать план-потомок, управляющий частью "подопечных" боевых единиц
- завершиться (вместе со всеми своими планами-потомками) и сообщить плану-предку результат: успешное или неуспешное завершение (2007: и если неуспешное, то с каким именно "кодом возврата")
- обрабатывать текущие изменения ситуации. Для этого, как правило, каждый план, получивший управление, по очереди передает его всем потомкам. На каждом такте управление вначале получает главный план, управляющий всеми войсками ("командир"). Затем управление по очереди переходит к каждому его потомку, и так по всему дереву. План может иметь нескольких «потомков», каждый из которых управляет работой части персонажей, управляемых планом-родителем. Эти части не пересекаются. Таким образом, каждому персонажу соответствует цепочка вложенных планов. Например, «сейчас я следую по пути Х (это план нижнего уровня, управляющий данным персонажем), потому что с другими персонажами еду окружать точку У (план среднего уровня, управляющий группой), потому что наша стратегия в данной игровой сессии - отрезАть противника от источников ресурсов (план высокого уровня, управляющий всеми)».
(2007: Изложенный способ при большом количестве персонажей, очевидно, вычислительно очень затратен: на каждый логический такт мы проходим по всей логике. Можно, пожертвовав простотой, организовать обработку изменений ситуации гораздо более эффективным образом. Я, если будет интересно, расскажу об этом отдельно)

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

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

Ассоциированная логическая цепочка – это некое промежуточное звено между идеей и кодом. Она все еще выражена человеческим языком (а потому понятна и программистам, и гейм-дизайнерам, и тестерам), но уже гораздо более подробна.

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

«Управляемые войска едут по направлению к небольшой группе противников. Реализуем план «погоня». Все это время длина пути между управляемыми войсками и группой противников постепенно уменьшается, наши силы не уменьшаются, а размер группы противников не увеличивается. Не далее, чем через время Х, которое можно оценить заранее, управляемые войска вступают в бой с этой группой. Реализуем план «быстрый бой». Поскольку группа противника невелика по сравнению с управляемыми войсками, не более, чем через время Y, которое можно оценить заранее, группа противника уничтожена. Управляемые войска отступают на исходные позиции, так как они более защищенные по сравнению с текущими».

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

После того, как цепочка сформулирована, можно увидеть, что данный план может провалиться просто на любом своем пункте. Количество разнообразных непредвиденных обстоятельств, которые могут нарушить любое из условий, огромно. Возьмем, например, утверждение «все это время длина пути между управляемыми войсками и группой противников постепенно уменьшается, наши силы не уменьшаются, а размер группы противников не увеличивается». Мы могли ошибиться в оценках скорости; наши силы могли застрять на узком участке (например, на мосту, что встречается, по-моему, в любой RTS, где есть мосты); они могут подвергнуться бомбардировке, встретить на своем пути минное поле, попасть в непредвиденную стычку. Может помешать использование врагом какой-то военной хитрости (например, засады), скриптово-сценарное событие, использование «жульничества» со стороны игрока, и т.д. и т.п. Что же делать, ведь мы едва ли можем безошибочно учесть все заранее?

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

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

Нам часто понадобятся конструкции, реализующие логику «Пока не Х и не выполнены условия критического провала и возможно Y или Z, делай Y или Z. Если не Х, и ни Y, ни Z невозможны, то завершись с результатом «провал»». К сожалению, при реализации таких конструкций на С++ получаются существенно более громоздкий код, чем это можно было бы предположить исходя из того, как просто они формулируются на «естественном» языке. Если у вас так произойдет, не пугайтесь - увы, так и должно быть, С++ действительно для них не очень хорошо подходит.

Обработка нарушений логической цепочки («провала плана-потомка») внутри плана-родителя может вестись по крайней мере двумя способами:
1) если в коде AI существует только один тип плана, реализующий требуемый тактический прием, и этот план провалился, то и план верхнего уровня также считается проваленным.
2) если в коде AI существует несколько классов планов, реализующих требуемый тактический прием (например, «атаковать» часто можно многими способами), и экземпляр одного из этих классов провалился, то создается план следующего подходящего класса. Если провалились все - план верхнего уровня также считается проваленным.
Что происходит, если все планы, управляющие данной боевой единицей, провалены (то есть, остался только план самого верхнего уровня, который не может придумать для нее задачи)?

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

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

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

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

Что мы теряем

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

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