Skip to content

Behavior Connections

Simon Love edited this page May 30, 2013 · 3 revisions

This guide will take you through the process of coding behavior connections. It will not cover all facets of the Behavior system. This doc mainly shows how to define behavior inputs, outputs, and raising the signals.

The following guide(s) are recommended before you begin this tutorial:

What you need

  • A local checkout of Torque 2D
  • A text editor. Generally, any text editor will work fine as long as it does not introduce whitespace symbols. Avoid any advanced word processor, such as Microsoft Word. Recommended editors include, but are not limited to:
    • Windows: Torsion, Notepad++, Visual Studio
    • OS X: Xcode, TextWrangler, TextEdit (in "no formatting" mode)

Table of Contents

Chapter 1. Overview

Chapter 2. Adding outputs to a behavior

Chapter 3. Adding inputs to a behavior

Chapter 4. Connecting outputs and inputs

Chapter 5. Disconnecting outputs and inputs

Chapter 6. Timed raises

Chapter 7. Self raises

Chapter 8. Enumerating connections

Chapter 9. Persistence

Chapter 10. Miscellaneous Info

Chapter 11. Conclusion

Chapter. 1 Overview

A new feature of Torque 2D's behavior system is creating connections between behaviors. This is controlled by a behavior defining outputs. An output is a simple name much like a behavior field and can have a signal raised on it. Typically the behavior itself performs an operation that states that the output should be signaled (raised), but any object can perform this action.

When an output is raised, the behavior performs a callback on itself of the name of the output. The behavior then checks to see if any connections are made from this output. A connection from an output can be made via a behavior input. A behavior input is much like a behavior output except that an input is signaled only by it being connected to an output that is signaled.

If an input is connected to an output that is raised then the behavior that owns the connected input performs a callback detailing the behavior and output that caused it to be raised. This is a form of decoupling where an output can be raised on a behavior that has no knowledge of what is connected to it therefore decoupling the behavior itself. This is the observer pattern where the behavior that owns an input is observing a behavior that owns an output to which it is connected.

Chapter. 2 Adding Outputs to a behavior.

Given a behavior, adding an output is similar to adding a field:

%template = new BehaviorTemplate(OutBehavior);
    
// Add a behavior output named "Ping".
%template.addBehaviorOutput( Ping, "Test Output Signal", "Just a test" );

This output can now be raised using the following:

%object = new SceneObject();

// Create behavior.
%outInstance = OutBehavior.createInstance();

// Add behaviors.
%object.addBehavior( %outInstance );
%object.addBehavior( %inInstance );

// Raise the "Ping" output.
%object.Raise( %outInstance, Ping );

In the case above, the "Ping" callback will be performed (assuming it's defined) like so:

function OutBehavior::Ping( %this )
{
    echo( "Ping was raised!" );
}

Chapter. 3 Adding Inputs to a behavior.

In the example above, nothing is connected to the "Ping" output so calling ".Raise( Ping )" only results in the callback above. You can connect any number of inputs to an output. Given a behavior, an input is similar to adding a field:

%template = new BehaviorTemplate(InBehavior);
    
// Add a behavior input named "Pong".
%template.addBehaviorInput( Pong, "Test Input Signal", "Just a test" );       

You cannot directly raise an input i.e. performing ".Raise( Pong )" will emit an error saying it cannot find the output "Pong". An input is raised by the fact that it is connected to an output that is raised.

Chapter. 4 Connecting Outputs and Inputs.

Given the two example behaviors, you can connect an input to an output like so:

%object = new SceneObject();

// Create behaviors.
%outInstance = OutBehavior.createInstance();
%inInstance = InBehavior.createInstance();

// Add behaviors.
%object.addBehavior( %outInstance );
%object.addBehavior( %inInstance );

// Connect the "Ping" output and the "Pong" input.
%object.Connect( %outInstance, %inInstance, Ping, Pong );

// Raise the "Ping" output.
%object.Raise( %outInstance, Ping );

The code above will perform the "Ping" callback as shown before but now, because the "Pong" input on another behavior is connected to it, it will also raise the following:

function InBehavior::Pong( %this, %fromBehavior, %fromOutput )
{
    echo( "Pong was called!" );
}

You can connect as unlimited number of inputs to a single output.

Chapter. 5 Disconnecting Outputs and Inputs.

Given the above connected output and input, you can disconnect using the following:

// Connect the "Ping" output and the "Pong" input.
%object.Disconnect( %outInstance, %inInstance, Ping, Pong );

Chapter. 6 Timed Raises.

When raising an output, there is an optional third parameter which specifies a delta-time (in milliseconds) when the raise should happen. Essentially the raise will be scheduled to execute in the specified time. This can be very handy when you wish for a signal condition to occur some time in the future, maybe even repeat schedules.

Chapter. 7 Self Raises.

There is nothing stopping you connecting an output on a behavior to an input on the same behavior. Interesting looping conditions can be created using this, especially when using timed raises. Care should always be taken in ensuring that the behavior logic does not cause an infinite loop of raises.

Chapter. 8 Enumerating Connections

You can enumerate the connections on a behavior easily. First you typically get the behavior connection count and then iterate over the connections like so:

%connectionCount = %object.getBehaviorConnectionCount();

for( %index = 0; %index < %connectionCount; ++%index )
{
    echo( %object.getBehaviorConnection( %index ) );
}

When getting a behavior connection, a comma-delimited string of the arguments passed into the "connect()" method are returned like so:

<OutputBehavior>,<InputBehavior>,<OutputName>,<InputName>

The "OutpuBehavior" and "InputBehavior" are SimObjectId of the behavior instances.

Chapter. 9 Persistence

When a behavior is persisted, it persists field settings as well as behavior connections. This format follows the expected TAML serialization standard. The format is as follows:

XML

<SceneObject
    Name="TestObject">
    <SceneObject.Behaviors>
        <OutBehavior
            Id="1" />
        <InBehavior
            Id="2" />
        <Connection
            Ping="1"
            Pong="2" />
    </SceneObject.Behaviors>
</SceneObject>

JSON

{
    "SceneObject": {
        "Name": "TestObject",
        "SceneObject.Behaviors": {
            "OutBehavior[0]": {
                "Id": "1"
            },
            "InBehavior[1]": {
                "Id": "2"
            },
            "Connection[2]": {
                "Ping": "1",
                "Pong": "2"
            }
        }
    }
}

In both examples, the object has a behavior collection (SceneObject.Behaviors). Each behavior has its own entry in the collection (OutBehavior, InBehavior). The important fields to focus on are the behavior ID and Connection. Each behavior has its own ID within the Behaviors collection, which is then referenced in the Connection field.

In the above example, the Connection is defining something very simple. When the "Ping" function of the behavior with ID 1 (OutBehavior) is raised, the "Pong" function on the behavior with ID 2 (InBehavior) should be called.

Chapter. 10 Miscellaneous Info.

These Ids used above are used internally. The actual values are only used when writing/reading the behaviors and are not used whilst the behaviors are active. When you create a behavior instance, an Id is automatically allocated behind-the-scenes.

Unless you are manually adding behaviors in a persisted file, these are not relevant. Note that assigning the same Id to different behavior instances will result in an error. Also note that a Behavior Id is not a SimObjectId and that an Id of "0" is invalid.

Adding inputs and outputs to a behavior template incurs zero cost on a behavior instance i.e. the inputs and outputs are not copied when creating the instance. The only cost incurred is when a connection is made.

This connection information is actually stored on the "BehaviorComponent" therefore no per-instance cost even for connections is incurred. This is primarily why the methods such as "connect", "disconnect" and "raise" are actually defined on the "BehaviorComponent".

Unlike the previous code, you are now free to delete a behavior instance directly given its SimObjectId. This will not only remove the behavior but will also remove any connections made from it or to it.

Chapter. 11 Conclusion

Connections offer a simple method of decoupling behaviors but still allowing logical connections. Behaviors can be designed that raise all sorts of interesting outputs i.e. "PlayerDie" or "PlayerHealthLow". Other behaviors can then be connected to these via Inputs which perform actions. In other words, you can have a sound played when an input on a certain behavior is signaled. By connecting this to, say, the "PlayerDie" output, the sound will be played when the player dies. In this way, simple logic can be created.