Max Payne: Часть 3. ИИ Персонажей

Скрипты в MaxEd 2
Следующим шагом в освоении скриптинга станет создание противников. Я решил сделать спецназовскую четверку, состоящую из командира, снайпера и двух штурмовиков. В режиме F3 нажмите N и выберите тип Enemy. Откроется окно характеристик персонажа. Выберите соответствующий скин, и переходите в окно FSM. Впишите в блок Startup следующее:
this->C_PickupWeapon(PumpShotgun);
this->C_PickupAmmo(PumpShotgun, 140);
this->AI_SetTactic(Combat);
Изначально персонаж не имеет оружия, нужно выдать ему его вручную. Можно одно из оружий сделать бесконечным – C_RemoveAllWeapons(Оружие), тогда не будет ситуации, когда противник не сможет оказать сопротивление. SetTactic работает следующим образом:
Idle – начальное значение, персонаж никак не реагирует на цель
Combat – атакует цель, активно ищет ее, если не видит
Follow – следует за игроком
FollowCombat – следует за игроком и атакует цель
Cover – ищет укрытие, не атакуя цель
CoverCombat – ищет укрытие, атакуя цель
Guard – атакует цель, но не гоняется за ней
GuardCombat – когда видит цель – Combat, когда нет – Guard
Сделайте еще двух штурмовиков (одному из них я дал гранаты – пусть будет гренадером) и снайпера. Снайперу подходит тактика Guard – терпеливый и усидчивый снайпер заставит игрока изрядно попотеть, выбивая его с насиженного места.
В тактическом режиме персонажи перемещаются по линиям, проложенными нодами (AI Nodes). Каждый нод соединяется с каждым; вдоль таких линий и перемещаются персонажи; стоять они могут только над нодом.. Ставится нод в F3: N -> AIN. Лично мне всегда удобнее копировать уже стоящие ноды, чем создавать новые. Стало быть, у меня получилась вот такая сеть:
Не могу сказать, что я долго раздумывал над расстановкой, но и от балды расставлять ноды не советую. Если необходимо блокировать какие-либо соединения нодов, можно использовать функции AIN_CreateAIBlock (RemoveAllAIBlocks для отмены). Но я использовал материал ai_node_collision_nodraw – лучше видно, где проходит «барьер», по сравнению с радиусом в CreateBlock.
Наши враги готовы к «использованию». Но я предпочел их немного усовершенствовать. На данном этапе они могут только поджидать игрока за дверью. А пусть через какое-то время спезназовцы сами вломятся в помещение и разбегутся по позициям? Будет повеселее.
Для того, чтобы направить персонажа в какое-нибудь место, используются вэйпоинты (Waypoints). Причем до вэйпоинта он будет добираться опять-таки по нодам. Я расставил цели вот так:
Для того, чтобы персонаж совершил какое-либо действие, используются AI_AddCommand и AI_PushCommand. Действия, заданные этими функциями, ставятся в очередь; Add добавляет действие в конец очереди, а Push - в начало, при этом прерывая текущее. Вызов этих функций имеет общий вид AI_****Command(Move, Act, Parameters). Сверьтесь с таблицей, ибо редактор не проверяет несоответствие параметров друг другу. А игра от такой некорректной функции вылетает.
Добавьте каждому из спецназовцев в блок Startup следующее:
this->AI_AddCommand(CROUCH, NOTHING, "");
Теперь в FSM командира создайте таймер на открытие двери (ПКМ -> Add FSM Timer), у меня он называется Takedown. Задайте ему желаемую длительность (ПКМ -> Change timer length). Обратите внимание, что таймер может работать как во времени игры, так и в реальном времени, то есть, будет ли игровое замедление и ускорение влиять на ход таймера. Пропишите следующее:
**# В блоке Startup:**
this->FSM_StartTimer(Takedown);
**# В блоке Takedown->OnEndTimer: **
::Backyard::Trigger_Outside->T_Activate();
::Backyard::Assaulter->AI_PushCommand( RUN, NOTHING, ::Hangar::Assaulter_Target );
::Backyard::Grenader->AI_PushCommand( RUN, NOTHING, ::Hangar::Grenader_Target );
this->AI_PushCommand( RUN, NOTHING, ::Hangar::Commander_Target );
Вы, возможно, заметили, что среди доступных действий нет Use. Использование персонажем каких-либо объектов нужно задавать напрямую, как здесь — с триггером двери. При необходимости можно анимировать действие персонажа, но в моем случае это не нужно, так как игрок его не увидит. Мы используем именно функцию Push, так как прерываем текущее «бесконечное» действие персонажа (см. Crouch). Снайперу мы ничего не приказываем — пусть сидит, где сидит.
Если вы сейчас запустите уровень, то увидите, как спецназовцы пытаются пробежать сквозь еще не открытую дверь — ведь приказ бежать идет одновременно с открытием. Пусть они начнут бег только тогда, когда дверь уже достаточно откроется. Сделайте дополнительный таймер (Orders в моем случае; у меня он длится две секунды), и в его OnEndTimer перенесите из Takedown->OnEndTimer 3 строчки с PushCommand. А вместо них напишите:
this->FSM_StartTimer(Orders);
В результате мы имеем два таймера, связанные между собой. Orders начинает работу ровно тогда, когда заканчивается Takedown. Спецназовцы терпеливо сидят и дожидаются открытия двери, и только потом срываются с места.
Предположим, игрок справился с врагами. В нашем случае это означает конец уровня. Значит, нам нужен триггер, срабатывающий только после гибели всех спецназовцев. Пусть в дальней части уровня будет выход. Сделайте барьер из материала из категории player_collision_nodraw и прямо перед ним поставьте триггер (у меня Trigger_Exit) и отключите его.
Теперь сделаем счетчик фрагов. Где-нибудь рядом положите FSM-точку (Team). Добавьте ей в блок States состояния "0" (default), "1", "2" и"3", а также событие Teammate_Down:
%% 0 :
this->FSM_Switch(1);
%% 1 :
this->FSM_Switch(2);
%% 2 :
this->FSM_Switch(3);
%% 3 :
::Backyard::Trigger_Exit->T_Enable(1);
Каждому врагу в OnDeath пропишите:
::Backyard::Team->FSM_Send(Teammate_down);
Принцип работы, я думаю, уже виден - независимо от порядка гибели врагов, с каждым новым Teammate_Down счетчик переключается во все более дальнее состояние, по сути "увеличиваясь на единицу". Количество состояний счетчика - это количество врагов (то есть, того, что подсчитываем) минус один. Из последнего состояния он уже запускает финальный скрипт. В данном случае, включает триггер на выход. А пока триггер не включен, игрок просто упирается в невидимую стену.
По окончании уровня нужно выйти в меню и не дать игроку зайти обратно. Делается это так:
MaxPayne_GameMode->GM_SetStoryEventOccured(exitlastlevel,1);
X_ModeSwitch->S_ModeSwitch(menu);
Готово!

Просмотров: 4 622

Комментарии пока отсутcтвуют