Логика NPC

Материал из Mod Wiki.

(Различия между версиями)
Перейти к: навигация, поиск

BAC9-FLCL (Обсуждение | вклад)
(Новая: =Теория= Предполагается, что читатель этой статьи знаком с языком LUA и основами объектно-ориентирован...)
К следующему изменению →

Версия 09:45, 9 июня 2007

Содержание

Теория

Предполагается, что читатель этой статьи знаком с языком LUA и основами объектно-ориентированного программирования.

История

Подход к решению проблемы игрового ИИ, выбранный создателями S.T.A.L.K.E.R (далее буду писать просто Сталкер), был впервые применён в 1957 году Гербертом Саймоном (Herbert Simon) и Алленом Ньюэллом (Allen Newell) в программе GPS (General Problem Solver или Универсальный Решатель Задач).

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

В Сталкере подсистема поиска последовательности операторов называется «планировщик».

ИИ в Сталкере

Как видно, этот способ решения проблем не подходит для шутера, так как ситуация в игре может меняться непредсказуемым образом и построенный план решения (последовательность операторов) станет неприменимым к текущей ситуации. Поэтому планировщик запускается каждый раз при непредвиденном развитии событий и создаёт новую последовательность действий (операторов).

Условия в игре тоже вычисляются динамически. Для этого используются специальные объекты – эвалуаторы. Эвалуатор должен содержать метод evaluate(), возвращающий true, если условие выполняется и false в противном случае. Операторы также представлены как объекты. Планировщик вызывает метод initialize() при начале работы оператора, затем он периодически вызывает метод execute().

Например, можно создать эвалуатор для условия «NPC голоден», и привязать к этому условию оператор «поесть». Планировщик будет периодически проверять это условие (вызывать метод evaluate() эвалуатора), и если оно выполняется, инициализирует и будет выполнять оператор «поесть» до тех пор, пока условие не станет ложным.

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

Разбор настройки и работы планировщика на примере скрипта xr_kamp

Рассмотрим скрипт xr_kamp, заставляющий сталкеров сидеть у костра и рассказывать анекдоты.

Настройка планировщика осуществляется в функции add_to_binder. Параметры функции: object – объект для которого настраивается планировщик (в нашем случае это сталкер), ini, scheme, section – инициализационный файл, название схемы действий, секция ини-файла (эти параметры будут подробно разобраны в части по созданию мода), storage – таблица для хранения текущих параметров схемы действий. Разберём, что делает эта функция.

Сначала получаем планировщик для текущего объекта (object).

local manager = object:motivation_action_manager()

Затем присваиваем идентификаторы операторов и условий элементам массива. Это сделано просто для удобства. Идентификаторы могут иметь любое целочисленное значение, главное, чтобы они были уникальными, то есть не использовались для других операторов и условий.

properties["kamp_end"]=xr_evaluators_id.stohe_kamp_base+1
properties["on_position"]=xr_evaluators_id.stohe_kamp_base+2
properties["contact"]=xr_evaluators_id.stohe_meet_base+1
operators["go_position"]=xr_actions_id.stohe_kamp_base+1
operators["wait"]=xr_actions_id.stohe_kamp_base+3

Для каждого идентификатора условия создадим соответствующий эвалуатор и добавим его в планировщик. В данном случае это условия: «закончить ли посиделки около костра?» и «пришёл ли я на своё место у костра?»

manager:add_evaluator (properties["kamp_end"],    this.evaluator_kamp_end   ("kamp_end", storage, "kamp_end"))
manager:add_evaluator (properties["on_position"], this.evaluator_on_position  ("kamp_on_position", storage, "kamp_on_position"))

Теперь создадим оператор «сидеть около костра, рассказывать анекдоты, жевать колбасу и т.д.». Можно было бы реализовать эти действия как набор разных операторов, выбором которых занимался бы планировщик, но автор скрипта решил сделать один сложный оператор.

local action = this.action_wait (object:name(),"action_kamp_wait", storage)

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

action:add_precondition   (world_property(stalker_ids.property_alive, true))

я живой,

action:add_precondition   (world_property(stalker_ids.property_danger,false))

опасностей нет,

action:add_precondition   (world_property(stalker_ids.property_enemy, false))

врагов нет,

action:add_precondition   (world_property(stalker_ids.property_anomaly,false))

аномалий поблизости нет,

xr_motivator.addCommonPrecondition(action)

выполняются другие важные условия (ГГ не собирается со мной поговорить, я не собираюсь никого бить по морде, я не ранен, я не собираюсь стрелять по вертолёту),

action:add_precondition   (world_property(properties["on_position"],  true))

я уже нахожусь около костра.

Скажем планировщику, что он должен ожидать от выполнения этого оператора. В данном случае после выполнения этого оператора условие «закончить ли посиделки около костра?» должно стать истинным. То есть если условие стало истинным, планировщик прекратит выполнение оператора.

action:add_effect     (world_property(properties["kamp_end"],   true))

Создание оператора завершено. Добавим его в планировщик.

manager:add_action (operators["wait"], action) 

Эта строчка не имеет отношения к работе планировщика. Если коротко, то она позволяет объекту получать уведомления об определённых событиях (смерть NPC – вызывается метод death_callback(), попадание пули в NPC – вызывается метод hit_callback() и т.д.)

xr_logic.subscribe_action_for_events(object, storage, action)

Создаем оператор, отвечающий за доставку NPC к его месту у костра.

action = this.action_go_position (object:name(),"action_go_kamp", storage)

Добавляем предусловия, как и для предыдущего оператора.

action:add_precondition   (world_property(stalker_ids.property_alive, true))
action:add_precondition   (world_property(stalker_ids.property_danger,false))
action:add_precondition   (world_property(stalker_ids.property_enemy, false))
action:add_precondition   (world_property(stalker_ids.property_anomaly,false))
xr_motivator.addCommonPrecondition(action)
action:add_precondition   (world_property(properties["on_position"],  false))

Единственное отличие – последнее условие. Этот оператор будет выполняться только если NPC ещё не находится на своем месте у костра, то есть если функция evaluator_on_position.evaluate() возвращает false.

В результате выполнения этого действия условие «на своём ли я месте у костра?» должно стать истинным.

action:add_effect     (world_property(properties["on_position"],  true))

Создание оператора завершено. Добавляем его к планировщику.

manager:add_action (operators["go_position"], action)

Осталось ещё одна задача. Нужно запретить планировщику активировать оператор «alife», тот самый оператор, который заставляет NPC болтаться по карте, отстреливать собачек и в конце концов попадать в аномалию. Впрочем, отстрелом врагов занимается другой оператор с идентификатором stalker_ids.action_combat_planner.

Для этого мы получает оператор «alife»

action = manager:action (xr_actions_id.alife)

И добавляем к его предусловиям следующее: условие «закончить ли посиделки у костра?» должно быть истинным.

action:add_precondition   (world_property(properties["kamp_end"],   true))

Итак, мы настроили планировщик. Посмотрим как всё это будет работать.

В некоторый момент времени гулаг, в который попал NPC, назначает ему работу: сидеть у костра. В результате условие «закончить ли посиделки у костра?» становится ложным. Планировщик видит это изменение и пытается выработать последовательность операторов, после выполнения которой, условие бы стало истинным и NPC снова бы вернулся к выполнению высокоприоритетного оператора «alife». Для выполнения этой задачи подходит оператор «посиделки у костра», но для него не выполняется условие «я на своем месте у костра». Поэтому планировщик создаёт план из двух операторов: «дойти до костра» и «посиделки у костра». Если во время выполнения одного из операторов возникнет непредвиденная ситуация (появится враг, главный герой начнёт приставать с вопросами и т.п.), то планировщик скорректирует план, добавив оператор для устранения этой непредвиденной ситуации. Как видно система ИИ в Сталкере обладает весьма большой гибкостью, что мы и продемонстрируем в следующей части.

Создаем мод

В этом разделе мы сделаем мод, позволяющий сказать дружественно настроенному NPC, чтобы он лечил главного героя во время боя. В данный момент эта часть статьи еще не написана. Ориентировочно - она появится завтра...

Личные инструменты