Unreal Engine: Типы данных и управление ними

Основы UnrealScript: Учимся на практике
Надеюсь, все знают, что такое переменная. Кто не знает - посмотрите в любом школьном учебнике по программированию, я даже не знаю, как о ней нормально рассказать. В общем, будем отталкиваться от предположения, что уже все знают, что такое переменная. О том, какие типы данных есть в Unreal Script, и как с ними обходиться, я и расскажу в этой статье. Пишу без примеров, но если кому-то примеры понадобятся, говорите, переработаю.

Boolean

Булеан (или как оно там произносится), для краткости просто бул (bool), или логическая переменная - самый простой тип переменных. Он может принимать два значения - правда или ложь, да или нет, единица или ноль - в принципе, разные проявления одной и той же пары значений. В общем, для первых значений соответствует true, для вторых - false. Создается логическая переменная командой
var bool bVarName;
var говорит игре, что мы хотим создать переменную. bool указывает непосредственно тип создаваемой переменной, а bVarName - ее имя. Все булы желательно называть именем с b в начале, так их будет проще распознать. Очевидно, что пробелы в именах переменных не допускаются, зато подчеркивания - допускаются. Также, желательно избегать использования в именах слов No или Not. Почему? Тут идет аналогия с вопросами типа "Ты не едешь домой?" - оба ответа можно понять как "да, не еду", "нет, не еду". В конце строчки стоит точка с запятой. Обязательно заканчивайте строчки точкой с запятой.
Важно: это правило действует обратным образом для блока defaultproperties, точка с запятой в конце строки этого блока будет считаться ошибкой.
Ну и последний общий аспект работы с переменными - новые значения присваиваются через знак равенства.
bVarName = false;
Ах да, если вы не задали созданной булевской переменной значения, по умолчанию оно равно false.

Integer и float

Integer и float содержат числа, только первое содержит целые, второе - с дробной частью.
Создание integer-переменной
var int IntVar;
По умолчанию, целочисленным переменным присваивается ноль. Подмечу также, что при присвоении инту не целого числа, ошибки при компиляции не будет, переменная просто проигнорирует дробную часть. К примеру
IntVar = 12.7
на выходе все равно даст 12. Также,
IntVar = 10/3
вместо 3.3333 выдаст 3.
float, в принципе, все то же самое, не считая последних особенностей. Создается, ясное дело, так
var float FlVar;
Соответственно, при присвоении значений с дробной частью гордо эту часть хранит.

String

Нет, не те стринги. В программировании, стринг - тип данных, который хранит массив символов, цифр, знаков, или все это подряд. В общей, строчки текста. В общем, о них почти нечего рассказывать, используются они редко, хотя, к слову, именно их мы в лог и выводим, по сути (любые другие типы данных, которые мы подаем в команду-лог, сначала превращаются в текст, а потом уже выводятся). При создании им присваивается... ничего, что уж тут поделать, просто пустая строка.
var string StringVar;

StringVar = "This is some text";

Enum

Энумы (от англ. Enumeration, перечень) - не очень понятный тип данных. Работают как перечень значений, каждое может быть представлено в виде своего имени или номера. Последнее позволяет упорядочено с ними работать и сравнивать с обычными числами. К примеру, как создать перечень, характеризующий высоту:
enum EAltitude
{
    ALT_Underground,
    ALT_Surface,
    ALT_Sky,
    ALT_Space,
};
С порядка элементов ясно, что, к примеру, ALT_Space будет больше, чем ALT_Sky. Не всегда, работая с перечнем, нам важен порядок элементов, например, перечень EMoveDir с Actor.uc. Он пересчитывает, в какие стороны может двигаться актор, и нам важно только то, что переменная перечня может принимать только одно значение. Их порядок же не важен.
Каждый элемент перечня также представляет собой байтовое число
ALT_Underground = 0
ALT_Surface = 1
ALT_Sky = 2
ALT_Space = 3
Создается переменная определенного перечня аналогично обычным переменным
var EAltitude Altitude;
Хранит переменная только одно значение. При выводе в лог этой переменной, кстати, будет выводиться не число, а именно текстовое название элемента.
Ну и очевидно, что сравнение (ALT_Sky>ALT_Space) выдаст false.
Перечни не очень часто используются, но иногда они сохраняют немало нервов и делают код более компактным.

Массивы

Припустим, вам нужно три переменные int.
var int IntVar1;
var int IntVar2;
var int IntVar3;
Многовато текста, не кажется? Как насчет использовать один var int вместо трех? Можно.
var int IntVar1, IntVar2, IntVar3;
Но если вам будет необходимо создать действительно большое количество переменных одного типа, то без массивов вам не обойтись. Массив - переменная, содержащая несколько переменных одного типа одновременно.
var int IntVar[4];
Доступ к каждому элементу массива совершается по номеру (индексу) этого элемента.
IntVar[0] = 3;
IntVar[1] = 13;
IntVar[2] = 4;
IntVar[3] = 1;
Важно: запомните, что нумерация элементов массива начинается с нуля, а не с единицы.
Соответственно замечанию сверху, попробовав задать значение элементу массива с индексом 4, мы бы получили ошибку при компиляции.
При доступе к элементу массива, в качестве индекса спокойно может использоваться другая целочисленная переменная.

Динамические массивы

Указанный сверху массив всегда будет иметь длину в четыре элемента. Иногда же, нам понадобятся массивы, способные менять свою длину по необходимости. Таковыми являются динамические массивы. Задаются они так.
var array<int> IntVar;
По умолчанию, подобные массивы пустые. Присвоив же еще несуществующему элементу массива определенное значение, мы его одновременно создадим.
IntVar[3] = 9;
Попробовав считать значение с IntVar[4], мы получим ошибку, потому что этого элемента еще не существует. Если вам необходимо узнать длину динамического массива, это можно сделать функцией length, которая вызывается с самого массива.
var int L;
L = IntVar.length;
Если мы выведем L, ее значение будет равно 4, с чего можно сделать вывод - присвоив N-тому элементу массива значение, мы создаем не только этот элемент, но и все несуществующие элементы с индексом ниже N, в нашем случае, 0, 1 и 2.
Функцию length также можно использовать в качестве индекса, например IntVar[IntVar.length-1] обозначает последний элемент массива (отнимаем единицу, с учетом того, то нумерация с нуля).
Думаю, важно упомянуть, что количество элементов увеличивается еще в момент, когда мы собираемся присваивать новое значение. К примеру, если нам нужно скопировать массив TestArray в массив CopyArray, мы можем использовать следующую строчку
CopyArray[CopyArray.length] = TestArray[CopyArray.length – 1];
В левой части кода CopyArray.length все еще равно нулю, но сам факт перехода в правую часть (процесс присвоения) уже обозначает, что мы создали нулевой элемент массива CopyArray, и длинна его уже не ноль, а единица, поэтому нулевому элементу TestArray соответствует индекс CopyArray.length – 1, а не просто CopyArray.length. Вот так вот. Да, я и сам мало чего понял с того, что написал

Структуры

Структура - это переменная, которая содержит несколько переменных. Например, структура, характеризующая цилиндр
struct Cylinder
{
    var float Radius, Height;
};
Тогда, доступ к переменным такой структуры мы можем получить так:
var Cylinder MyCylinder;

MyCylinder.Radius = 50;
В структуру могут входить почти все типы данных - численные, текстовые, логические, массивы, и даже другие структуры. Например, создадим структуру для студента, в которой будет использоваться структура для родителей
struct Parents
{
    var string Name;
    var string SName;
    var int Age;
};

struct Student
{
    var string Name;
    var string SName;
    var int Age;
    var float Scores[10];
    var Parents Father;
    var Parents Mother;
};
Доступ к элементам "дочерних" структур аналогичен доступу к любым другим переменным
var Student Vasya;

Vasya.Name = "Vasya";
Vasya.Scores[2] = 3.5;
Vasya.Father.Name = "Petr";
Структуры - очень полезная вещь, и часто используется в исходных кодах. Например, переменная Location в Actor.uc являет собой вектор, который, в свою очередь, задается как структура
struct immutable Vector
{
    var() float X, Y, Z;
};
К слову, о векторах

Векторы

Попросту говоря, трехмерная координата. Несмотря на то, что это просто структура, имеет крайне широкое применение в программировании. Ими можно задать местоположение, направление, скорость, даже угол наклона поверхности. Взаимодействуют между собой компьютерные векторы абсолютно также, как и настоящие. Например, два вектора в сумме дадут третий, каждая компонента которого будет равна суме соответственных компонент слагаемых векторов. Таким образом можно, например, изменить положение нашего актора. Попробуйте добавить следующий код в скрипт TestActor после placeable (предварительно удалите текущую функцию PostBeginPlay() и строчку HiddenGame=True).
var vector LocationOffset;

function PostBeginPlay()
{
    LocationOffset.Z = 64.0;
    SetLocation(Location + LocationOffset);
}
LocationOffset.Z обозначает, что мы присваиваем значение именно третьей компоненте вектора. Функция SetLocation(vector A) перемещает актор в координату, обозначенную вектором А, а Location - свойство актора, обозначающее его текущую координату. Таким образом, наш актор должен подняться на 64 юнита вверх относительно текущего положения. Скомпилируйте, запустите игру и увидите, что так оно и есть.
Если вы не хотите создавать отдельную переменную, чтобы добавить к вектору известную вам величину, можно использовать функцию vect(значение X, значение Y, значение Z), которая просто преобразует заданные числа в вектор (X,Y,Z). Так что наша функция для смены локации будет выглядеть так.
SetLocation(Location + vect(0,0,64));
И не нужно создавать никаких "левых" переменных.
Из полезных функций также можно упомянуть VSize(vector A) - функцию, которая считает длину вектора. Скомбинировав ее с основами знаний о векторах, можно, например, найти расстояние между акторами А и B
Distance = VSize(A.Location - B.Location);

Вращатели

Вращатели (rotators) - ближние родственники векторов, только вместо координат, их компонентами являются углы наклона по определенной оси.
struct immutable Rotator
{
    var() int Pitch, Yaw, Roll;
};
Чтобы понять, что и где, картинка самолета, соответственными обозначениями углов наклона.
В общем, так. Вытяните руку вперед - это ось Х. Вытяните руку в сторону - это ось Y. Ну ось Z направлена вверх. Таким образом, Pitch отвечает за наклон вперед/назад (ось Y), Yaw - за вращение в вертикали (ось Z), а Roll - наклон влево/вправо (ось X).
Каждый актор, вдобавок к местоположению, имеет также свойство Rotation, характеризующее, куда тот направлен. Текущее направление взгляда игрока является ротатором, ротатор актора направленного света показывает, в каком направлении падают лучи, ротатор точки старта для игрока показывает, куда будет смотреть игрок, когда появится в игре.
Аналогично местоположению, направление актора можно установить с помощью функции SetRotation(rotator A), переменную ротатора, аналогично с vect, можно заменить функцией rot(значение Pitch, значение Yaw, значение Roll).
Важно: значения углов в ротаторе являются int, 32-битными, поэтому, вместо 360 градусов, максимальное значение поворота являет 65536, так что при задании ротаторов выбирайте углы, соответствующие этой особенности, или сделайте функцию для пересчитывания значений углов.

Итог

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

Просмотров: 3 924

Errrrd #1 - 5 лет назад 0
Уважаемый автор. В структурах "Parents" и "Student" отсутствует ";" после фигурной скобки. У меня возникает вопрос в этом случае: наличие ";" - не критично для Unreal Script, или правильно все же, как в "векторе" (т.е. синтаксис, как в С++)?
П.С. Я ни в коем случае не хочу придраться, и благодарен Вам за Ваши статьи. С уважением, Дмитрий.
lentinant #2 - 5 лет назад 0
Errrrd, видно, это мой косяк. В коде буквально немного выше над указанными есть общее объявление структуры, там скобки есть. Спасибо за подмеченную ошибку.