Game Dev: Акторы как переменные

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

Итак, нам понадобится несправедливо забытый нами TestActor. Он должен быть видимым, так что позаботьтесь о том, чтобы его свойства были соответствующими.
class TestActor extends Actor;

defaultproperties
{
     Begin Object Class=SpriteComponent Name=Sprite
     Sprite=Texture2D'EditorResources.S_NavP'
     End Object
     Components.Add(Sprite)
}
Поскольку спавнить его мы будем непосредственно во время игры, мы больше не нуждаемся в ключе placeable. PostBeginPlay тоже можно убрать, так как с самым актором мы ничего не собираемся делать.
Дальше, в нашем TestPlayerController, мы будем использовать функцию StartFire, которая вызывается нажатием на левую кнопку мыши, и отвечает за стрельбу.
exec function StartFire( optional byte FireModeNum )
{
     super.StartFire(FireModeNum);
}
FireModeNum в этой функции отвечает за соответственный режим стрельбы, например, плазменные сгустки или плазменный луч в нашем стартовом оружии. Но для нас это не важно, важно только, чтобы вызывалась "супер"-версия функции.
Теперь объявим новую переменную в нашем TestPlayerController, а типом укажем TestActor
var TestActor MyTestActor
Если у вас возникнет вопрос, какое же значение принимают акторы по умолчанию, то это легко проверить, дополнив функцию PostBeginPlay нашего TestPlayerController соответственной строчкой вывода.
simulated function PostBeginPlay()
{
     super.PostBeginPlay();
     bNoCrosshair = true;
     `log(MyTestActor @ "<-- Default for MyTestActor");
}
» Полный код нашего TestPlayerController
class TestPlayerController extends UTPlayerController;

var TestActor MyTestActor;
var vector PlayerViewOffset;

simulated function PostBeginPlay()
{
     super.PostBeginPlay();
     bNoCrosshair = true;
     `log(MyTestActor @ "<-- Default for MyTestActor");
}

exec function StartFire( optional byte FireModeNum )
{
     super.StartFire(FireModeNum);
}

simulated event GetPlayerViewPoint(out vector out_Location, out Rotator out_Rotation)
{
     super.GetPlayerViewPoint(out_Location, out_Rotation);
     if(Pawn != none)
     {
          Pawn.Mesh.SetOwnerNoSee(false);
          if(Pawn.Weapon != none)
          Pawn.Weapon.SetHidden(true);
          out_Location = Pawn.Location + PlayerViewOffset;
          out_Rotation = rotator(Pawn.Location - out_Location);
     }
}

function Rotator GetAdjustedAimFor( Weapon W, vector StartFireLoc)
{
     return Pawn.Rotation;
}

defaultproperties
{
     PlayerViewOffset=(X=-64,Y=0,Z=1024)
}
Немало уже набралось, не правда ли? Вот именно по этому, переменным стоит давать четкие ясные имена, дабы не путаться.
Скомпилируйте код и проверьте наш лог. Найдете там следующее:
[0008.17] ScriptLog: None <-- Default for MyTestActor
Как видим, для акторов значение по умолчанию - None. Где-то мы уже это видели. Мы использовали это слово в нашем "контроле потока", чтобы спрятать оружие павна:
if(Pawn.Weapon != none)
     Pawn.Weapon.SetHidden(true);
В конкретном случае, у павна есть актор-переменная, под именем Weapon. Можно найти ее в коде класса Pawn
/** Weapon currently held by Pawn */
var Weapon Weapon;
Как видите, переменной можно спокойно давать имя, сходное с классом, но тогда весьма легко запутаться. Именно поэтому я назвал нашу переменную MyTestActor - легко понять, что это переменная, при этом сразу же определив ее класс.
Мы проверили, является ли переменная Weapon каким-либо актором. Если да, то условие исполняется, и ход программы идет дальше.
Стоит подметить, что, с одной стороны, далеко не все акторы являются переменными в каком-то классе, а с другой - объявление новой переменной-актора в классе не создает этот самый актор. Переменные-акторы являются лишь методом привязать существующие акторы к какому-либо классу. К примеру, в предыдущых подстатьях мы размещали наш TestActor на уровне просто в редакторе, и не было никаких переменных, связанных с ним.
Так как же присваивать переменной значение в виде актора? Тут есть несколько способов. Первый - приравнять переменную определенного класса к другой переменной этого же класса. Например, создадим переменную типа Weapon в нашем TestPlayerController.
var Weapon AnotherWeaponVariable;
PostBeginPlay в нашем случае срабатывает слишком рано, следовательно, не советую им пользоваться. А вот StartFire на подходит по всем параметрам - можно выводить значение переменной каждый раз, когда мы стреляем. Измените эту функцию следующим образом:
exec function StartFire( optional byte FireModeNum )
{
     super.StartFire(FireModeNum);
     AnotherWeaponVariable = Pawn.Weapon;
     `log(AnotherWeaponVariable);
}
Скомпилируйте, запустите игру, выстрелите (неважно сколько раз), и проверьте лог.
[0005.79] ScriptLog: UTWeap_LinkGun_0
С этой строчки можно сделать два вывода. Первый - синтаксис названий нам уже встречался, когда мы ставили два TestActor на уровне: прочерк и порядковый номер. Второй - несмотря на то, что переменная типа Weapon, нам выдало именно UTWeap_LinkGun_0, с чего можно сделать итог, что переменной определенного класса можно также присваивать его подклассы, что весьма упрощает создание игры (вместо создания переменной под каждый тип оружия, мы обходимся одной переменной "материнского" класса).
Теперь, когда AnotherWeaponVariable является равной переменной Pawn.Weapon, мы можем оперировать ею точно так же, как и Pawn.Weapon. Например, следующий код:
if(Pawn.Weapon != none)
     Pawn.Weapon.SetHidden(true);
можно спокойно заменить на
if(AnotherWeaponVariable != none)
     AnotherWeaponVariable.SetHidden(true);
Но мы этого делать не будем (а зачем?).
Важно помнить, что даже если мы присвоили AnotherWeaponVariable значение Pawn.Weapon, мы сделали это только один раз. Если Pawn.Weapon изменится, это не значит, что изменится AnotherWeaponVariable. Чтобы было яснее, гламурный пример о цвете: припустим, тваш любимый цвет пурпурный. Далее, я говорю, что мой любимый цвет такой же, как и ваш, то есть, пурпурный. Но потом вы поменяли свой любимый цвет на синий. Но это же не значит, что мой любимый цвет тоже поменялся на синий, до тех пор, пока я снова не скажу, что мой любимый цвет такой же, как и ваш. Знаю, что пример несколько бредовый, но так было в оригинале.

Второй метод присвоить переменной актор - создать актор самому. Поменяем нашу функцию StartFire следующим образом:
exec function StartFire( optional byte FireModeNum )
{
     super.StartFire(FireModeNum);
     MyTestActor = spawn(class'TestActor',,, Pawn.Location);
     `log(MyTestActor @ "<-- MyTestActor");
}
Мы использовали функцию spawn, давайте же посмотрим, как она работает:
native noexport final function coerce actor Spawn
(
     class<actor> SpawnClass,
     optional actor SpawnOwner,
     optional name SpawnTag,
     optional vector SpawnLocation,
     optional rotator SpawnRotation,
     optional Actor ActorTemplate,
     optional bool bNoCollisionFail
);
Как видим, единственным обязательным параметром является только первый - класс актора, который мы спавним. Остальные являются необязательными. Но нам же нужно указать, где будет спавниться этот актор, а это четвертый параметр, SpawnLocation, в качестве которого мы указываем Pawn.Location - расположение самого павна. Вот тут важно запомнить, что, даже если мы можем пропускать необязательные параметры, если нам нужен, к примеру, только четвертый необязательный параметр, то мы должны указать, что это именно четвертый параметр, именно поэтому после первого параметра стоят три запятые. Следующие же переменные можно пропустить. Помимо прочего, мы можем сразу же присвоить свежеспеченный актор как значение соответственной переменной, что мы и сделали.
Скомпилируйте, запустите игру, постреляйте несколько раз, и посмотрите что будет.
Как видим, все исправно спавнится. Теперь заглянем в лог:
[0007.76] ScriptLog: TestActor_0 <-- MyTestActor
[0008.11] ScriptLog: TestActor_1 <-- MyTestActor
[0008.52] ScriptLog: TestActor_2 <-- MyTestActor
[0008.81] ScriptLog: TestActor_3 <-- MyTestActor
[0021.36] ScriptLog: TestActor_4 <-- MyTestActor
Ка видим, каждый раз когда мы спавним новый актор, он присваивается нашей переменной, заменяя старое значение, и при этом старые акторы не исчезают.

Еще один способ присвоить значение переменной-актору - итераторы. Итератор - функция, схожая с циклом, вот только вместо условия, она выполняет свои действия по разу на каждый экземпляр определенного класса. То есть, необходимо, чтобы акторы этого класса уже существовали в игре. Давайте разместим один TestActor на уровне. Не забудьте вернуть placeable в первую строчку, чтобы сделать размещение возможным.
Для того, чтобы итератор нашел наш актер, можно воспользоваться PostBeginPlay в контроллере:
simulated function PostBeginPlay()
{
     super.PostBeginPlay();
     bNoCrosshair = true;

     foreach DynamicActors(class'TestActor', MyTestActor)
          break;

     `log(MyTestActor @ "<-- MyTestActor");
}
В общем, вот как это работает. foreach ищет все акторы указанного класса (в нашем случае TestActor), находит первый, присваивает его переменной (в нашем случае MyTestActor), делает указанные после него действия (если в действия состоят из более чем одной строки, нужно использовать фигурные скобки), находит второй актер, присваивает его переменной, ну и т.д.. Но в нашем случае, действия состоят из одной команды break, которая, как и в любом цикле, просто останавливает этот самый цикл. В итоге, эта комбинация просто находит первый попавшийся актор и присваивает его переменной. Которую мы потом и выводим в лог. В общем, компилируем, запускаем игру, проверяем.
[0004.62] ScriptLog: TestActor_0 <-- MyTestActor
Красота. Все работает как надо.

В общем, использование акторов в качестве переменных сильно упрощает создание игры, так как без него было бы очень трудно прописывать взаимодействия между игровыми элементами, весь код не выглядел бы таким цельным и т.д.. Как и в случае с обычными переменными, тут применимы операторы != и ==, а также массивы. Если актор присвоенный переменной, у программиста есть доступ ко всем его параметрам, делая возможным его изменение не только через меню свойств в редакторе, но и через код. С другой стороны, нельзя использовать их в свойствах по умолчанию, так как классы - всего лишь схемы для еще несуществующих объектов.

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

Просмотров: 511

Комментарии пока отсутcтвуют