Статьи по программированию под движком 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  
|