Unreal Development Kit Game Programming with UnrealScript:Beginner's Guide
上QQ阅读APP看书,第一时间看更新

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 .

  1. For this experiment we'll need AwesomeActor to be visible, so let's make sure our default properties are set up for that. Our AwesomeActor 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 the PostBeginPlay function for now.

  2. In our AwesomePlayerController, we're going to use the function that's called when we click the left mouse button to fire, called StartFire. 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.

  3. Now let's declare a variable of our AwesomeActor type at the top of our AwesomePlayerController class.
    var AwesomeActor MyAwesomeActor;
  4. 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");
    }
  5. 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.

  6. 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
  7. 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 an Actor variable called Weapon . 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 the Actor 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 our AwesomeActor as MyAwesomeActor . 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 in UnrealEd. There was no AwesomeActor variable in any other class that was referencing them.

  8. 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;
  9. 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 our StartFire function to look like this:
    exec function StartFire( optional byte FireModeNum )
    {
        super.StartFire(FireModeNum);
        AnotherWeaponVariable = Pawn.Weapon;
        `log(AnotherWeaponVariable);
    }
  10. 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 in GetPlayerViewPoint :

    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 to Pawn.Weapon , we only did it once. If Pawn.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?

  11. 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 our StartFire 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.

  12. 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.
    Time for action – Experiments with Actors as variables

    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 our MyAwesomeActor 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.

  13. 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 an AwesomeActor in the level ourselves so we can see if we can get a reference to it. Change AwesomeActor's code to read the placeable keyword:
    class AwesomeActor extends Actor
        placeable;
    
    defaultproperties
    {
        Begin Object Class=SpriteComponent Name=Sprite
            Sprite=Texture2D'EditorResources.S_NavP'
        End Object
        Components.Add(Sprite)
    }
  14. 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.
  15. 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.

  16. 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?

Note

Hint – Remember our lessons on adding elements to dynamic arrays.