Using Objects
This follows on from the Object Oriented Programming Overview.
Object References
What can object references be used for?
Remember in the overview when we made up a recipe (class) for a simple and a better pizza and created (spawned) actual pizzas?
local Pizza MyPizza; // That's where I'm going to hold the pizza. Like a dish. MyPizza = Spawn(class 'Pizza'); // Hooray! Pizza's ready!
Just like we could define variables such as bHasSauce to store information, we can define variables that will store a reference to an object. This is how we can refer to other objects and interact with them. In the example above, the "MyPizza" variable was a reference to the pizza we spawned. Once we have a reference to an object, we can interact with it, like we did before:
MyPizza.EatParts(2);
This breaks down fairly simply. The first part, "MyPizza," is our reference variable. It specifies what we want to interact with. The second part, "EatParts(2)," specifies how we want to interact with it.
You can call functions on another thing when you have a reference to it. EatParts was a function, as you may recall, and by using the reference we can call that function on the pizza. Some more examples: (not examples of actual UT2003 code, but for illustrative purposes)
TheGun.Fire(); ThatButton.Press(); APie.ThrowAtClown(); MyPizza.AddSauce();
We can also use a reference to an object to find out information about it. For instance, suppose we wanted to find out whether the pizza still has sauce on it. The "MyPizza" variable refers to the pizza, so we can clearly state what we want to know about, then we need only specify what we want to know about it, like so:
if ( MyPizza.bHasSauce ) // If MyPizza still has sauce on it... MyPizza.EatSauce(); // ... then eat the aforementioned sauce.
We can also use the variables in our referenced object for anything we could use our own variables for, and we can even change them:
local float HalfThePieces; HalfThePieces = MyPizza.NumberOfParts / 2; if ( TheGun.Bullets > 0 ) { TheGun.Fire(); TheGun.Bullets = TheGun.Bullets - 1; }
Getting a Reference to an Object
In order to call functions on an object or to access it's variables, we must first obtain a valid reference to it. When we first declare the reference, it is empty. Before we can use it for anything productive we must assign an object to it. Remember this line?
MyPizza = Spawn(class 'Pizza'); // Hooray! Pizza's ready!
The spawn function not only creates the object you specify, but returns a reference to it. This is an easy way to obtain a reference to an object. Just as you would assign a number to a numeric variable, you can assign an object to an object reference. Generally an object reference is set by setting it equal to another object reference, either obtained through Spawn or through another function call. Sometimes, however, you want a reference to an object that is already in the game but is not currently interacting in any way with your class. To find a reference to it you will generally have to use an iterator.
Casting References
Remember when we made the BetterPizza from before? It had properties and functions that the regular pizza did not. However, we can still use a regular pizza reference to hold it.
local Pizza MyPizza; MyPizza = Spawn(class 'BetterPizza'); // Hooray! Pizza's ready!
Similarly, we could use something even lower level to hold the pizza, such as the Meal class that the pizza was derived from. You can hold a more complex class in a reference to simpler class, but you cannot store a simpler class in a reference to a more complex class. For instance, we can use a reference to Meal or Pizza to refer to a BetterPizza, but we cannot use a reference to a BetterPizza to store a regular Pizza. This is because a BetterPizza is a special type of Pizza, but it's still a Pizza. A BetterPizza is a special type of Meal too, because it's a special type of Pizza and a Pizza is a special type of Meal. However, a Pizza is not a special type of BetterPizza, so we can't use a BetterPizza to refer to a Pizza.
local Meal AMeal; local Pizza MyPizza; local BetterPizza MyBetterPizza; AMeal = Spawn(class 'BetterPizza'); // A BetterPizza is a type of meal. MyPizza = Spawn(class 'BetterPizza'); // It's also a type of Pizza. MyBetterPizza = Spawn(class 'Meal'); // This won't work. A Meal is not a type of BetterPizza.
Now, this all seems fine and dandy, but what's the catch? When you store a reference to an object you can only call functions and access variables that are in the class to which that reference was defined. So, for instance, you can't do the following:
local Meal AMeal; AMeal = Spawn(class 'BetterPizza'); AMeal.EatSauce(); // This won't work. Meals don't have sauce, and although // this meal happens to be a Pizza, which does have // sauce, the game has no way of knowing when it // compiles that there is indeed a pizza in that meal // reference, so it goes to the lowest common // denominator: the meal.
However, there is a way around this: If you know ahead of time that a reference to a certain class actually contains a subclass of it, you can cast it to let the game know just to take your word for it. The cast lets you temporarily access the functions and variables of a class just as if the reference was actually one to the subclass you specify. It makes no permanent change to the reference itself, though, so you must cast the reference every time you want to access it's subclasses' special functions. This is best illustrated by examples. This is done like so:
local Meal AMeal; AMeal = Spawn(class 'Pizza'); Pizza( AMeal ).EatSauce(); // This one works. We have told the compiler that // although the reference is to a meal, the reference // actually points to a special type of meal that has // some added functions. By doing this, we can access // that subclasses special variables and functions.
If you try to cast something into a class that it is not, or a class that it is not a subclass of, it will return an empty reference. In the above example, we can cast the meal to a pizza, but not to a BetterPizza because it doesn't actually store a BetterPizza, it only stores a Pizza. The compiler will let you cast to any subclass, but it will cause problems in-game if you cast a reference to a class that it does not actually point to. As shown above, the syntax for casting is to put the name of the class as if it were a function, with the argument being the reference that you are casting. Some more examples:
Pawn( Owner ).Health = 100; // Owner is an actor reference to a pawn object. We have casted it to // Pawn so we can access it's health, which is declared // in pawn, not in actor. AIController( Controller ).AIScript = None; // Controller's don't have AIScripts, only // AIControllers do. But, if we know for sure // that a Pawns controller is an AIController, // we can cast it to access the AIScript anyway. if ( BetterPizza( MyPizza ).bHasEgg ) // Only BetterPizzas have eggs. { BetterPizza( MyPizza ).bHasEgg = false;// I don't like egg on pizza. Pick it off. MyPizza.EatSauce(); // Note that although I'm not calling the betterpizzas // EatSauce directly, it will still get the call and // still call the betterpizzas EatSauce. You only have // to cast for things that didn't exist in the // superclass. You don't have to cast to use functions // that were only overridden. MyPizza.EatParts( 2 ); // But all pizzas can have parts eaten, no cast is // required. }
IsA and ClassIsChildOf
Now you know how to cast a class, in circumstances where you already know for sure that a reference points to a certain subclass. However, what about situations where you don't know for sure? Suppose you had a function like the following:
// Called to determine what type of food something is. function string TypeOfMeal( Meal M )
This function takes a meal as a parameter, and returns a string describing what type of food it is. We want to return "Spicy" if it is a pizza with sauce, "Bland" if it is a pizza without sauce. Otherwise, we want to return "Unknown."
// Called to determine what type of food something is. function string TypeOfMeal( Meal M ) { if ( Pizza( M ).bHasSauce ) return "Spicy"; else if ( !Pizza( M ).bHasSauce ) return "Bland"; else return "Unknown"; }
This seems like it would work, but it does not. If the pizza has sauce, it returns "Spicy." If the pizza does not have sauce, it returns "Bland." This is what we want, so far. However, if the meal is not a pizza at all, we want to return "Unknown." This function will return "Bland" in that situation. Here's why: If M is not a pizza, then Pizza( M ) returns None. None.bHasSauce will always evaluate as false, as well as generating an error message in the log. Before we can safely cast to the Pizza, we need to know for sure whether or not the meal M actually is a Pizza. There are four ways to do this:
- As mentioned, a cast to something that isn't that class will always return None. We can test this to see if it really is a Pizza or not. If Pizza( M ) == None, then M is either an empty reference, or it isn't a reference to a pizza.
- There is a native function called IsA that does basically the same thing. IsA takes a name argument, like 'Pizza'. To find out if M is a pizza, just call M.IsA( 'Pizza' ); It will return true if M is a pizza, or false if M is not a pizza.
- Every object has a class variable that holds it's own class. We can check this to determine if a class is a pizza or not. However, this method is not ideal because it will not catch subclasses. A pizza checked in this manner will not register as a pizza if it is a BetterPizza.
- ClassIsChildOf is another native function that allows us to investigate the class tree. However, like the above method, ClassIsChildOf is less than ideal for this application as it will return only subclasses of the specified class, not that class itself. It can be combined with the above methods to get the same results as either of the top two methods. It's syntax is below.
if ( Pizza( M ) == None ) return "Unknown"; if ( M.IsA( 'Pizza' ) ) { if ( Pizza( M ).bHasSauce ) return "Spicy"; else if ( !Pizza( M ).bHasSauce ) return "Bland"; } if ( Class != class'Pizza' && !ClassIsChildOf( 'Pizza' ) ) return "Unknown";
References to Classes
As you may have noticed, the class comparing and ClassIsChildOf both use a "class" notation. This is a special type of variable, somewhere between a reference and a primitive. It holds the class name of an object. Remember how we spawned the Pizza?
Spawn(class 'Pizza');
Note that the class designation was used there as well. In those cases, a variable is not used, just an absolute value. For instance, the number 5 is not the same as a variable holding the number 5, but both can be used interchangeably. Similarly, a class variable and the constant version (as illustrated above) can be used interchangeably. You can set a class variable in the same way you would set any other variable:
local class<Meal> PizzaClass; // The triangle brackets in the declaration narrow down what type // of class this can point to, but it is not necessary. local Pizza MyPizza; local Pizza AnotherPizza; MyPizza = Spawn(class'Pizza'); PizzaClass = class'Pizza'; AnotherPizza = Spawn( PizzaClass );
The constant syntax should be obvious, it is simply the word class, followed by a single quote, followed by the name of the class, followed by another single quote. It is useful in cases where you do not know at compile time what the class is going to be. For instance, weapons use this for their projectiles. Instead of having to change every reference to the projectile class in the code, you can change it all by modifying one simple variable. In other cases, that may not even be an option, and a class variable is required. Here's an example: Let's say we were making a duplicator gun. Whenever it hit something, it spawns a duplicate of that thing. The code for the duplication function might look something like the following:
function Duplicate( actor Other ) { local class<actor> OtherClass; OtherClass = Other.Class; Spawn( OtherClass ); }
Related Topics
These pages cover the above in more detail:
Sobiwan: I dont 'remember when we spawned a pizza'. The previous section only defined a pizza (the class), but there was no mention of spawning or what spawning is. Hence, I am confused until this first sentence is clearer.
Tarquin: we spawned one in the section 'Where's My Pizza?'. But it could be clearer. I'll work on it.
Don: Forgive the professor mode here, but I'd suggest changing the example that includes "TheGun.Fire(); TheGun.Bullets = TheGun.Bullets - 1;" - it may just be an example but it completely disregards proper encapsulation.
Tarquin: Good point. Change it
n8: Looks like Don copped out. Did he mean change to: "TheGun.Fire(); TheGun.Bullets -= 1;"?
Pachacutec: Hmmm. I want to alter an object reference in a class post its initialization instead to one of its subclasses. This way, all existing calls to that reference can stay the same, the functionality is updated with no hassle. I think this is called protoyping in javascript. I can do this here with Spawn? I'm not adding new function NAMES, but one of the functions will have extra commands in the new sub. Is this possible?
Pachacutec: "You don't have to cast to use functions that were only overridden." Sorry, missed that comment. thanks for this great page
HoMeRS}i{MpSoN: It looks to me like the ClassIsChildOf function has changed in UT2k4 (and I am guessing 2k3 before it):
native(258) static final function bool ClassIsChildOf( class TestClass, class ParentClass );
This requires a statement such as:
if (ClassIsChildOf(V.Class, class'Volume')) ...blah blah
instead of the examples above.
SuperApe: This restaurant family of beginner UScripting pages should be linked from the Modding pages. Currently, Making Mods and Mutator Topics are the ones that get the most foot traffic.
Stelcontar: In UT2004 the ChildIsClassOf function returns true if the test class is a child of the "parent" class OR is the same as the parent class. Example:
//the following return true in UT2004 ClassIsChildOf(class'ShockRifle',class'ShockRifle') ClassIsChildOf(class'ShockRifle',class'Weapon') ClassIsChildOf(class'ShockRifle',class'Inventory')
KewlAzMe For the Duplicate function example, do you always need to make a variable for that class? Wouldn't this work:
function Duplicate( actor Other ) { Spawn( Other ); }
???