Теорія
Передбачається, що читач цієї статті знайомий із мовою LUA та основами об'єктно-орієнтованого програмування.
Історія
Підхід до вирішення проблеми ігрового ІІ, обраний творцями STALKER (далі писатиму просто "Сталкер"), був вперше застосований в 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 operation _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 ( world_property ( stalker_en ) tion ( 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 . Для виконання цього завдання підходить оператор посиденьки біля багаття , але для нього не виконується умова на своєму місці біля багаття . Тому планувальник створює план із двох операторів: дійти до багаття та посиденьки біля багаття . Якщо під час виконання одного з операторів виникне непередбачена ситуація (з'явиться ворог, головний герой почне чіплятися з питаннями тощо), то планувальник скоригує план, додавши оператора для усунення цієї непередбаченої ситуації.
Як видно, система ІІ в Сталкері має дуже велику гнучкість, що ми і продемонструємо при створенні мода.