TX/Tutorials and Guides/TX UserGuide/Component System

From TDN

This page is a Work In Progress.

Contents


Return To Contents Page

Introduction to Torque Components


Torque X provides a component system that is used to extend its game objects. If you look at the built-in starter kits for Torque X, you will see that most of the game functionality is implemented in components. This document describes the component system and how it is used.

Implementing game objects


Before we delve into what components are, lets consider a basic implementation problem.

In most object oriented programming languages, including C#, the main built-in way to provide new functionality to an object is to subclass the object and modify the subclass. For instance, suppose you had a need for physics and collision capabilities in particular game object, say your Player. You implement the logic in the Player class. Later, you find you have another class, Monster, which needs the same functionality. You could copy and paste all of the code from the Player class into the Monster class - a bad idea, because aside from bugs resulting from the integration of the new code in the Monster class, now you have two copies of potentially complicated code that you will have to maintain. Instead, you can create a new class that contains the code, and have both Player and Monster extend from that class. This sounds reasonable, so you create a class BaseActor, and put the physics and collision code in that class. Then you have Player and Monster derive from the BaseActor class.

Later you want your players to be able to pick up game objects. So you add pickup code to your Player class. But maybe you decide that Monsters need pickup code too (so they can grab a sword instead of beating on the player with claws), so the pickup code gets promoted to BaseActor. In the meantime, you've added an NonPlayerCharacter class, derived from BaseActor, and you don't want NPCs to be able to pickup objects - perhaps their AI is too primitive, so they just wander around, and you don't want them scooping up all of your pickups like a vacuum cleaner. But now you are stuck, because the NPC inherits the pickup functionality from BaseActor, and there is no way to disable it - yet! You fix that by adding a PickupEnabled flag to BaseActor, and then add "if" statements where needed in the BaseActor code to respect the new flag. Now NPCs can subclass BaseActor normally and set PickupEnabled to false.

You may have guessed by now that the code in BaseActor is starting to look pretty ugly. You're right, but the truth is, its only the beginning. Soon your game code will start requiring that everything be a subclass of BaseActor, because BaseActor provides everything that the game could possibly want. The basic problem is that BaseActor becomes your one-stop shop for all shared functionality, so it gets huge, buggy, mandatory to use, and hard to maintain.

This example illustrates one of the pitfalls with language inheritance. Inheritance is a powerful tool, and should be used judiciously when appropriate, but there are other tools which might solve our problem better. An alternative to inheritance is aggregation (also called delegation in some contexts). The basic idea with aggregation is that instead of having objects subclass other objects to get new functionality, each object contains one or more objects that provide the functionality. The object is an aggregate of other objects. To make an analogy, all of your objects become "bags", and things like pickups, physics, and collision become separate objects that you either put in or remove from the bag. The "bag" itself usually does not contain code that manipulates the things it contains, so it often doesn't require any specific things to be in the bag.

Solving the problem with Torque X components


The Torque X component system is an implementation of the aggregation model. A component is a small object that adds functionality to a collection object. Applying the component system to our example model above, we would start by making BaseActor derive from the TorqueObject class. This gives BaseActor the ability to have components. Then we define new components, a PhysicsComponent, CollisionComponent, and PickupComponent, and derive them from TorqueComponent (the base class for all components). We don't even need to create specialized subclasses, like Player, Monsters, or NPCs. Instead we can use named objects or object type masks to differentiate between the objects. Here is some simple code that puts this all together:

// declare the classes that we will need
public class BaseActor : TorqueObject
{
	// implementation omitted
}
public class PhysicsComponent : TorqueComponent
{
	// implementation omitted
}
public class CollisionComponent : TorqueComponent
{
	// implementation omitted
}
public class PickupComponent : TorqueComponent
{
	// implementation omitted
}
	
public class Game
{
	public static void Main()
	{
		BaseActor player = new BaseActor();
		player.ObjectType = TorqueObjectDatabase.Instance.GetObjectType("player");
		player.Components.AddComponent(new PhysicsComponent());
		player.Components.AddComponent(new CollisionComponent());
		player.Components.AddComponent(new PickupComponent());

		BaseActor monster = new BaseActor();
		monster.ObjectType = TorqueObjectDatabase.Instance.GetObjectType("monster");
		monster.Components.AddComponent(new PhysicsComponent());
		monster.Components.AddComponent(new CollisionComponent());
		monster.Components.AddComponent(new PickupComponent());
			
		BaseActor npc = new BaseActor();
		npc.ObjectType = TorqueObjectDatabase.Instance.GetObjectType("npc");
		npc.Components.AddComponent(new PhysicsComponent());
		npc.Components.AddComponent(new CollisionComponent());
			
		// run the game 
		// ...
	}
	// ... for wiki formatting ...
}

Here are a few things that this component system solves:

1. The base game class, BaseActor, can be as lightweight as we want, balancing ease of use. That is, for convenience we might define Physics and Collsion properties on the BaseActor which return those components, if they exist in the object.

2. The actual logic of physics, collision, and pickup is split out into separate classes and thus starts off well isolated from other components and objects.

3. Component logic is simple to reuse in a new object type; in many cases we can just add the component to the new object type and it will work properly. Reuse is further extended even across games; one can build up a component library that is used from game to game, evolving somewhat each time.

4. Its easy to omit functionality. The NPC object can't pick up items, because it has no pickup component.

5. Because the components are distinct from objects, they are more easily manipulated by external tools, such as the TXB editor.

Observant readers my have noticed something a little odd in the component example given above. In the original inheritance model, we defined new classes for our game objects, such as Player, Monster, and NPC. In the component model, we did not define new classes, instead we created one new instance each of the player, monster, and npc objects. Suppose we wanted more than one Monster? (As we all know, you can never have enough Monsters). In the inheritance model, we would just create new Monsters until the cows came home:

// Create a hoard of vicious Monsters!
for (int i = 0; i < 100; ++i)
{
	Monster m = new Monster();
	m.Position = FindMonsterSpawn(); // FindMonsterSpawn() will find a unoccupied spawn point for our monster.
	TorqueObjectDatabase.Instance.Register(m);
	m.Attack(player);
}

How do we handle this in the component model? Do we have to repeat all 5 lines required to create a component-based monster everywhere we create monsters? That would be a pain. Fortunately, we don't have to do this. Instead, we can make our component-based objects be Templates, and then create new instances from the templates. If you have not read the "Template Guide", the following discussion may be unfamiliar - but keep reading and then read the Template Guide afterwards.

The template system lets us create a component-based object and mark it as a template. Later, we can clone the template to make new objects that we can register with the engine. Here is the monster code from above defined as a template:

// Create the monster template
BaseActor monsterTemplate = new BaseActor();
monsterTemplate.Name = "MonsterTemplate";
monsterTemplate.ObjectType = TorqueObjectDatabase.Instance.GetObjectType("monster");
monsterTemplate.IsTemplate = true;
monsterTemplate.Components.AddComponent(new PhysicsComponent());
monsterTemplate.Components.AddComponent(new CollisionComponent());
monsterTemplate.Components.AddComponent(new PickupComponent());

Note the three changes we made to make our monster into a template:

1. We added the IsTemplate flag and set it to true. This is the only step that is required to make an object into a template.

2. We gave the object a name, "MonsterTemplate". Naming a template is optional, but if you don't name it, then you can't use the TorqueObjectDatabase to find the object later.

3. We changed the variable name to "monsterTemplate". This is also optional but it makes it a bit more clear that we are defining a template.

Now we have all our monster-configuration code contained in a convenient template. When we want to create a monster, we just clone the template, configure the resulting object as needed, and then register it. All of the components on the original template, just like other fields, will be present on the cloned object. The exception is that IsTemplate will be false on the objects returned by clone. Here is example code code that creates a hoard of vicious monsters to attack the player.

// Create a hoard of vicious Monsters!
for (int i = 0; i < 100; ++i)
{
	BaseActor m = (BaseActor)monsterTemplate.Clone();
	m.Position = FindMonsterSpawn(); // FindMonsterSpawn() will find a unoccupied spawn point for our monster.
	TorqueObjectDatabase.Instance.Register(m);
	m.Attack(player); // ex-terrrrrr-minate!
}

Distinction from XNA Components


The XNA Framework object supports extensibility via its own Component model. This system shares the same name and is similar in concept to the Torque Component system, but they are really two separate systems intended for different purposes. The Torque system's purpose is to make it easier to define Torque game objects. The XNA system is designed to make it easier for different vendors to provide subsystems that can be integrated smoothly into a single game. Put another way, the XNA system is a component model for the Game, whereas the Torque system is a component model for the objects in the Game. Torque X is fully compatible with the XNA component model, and a Torque Game can contain any number of XNA components. In fact, most of the low-level engine processing is driven from an XNA component (the TorqueEngineComponent).

Another Component Example


Here is another component example that shows more details of component game logic and integration with the engine. If you haven't already done so, it is recommended you read the Introduction of this document and the Template System concept guide so that you can follow this example more easily.

To get a feel for how the component system in Torque X works, consider the following code which creates a "Tankette" in TankBuster. Note that in TankBuster and in most of the other Starter Kits, components are not usually created from code like this; instead they are deserialized from XML. Here we illustrate the process using C# code to make it more clear.

 // set up sprite
 _tankTemplate = new T2DStaticSprite();
 _tankTemplate.Material = (DefaultEffect)TorqueObjectDatabase.Instance.FindObject("TanketteMaterial");
 _tankTemplate.Size = new Vector2(15.0f, 14.0f);
 _tankTemplate.Layer = Game.Instance.Ground.Layer - 1;
 _tankTemplate.ObjectType = TanketteObjectType;
 
 // add tank smarts
 TankAIComponent ai = new TankAIComponent();
 ai.ProjectileTemplate = GetProjectileTemplate();
 _tankTemplate.Components.AddComponent(ai);
 
 // add some mount points
 _tankTemplate.Components.AddComponent(new T2DLinkPointComponent());
 _tankTemplate.LinkPoints.AddLinkPoint("turret", new Vector2(-0.5f, -1.0f), 0.0f);
 _tankTemplate.LinkPoints.AddLinkPoint("dust", new Vector2(0.75f, 0.5f), 0.0f);
 
 // add combustible so we blow up
 CombustibleComponent boom = new CombustibleComponent();
 boom.OnGround = true;
 boom.CollidesWith = BombObjectType + PlayerObjectType;
 boom.DestroyOnCollision = true;
 boom.ExplosionTemplate = GetBombExplosionTemplate();
 boom.Offset = new Vector2(0, boom.ExplosionTemplate.Size.Y * 0.5f);
 boom.CameraShake= new Vector2(1.25f, 1000.0f);
 _tankTemplate.Components.AddComponent(boom);
 
 // mount turret
 T2DStaticSprite turret = (T2DStaticSprite)TorqueObjectDatabase.Instance.FindObject("TanketteCannon");
 turret.Mount(_tankTemplate, "turret", true);
 turret.TrackMountRotation = false;
 
 // mount dustcloud
 T2DAnimatedSprite dustCloud = (T2DAnimatedSprite)TorqueObjectDatabase.Instance.FindObject("DustCloud");
 dustCloud.Mount(_tankTemplate, "dust", true);

This code snippet creates a simple sprite and adds a few components to it to create a relatively complex object. First it adds a TankAIComponent which gives it aiming and firing behavior. This is a special component written specifically for tankettes in TankBuster. Next we create a combustible component and configure it so that it collides when we hit bomb or player objects and what to do when that happens (destroy ourselves and create an explosion). This was a class written for TankBuster which is used by all the game objects not just the tankette. After this a bounds checker component is added which makes sure the tankette is removed from the game when it goes off screen. Finally, we mount a couple objects on the tankette to link points which were set up by the link point component.

Below is the code for the BoundscheckerComponent in it's entirety:

class BoundsCheckerComponent : TorqueComponent, IAnimatedObject
{
	//======================================================
	#region Public methods
	public void UpdateAnimation(float elapsed)
	{
		T2DSceneCamera cam = T2DSceneGraph.Instance.Camera as T2DSceneCamera;
		if (_sceneObj.Position.X < cam.SceneMin.X - _sceneObj.Size.X)
		{
			Owner.Manager.Unregister(Owner);
			return;
		}
		// ... for wiki formatting ...
	}
	#endregion
  
	//======================================================
	#region Private, protected, internal methods
	protected override bool _OnRegister(TorqueObject owner)
	{
		if (!base._OnRegister(owner) || !(Owner is T2DSceneObject))
			return false;
 
		_sceneObj = Owner as T2DSceneObject;
		ProcessList.Instance.AddAnimationCallback(Owner, this);

		return true;
	}
	#endregion
  
	//======================================================
	#region Private, protected, internal fields
	T2DSceneObject _sceneObj;
	#endregion
}

As an exercise, let's go through the code for this component bit by bit:

class BoundsCheckerComponent : TorqueComponent, IAnimatedObject
{
	// The BoundsCheckerComponent is derived from TorqueComponent and also inherits
	// from the IAnimatedObject interface.
	// IAnimatedInterface allows the component to receive UpdateAnimation callbacks.
  
	//======================================================
	#region Public methods
	public void UpdateAnimation(float elapsed)
	{
		T2DSceneCamera cam = T2DSceneGraph.Instance.Camera as T2DSceneCamera;
		if (_sceneObj.Position.X < cam.SceneMin.X - _sceneObj.Size.X)
		{
			Owner.Manager.Unregister(Owner);
			return;  
		}
		// ... for wiki formatting ...
	}
	#endregion 

This is the UpdateAnimation callback for the BoundsCheckerComponent, it looks up the camera via the scene graph and checks the position of the containing object (held in the _sceneObj member variable) to see if it's still on the screen. If the object is off screen, the owning object is unregistered (removed from the game). We could also do this by setting MarkedForDelete on Owner to true (Owner.MarkedForDelete = true).

	//======================================================
	#region Private, protected, internal methods
	protected override bool _OnRegister(TorqueObject owner)
	{
		if (!base._OnRegister(owner) || !(Owner is T2DSceneObject))
		  return false;
  
		_sceneObj = Owner as T2DSceneObject;
		ProcessList.Instance.AddAnimationCallback(Owner, this);
  
		return true;
	}
	#endregion

This code initializes the component when the containing object is registered. We call base._OnRegister to make sure our parent class (in this case TorqueComponent) initializes ok (if it doesn't, we return false to indicate an error). We also check to make sure that our owner is a T2DSceneObject (if it isn't, we return false). We save off the owner as a scene object in the _sceneObj variable (just a convenience) and register for an animation callback with the process list. Note that any number of components can register for callbacks with the same object. The callbacks for each object occur successively (i.e., all callbacks for objectA occur before any callbacks for objectB occurs). You can control what order a given objects callbacks occur in by supplying an order parameter for each callback (a third, but optional, parameter to the AddAnimationCallback method). Finally, the _OnRegister method returns true to indicate that everything was successfully initialized.

	//======================================================
	#region Private, protected, internal fields
	T2DSceneObject _sceneObj;
	#endregion
}

_sceneObj is the only data member of this particular component. It is simply a convenient caching of the owning object as a T2DSceneObject (recall that if we aren't owned by a T2DSceneObject we return false in _OnRegister and the entire object is not registered).

By adding this component to ANY object, the object will unregister itself once it is off screen. This is only a very simply example, but complex objects can be created by the combination of several simple components. More complex examples of components are T2DPhysicsComponent, T2DCollisionComponent, T2DLinkPointComponent, and T2DForceComponent. We'll discuss each of these and several others below when discussing T2D.

Often times two components of an object need to communicate with each other. They can do this by sharing TorqueInterfaces with each other. For example, the T2DForceComopnent exposes forces which the T2DPhysicsComponent is affected by. The way the force component is able to influence the functioning of the physics component is by exposing a force interface which the physics component picks up. Here is what that looks like:

// T2DForceComponent defines _RegisterInterfaces:
protected internal override void _RegisterInterfaces(TorqueObject owner)
{
	base._RegisterInterfaces(owner);


  	...


	// cache force interface and match empty name only
	Owner.RegisterCachedInterface("force", String.Empty, this, _forceInterface);
}

The last line in the method registers the force interface held in the _forceInterface member variable as an interface of type "force" and with no name (String.Empty).

Inside the _OnRegister method of T2DPhysicsComponent we find these lines:

owner.Components.GetInterfaceList<TorqueInterfaceWrap<IT2DForceGenerator>>("force", String.Empty, _forceGenerators);

This line finds all the interfaces of type "force" and name "" in the containing object (owner) and puts them in the _forceGenerators list. Now the physics component will use the forces exposed by the force component. Not only that, you can write your own component which creates forces and it will work with the physics component so long as you expose a force interface for it to find.

Image:HubHeaderLeftA.gif Image:HubHeaderRightA.gif
Image:HubHeaderLeftB.gif

Note

Image:HubHeaderRightB.gif


Note that the C# type of these interfaces is TorqueInterfaceWrap<IT2DForceGenerator>, which means that it's a TorqueInterface which wraps the C# interface IT2DForceGenerator. Another common interface type is ValueInterface<float> which simply means that the interface wraps a float variable. E.g., T2DSceneObject exposes "alpha" as a float interface, allowing you to animate that parameter using the T2DAnimationComponent or any other component which looks up "alpha".

Image:HubFooterLeftA.gif Image:HubFooterRightA.gif
Image:HubFooterLeftB.gif Image:HubFooterRightB.gif


Return To Contents Page

This is Community Maintained, you may, and probably will, find out of date material. Feel free to update any of these articles yourself.