Искусственный интеллект Half-Life SDK: ретроспектива

в 6:41, , рубрики: artificial intelligence, half-life, искусственный интеллект, исходный код, разработка игр

image

На момент выпуска в 1998 году Half-life получил тёплый приём за свой гейм-дизайн, который стал возможным благодаря искусственному интеллекту. Это влияние AI привело к тому. что HL назвали одной из самых важных игр в истории.

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

В этой статье мы рассмотрим открытый SDK для Half-Life 1, проанализируем различные аспекты AI, такие как система планировщика задач, её реализация, похожая на конечные автоматы, и сенсорная система. Прочитав статью, вы глубже поймёте принцип использования этих концепций и их реализации в играх.

Cooperative AI and Monsters
Скриншот 1: охранник Барни сражается с одним из монстров

Загрузка Half-Life SDK

Установить SDK Valve для Half-Life очень просто (с отличие от инструментов F.E.A.R.) и если вы хотите разрабатывать моды, то для него требуется только оригинальная игра. Вот, что вам будет нужно:

  1. Скачайте версию 2.3 SDK Half-Life, или только исходники без ресурсов, или копию полного SDK с моделями.
  2. Распакуйте файл в любой каталог, лучше в папку с игрой, если вы хотите разрабатывать с помощью SDK моды. Это займёт несколько секунд, в результате у вас будет пачка каталогов с моделями и исходным кодом.

Half-Life SDK AI Files
Скриншот 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 также содержатся похожие низкоуровневые файлы, используемые движком и кодом игры.

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

Scientist AI
Скриншот 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 имеют похожие названия.

Искусственный интеллект Half-Life SDK: ретроспектива - 5
Скриншот 4: десантники подняли тревогу в исследовательском центре.

Конечный автомат

На практике все эти планы и задачи соединены вместе в структуру, похожую на конечный автомат. На верхнем уровне для обновления ИИ вызывается функция в monsterstate.cpp:

void CBaseMonster :: RunAI ( void );

Она, в свою очередь, вызывает перегруженные функции, отвечающие за проверку с помощью MaintainSchedule() применимости текущего плана и выбор новых с помощью GetSchedule(). Их можно изменять в зависимости от потребностей с помощью порождённых классов, см., например, barney.cpp или scientist.cpp.

На нижнем уровне функции StartTask() и RunTask() реализуют логику для каждого из идентификаторов задач, определённых в конструкции enum. Они реализованы в классах, тоже унаследованных из CBaseMonster. В результате это во многом выглядит как конечный автомат, реализованный как конструкция switch.

void CScientist :: RunTask( Task_t *pTask )
{
  switch ( pTask->iTask )
  {
  case TASK_RUN_PATH_SCARED:
    if ( MovementIsComplete() )
      TaskComplete();
    if ( RANDOM_LONG(0,31) < 8 )
      Scream();
    break;

  case TASK_MOVE_TO_TARGET_RANGE_SCARED:
    /* ... */
    break;

  case TASK_HEAL:
    if ( m_fSequenceFinished )
    {
      TaskComplete();
    }
    else
    {
      if ( TargetDistance() > 90 )
        TaskComplete();
      pev->ideal_yaw = UTIL_VecToYaw( /* ... */ );
      ChangeYaw( pev->yaw_speed );
    }
    break;

  default:
    CTalkMonster::RunTask( pTask );
    break;
  }
}

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

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

Искусственный интеллект Half-Life SDK: ретроспектива - 6
Скриншот 5: игровая катсцена с учёным.

Реализация сенсорной системы

В базовом monster.[h,cpp] есть код, дающий всем акторам зрение, обоняние и слух.

void CBaseMonster :: Look ( int iDistance );

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

CSound* CBaseMonster :: PBestSound ( void );

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

Итоги и дополнительное чтение

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

Искусственный интеллект Half-Life SDK: ретроспектива - 7
Скриншот 6: стихийное поведение отрядов в Half-Life.

В коде AI Half-Life содержатся и другие интересные идеи.

  1. Код игры представляет навигационные точки в виде только 3D-вектора и типа локации! Они привязываются к нижестоящей навигационной системе, но их можно использовать и в олдскульной системе «хлебных крошек», по которым следуют монстры.
  2. Half-Life удивила многих поведением отрядов. Однако в игре нет никакого AI верхнего уровня, управляющего этими отрядами, то есть всё поведение проявляется стихийно.

Если вы хотите воссоздать что-то большее, чем просто монстр из Half-Life, лучше всего изучить фреймворк ботов. Он позволит создавать AI-ботов для многопользовательской игры, которых можно применять в сторонних модах Half-life. Их можно найти здесь:

Автор: PatientZero

Источник

* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js