Objects, Self

While Actors are useful for a lot of things, there are instances where much of their functionality is not required or may possibly not even make sense. For this, we have objects.

Object Class

Objects are classes that derive from Object or from one of its non-actor subclasses. They can be used to hold data, functionality, exec functions, etc...

Like Actors, Objects are also classes, meaning that they have accesses to much of the same base-level UnrealScript functionality, such as:

  • Inheritance
  • Member variables
  • Functions
  • Function overriding
  • Interface implementation
  • Exec functions
  • Default properties

But additionally, Objects have access to a very important keyword: within

Constructing Objects

Consider the following Object class named SpecialObject:

class SpecialObject extends Object;

var int AnInt;
var float AFloat;

function increment()
{
    AnInt++;
    AFloat++;
}

Defaultproperties
{
    AnInt=6
    AFloat=7
}

This is a basic Object class that contains two members and a function. We can construct and store it as follows. Consider the following Actor:

class SpecialActor extends Actor dependson(SpecialObject);

var SpecialObject MySpecialObject;

function PostBeginPlay()
{
    MySpecialObject = new class'SpecialObject';
}

Defaultproperties
{
}

Notice a few interesting things:

  • dependson keyword
    • This keyword forces the compiler to compile the given class/struct first before compiling this class. This is important when multiple classes make use of the same classes/structs. Typically this is not required but when dealing with objects and/or structs, it is recommended.
  • MySpecialObject variable
    • Notice that our declaration of a variable of type MySpecialObject is very similar to how you would declare any other variable
  • Notice how we populate the MySpecialObject variable in the PostBeginPlay event (called after this actor is constructed)
    • To construct an Object we use the new keyword. This keyword is not used to construct actors! Only objects are constructed this way!
    • The new keyword has a special syntax but it is straight forward
      • The simple version we used above can be prototyped as follows: ObjectVar = new <class'InClass'>;
      • The full prototype is as follows: ObjectVar = new[(InOuter, InName, InFlags)] <class'InClass'>[(InTemplate)];
        • We will ignore inName, inFlags, and inTemplate for now. We will look at InOuter in the next section

Outer Objects, Within, and Self

Objects can be instantiated with what is known as an Outer object encapsulating them.

If the instantiated Object class also declares the keyword within, that Object will have access to all of the members of the containing object declared in the Within specifier. The within specifier will also make it impossible to instantiate this Object class without passing in an instance of the required outer Object

This is best described with an example. Consider the following two classes:

class ContainerActor extends Actor;

var ContainedObject contained;
var int num

function PostBeginPlay()
{
    contained = new(self) class'ContainedObject';

    contained.inc();
    contained.print();
}

Defaultproperties
{
    num=6
}
class ContainedObject extends Object within ContainerActor;

function inc()
{
    num++;
    // This could also be written Outer.num++;
}

function print()
{
    `Log(num);

    // This could also be written as: `Log(Outer.num);
}

Defaultproperties
{
}

ContainerActor

First look at the actor. Notice that we have used the new keyword slightly differently this time. In parentheses, we are passing self in as a parameter immediately after the new statement. This is how we pass in an outer object. In this case, self is a special keyword that implies the current object. The net result of this statement is that an object of class ContainedObject will be instantiated and it will have the current Actor attached as an Outer for it.

The new keyword also accepts a few more parameters but they are not important; as far as we are concerned, these are the only two relevant uses of the new keyword.

ContainedObject

Let's look at ContainedObject. By specifying the within keyword on class declaration, we are giving this object access to all of the members of a ContainerActor object. It also means that ContainedObject can never be instantiated without being passed a ContainerActor as an Outer.

Looking at the functions, notice that we liberally make use of the member variables of the Outer ContainerActor. For example, in inc and in print, we update and read the num variable, respectively.

Note that we could have also accessed that variable through the builtin Outer variable. This variable gives you a way to get a reference to the Outer object as an Object (so that you could cast it if you wanted to, or pass it to another function, etc...). The type of the Outer variable is the same as what is declared in your within specifier. If your object does not have a within specifier, Outer's type will be Object.