Engine Events, Super, Debugging

The UDK is a game engine and utilizes an event-based programming style. If you've never seen that term before, it means that an overarching framework exists where some kind of simulation is occurring and as part of the simulation, events are fired that waiting components can then respond to. In this style, independent components do not really take proactive action, they wait for the framework to tell them:

A particular event has occurred, looks like you signed up for updates for that event, please respond to the event's firing

In this case, the overarching framework is the UDK and the simulation is the game running in one of its various states. All UnrealScript classes participate in the event response by overriding special functions defined in Object and more commonly in Actor. We call these special functions that are built to be overridden Engine Events.

Basic Events

The following events are all defined on Actor. There are many events that are defined on Actor. For a more complete list, see Actor.uc or BeyondUnreal Actor Events). Listed here are examples of critical events:

  • function PostBeginPlay()
    • Called after the Actor has been constructed and initialized
    • This function can serve as a sort of initializer or default constructor for an Actor
    • Be warned that it is NOT GUARANTEED that this function is executed BEFORE some other actor may have gained a reference to this Actor
    • From BeyondUnreal:
      • Called by the engine pretty soon after the Actor has been spawned or the game has begun
      • Most actors use PostBeginPlay() to initialize their UnrealScript values. The actor's PhysicsVolume and Zone are valid but the actor is not yet in any State
  • function Tick(float deltatime)
    • Called every frame
    • Deltatime is the time, in seconds, since the last tick event for the engine
  • function Destroyed()
    • Called before an Actor has been deallocated, but after Destroy() has been called on this Actor
    • Pretty soon after this function is called, all other actors in the simulation will lose their reference to this Actor
      • At some point in the future, this Actor will be garbage collected

Super

When overriding a function, it is frequently useful that we want to add functionality to our parent's version of the function, as opposed to completely replacing it. This would require the ability to call our parent's version of a function. This is done by using the super keyword.

The super keyword syntactically provides an object that represents all of the parent class' members. Calling any functions through the super keyword will execute the parent's version of that function. For example:

class FooPawn extends Pawn;

function PostBeginPlay()
{
    // Calls own version of tick
    tick(100);

    // Calls parent's version of tick
    super.tick(200); 

    // Calls parent's version of PostBeginPlay
    super.PostBeginPlay();
}

function tick(float deltatime)
{
    `Log(deltatime$" since the last tick");
    super.tick(deltatime);
}

Defaultproperties
{
}

Notice:

  • Class extends Pawn (an important class in the UDK, prominent part of a UDK character)
  • Function tick produces a log message at every call and then calls the parent's version of tick
  • Function PostBeginPlay calls the following, in sequence:
    • Own version of tick, passing it 100 (ie 100 seconds)
    • Parent version of tick, passing it 200 (ie 200 seconds)
    • Parent version of PostBeginPlay

This is not great code! Your code should not need to explicitly call tick from PostBeginPlay, ever... but it gets the point across about events.

`Log

Logging is an universally accepted form of debugging and it is useful for our purposes. Logging in the UDK is done using the `Log macro function. Note the ` in front of the symbol name; this is required! Log is not a function, it is a macro. The UDK mandates that calling a macro requires ` ahead of the macro's name (if you're wondering how to say it, the ` symbol is called grave or in the unix world, backtick).

This macro accepts any value type and dumps its value to the script log. Any value can be passed to Log and be printed. Longer messages can be composed by using the operators @ and $. For example:

class Sup extends Actor;

var float A;
var int B;

function PostBeginPlay()
{
    `Log("[A: "$A@", B: "@B$"]");
}

Defaultproperties
{
    A=6.5
    B=72
}

Spawning an Actor of type Sup would produce the following message once PostBeginPlay was called, ie after construction: [A: 6.5, B: 72]

Note that @ and $ are completely interchangeable. They splice the two items around them, producing a concatenated string.

ScriptTrace

Script tracing shows you how and when your functions are being called. There are two functions that we can use to perform script tracing:

  • ScriptTrace()
    • Dumps the script trace immediately to the log
  • GetScriptTrace()
    • Returns the script trace as a string which you can use however way you see fit, including logging it via `Log

Here's an example of how a trace would look like once dumped to log. Consider the following set of class excerpts:

class Base extends Actor;

function Tick(float deltatime)
{
    ScriptTrace();
}
class Derived extends Base;

function DerivedFunction(float dt)
{
    super.Tick(dt);
}

function Tick(float deltatime)
{
    DerivedFunction(deltatime);
}

The following would be the script trace that is produced when Derived's tick function is called by the engine:

ScriptLog: Script call stack:
        Function SomePackage.Derived:Tick
        Function SomePackage.Derived:DerivedFunction
        Function SomePackage.Base:Tick

Notice that the function at the bottom of the stack is the one currently executing. This is because the ScriptTrace() call is placed in Base.Tick. Each level in the trace is called a stack frame and it represents the function that called the function in the stack frame immediately below it.

As you can see, script tracing can be extremely useful for finding when your functions are being called and if they are behaving correctly.