UnrealScript. Справочное руководство
Автор: Vincent Barabus
Введение
Ссылки
Назначение этого документа
Причины разработки языка UnrealScript
Новое в Unreal Engine 3
Примерная структура программы
Виртуальная машина Unreal
Иерархия объектов
Классы
Переменные
Типы переменных
Встроенные типы
Составные типы данных
Типы Unreal
Спецификаторы переменных
Возможность редактирования
Массивы
Структуры
Спецификаторы структур
Перечисления
Константы
Переменные ссылок на объекты и акторы
Переменные ссылок на классы
Выражения
Присваивание
Преобразование ссылок на объекты и классы
Функции
Объявление функций
Спецификаторы параметров функций
Переопределение функций
Управляющие структуры
Структуры повторения
Циклы for
Циклы do
Циклы while
Оператор continue
Оператор break
Структуры выбора
Выражения if-then-else
Выражения case
Оператор goto
Функциональность языка
Встроенные операторы и их приоритет
Функции общего назначения
Создание объектов
Функции для операций над целыми числами
Функции для операций над числами с плавающей точкой
Функции для операций над строками
Функции для операций над векторами
Функции для работы с таймерами
Функции для отладки
Препроцессор UnrealScript
Инструменты и утилиты UnrealScript
Профилирование сценариев
Отладчик сценариев
Введение
Ссылки
Ознакомьтесь со шпаргалкой UnrealScript и книгой Mastering UnrealScript
.
Назначение этого документа
Этот технический документ описывает язык UnrealScript. Это не учебник и не он содержит подробных примеров полезного кода UnrealScript. Примеры сценариев UnrealScript вы найдете в исходном коде движка, содержащем десятки тысяч строк рабочего кода UnrealScript, который решает такие задачи, как ИИ, движение, инвентарь и триггеры. Для начала изучите реальзацию сценариев "Actor", "Object", "Controller", "Pawn" и "Weapon".
Этот документ предполагает, что читатель хорошо знает язык C/C++ или Java, знаком с объектно-ориентированным программированием, играл в Unreal и пользовался редактором UnrealEd.
Программистам, новичкам в ООП, я настоятельно рекомендую посетить Amazon.com или книжный магазин и купить книгу по программированию на Java. Java очень похож на UnrealScript, а его изучение позволит понять язык UnrealScript проще и быстрее.
Причины разработки языка UnrealScript
UnrealScript был создан, чтобы обеспечить команду разработчиков Unreal и сторонних разработчиков мощным встроенным языком программирования, охватывающим все задачи и нюансы программирования игр.
Преимущественно язык UnrealScript разработан:
- Для поддержки основных концепций времени, состояний, свойств и сетей, которые не охватываются традиционными языками программирования. Это значительно упрощает код UnrealScript. Основными сложностями при программировании на C/C++ являются разработка ИИ и программирование игровой логики, заключающиеся в работе с событиями, занимающими строго отведенное количество игрового времени, и с событиями, зависящими от аспектов состояний объектов. При программировании на C/C++ это приводит к созданию запутанного кода, который трудно писать, понимать, поддерживать и отлаживать. Язык UnrealScript включает в себя встроенную поддержку времени, состояний и репликации сети, что значительно упрощает программирование игр.
- Для обеспечения простоты объектно-ориентированного программирования в стиле Java, и выявления ошибок в процессе компиляции. Подобно тому, как Java приносит чистую программную платформу для веб-программистов, UnrealScript представляет из себя чистый, простой и надежный язык для программирования 3D-игр. Основные концепции программирования языка UnrealScript, вытекающие из Java:
- отсутствие указателей и автоматическая уборка мусора;
- невозможность множественного наследования классов;
- строгая проверка типов при компиляции;
- безопасное выполнение на стороне клиента "sandbox" (песочница);
- знакомый внешний вид кода, сходный с C/C++ и Java.
- Для обеспечения высокоуровневого программирования игровых объектов и взаимодействий, а не манипуляции с битами и пикселями. Для обеспечения простоты и в целях повышения мощности языка UnrealScript мы пожертвовали скоростью исполнения. В конце концов, низкоуровневые задачи, для которых критична скорость исполнения, и другие важные участки кода Unreal, для которых высокая производительность намного важнее, чем простота реализации, написаны на языке C/C++. UnrealScript работает на уровне объектов и взаимодействий, а не на уровне битов и пикселей.
С начала разработки UnrealScript и до его текущей реализации, было изучено и отброшено несколько различных крупных парадигм программирования. Во-первых, мы исследовали возможность использования в качестве основы для языка сценариев Unreal платформы Sun и Microsoft Java VM для Windows. Оказалось, что преимущества Java, по сравнению с применением C/C++, в контексте Unreal добавляют разочаровывающие ограничения, связанные с отсутствием необходимых возможностей языка (например, переопределения операторов), а скорость выполнения программ оказалась довольно медленной из-за накладных расходов VM по переключению между задачами и из-за неэффективности сборщика мусора Java в случае сложной структуры наследования объектов. Во-вторых, мы рассматривали возможность реализации языка UnrealScript на базе Visual Basic, который хорошо работал, но был менее дружественным для программистов, привыкших к C/C++. Окончательное решение заключалось в реализации языка UnrealScript, сочетающего возможности языков C++ и Java, и включающего категории, отражающие объекты, специфичные для разработки игр. Это оказалось правильным решением, значительно упростившим многие аспекты кода Unreal.
Новое в Unreal Engine 3
Для тех, кто уже знаком с UnrealScript, мы представляем краткий обзор основных изменений в UnrealScript, дополняющих версию языка сценариев движка Unreal Engine 2.
- Репликация - формулировки репликации в UE3 изменились:
- Блок репликации теперь используется только для переменных
- Репликация функций теперь определяется с помощью спецификаторов функций ( Server, Client, Reliable )
- Стек состояний - Теперь вы можете помещать состояния в стек и извлекать их обратно
- Препроцессор UnrealScript - поддержка макроопределений и условной компиляции
- Отладочные функции - были добавлены новые функции, связанные с отладкой
- Свойства по умолчанию - обработка блока defaultproperties была незначительно изменена и улучшена
- Свойства по умолчанию для структур - теперь структуры также могут иметь свойства по умолчанию
- Больше нельзя установить значения по умолчанию для файлов конфигурации или локализованных переменных
- Defaultproperties во время исполнения теперь доступны только для чтения, выражения class'MyClass'.default.variable = 1 теперь не допускаются
- Динамические массивы - для динамических массивов теперь доступна функция find(), осуществляющая поиск элемента по индексу
- Итераторы динамических массивов - Для динамических теперь доступен оператор foreach.
- Делегаты, как аргументы функций - UE3 теперь позволяет передавать делегаты в качестве аргументов функций
- Интерфейсы - Добавлена поддержка интерфейсов
- Доступ к константам из других классов: class'SomeClass'.const.SOMECONST
- Поддержка множества таймеров
- Значения по умолчанию для аргументов функций - Для необязательных аргументов функции теперь могут быть указаны значения по умолчанию.
- Поддержка подсказок - окна редакторов свойств при наведении указателя мыши на сфойство теперь отображают подсказки, если это свойство имеет комментарии вида /** текст всплывающей подсказки */ над его объявлением в UnrealScript.
- Поддержка метаданных - Расширение внутриигровой функциональности и функциональности редактора путем связывания свойств с различными типами метаданных.
Примерная структура программы
Этот пример иллюстрирует типичный простой класс UnrealScript и подчеркивает синтаксис и особенности языка UnrealScript. Обратите внимание, что этот пример может отличаться от его реализации в исходном коде Unreal, так как этот документ не синхронизирован с исходным кодом движка.
//===================================================================== // TriggerLight. // A lightsource which can be triggered on or off. //===================================================================== class TriggerLight extends Light; //--------------------------------------------------------------------- // Variables. var() float ChangeTime; // Time light takes to change from on to off. var() bool bInitiallyOn; // Whether it's initially on. var() bool bDelayFullOn; // Delay then go full-on. var ELightType InitialType; // Initial type of light. var float InitialBrightness; // Initial brightness. var float Alpha, Direction; var actor Trigger; //--------------------------------------------------------------------- // Engine functions. // Called at start of gameplay. function BeginPlay() { // Remember initial light type and set new one. Disable( 'Tick' ); InitialType = LightType; InitialBrightness = LightBrightness; if( bInitiallyOn ) { Alpha = 1.0; Direction = 1.0; } else { LightType = LT_None; Alpha = 0.0; Direction = -1.0; } } // Called whenever time passes. function Tick( float DeltaTime ) { LightType = InitialType; Alpha += Direction * DeltaTime / ChangeTime; if( Alpha > 1.0 ) { Alpha = 1.0; Disable( 'Tick' ); if( Trigger != None ) Trigger.ResetTrigger(); } else if( Alpha < 0.0 ) { Alpha = 0.0; Disable( 'Tick' ); LightType = LT_None; if( Trigger != None ) Trigger.ResetTrigger(); } if( !bDelayFullOn ) LightBrightness = Alpha * InitialBrightness; else if( (Direction>0 && Alpha!=1) || Alpha==0 ) LightBrightness = 0; else LightBrightness = InitialBrightness; } //--------------------------------------------------------------------- // Public states. // 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' ); } } // Trigger toggles the light. state() TriggerToggle { function Trigger( actor Other, pawn EventInstigator ) { log("Toggle"); Trigger = Other; Direction *= -1; Enable( 'Tick' ); } } // Trigger controls the light. state() TriggerControl { function Trigger( actor Other, pawn EventInstigator ) { Trigger = Other; if( bInitiallyOn ) Direction = -1.0; else Direction = 1.0; Enable( 'Tick' ); } function UnTrigger( actor Other, pawn EventInstigator ) { Trigger = Other; if( bInitiallyOn ) Direction = 1.0; else Direction = -1.0; Enable( 'Tick' ); } }
Ключевые элементы этого сценария, на которые вам следует обратить внимание:
- Объявление класса. Каждый класс "расширяет" (основан на) один из базовых классов, а каждый класс включается в "пакет", набор объектов, распространяемых вместе. Все функции и переменные, принадлежащие классу, доступны только через актора, принадлежащего этому классу. Это не общесистемные глобальные функции и переменные.
- Объявления переменных. UnrealScript поддерживает очень разнообразный набор типов переменных, включая большинство базовых типов C/Java, ссылки на объекты, структуры и массивы. Кроме того, переменные могут быть сделаны как редактируемые свойства, к которые могут получить доступ в UnrealEd дизайнеры, не прибегая к программированию. Эти свойства синтаксически обозначаются с помощью var(), вместо var.
- Функции. Функции могут принимать список параметров и, при необходимости, возвращать значение. Функции могут включать локальные переменные. Некоторые функции вызываются движком Unreal напрямую (например, BeginPlay), а некоторые функции, вызываются из кода других сценариев (например, Trigger).
- Код. Поддерживаются все стандартные ключевые слова C и Java, включая for, while, break, switch, if и прочие. Фигурные скобки и точка с запятой в UnrealScript применяются также, как и в C, C++ и Java.
- Актор и ссылки на объекты. Здесь вы видите несколько случаев, когда функция вызывается внутри другого объекта с использованием ссылки на объект.
- Этот сценарий определяет несколько "состояний", являющихся группами функций, переменных и кода, которые выполняются только тогда, когда актор в находится в определенном состоянии.
- Отметим, что все имена переменных, функций и имена объектов в языке UnrealScript не чувствительны к регистру. В UnrealScript имена Demon, demON и demon - это одно и то же.
Виртуальная машина Unreal
Виртуальная машина Unreal состоит из нескольких компонентов: сервер, клиент, движок визуализации и код поддержки движка.
Сервер Unreal контролирует весь геймплей и взаимодействия между персонажами и акторами. В одиночной игре, как клиент Unreal и сервер Unreal работают на одной машине, в интернет-игре, выделенный сервер работает на одной машине, а все игроки подключаются к этой машине как клиенты.
Все игры происходят внутри "уровня" - автономной среды, включающей геометрию и акторов. Хотя UnrealServer может одновременно работать и более, чем с одиним уровнем, все уровни работают независимо и защищены друг от друга: акторы не могут перемещаться между уровнями, а акторы одного уровня не могут связываться с акторами других уровней.
Каждый актор на карте может быть под контролем игрока (в сетевой игре игроков может быть много) или управляться сценарием. В случае, когда актор контролируется сценарием, сценарий полностью определяет поведение и взаимодействует актора с другими акторами.
Посмотрев на все эти бегающие акторы, выполняющиеся сценарии и событиями, происходящие в игровом мире, вы, вероятно, задаетесь вопросом, как как это все реализовано в UnrealScript. Ответ на этот вопрос ниже:
Для управления временем Unreal делит каждую секунду геймплея на кванты времени, тики ("Ticks"). Тик - это наименьшая единица времени, за которую обновляются все акторы на уровне. Тик обычно занимает от 1/100 до 1/10 доли секунды. Время тика ограничивается только мощностью процессора; чем быстрее машина, тем ниже продолжительность тика.
В UnrealScript время выполнения некоторых команд занимает ноль тиков (то есть их выполнение происходит без затрат игрового времени), а выполнение других команд занимает много тиков. Функции, выполнение которых занимает игровое время, называюься "латентными функциями" ("latent functions"). Латентными функциями, например, являются Sleep, FinishAnim и MoveTo. Латентные функции в UnrealScript могут вызываться только из "кода состояний" ("state code"), а не из кода функций (включая функции, определенные в состоянии).
Пока актор выполняет латентную функцию, он не может выполнить код состояния до завершения выполнения кода этой латентной функции. Однако при этом функции текущего актора могут вызывать другие акторы или VM. В итоге все функции UnrealScript все же могут вызываться в любой момент, даже во время выполнения латентных функций.
В традиционных терминах программирования, UnrealScript действует так, как будто каждый актор на уровне имеет свой собственный исполняемый "поток". Внутренне, Unreal не использует потоки Windows, потому что это было бы крайне неэффективно (Windows 95 и Windows NT не могут одновременно эффективно обрабатывать тысячи потоков). Вместо этого, UnrealScript эмулирует потоки. Этот факт является прозрачным для кода на UnrealScript, но становится очень очевидным, когда вы пишете код на C++, взаимодействующий со сценариями UnrealScript.
Все сценарии UnrealScript выполняются независимо друг от друга. При наличии 100 монстров на уровне все 100 сценариев этих монстров выполняются одновременно и независимо от "тиков" акторов каждого монстра.
Иерархия объектов
Перед началом работы с UnrealScript важно понять высокоуровневые взаимоотношения между объектами Unreal. Архитектура Unreal серьезно отличается от большинства других игр: движок Unreal является полностью объектно-ориентированным (подобно COM или ActiveX), он имеет четко определенную модель объектов с поддержкой высокоуровневых объектно-ориентированных концепций, таких как графы объектов, сериализация, время жизни объекта и полиморфизм. Исторически, большинство игр разрабатывались монолитно, а их основные функции жестко привязывались на уровне объектов, хотя многие игры, такие как Doom и Quake, оказались расширяемыми на уровне контента. Основное преимущество модели объектно-ориентированного программирования Unreal в том, что новые функциональные возможности и типы объектов могут добавляться к Unreal во время выполнения. Расширение функциональности может осуществляться путем создания подклассов, а не, к примеру, путем модификации кучи существующего кода. Эта модель расширения является чрезвычайно мощной и привлекательной для сообщества Unreal, позволяя вносить в Unreal дополнительные модификации и усовершенствования.
Object - это базовый класс всех объектов Unreal. Все функции в класса Object доступны для всех объектов. Object - это абстрактный базовый класс, сам по себе он не имеет полезной функциональности. Вся функциональность обеспечивается подклассами, например, классами Texture (текстуры), TextBuffer (кусок текста) и Class (который описывает классы других объектов).
Actor (расширяет класс Object) является базовым классом для всех автономных игровых объектов Unreal. Класс Actor включает всю функциональность, необходимую для перемещения акторов, их взаимодействия между собой, воздействия на игровое окружение, для осуществления других полезных вещей, связанных с игрой.
Pawn (расширяет класс Actor) - это базовый класс всех созданий и персонажей Unreal, которые контролируются высокоуровневым ИИ или управляются непосредственно игроком.
Class (расширяет класс Object) - это особый вид объекта, описывающий класс объектов. На первый взгляд он может показаться запутанным: класс объекта и класс описывает определенные объекты. Но в работе вам встретится множество случаев, когда Вы будете иметь дело с объектами Class. Например, при воплощении нового актора в UnrealScript, вы можете указать класс нового актера, применяя объект Class.
С помощью UnrealScript вы можете писать код для любого класса, расширяющего класс Object, но 99% времени вы будете писать код для классов, производных от класса Actor. Большинство полезных функций UnrealScript, связанных с игрой, - это действия с акторами.
Классы
Каждый сценарий соответствует одному отдельному классу и начинается с объявления класса, наследуемого класса и другой дополнительной информации, относящейся к классу. Простейшая форма:
class MyClass extends MyParentClass;
Здесь мы объявили новый класс с именем "MyClass", который наследует функциональность класса "MyParentClass". Кроме того, объявленный класс находится в пакете с именем "MyPackage".
Каждый класс наследует все переменные, функции и состояния от своего базового класса. Далее вы можете добавить новые переменные, новые функции (или переопределить существующие функции), новые состояния (или добавить функциональность к существующим).
Типичный подход к разработке класса UnrealScript заключается в создании нового класса (например чудовища минотавра), расширяющего существующий класс, который имеет большинство необходимых функций (например, класс Pawn, базовый класс для всех монстров). При таком подходе вам не нужно заново изобретать колесо - вы можете просто добавить новые необходимые вам функции, сохраняя при этом все существующие функции, не нуждающиеся в модификации. Этот подход особенно эффективен для реализации ИИ: встроенная система ИИ Unreal предоставляет огромное количество базовых функциональных возможностей, применимых в качестве строительных блоков для собственных существ.
Объявление класса может включать несколько дополнительных спецификаторов, которые влияют на класс:
- Native(PackageName)
- Указывает, что "этот класс использует за-кадром код на C++". Unreal ожидает наличие встроенных (native) классов в коде на C++, реализованных в основном исполняемом модуле (.exe). Объявлять встроенные функций или осуществлять реализацию встроенных интерфейсов могут только встроенные классы. Встроенные классы могут быть производными только от других встроенных классов. Для встроенных классов создается автоматически сгенерированный заголовочный файл C++ с описателями, необходимыми для взаимодействовия переменных и указанных функций сценария к кодом на C++. По умолчанию, PackageName - это имя пакета, в котором находится сценарий класса. Например, если в классе определено имя пакета Engine, то в результате автоматически сгенерированный заголовок будет называться EngineClasses.h.
- NativeReplication
- Указывает, что репликация значений переменных для данного класса осуществляется в реализации на C++. Допустимо только для встроенных классов.
- DependsOn(ClassName[,ClassName,...])
- Указывает, что класс ClassName компилируется перед компиляцией текущего класса. ClassName должно указывать на класс в текущем (или предыдущем) пакете. Несколько зависимостей можно определить с помощью DependsOn в одну строку, разделив имена классов запятыми, или же в несколько строк, определив DependsOn для каждого класса.
- Abstract
- Объявляет класс как "абстрактный базовый класс". Это позволяет пользователю добавлять акторов этого класса в мире с помощью UnrealEd или создвать экземпляры этого класса во время игры, потому что этот класс не имеет смысла сам по себе. Например, базовый класс "Actor" является абстрактным, в то время как подкласс "Ladder" не является абстрактным - Вы можете разместить Ladder в мире, но вы не можете поместить в мире актор. Это ключевое слово распространяется на внутренние дочерние классы, но не распространяется на дочерние классы сценариев.
- Deprecated
- Все объекты этого класса будут загружены, но не могут быть сохранены. Размещение экземпляра любого устаревшего (deprecated) актора будет выдавать дизайнерам уровней предупреждение при загрузке карты в редакторе. Это ключевое слово применимо к дочерним классам.
- Transient
- Указывает, что "объекты, принадлежащие к этому классу, никогда не должны сохраняться на диск". Имеет смысл только в сочетании с определенными видами встроенных классов, которые не являются устойчивыми по своей природе, например, как игроки или окна. Это ключевое слово распространяется на дочерние классы; дочерние классы могут переопределить этот флаг путем использования ключевого слова NonTransient.
- NonTransient
- Отменяет спецификатор Transient, унаследованный от базового класса.
7 июня 2012 (Обновление: 18 мая 2013)