Искусственный интеллект Half-Life SDK: ретроспектива
На момент выпуска в 1998 году Half-life получил тёплый приём за свой гейм-дизайн, который стал возможным благодаря искусственному интеллекту. Это влияние AI привело к тому. что HL назвали одной из самых важных игр в истории.
И даже двадцать лет спустя, изучив её код, можно многое узнать о создании простых, но эффективных систем AI. Вся логика AI жёстко закодирована на C++ и не слишком объектоориентирована, поэтому в ней гораздо легче разобраться, чем в более свежих движках (хотя и расширять её не так просто).
В этой статье мы рассмотрим открытый SDK для Half-Life 1, проанализируем различные аспекты AI, такие как система планировщика задач, её реализация, похожая на конечные автоматы, и сенсорная система. Прочитав статью, вы глубже поймёте принцип использования этих концепций и их реализации в играх.
Скриншот 1: охранник Барни сражается с одним из монстров
Загрузка Half-Life SDKУстановить SDK Valve для Half-Life очень просто (с отличие от инструментов F.E.A.R.) и если вы хотите разрабатывать моды, то для него требуется только оригинальная игра. Вот, что вам будет нужно:
- Скачайте версию 2.3 SDK Half-Life, или только исходники без ресурсов, или копию полного SDK с моделями.
- Распакуйте файл в любой каталог, лучше в папку с игрой, если вы хотите разрабатывать с помощью SDK моды. Это займёт несколько секунд, в результате у вас будет пачка каталогов с моделями и исходным кодом.
Скриншот 2: код игры на C++ в SDK Half-Life версии 2.3.
Разбираемся с кодомКодовая база не так хорошо структурирована, как в F.E.A.R. или даже в Quake 3. В ней есть несколько подкаталогов, но файлы имеют не очень понятные названия, а реализация классов C++ разбросана по нескольким файлам, из названий которых почти ничего нельзя понять.
- В полном SDK есть две папки, в которых содержится код: Single-Player Source и Multiplayer Source . Обе они имеют схожую структуру каталогов.
- Бо́льшая часть игровой логики находится в подкаталоге /dll/ , в котором содержатся все файлы, необходимые для сборки hl.dll, который также является фреймворком для модов. Кроме того, в этом каталоге содержится код ИИ, разбросанный по множеству файлов, с названиями типа *monster*.[h,cpp] , *ai*.[h,cpp] и других файлах
- В каталоге с исходным кодом есть и другие каталоги, например engine , в котором содержатся файлы заголовков, взаимодействующие с основным исполняемым файлом (как базовые сущности). В каталоге common также содержатся похожие низкоуровневые файлы, используемые движком и кодом игры.
Скриншот 3: катсцена из игры с учёным.
Планировщик и система целейВ файлах schedule.[h,cpp] находится очень простая система, управляемая целями. Она состоит из нескольких уровней задач, которые можно процедурно объединять.
ЗадачиЗадачи — это короткие атомизированные поведения, имеющие конкретное назначение. Например, большинство акторов Half-Life поддерживает следующие задачи: TASK_WALK_PATH , TASK_CROUCH , TASK_STAND , TASK_GUARD , TASK_STEP_FORWARD , TASK_DODGE_RIGHT , TASK_FIND_COVER_FROM_ENEMY , TASK_EAT , TASK_STOP_MOVING , TASK_TURN_LEFT , TASK_REMEMBER . Они определяются как перечисления в файле заголовка и реализуются как методы C++.
УсловияУсловия используются для выражения ситуации актора в мире. Поскольку логика задана жёстко, условия можно выразить очень компактно, как битовые поля, но в таком случае условий может быть не больше 32. Например, условиями являются COND_NO_AMMO_LOADED , COND_SEE_HATE , COND_SEE_FEAR , COND_SEE_DISLIKE , COND_ENEMY_OCCLUDED , COND_ENEMY_TOOFAR , COND_HEAVY_DAMAGE , COND_CAN_MELEE_ATTACK2 , COND_ENEMY_FACING_ME .
ПланыПлан состоит из серии задач (с произвольными параметрами) и учитывает битовое поле условий, чтобы определить, когда план неприменим. Для удобства отладки объекты планов имеют имена.
Цели находятся на более высоком уровне и состоят из планов. Логика цели может при необходимости выбирать план на основании проваленной задачи и текущего контекста. Примеры целей из Half-Life: GOAL_ATTACK_ENEMY , GOAL_MOVE , GOAL_TAKE_COVER , GOAL_MOVE_TARGET и GOAL_EAT .
Использованный Valve код извлечён из движка Quake, и до сих пор достаточно очевиден, несмотря на то, что был преобразован в C++; файлы и struct имеют похожие названия.
Скриншот 4: десантники подняли тревогу в исследовательском центре.
Конечный автоматНа практике все эти планы и задачи соединены вместе в структуру, похожую на конечный автомат. На верхнем уровне для обновления ИИ вызывается функция в monsterstate.cpp :
Она, в свою очередь, вызывает перегруженные функции, отвечающие за проверку с помощью MaintainSchedule() применимости текущего плана и выбор новых с помощью GetSchedule() . Их можно изменять в зависимости от потребностей с помощью порождённых классов, см., например, barney.cpp или scientist.cpp .
На нижнем уровне функции StartTask() и RunTask() реализуют логику для каждого из идентификаторов задач, определённых в конструкции enum . Они реализованы в классах, тоже унаследованных из CBaseMonster . В результате это во многом выглядит как конечный автомат, реализованный как конструкция switch .
Более типичным подходом была бы реализация каждого из этих блоков case в их собственном классе, но при существующей реализации гораздо проще при необходимости использовать логику одного объекта в другом, хотя и ценой модульности.
Интересно также заметить, что AI хранит два состояния: одно идеальное и одно текущее. Таким образом коду игры проще создавать для акторов цели, и заставлять их находить наилучшие способы их достижения. Это интересное сочетание конечного автомата и целенаправленной системы.
Скриншот 5: игровая катсцена с учёным.
Реализация сенсорной системыВ базовом monster.[h,cpp] есть код, дающий всем акторам зрение, обоняние и слух.
Функция зрения проверяет различные флаги, такие как SF_MONSTER_PRISONER и SF_MONSTER_WAIT_TILL_SEEN , чтобы при необходимости обеспечивать дизайнерам возможность контроля. В уравнении также учитываются такие параметры, как область видимости и угол обзора.
Код слуха и обоняния работает похожим образом, только использует события звука. Хранится список объектов, требующих внимания монстров, а сенсорная система выбирает для фокусировки лучший из них.
Итоги и дополнительное чтениеВ целом, исходный код, стоящий за этой системой, хотя и прост, очень информативен. Если вы хотите подобрать лёгкую реализацию принятия решений искусственным интеллектом, то стоит выбрать этот подход. Однако, возможно, стоит реализовать каждую задачу в своём собственном объекте: в наши дни в коммерческих играх обычно используют такое решение.