Модель поведінки
Почнемо створення моделі поведінки із розробки евалуаторів. Евалуатор повинен бути об'єктом класу успадкованого від класу property_evaluator .
Візьмемо спочатку евалуатор evaluator_faraway , що визначає, що NPC знаходиться надто далеко від ГГ. Цей евалуатор потрібен для того, щоб NPC не відходив надто далеко від ГГ і міг у разі потреби швидко підбігти до нього та надати допомогу.
Оголошуємо клас евалуатора:
class "evaluator_faraway" ( property_evaluator )
Визначаємо функцію ініціалізації (у LUA це аналог конструктора об'єкта):
function evaluator_faraway:__init ( name, storage ) super ( nil , name ) self.st = storage end
Ключове слово super слугує для виклику конструктора базового класу. Член st зберігатиме посилання на таблицю стану нашої моделі поведінки.
Тепер потрібно визначити функцію evaluate() , заради якої створювався евалуатор. Очевидно все просто, потрібно перевірити відстань від NPC до РР і повернути true , якщо ця відстань більша за певне значення. Але подумаємо. Коли евалуатор поверне true , запрацює оператор, який змушує NPC підійти ближче до ГГ, тобто відстань миттєво зменшиться і евалуатор почне повертати false , що призведе до переходу NPC під керування ІГ. ІІ може знову вирішити піти від ГГ, що призведе до повторного спрацьовування евалуатора. В результаті виникне замкнутий цикл, і NPC крутитиметься на одному місці (насправді цей цикл рано чи пізно розірветься через зміну ігрової ситуації, але краще взагалі уникнути його).
Ви можете використовувати різні шляхи для вирішення цієї проблеми. Спробуємо зробити так: використовуватимемо дві відстані, евалуатор спрацює при досягненні першої і залишатиметься активною, поки відстань не стане меншою за другу.
local min_faraway_dist= 10 local max_faraway_dist= 20 function evaluator_faraway:evaluate ( ) local actor=db.actor if not actor then -- ГГ ще не спаунізувався return false end local dist=actor:position ( ) :distance_to ( self.object:position ( ) ) if dist>max_faraway_dist then self .st.faraway= true elseif dist<min_faraway_dist then self.st.faraway= false end return self.st.faraway== true end
Евалуатор готовий, але треба його протестувати. Тому давайте створимо мінімальну модель поведінки з однієї умови та одного оператора. Нам потрібен оператор, який переміщує NPC ближче до РР. Оголошуємо клас action_stay_close , успадкований від action_base , і визначаємо конструктор.
class "action_stay_close" ( action_base ) function action_stay_close:__init ( name, storage ) super ( nil , name ) self.st=storage end
Оператор повинен містити функції initialize() , execute() і, можливо, finalize() .
Функція initialize() викликається початку роботи оператора, тобто у момент, коли планувальник ставить цей оператор у першу позицію плану.
function action_stay_close:initialize ( ) local npc=self.object -- Не знаю навіщо ці дві функції, але вони використовуються у всіх операторах npc :set_desired_position ( ) npc:set_desired_direction ( ) -- Скидаємо поточні анімації npc :clear параметри руху npc:set_detail_path_type ( move.line ) npc:set_body_state ( move.standing ) npc:set_movement_type ( move.run ) npc:set_path_type ( game_object.level_path ) -- Експерименти показали, що експерименти показали, що мінімальна швидкість, anim.free - нормальна, anim.panic - максимальна ) npc :set_mental_state ( anim.panic ) -- Підвищуємо пильність NPC npc:set_sight ( look.danger, nil , 0 ) -- Звільним сталкера від усіх ідіотських обмежень remove_all_restrictions ( ) -- Задамо зміщення точки призначення, щоб помічники не збивалися в купу self.offset=vector ( ) :set ( math . random ( ) * 6 -3 , 0 , math . random ( ) * 6 -3 ) self. offset:normalize ( ) end
Функція execute() періодично викликається під час виконання оператора. Частота дзвінків, мабуть, залежить від відстані від NPC до РР.
function action_stay_close: execute ( ) local npc=self.object local actor=db.actor if not actor then -- Хм, щось негаразд. Можливо, ГГ перейшов на інший рівень? Забороняємо схему поведінки self.st.enabled= false end -- Отримуємо найближчу доступну точку в 5 метрах від РР -- Спочатку я спробував використати функцію npc:vertex_in_direction, але вона не працює local vertex_id= level.vertex_in_direction ( actor ,self.offset, 5 ) local act_v_id=actor:level_vertex_id ( ) -- Відправляємо нашого NPC у знайдену точку local acc_id = utils.send_to_nearest_accessible_vertex ( npc, vertex_id ) if self.st.dist and self.st.d. - якщо NPC знаходиться занадто далеко від ГГ нехай пробігтися швидше npc :set_mental_state ( anim.panic ) else npc :set_mental_state ( anim.free )
Функція налаштування планувальника.
function add_to_binder ( object, char_ini, scheme, section, st ) local manager = object:motivation_action_manager ( ) local property_wounded = xr_evaluators_id.sidor_wounded_base -- Видаляємо евалуатор, так як в xr_motivator ми встановили його в property_evaluator_const manager : remove_evaluator ( property_faraway ) -- і замінюємо його нашим manager :add_evaluator ( property_faraway, evaluator_faraway ( = evaluator_faraway ) "action_stay_close" , st ) - і налаштовуємо передумови. 1. Сталкер живий action:add_precondition ( world_property ( stalker_ids.property_alive, true ) ) -- 2. Сталкер не поранений action:add_precondition ( world_property ( property_wounded, false ) ) -- Я використовую свій мод для обходу а . if anomaly_evader then -- 3. Поруч немає аномалій action : add_precondition ( world_property ( 1099 , false ) ) end -- 4. Сталкер занадто далеко від ГГ , false ) )) - Додаємо оператор у планувальник manager:add_action ( act_stay_close, action ) - Тепер підкоригуємо стандартні оператори, щоб помічник не відволікався на будь-яку нісенітницю. action=manager:action ( stalker_ids.action_alife_planner ) action :add_precondition ( world_property ( property_faraway, false ) ) action = manager: action ( stalker_ids.action_combat_planner ) action : add_precondition ( world_property ker_ids . action_danger_planner ) action:add_precondition ( world_property ( property_faraway, false ) ) end
Додамо функції активації/деактивації схеми поведінки.
function set_help ( npc , ini ) local st = xr_logic.assign_storage_and_bind ( npc , ini , " actor_need_help " ) _ _ _ _ _ _ _ _ ] if st then st.enabled = false end end
Змінимо діалогові функції-заглушки.
function activate_scheme ( talker, target ) set_help ( talker , talker : spawn_ini ( ) ) scheme_status [ talker : id ( ) ] = true end функція deactivate_scheme ( talker , target ) disable_scheme ( talker, " actor_need_help ] = nil end
Додамо до функції xr_motivator.addCommonPrecondition() наступні рядки, щоб заблокувати стандартні схеми поведінки.
if actor_need_help then action:add_precondition ( world_property ( actor_need_help.property_faraway, false ) ) end
Якщо спробувати запустити мод зараз, гра просто вилетить. Причина в тому, що ми додали передумову стандартних схем поведінки, але не додали евалуатор цієї умови. Тому додаємо у функцію xr_motivator.net_spawn() наступні рядки:
local manager = self.object:motivation_action_manager ( ) if actor_need_help then manager:add_evaluator ( actor_need_help.property_faraway, property_evaluator_const ( false ) ) end
Для того щоб знизити навантаження на процесор, використовуємо property_evaluator_const , який завжди повертає одне і те ж значення. В результаті тестування з'ясувалося, що не всі NPC підкоряються нашій схемі поведінки. Причини цього поки не зрозумілі, потрібно додаткове тестування і дуже бажана допомога розробників (хоча б для того, щоб дізнатися, як з'ясувати, який оператор діє в даний момент на NPC).