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 имеют похожие названия.


Скриншот 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 хранит два состояния: одно идеальное и одно текущее. Таким образом коду игры проще создавать для акторов цели, и заставлять их находить наилучшие способы их достижения. Это интересное сочетание конечного автомата и целенаправленной системы.


Скриншот 5: игровая катсцена с учёным.

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


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

void CBaseMonster :: Look ( int iDistance );

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

CSound* CBaseMonster :: PBestSound ( void );

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

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


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


Скриншот 6: стихийное поведение отрядов в Half-Life.

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

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

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

Поделиться с друзьями
-->

Комментарии (11)


  1. jankovsky
    26.06.2017 13:17
    -3

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


    1. libpony
      26.06.2017 22:35

      Если речь о статье, то да, так и есть. Статья похожа на прототип.


  1. Idot
    26.06.2017 13:17

    А военные в Half-Life — реально слаженно действуют, или это иллюзия слаженных действий?


    1. PatientZero
      26.06.2017 13:33
      +3

      в игре нет никакого AI верхнего уровня, управляющего этими отрядами, то есть всё поведение проявляется стихийно

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


      1. Idot
        26.06.2017 13:46

        Любопытная концепция!


      1. PatientZero
        26.06.2017 17:27
        +5

        Ага, вот, нашёл:

        The marines in Half-Life used the “Kung-Fu” style of fighting, meaning that regardless of the number of marines that the player is fighting, only two are actually allowed to shoot at the player at any given time. No actual communication exists between the marines. Instead, each squad of marines is given two attack slots; if a marine wants to attack and both slots are filled, he finds something else to do (such as reloading his weapon or moving to a new attack position). When one of the attacking marines runs out of ammo, he releases the attack slot and goes into his find-cover-and-reload behavior. Then, one of the non-attacking marines, finding an empty slot, grabs the attack slot and starts shooting at the player.

        A simple rule was added that whenever an attack slot opens up and there is more than one marine present, the marine vacating the attack slot should yell “Cover Me!” Although there is no communication between the marines, the perceived effect is one of a marine asking for cover and another opening fire to cover him. In reality, an
        attack slot opened up and was filled by another marine who knows nothing about the one reloading.


        1. Idot
          27.06.2017 12:08

          Можно сделать ещё эффектнее: пока двое атакуют, третий бросает гранату!


        1. Idot
          27.06.2017 13:07

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


    1. T-362
      26.06.2017 13:51

      Не то что-бы очень слаженно, но с учетом соратников — да. Причем не только военные но и пришельцы (собачки уж точно). Если до вечера никто не выложит — сам найду кину с ютуба серию видео с примерами коллективных и "природных" действий ИИ в халфе.


      1. devlato
        27.06.2017 01:25

        Вот во втором халф-лайфе вроде уже слаженно действовали, т.е был некий общий AI, управляющий врагами.


  1. xrip
    27.06.2017 09:48

    А разве ai для халфы писал не автор reaperbot для q1?