Главная
Новости
Статьи
Ссылки
Гостевая книга
Форум
Почта:
Прочее
Общая информация
Предыстория
Оружие
Кадры из игры
Концепт-арт
Скачать
Инструменты
Авторы проекта
Глоссарий объектов
Юмор в картинках
Статьи по программированию под движком Gold Source и работе с SDK

Искусственный Интеллект Half-Life 1
Расписания и задачи

Источник: Caleb 'Ghoul' Delnay (valve-erc)
Перевод : Dzug@ru   4.4.2004


Сейчас будем вас просвещать на тему событийного программирования. Рассмотрим пару понятий на примере событийного программирования конечных автоматов quake-подобного движка GoldSource, т.к. действие и взаимодействие конечных автоматов — это и есть поведение монстров. Хотя предполагается, что вы имеете определенные знания и некоторый опыт работы с Half-life Software Development Kit (SDK), в противном случае вам будет труднее разобраться в этой статье.

Вся система искусственного интеллекта (ИИ) в Half-life основывается на двух понятиях: задачи (tasks) и расписания (sheduls, а вернее schedules).
Задачи — это набор действий, выполняемых монстром, например проигрывание анимации, перемещение, атака и т. д.
Расписание — это набор задач, выполняемых в определенном порядке. Например, перемещение к противнику, проигрывание определенного движения модели и возврат в исходную точку — помните учёного, который подходит подлечить игрока и достаёт шприц? В Half-life существует набор стандартных расписаний, но почти каждый монстр использует кроме них и свои собственные.

Вот пример расписания, отвечающего за "победный танец" (victory dance), выполняемый human grunt'ом (фрагмент кода 1 в hgrunt.cpp)

//==================================
// Victory dance!
//==================================

Task_t tlGruntVictoryDance[] =
{
 { TASK_STOP_MOVING, (float)0 },
 { TASK_FACE_ENEMY, (float)0 },
 { TASK_WAIT, (float)1.5 },
 { TASK_GET_PATH_TO_ENEMY_CORPSE,(float)0 },
 { TASK_WALK_PATH, (float)0 },
 { TASK_WAIT_FOR_MOVEMENT, (float)0 },
 { TASK_FACE_ENEMY, (float)0 },
 { TASK_PLAY_SEQUENCE, (float)ACT_VICTORY_DANCE },
};

Schedule_t slGruntVictoryDance[] =
{
  {
    tlGruntVictoryDance,
    ARRAYSIZE ( tlGruntVictoryDance ),
    bits_COND_NEW_ENEMY |
    bits_COND_LIGHT_DAMAGE |
    bits_COND_HEAVY_DAMAGE,
    0,
    "GruntVictoryDance"
  },
};

Как нетрудно догадаться, здесь расписанием является slGruntVictoryDance[], а списком заданий - tlGruntVictoryDance[].
Всё, что идёт после ARRAYSIZE ( tlGruntVictoryDance ) — различные условия, при которых выполнение расписания будет прервано.
В данном случае такими условиями являются обнаружение нового врага или получение легкого и тяжёлого урона. Ноль(0) в расписании является набором звуком, которые также его могут прервать. Здесь таких звуков нет вообще.
GruntVictoryDance — это название расписания. Если набрать в консоли impulse 103, наведя прицел на врага, то будет выведено сообщение о текущем статусе его ИИ.
Теперь поглядим на tlGruntVictoryDance[].
Это массив, состоящий из набора заданий для описанного расписания. (float)0, следующий за каждой задачей, является значением этой задачи. Большинство задач не требуют ничего, кроме (float)0 [кто б сомневался :] — Dzu], но некоторые всё-таки его используют, к примеру TASK_PLAY_SEQUENCE требует в качестве параметра ACT.

Ещё один кусок кода из hgrunt.cpp (фрагмент кода 2):

DEFINE_CUSTOM_SCHEDULES( CHGrunt )
{
  slGruntFail,
  slGruntCombatFail,
  slGruntVictoryDance,
  slGruntEstablishLineOfFire,
  slGruntFoundEnemy,
  slGruntCombatFace,
  slGruntSignalSuppress,
  slGruntSuppress,
  slGruntWaitInCover,
  slGruntTakeCover,
  slGruntGrenadeCover,
  slGruntTossGrenadeCover,
  slGruntTakeCoverFromBestSound,
  slGruntHideReload,
  slGruntSweep,
  slGruntRangeAttack1A,
  slGruntRangeAttack1B,
  slGruntRangeAttack2,
  slGruntRepel,
  slGruntRepelAttack,
  slGruntRepelLand,
};

IMPLEMENT_CUSTOM_SCHEDULES( CHGrunt, CSquadMonster );

Этот кусок кода описывает все расписания, используемые монстром и позволяет их использовать в дальнейшем коде (фрагмент кода 3):

//==================================
// monster-specific schedule types
//==================================

enum
{
  SCHED_GRUNT_SUPPRESS = LAST_COMMON_SCHEDULE + 1,
  SCHED_GRUNT_ESTABLISH_LINE_OF_FIRE,
  SCHED_GRUNT_COVER_AND_RELOAD,
  SCHED_GRUNT_SWEEP,
  SCHED_GRUNT_FOUND_ENEMY,
  SCHED_GRUNT_REPEL,
  SCHED_GRUNT_REPEL_ATTACK,
  SCHED_GRUNT_REPEL_LAND,
  SCHED_GRUNT_WAIT_FACE_ENEMY,
  SCHED_GRUNT_TAKECOVER_FAILED,
  SCHED_GRUNT_ELOF_FAIL,
};

Здесь объявляются типы расписаний (shedul types не нужно путать с sheduls!). Учитывайте, что первым идёт SCHED_GRUNT_SUPPRESS = LAST_COMMON_SCHEDULE + 1, если его опустить, то порядок стандартных расписаний изменится [я так полагаю, что они перезапишутся новыми — Dzu] и с монстром будет твориться чёрт-те-что.

Фрагмент кода 4 — задачи монстра:

//==================================
// monster-specific tasks
//==================================

enum
{
  TASK_GRUNT_FACE_TOSS_DIR = LAST_COMMON_TASK + 1,
  TASK_GRUNT_SPEAK_SENTENCE,
  TASK_GRUNT_CHECK_FIRE,
};

Аналогично для типов заданий. Снова обращаю ваше внимание на первую строчку — LAST_COMMON_TASK + 1. Двигаемся дальше.
Код, который определяет, какое собственно расписание выполнять в данный момент времени, состоит по сути дела из двух функций — GetSchedule и GetScheduleOfType.

GetSchedule проверяет текущее состояние монстра (бой, простаивание, паника и т.д.). И подбирает необходимые расписания для различных ситуаций. А вторая функция возвращает значение GetScheduleOfType (SCHEDULE_TYPE). GetScheduleOfType использует типы расписаний для возвращения корректного значения.

Пример: AI_BaseNPC_Schedule.cpp [прим. — код взят из старых SDK, для 2.2 это schedule.cpp — Dzu].

Функция GetSchedule (фрагмент кода 5):

if ( HasConditions( bits_COND_ENEMY_DEAD )
&& LookupActivity( ACT_VICTORY_DANCE ) != ACTIVITY_NOT_AVAILABLE )
{
return GetScheduleOfType ( SCHED_VICTORY_DANCE );
}

и для hgrunt.cpp (фрагмент кода 6):

case SCHED_VICTORY_DANCE:
{
  if ( InSquad() )
  {
    if ( !IsLeader() )
    {
    return &slGruntFail[ 0 ];
    }
  }

  return &slGruntVictoryDance[ 0 ];
}

Что делает этот код? В теле GetSchedule игра проверяет, не является ли враг убитым, и есть ли у персонажа анимация, связанная с ACT_VICTORY_DANCE. Если да, то выполняется GetScheduleOfType с параметром SCHED_VICTORY_DANCE.
В теле GetScheduleOfType обрабатываются все типы расписаний. В нашем случае проверяется, не входит ли спецназовец в боевой отряд, и если он не его лидер, то возвращается &slGruntFail[ 0 ] , т.е. расписание, отвечающее за "победный танец".

Всё? Закончили? Ладно рассмотрим ещё и задачи (tasks). Как и с расписаниями, для управления задачами используются две функции — StartTask и RunTask. Первая вызывается при начальной инициализации задач, вторая — при каждом событии think, до тех пор, пока не выполнится задача. Вот например, задача, которая заставляет спецназовца повернуться лицом к цели перед броском гранаты:

для StartTask (фрагмент кода 7) -

case TASK_GRUNT_FACE_TOSS_DIR:
break;

Как вы видите, при первом запуске задачи ничего не происходит.
для RunTask (фрагмент кода 8) -

case TASK_GRUNT_FACE_TOSS_DIR:
{
// project a point along the toss vector and turn to face that point.
MakeIdealYaw( pev->origin + m_vecTossVelocity * 64 );
ChangeYaw( pev->yaw_speed );

if ( FacingIdeal() )
{
m_iTaskStatus = TASKSTATUS_COMPLETE;
}
break;
}

Код заставляет спецназовца повернуться лицом к точке броска, и когда он наконец повернётся, задача будет считаться выполненной. Код, который завершает задачу — m_iTaskStatus = TASKSTATUS_COMPLETE; Можно также использовать TaskComplete() и TaskIsComplete(). Первый вызывается для проверки провалено ли задание или нет, а второе банально возвращает m_iTaskStatus = TASKSTATUS_COMPLETE;
Очень важно вовремя завершать задания, в противном случае они будут выполняться вечно, а монстр впадет в ступор.

Чтобы в игре узнать текущее расписание и задачу у монстра, нужно навести на него прицел любого оружия и, зайдя в консоль, набрать код:
impulse 103
В консоли увидите его класс, статус — State, активность — Activity, выполняемое расписание — Schedule, задачу — Task, считает ли он кого-то за врага, направление, лицом к которому он стоит — Yaw и здоровье —Health.

Что ж, основы мы с вами разобрали. Рекомендую изучить в SDK содержимое файлов defaultai.cpp и AI_BaseNPC_Schedule.cpp [для старых SDK — schedule.cpp — Dzu], чтобы узнать список всех стандартных задач и расписаний и лучше понять, как работает ИИ в ХЛ.


  Обсудить статью на форуме half-life.ru

Dzug@ru  






Half-life Red Alert Expantion
Presented by Dzugaru & ObaGlaza
All rights reserved
  Counter.CO.KZ -     !
Hosted by uCoz