Скриптинг И.И.: Цели

Материал из CryWiki Russia

Перейти к: навигация, поиск
О статье
АвторAndreyFilantrop
СложностьСредняя
ТребованияТекстовый редактор, Crysis 2 Mod SDK.
Дата добавления01/02/11


Содержание

Цели (Goals)

Краткий обзор

ИИ Объекты в CryAISystem (системе искусственного интеллекта в Crysis) способны выполнять конкретные действия для достижения целей. Цели могут быть очень простыми - например, приблизиться на 2 метра к определенному объекту, или довольно сложным - например, спрятаться в крайнем левом препятствии на пути к вашей текущей цели. Низкоуровневая ИИ система отвечает за выполнение и управление этими целями, и она также контролирует что случается если цели удачно выполнены или не выполнены. Другие функции включают определение того, какие функции выполняются параллельно, а какие – последовательно. Цели могут быть atomic (монолитные или «атомарные») и derived (производные). Атомарная цель - простая цель, которая не может быть далее разделена на под-цели, и является одной логической единицей. Производная цель, это цель, которая представляет собой сочетание нескольких атомных или производных целей, которые могут выполняться параллельно или последовательно. В то время, как атомарная цели определяется системой и внедрение новых атомарных целей потребует работы на низком уровне ИИ, пользователь может определять производные цели сам. Определение новых производных целей - отныне я буду называть производные цели просто цели - может быть сделано в C + + или в LUA (обе системы содержат набор функций, которые работают с целями). Обычно цели определяются исключительно в LUA скрипте. Так как производные цели создаются путем объединения атомарных и других производных целей, должен быть способ организации нескольких целей в логическое объединение - это программа, - что имеет начальную точку, тело для выполнения и условия для окончания. Это логическое объединение существует в CryAISystem и называется GOAL PIPE.

Голпайпы (Goal Pipes)

Логическое объединение атомарных и производных целей для того, чтобы создать еще одну производную цель называется целевая труба (goal pipe или голпайпа). Она называется так, потому что концептуально это напоминает трубу, в которой цели толкаются из одного конца, и когда они выполняются они выходят на другом конце, и снова толкаются от дна - как на картинке. Это означает, что цели выполняются в определенной последовательности, - что обычно правда - но есть также путь заставить цели выполняться параллельно. Даже голпайпа может быть запихана как цель в другую голпайпу. Это порождает такие экзотические концепты как вложенные (nesting) голпайпы, и самоповторяющиеся (рекурсивные - recursing) голпайпы. Нет ограничения количества вложений с голпайпами, так что это остаётся на усмотрение пользователя. Лично я бы не рекомендовал использование самоповторения (recursion) в пайпах, но система в общем поддерживает это. Объединив атомарные цели в более крупные цели, вы можете развить любой уровень абстракции для персонажей в игре. Например, выход из комнаты может включать в себя нахождение двери и использование её. Таким образом, вы разбили более высокоуровневую цель на две меньших цели. Поиск двери распадается на нахождение объекта - двери и приближение к нему - оба этих действия являются довольно атомарными. Использование двери включает приближение на расстояние 1 метра и нажатие «Использовать» (USE). Так что хотя вы можете создать одну большую голпайпу, которая находит объект-дверь, приближается к ней, становится на 1 метр, нажимает «использовать», вы также можете разбить действие на 2 более высокоуровневые подцели и при необходимости повторно использовать их когда-нибудь позже. По тому же принципу возможно вы захотите использовать exit_room голпайпу находящуюся внутри большей exit_building голпайпы и так далее. Именно для этой цели предусмотрены вложенные голпайпы. Все голпайпы создаются при загрузке игры. Голпайпы не являются собственностью ИИ объекта, они являются собственностью самой ИИ системы, так что вам не нужно беспокоиться о том, чтобы каким-то образом реализовывать их или каким-то образом поддерживать их. Каждый ИИ объект которому необходима голпайпа, получает клон оригинала – это означает, что у него имеется своя собственная копия голпайпы и не может повлиять на операции других ИИ объектов, которые могут использовать ту же голпайпу. ИИ объекты являются владельцами этих клонированных голпайпов и они автоматически представляют их поддержку, так что это уже другая вещь и она не должна беспокоить пользователя. Для создания голпайпы вы должны вызвать следующую функцию (в LUA – есть также эквивалентная функция в C++, но мы сфокусируемся на LUA функциях:

Lua

AI.CreateGoalPipe("crouchfire");


Глобальный LUA скрипт ИИ объекта предлагает эту функцию (также как и другие, которые будут рассматриваться позже). Функция проста с единственным аргументом – именем, под которым эта голпайпа будет идентифицироваться в дальнейшем. Особое внимание должно быть уделено наименованию голпайпы. Во-первых, оно не должно быть именем существующей атомарной цели - полный список включен в приложениях. В идеале, оно должно быть уникальным именем, которое легко запоминается и должно объяснять, что голпайпа будет делать. Глядя на имя образца голпайпы легко предположить, что эта голпайпа будет заставлять entity, которая её использует, стрелять во время передвижения на корточках. Если будет попытка создания пайпы с уже существующим именем, то старая версия голпайпы будет удалена и очищена, и будет полностью заменена новой версией. Это не значит, что если кто-то уже использовал старую версию, не будет знать что делать или даже использовать новую версию. Голпайпы КЛОНИРУЮТСЯ для использования, поэтому кто бы ни использовал старую версию голпайпы, будет держаться клонированной версии старой голпайпы и заканчивать её выполнение, в то время, как тот кто захочет использовать новую версию голпайпы будет иметь только новую версию с КОТОРОЙ будет произведено клонирование. В этом смысле конфликты имен голпайп – не обязательно такая уж страшная вещь. Голпайпам не обязательно быть созданными ТОЛЬКО при запуске. Пользователь волен создать голпайпу в любой точке процесса игры. Преимуществом этого является то, что вы можете создать голпайпу, которая зависит от определенного условия, или голпайпу, которая предназначена для очень специфических нужд - ту, которая может не быть доступна при старте игры. Создание голпайпы ОЗНАЧАЕТ распределение памяти и доступа, но не очень значительное, если используется в меру. Хорошим эмпирическим правилом является то, что голпайпы могут быть созданы в ответ на событие в поведении, потому что такие события обычно не часты. Создание новых голпайпов при каждом обновлении кадров – плохая идея.

Запихивание целей в голпайпы.

С того момента, как голпайпа удачно создана, вы можете начать запихивать в неё цели. Как упоминалось ранее, голпайпа работает в сущности как FIFO буфер (first in first out – первый зашел первый вышел). Это означает, что цели запиханные в трубу будут выполняться в том же порядке в котором они в начале были запиханы. Нет предела (кроме здравого смысла) в количестве целей, которые вы можете запихнуть в голпайпу, и помните, что вы также можете запихнуть суб-голпайпу в голпайпу. Для того, чтобы запихнуть цель в трубу вы можете воспользоваться следующей функцией:

Lua

AI.PushGoal("crouchfire", "firecmd", 0, 1);


Обратите внимание, что функция это часть функциональности, которую глобальный ИИ объект предлагает. Аргументы этой функции говорят сами за себя: в первую очередь голпайпа в которую мы хотим запихать что-либо должна быть определена по имени. Далее, фактическая цель, которую нужно запихать в голпайпу должна быть указана, также по имени. В нашем примере это простая цель firecmd, которая указывает исполняющему врагу разрешено стрелять или нет, исходя из далее следующих параметров. Третий параметр этой функции это интегер, который может иметь только два значения 0 или 1. Это последний параметр который является общим для всех запихиваемых целей – все параметры после него являются целе-специфичными, т.е. означает что они могут быть различными основываясь на самой цели, которая должна быть запихана. Этот третий параметр называется блокирующий флаг. Если он установлен на 1, это означает, что когда цель начинает выполняться, дальнейшее выполнение голпайпы будет СТОЯТЬ до тех пор, пока текущая цель не будет завершена. Если оно установлено на 0, это означает, что даже хотя текущая цель может не быть законченной, выполнение голпайпы должно продолжаться.

Это обеспечивает механизм определения, будут ли цели выполняться последовательно или параллельно. Различные производные цели требуют разное поведение, вот почему это свойство можно контролировать на уровне каждой цели. Например, если мы пишем цель, в которой надо использовать кнопку лифта, нормально, что мы должны ждать до нажатия кнопки «использовать», пока мы не подойдем к лифту на определенное расстояние. В этом примере мы сделаем цель приближения к нему блокирующей, и следующей за ней будет цель, которая генерирует нажатие кнопки соответствующей кнопке «использовать».

СУБ-ГОЛПАЙПЫ (под-голпайпы или субпайпы – это варианты перевода одного и того же слова) НЕ МОГУТ БЫТЬ НЕ БЛОКИРУЮЩИМИ. Они являются блокирующими по определению и любые голпайпы, которые имеют субпайпы, должны ожидать пока эта субпайпа не закончит своё выполнение, перед тем как они смогут продолжить своё собственное выполнение. Справочный раздел в конце этого документа содержит полное описание всех атомарных целей, которые система распознаёт в своём текущем состоянии. Обращайтесь туда при конкретном обсуждении по параметрам для конкретной цели и её поведению. GoalPipes директория в ИИ скрипт-директории содержит все голпайпы, которые были использованы в игре, будучи распределёнными по нескольким файлам скриптов. Не все из этих пайп были использованы. Обращайтесь к этим файлам для получения идеи, как могут выглядеть законченные голпайпы, а также за примерами использования атомарных целей, вложенных целей и др.

Выбор голпайп для выполнения

После того как голпайпа создана и в неё запиханы цели, она готова для того чтобы быть выбранной любой сущностью (entity), которая имеет ИИ объект для исполнения. Обычно голпайпы выбираются для исполнения в ответ на событие в игре и они указывают сущности (entity), которая получает событие, что ей надо делать.

Выбор голпайпы, это простейшая и наиболее часто используемая функция в скриптовых поведениях для врагов. Посредством выбора определенной голпайпы для определенной сущности, вы заставляете эту сущность выполнять цели, расположенные внутри этой голпайпы.

Одна сущность может выполнять ТОЛЬКО ОДНУ голпайпу одновременно. Когда новая голпайпа выбрана (и это асинхронная операция), старая пайпа удаляется и стирается, любые оставшиеся цели от старой пайпы также стираются, и новая пайпа клонируется и устанавливается к исполнению.

Чтобы выбрать голпайпу для исполнения её определенной сущностью, следующая функция должна быть выбрана в скриптовом объекте сущности:

Lua

entity:SelectPipe(0, "cover_look_closer");


Скриптовый объект сущности передаётся в каждую обрабатывающую событие функцию, так что становится просто вызвать функции на это и менять его состояние. Параметры этого вызова очень просты: первый интегер-параметр зарезервирован и всегда должен быть 0 (частично в силу исторически сложившихся причин). Второй параметр указывает имя голпайпы, которая должна быть выбрана. Это должно быть имя существующей голпайпы, т.к. в противном случае вызов функции не удастся.

Является бессмысленным осуществлять множественный вызов SelectPipe функции на единственное событие. Из-за ограничения возможности только ОДНОЙ голпайпы быть выполняемой одномоментно, только последний вызов SelectPipe функции будет в силе, и только последняя пайпа будет той единственной, которая сможет закончить своё выполнение.

Существует механизм для асинхронной вставки голпайп в любую голпайпу, которая на данный момент выполняется для сущности. В любой момент времени можно ВСТАВИТЬ голпайпу в выполняющуюся на данный момент. Эта операция остановит выполнение текущей пайпы, вставит выбранную голпайпу как её встроенную субпайпу и продолжит выполнение внутри встроенной голпайпы.

Для вставки субпайпы в текущую выполняющуюся голпайпу, следующая функция должна быть выбрана в скриптовом объекте сущности:

Lua

entity:InsertSubpipe(0, "setup_stealth");


Субпайпа всегда будет вставлена в текущую выполняющуюся голпайпу, независимо от того, на каком этапе повторения она в данный момент находится. Это означает, что если голпайпа уже выполняла субпайпу (или более) во время вставки, то новая голпайпа будет вставлена в субпайпу. Вставки пайп могут быть использованы в более широком контексте, например выстраиваясь в очередь нескольких пайп для выполнения в опрделенном порядке. Вызов InsertSubpipe много раз на одно и тоже событие имеет не те же результаты, что и вызов SelectPipe несколько раз на одно и то же событие. Голпайпы которые вставляются, вставляются на верх предыдущей голпайпы, так что, когда начинается выполнение, цели будут выполняться в последовательности начиная от цели в последнем вызове InsertSubpipe к цели в первом вызове InsertSubpipe. Например:

Lua

entity:InsertSubpipe(0, "setup_stealth");
entity:InsertSubpipe(0, "DRAW_GUN");


будет выполнять голпайпу DRAW_GUN (достать оружие) сначала, а когда она закончится, будет выполнять setup_stealth (положение пригнувшись) голпайпу. Когда встроенная субпайпа закончит выполнение, она вернётся к предыдущей голпайпе, которая была вставлена вначале.


Аргументы голпайп и подробности выполнения

Как говорилось ранее, голпайпы выполняют цели, запиханные в них в последовательности. Но что происходит, когда последняя цель пайпы выполнена? Ответ на этот вопрос зависит от того, была ли голпайпа выбрана или вставлена.

Выбранные голпайпы будут продолжать выполнение сначала если они выполняют до конца, т.е. они циркулируют. Если выбранная голпайпа никаким образом не прерывается (за счет выбора другой голпайпы или за счет вставки голпайпы), она будет повторяться вечно, вместе со всеми встроенными голпайпами, которые она имеет.

Если же пайпа была вставлена, то после выполнения своей последней цели, она будет удалена, стёрта и не будет выполняться снова до тех пор пока не будет опять вставлена. Это справедливо ТОЛЬКО для пайп, которые были вставлены асинхронно - если голпайпа вставлена в некоторую другую голпайпу за счет запихивания обычным образом, то она будет перевыполняться каждый раз, когда родительская пайпа её достигнет.

Выполнение голпайп довольно устойчиво. Вполне естественно, что некоторые голпайпы требуют некоторые условия для нормальной работы, и в большинстве случаев эти условия будут выполнены - например, цель приближения не может быть выполнена без цели привлечения внимания к объекту, потому что она не будет знать, к чему следует приближаться. Если такие ситуации встречаются во время выполнения целей, цель приостанавливается (завершается), и выполнение голпайпы продолжается не прерванным. Последствия этого иногда позитивны, а иногда негативны, в зависимости от контекста выполнения пайпы и желаемого поведения. Знание процедур оперирования целей по умолчанию которые не могут быть выполнены правильно, должно помочь дизайнеру поведения в диагностике проблем и нахождении решений.

Голпайпы также могут получать аргументы. Только один тип аргумента разрешен для голпайп, и это действительный ИИ объект - это может быть entity (сущность), tag point (точка назначения) и anchor (якорь), и т.д. Аргумент передается в пайпу во время выбора (вставки), и он помещается в целевой операнд последней операции. Там он может быть легко доступен любым выполнением цели в пайпе, т.к. большое количество целей может быть намечено к оперированию в задаче последней операции вместо задачи к которой привлечено внимание. Обсуждение задач к которым привлечено внимание и задач последней операции можно найти здесь.

Голпайпы помнят свои аргументы, что означает, что возможно вкладывать различные голпайпы с различными аргументами. Система будет убеждаться, что всегда корректный аргумент помещается в задачу (цель) последней операции перед тем, как вставленная или выбранная пайпа начнёт выполняться.

Чтобы задать аргумент голпайпе, вы должны предоставить его во время вызова SelectPipe или InsertSubpipe. Аргумент может быть только действующим ИИ объектом, но путь его указания может быть выполнен множеством способов. Например, вы можете определить целевую entity как аргумент за счет передачи её ENTITY ID в выбирающую функцию, или вообще любой ИИ объект указав NAME (имя) нужного объекта. ИИ система гарантирует, что имена объектов будут уникальными, но не обязательно теми, которые им присвоил пользователь. В связи с этим не рекомендуется жестко прописывать имена в аргументы, а наоборот, они должны быть получаемы и просто проходить по рукам.

Механизм передачи аргументов достаточно надёжен и будет обрабатывать любые странные операции, как, например, когда имя аргумента передаётся, что он не может найти. Выполнение голпайпы будет продолжаться в обычном режиме.

Вот пример того, как указать аргумент голпайпам:

Lua

OnGroupMemberDiedNearest = function (self, entity, sender) 
 
        -- кто-то из вашей группы погиб 
 
  entity:SelectPipe(0, "RecogCorpse", sender.id); 
 
end,


В этом примере пайпа RecogCorpse выбрана, и sender (отправитель) OnGroupMemberDiedNearest сигнала передаётся в качестве аргумента за счет использования своего entity id. Сигнал посылается умирающим врагом и получается членом команды, находящимся ближе всех к его позиции. Получивший этот сигнал вероятно попытается двинуться навстречу посылающему с целью проверить, жив ли он ещё.

Другой пример, который иллюстрирует передачу аргумента пайпе посредством его имени:

Lua

local gunrack = AI.FindObjectOfType(self:GetPos(), 30, AIAnchor.GUN_RACK); 
 
if (gunrack) then 
 
    self:InsertSubpipe(0, "get_gun", gunrack);


Функция FindObjectOfType будет возвращать имя ближайшего объекта в 30-метровом радиусе типа AIAnchor.GUN_RACK. Обратите внимание, что мы никогда не прописываем имя явно, оно просто проходит во вставочную функцию пайпы как возвращенное из Find function (находящей функции). Также обратите внимание, что сейчас сущность обозначается как self (сама), вместо entity (сущность). Это означает, что она вероятно вызывается из своего собственного скрипта, а не из скрипта behavior (поведения).

Передача дополнительных параметров в голпайпы как параметров цели (GoalParams)

Возможно передать динамический ряд параметров для конкретных голпайп как параметров цели (GoalParams).

Простой пример, как использовать GoalParams в LUA коде:

Lua

YOUR_FUNCTION = function( self, entity, sender, data ) 
 
 local tbl = {                
 
                  {},                
 
                  {}, 
 
                  {}, 
 
              } 
 
 tbl[1].name = "firstParamName" 
 
 tbl[1].value = data.fValue 
 
 tbl[2].name = "secondParamName" 
 
 tbl[2].value = data.point.x 
 
 entity:SelectPipe(0, "YourGoalPipeName", 0, 0, 1, tbl) 
 
end


Параметр 6 фанкции SelectPipe является тем самым, который относится к динамическим параметрам. Движок создаст внутреннюю структуру для хранения [parameter_name, parameter_value] которые конкретная голпайпа сможет интерпретировать позже, если необходимо.