UDKСтатьи

UnrealScript. Справочное руководство. Часть вторая

Автор:

Расширенные функции языка
  Таймеры
  Состояния
    Обзор состояний
    Метки состояний и латентные функции
    Правила наследования состояний
    Расширенное программирование состояний
  Репликация
  Итерация (ForEach)
  Спецификаторы вызова функций
  Доступ к статическим функциям из переменной класса
  Значения по умолчанию для пременных
    Доступ к значениям по умолчанию для переменных
    Доступ к значениям по умолчанию для переменных через ссылку на класс
    Определение значений по умолчанию с использованием блока defaultproperties
  Значения по умолчанию для структур
  Динамические массивы
    Переменная Length
    Итераторы динамических массивов
  Интерфейсы
  Функции делегаты
  Встроенные классы
  Поддержка метаданных
    Обзор метаданных
    Использование нескольких спецификаций метаданных
    Доступные спецификации метаданных
Расширенные технические вопросы
  Реализация UnrealScript
  Вопросы двоичной совместимости UnrealScript
  Технические примечания
  Стратегия программирования на UnrealScript

Расширенные функции языка

Таймеры

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

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

Обычно игровой цикл обновляет состояние каждого актора External Link | UnrealScript. Справочное руководство. Часть вторая один раз за кадр, а часть функции Tick() каждого актора в включает в себя вызов функции UpdateTimers(), которая проверяет все таймеры на истечение времени и вызывает функции UnrealScript, соответствующие таймерам.

Частота таймеров ограничивается временем одного тика и не зависит ни от аппаратных ресурсов, ни от ресурсов операционной системы. Код таймеров реализован на C++, так что вы можете безопасно обновлять сотни таймеров UnrealScript без каких-либо причин для беспокойства. Конечно, вам не следует устанавливать обновление всех таймеров каждый тик, так как таймеры выполняют функции (относительно медленного) кода сценариев.

Состояния

Обзор состояний

Исторически сложилось, что программисты игр используют концепцию состояний. Состояния (также известные как "машины программирования состояний") являются естественным путем управления поведением сложных объектов. Однако, пока поддержка состояний не была реализована на уровне языка UnrealScript, разработчикам для реализации состояний проиходилось создавать конструкции "switch" на языке C или C++. Такой код трудно было писать и обновлять.

Теперь UnrealScript поддерживает состояния на уровне языка

В UnrealScript каждый актор, расположенный в игровом мире, всегда находится в одном и только одном состоянии. Его состояние отражает действия, которые он должен выполнить. Например, перемещение кисти включает несколько состояний, например, "StandOpenTimed" и "BumpOpenTimed". Для объектов Pawn есть несколько состояний, таких как "Dying", "Attacking" и "Wandering".

В языке UnrealScript вы можете писать функции и код, относящиеся к определенным состояниям. Эти функции вызываются только тогда, когда актор пребывает в определенном состоянии. Например, при разработке сценария монстра, вы реализуете функцию "SeePlayer", которая вызывается в тот момент, когда монстр "видит" игрока. "Увидев" игрока монстр перейдет из состояния "Wandering" в состояние "Attacking", начав атаку.

Самый простой способ реализовать вышеописанное - это определить некоторые состояния (Wandering и Attacking), и написать различные версии "Touch" в каждом состоянии. Язык UnrealScript поддерживает эту идею.

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

Ниже приведены примеры состояний из сценария TriggerLight:

// Trigger turns the light on.
state() TriggerTurnsOn
{
   function Trigger( actor Other, pawn EventInstigator )
   {
      Trigger = None;
      Direction = 1.0;
      Enable( 'Tick' );
   }
}
 
// Trigger turns the light off.
state() TriggerTurnsOff
{
   function Trigger( actor Other, pawn EventInstigator )
   {
      Trigger = None;
      Direction = -1.0;
      Enable( 'Tick' );
   }
}

Здесь мы объявили два разных состояния (TriggerTurnsOn и TriggerTurnsOff) и написали в каждом состоянии по версии функции Trigger. Хотя мы могли бы решить эту задачу и без использования состояний, применение состояний делает код гораздо более модульным и расширяемым: в UnrealScript, вы можете реализовать дочерний класс на базе существующего класса, добавить новые состояния, а также новые функции. Если вы попытаетесь реализовать этот сценарий без без использования состояний, то в результате код будет сложнее расширять.

Состояние может быть объявлено как редактируемое, что означает, что пользователь сможет установить состояние актора в UnrealEd. Объявить редактируемое состояние вы можете следующим образом:

state() MyState
{
   //...
}

Объявить нередактируемое состояние вы можете следующим образом:

state MyState
{
   //...
}

Вы можете также задать автоматическое или начальное состояние, в которое актер должен установлен, с помощью ключевого слова "auto". Автосостояние указывает, что все новые акторы при первом создании должны быть установлены в данное состояние:

auto state MyState
{
   //...
}

Метки состояний и латентные функции

В дополнение к функциям, состояние может включать одну или несколько меток с кодом на UnrealScript. Например:

auto state MyState
{
Begin:
   Log( "MyState has just begun!" );
   Sleep( 2.0 );
   Log( "MyState has finished sleeping" );
   goto('Begin');
}

Приведенный выше код состояния выводит сообщение "MyState has just begun!", затем пауза в течение двух секунд, а затем выводит сообщение "MyState has finished sleeping". Самое интересное в этом примере - это вызов латентной функции "Sleep": эта функция возвращает значение не сразу после вызова, а по истечении определенного количества игрового времени. Латентные функции могут быть вызваны только из кода состояний, а не из функций. Латентные функции позволяют управлять сложными цепочками событий, которые предусматривают на некоторых этапах истечение определенного количества времени.

Код состояния начинается с определения метки, в приведенном выше примере метка называется "Begin". Метка указывает удобную точку входа в код состояния. Для меток кода состояний вы можете использовать любые имена, но метка "Begin" имеет особое значение: это начальная точка входа в код состояния.

Для всех акторов доступны три основные латентные функции:

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

Три встроенных функции UnrealScript особенно полезны при написании кода состояния:

Ниже приведен пример код состояния, отражающий описанные выше концепции:

// This is the automatic state to execute.
auto state Idle
{
   // When touched by another actor...
   function Touch( actor Other )
   {
      log( "I was touched, so I'm going to Attacking" );
      GotoState( 'Attacking' );
      Log( "I have gone to the Attacking state" );
   }
Begin:
   log( "I am idle..." );
   sleep( 10 );
   goto 'Begin';
}
 
// Attacking state.
state Attacking
{
Begin:
   Log( "I am executing the attacking state code" );
   //...
}

Когда вы запустите эту программу, а затем коснетесь актар, вы получите вывод:

I am idle...
I am idle...
I am idle...
I was touched, so I'm going to Attacking
I have gone to the Attacking state
I am executing the attacking state code

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

Правила наследования состояний

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

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

Ниже приведен пример наследования состояний:

// Here is an example parent class.
class MyParentClass extends Actor;
 
// A non-state function.
function MyInstanceFunction()
{
   log( "Executing MyInstanceFunction" );
}
 
// A state.
state MyState
{
   // A state function.
   function MyStateFunction()
   {
      Log( "Executing MyStateFunction" );
   }
// The "Begin" label.
Begin:
   Log("Beginning MyState");
}
 
// Here is an example child class.
class MyChildClass extends MyParentClass;
 
// Here I'm overriding a non-state function.
function MyInstanceFunction()
{
   Log( "Executing MyInstanceFunction in child class" );
}
 
// Here I'm redeclaring MyState so that I can override MyStateFunction.
state MyState
{
   // Here I'm overriding MyStateFunction.
   function MyStateFunction()
   {
      Log( "Executing MyStateFunction" );
   }
// Here I'm overriding the "Begin" label.
Begin:
   Log( "Beginning MyState in MyChildClass" );
}

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

Расширенное программирование состояний

Если в производном классе вы не переопределяете состояние базового класса, то вы можете указать ключевое слово "extends", чтобы дополнить состояние базового класса в дочернем классе. Это полезно в тех случаях, когда у вас есть группа подобных состояний (например, MeleeAttacking и RangeAttacking), которые имеют общую функциональность с базовым состоянием Attacking. Состояние Attacking вы можете дополнить следующим образом:

// Base Attacking state.
state Attacking
{
   // Stick base functions here...
} 
 
// Attacking up-close.
state MeleeAttacking extends Attacking
{
   // Stick specialized functions here...
} 
 
// Attacking from a distance.
state RangeAttacking extends Attacking
{
   // Stick specialized functions here...
} 

В состоянии может дополнительно быть использован спецификатор ignores для игнорирования определенных функций базового состояния, например:

// Declare a state.
state Retreating
{
   // Ignore the following messages...
   ignores Touch, UnTouch, MyFunction;
 
   // Stick functions here...
}

Функция GotoState('') позволяет вывести актор из всех состояний, то есть перевести его в состояние "no state". Когда актор пребывает в состоянии "no state", вызываются только его глобальные функции.

При каждом вызове функции GotoState для смены текущего состояния актора, движок может вызвать две специальные функции уведомления, если они определены: EndState() и BeginState(). EndState вызывается в текущем состоянии, непосредственно перед переходом в новое состояние, а BeginState вызывается сразу после перехода в новое государства. Эти функции предназначены для осуществления необходимых вам инициализации и очистки конкретных состояний.

Стек состояний

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

Состояние может быть помещено в стек только один раз. Попытка поместить состояние стеке второрично не удастся. PushState работает как GotoState, она принимает имя состояния и, необязательно, метку точки входа в состояние. Новое состояние получит событие PushedState, текущее состояние получит событие PausedState. После вызова PopState текущее состояние получает события PoppedState, а новое состояние (то, что находилось рядом в стеке) получит событие ContinuedState.

state FirstState
{
   function Myfunction()
   {
      doSomething();
      PushState('SecondState');
      // this will be executed immediately since we're inside of a function (no latent functionality)
      JustPushedSecondState();
   }
   
Begin:
   doSomething();
   PushState('SecondState');
   // this will be executed once SecondState is popped since we're inside of a state code block (latent functionality)
   JustPoppedSecondState();
}

state SecondState
{
   event PushState()
   {
      // we got pushed, push back
      PopState();
   }
}

Воспользовавшись функцией IsInState, вы сможете проверить, находится ли определенное состояние в стеке. Эта функция проверяет только имя состояния и для них не может быть использована для проверки по имени базового состояния. Например:

state BaseState
{
   ...
}

state ExtendedState extends BaseState
{
   ...
}

Если активно состояние ExtendedState, то IsInState('BaseState') вернет false. Конечно же, вызов IsInState('BaseState', true) вернет истину, если BaseState в стеке.

Репликация

Подробнее об использовании реприкачии в UnrealScript вы межете узнать на странице Обзор сетей External Link | UnrealScript. Справочное руководство. Часть вторая.

Итерация (ForEach)

Команда foreach языка UnrealScript упрощает работу с большими группами акторов, например, со всеми акторами на уровне или со всеми акторами на определенном расстоянии от указанного актора. "foreach" работает в сочетании с особой функцией, называемой "итератором" ("iterator"), назначением которой является перебор списка акторов.

Ниже приведен простой пример использования команды foreach:

// Display a list of all lights in the level.
function Something()
{
   local actor A;
 
   // Go through all actors in the level.
   log( "Lights:" );
   foreach AllActors( class 'Actor', A )
   {
      if( A.LightType != LT_None )
         log( A );
   }
}

Первым параметром во всех командах foreach является имя класса, определяющего тип акторов для поиска. Вы можете использовать этот параметр для ограничения диапазона поиска, например, перебирать только акторы, производные от класса Pawn.

Второй параметр команды foreach - это переменная, которой при каждой итерации цикла foreach присваивается ссылка на очередной актор из перебираемого списка.

Ниже приведены все функции итерации, которые работают с оператором "foreach".

Обратите внимание: Все функции итерации являются методами классов, наследующих класс Actor. Если вам необходимо перебрать определенные классы, не наследующие класс Actor, то воспользуйтесь переменной, ссылающейся на класс, наследующий Actor, и следующим синтаксисом:

Таким образом, с помощью класса Interaction вы можете сделать следующее:

Обратите внимание: Итераторы теперь поддерживают и динамические массивы. Подробнее о динамических массивах читайте в разделе "Итераторы динамических массивов".

Страницы: 1 2 3 Следующая »

#UDK, #Unreal Development Kit

7 июня 2012

Комментарии [3]