Time for action – Experiments with Actors as variables
For this experiment we're going to bring back our old friend the AwesomeActor
. We'll use him as a variable in our AwesomePlayerController
.
- For this experiment we'll need
AwesomeActor
to be visible, so let's make sure our default properties are set up for that. OurAwesomeActor
class should look like this:class AwesomeActor extends Actor; defaultproperties { Begin Object Class=SpriteComponent Name=Sprite Sprite=Texture2D'EditorResources.S_NavP' End Object Components.Add(Sprite) }
Since we'll be spawning
AwesomeActor
during gameplay we don't need it to be placeable, and we're not going to do anything more with it once it's spawned so we don't need thePostBeginPlay
function for now. - In our
AwesomePlayerController
, we're going to use the function that's called when we click the left mouse button to fire, calledStartFire
. Let's add that to our class:exec function StartFire( optional byte FireModeNum ) { super.StartFire(FireModeNum); }
The
FireModeNum
in this function is used for weapons that have more than one firing mode, like the plasma bolts versus the beam of the Link Gun. We don't need to worry about that variable for our experiment though, let's just make sure it calls the super so we don't completely override the function and our gun still works. - Now let's declare a variable of our
AwesomeActor
type at the top of ourAwesomePlayerController
class.var AwesomeActor MyAwesomeActor;
- We talked about the defaults of all of the other variables in the previous chapter, but what is the default for a variable of an Actor class? Only one way to find out! Let's log it in the AwesomePlayerController's PostBeginPlay function.
simulated function PostBeginPlay() { super.PostBeginPlay(); bNoCrosshair = true; `log(MyAwesomeActor @ "<-- Default for MyAwesomeActor"); }
- Our
AwesomePlayerController
should now look like this.class AwesomePlayerController extends UTPlayerController; var AwesomeActor MyAwesomeActor; var vector PlayerViewOffset; simulated function PostBeginPlay() { super.PostBeginPlay(); bNoCrosshair = true; `log(MyAwesomeActor @ "<-- Default for MyAwesomeActor"); } 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) }
Now you can see how code can get long and complicated, we're only doing simple stuff so far and look at all the code in this class! This is why it helps to have variables and functions with very descriptive names. A lot of the time you can get an idea of what's going on in the code just by reading it out loud to yourself. A descriptive name also makes it easier to search in UnCodeX to find out where things are being used.
- Compile our code and run it. In
Launch.log
we can find out what the default for Actor variables is.[0008.17] ScriptLog: None <-- Default for MyAwesomeActor
- As we can see, the default for Actor variables is None. Where have we seen that before? We already have an example of how to use Actor variables in flow control statements in our code right here!
if(Pawn.Weapon != none) Pawn.Weapon.SetHidden(true);
In this case, our
Pawn
has anActor
variable calledWeapon
. If we look at where that's declared in the Pawn class:/** Weapon currently held by Pawn */ var Weapon Weapon;
So the Pawn's
Weapon
is a variable of theActor
type of Weapon. It can be confusing when you're looking at code to see whether something's referring to a variable or a class, especially when the variable has the same name as the class it's a type of, so I wouldn't recommend doing this in your own code. That's why we named ourAwesomeActor
asMyAwesomeActor
. It still lets us easily tell what type of Actor it is while avoiding the confusion of the exact same name as the class.In the flow control statement using the Pawn's Weapon variable, we can see that we're checking to see that it's not equal to none. For Actor variables, this checks if this variable is referencing any Actor. If it is, then it won't be none and the flow control statement can continue.
One important thing to remember is that this does not mean that every Actor in the game is a variable or is assigned to one, or that declaring a variable of an Actor type automatically creates that Actor in the world. Actor variables are simply a way for us to store a reference to an Actor in the world. An Actor can be referenced by more than one variable in any number of different classes, or it may not be referenced by any variables. For instance, when we were first testing our
AwesomeActor
class, we were placing them directly in the level inUnrealEd
. There was noAwesomeActor
variable in any other class that was referencing them. - So how DO we assign things to our Actor variables? There are a few different ways of doing that. The first is by copying it from another variable that already has the reference stored. Let's say we created a variable of the type Weapon in our
AwesomePlayerController
:var Weapon AnotherWeaponVariable;
PostBeginPlay
is a bit too soon in the game's start up sequence to try and assign a reference to our weapon, so let's do it when we fire the gun. Let's change ourStartFire
function to look like this:exec function StartFire( optional byte FireModeNum ) { super.StartFire(FireModeNum); AnotherWeaponVariable = Pawn.Weapon; `log(AnotherWeaponVariable); }
- Compile the code and run it. While in game, fire the gun (more than once is fine, it won't hurt anything). Exit and let's take a look at the log.
[0005.79] ScriptLog: UTWeap_LinkGun_0
The format may look familiar, the underscore with a number after it also showed up in the editor during our
AwesomeActor
tests in the first chapter.Another thing that we'll notice is that even though the variable is declared as the Weapon class, a
UTWeap_LinkGun
actor was logged. Actor variables can reference either an actor of the variable type or any of its subclasses. This makes writing code more convenient, since we only need one variable to hold the player's Weapon instead of a different variable for every weapon class.Now that we have the reference, we can manipulate it the same way we would
Pawn.Weapon
. For instance, our if statement inGetPlayerViewPoint
:if(Pawn.Weapon != none) Pawn.Weapon.SetHidden(true);
Could be changed to this:
if(AnotherWeaponVariable != none) AnotherWeaponVariable.SetHidden(true);
One important thing to remember about this though is that even though we assigned
AnotherWeaponVariable
toPawn.Weapon
, we only did it once. IfPawn.Weapon
changed,AnotherWeaponVariable
wouldn't automatically change to match it. For example, say your favorite color was purple. If I said my favorite color was your favorite color, mine would be purple as well. If you changed your favorite color to blue, mine would still be purple unless I said my favorite color was your favorite color again. Make sense? - The second way of getting a reference to an Actor is by spawning that Actor ourselves. Using our
AwesomeActor
as an example, let's change ourStartFire
code to this:exec function StartFire( optional byte FireModeNum ) { super.StartFire(FireModeNum); MyAwesomeActor = spawn(class'AwesomeActor',,, Pawn.Location); `log(MyAwesomeActor @ "<-- MyAwesomeActor"); }
If we look at where the spawn function is declared in Actor.uc we can see how it's used:
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 );
The function line may look confusing, but the important part for us right now are the parameters. We tell it what class to spawn and the rest is optional. The only thing we're giving it for now is a location, which is our Pawn's location to make it easy to tell when the
AwesomeActor
has been spawned. - Compile the code and test it out. When we fire our weapon, an
AwesomeActor
should appear in game as well as the log. Fire the weapon a few times while moving around so we can see what happens.Our AwesomeActors are spawning! Now let's take a look at the log:
[0007.76] ScriptLog: AwesomeActor_0 <-- MyAwesomeActor [0008.11] ScriptLog: AwesomeActor_1 <-- MyAwesomeActor [0008.52] ScriptLog: AwesomeActor_2 <-- MyAwesomeActor [0008.81] ScriptLog: AwesomeActor_3 <-- MyAwesomeActor [0021.36] ScriptLog: AwesomeActor_4 <-- MyAwesomeActor
We can see that as each
AwesomeActor
was spawned, it was assigned to ourMyAwesomeActor
variable, but when a new one was spawned the reference was replaced with the new one. We can also see that this doesn't mean the old one was destroyed just because MyAwesomeActor's reference changed. - Another way we can get a reference to an Actor is by using what's called an iterator. There are a few functions in
Actor.uc
we can use that will cycle through all of the actors currently in the level and let us sort through them to find what we want. Before we do this we need to place anAwesomeActor
in the level ourselves so we can see if we can get a reference to it. Change AwesomeActor's code to read theplaceable
keyword:class AwesomeActor extends Actor placeable; defaultproperties { Begin Object Class=SpriteComponent Name=Sprite Sprite=Texture2D'EditorResources.S_NavP' End Object Components.Add(Sprite) }
- Compile the code, then open our test map in the editor and place an
AwesomeActor
near the player start. Save the map and close the editor. - Now for the iterator function. We can do this in
PostBeginPlay
, so let's put it there:simulated function PostBeginPlay() { super.PostBeginPlay(); bNoCrosshair = true; foreach DynamicActors(class'AwesomeActor', MyAwesomeActor) break; `log(MyAwesomeActor @ "<-- MyAwesomeActor"); }
The way iterators work is that for every Actor in the map it finds that is either the class we specify (AwesomeActor in this case) or a subclass of that class, it will assign it to the variable we specify (MyAwesomeActor) so we can do things to it or check things about it. The break line right afterward makes it exit the loop after the first one it finds, but the
MyAwesomeActor
variable will keep the reference to it. Let's try it out. - Compile the code and test. Close the game and take a look at the log:
[0004.62] ScriptLog: AwesomeActor_0 <-- MyAwesomeActor
The code found it!
What just happened?
Using Actors as variables is an important concept to grasp in UnrealScript. Without them objects in the world would have a hard time interacting with each other and it would be difficult to have any kind of complexity without them. As with any other type of variable we can use logical operators on them such as ==
or !=
to test if two variables are the same or not. They can also be made editable, since Actors placed in the world already exist and getting a reference to another one is as simple as typing its name into the property. They can't however be used in the default properties, since our classes are just blueprints for objects that haven't been created yet.
Before we move on to our next subject, let's try a challenge.
Have a go hero – Keeping references to spawned actors
In our experiment with getting a reference to an Actor as we spawned it, every time a new one was spawned the reference was replaced. But, if we had an array of our class declared like this:
var array<AwesomeActor> MyAwesomeActors;
How would we rewrite the StartFire
function to keep a reference to every AwesomeActor
that we spawned instead of just the latest one?