Skip to content

C2D 05 Writing your own Behaviour

Robert Silverton edited this page Aug 12, 2013 · 6 revisions

In the previous tutorial we saw how we can add a Behaviour to our scene in an ad-hoc way by getting a reference to the Component we’re interested in, and adding some logic that changes its properties. While this is useful for quick examples, in many ways it goes against the grain of what the Cadet library is trying to achieve. Namely, code re-use.

Your Own Behaviour screenshot

Rather than adding bespoke Behaviour in our enterFrameHandler that rotates our Component, why not wrap up this Behaviour into its own Component? This way we could reuse this Component on multiple ComponentContainers, or on projects in the future.

Note: Before we begin, first delete the code we added in the previous tutorial from your enterFrameHandler. We don’t want this interfering with the code we are about to create.

Create the following class:

package cadetHelloWorld.components.behaviours
{
	import cadet.core.Component;
	import cadet.core.ISteppableComponent;
	import cadet2D.components.transforms.Transform2D;

	public class AnimateRotationBehaviour extends Component implements ISteppableComponent
	{
		public var transform2D	:Transform2D;
		
		public var rotationSpeed	:Number = 15;
		
		public function AnimateRotationBehaviour()
		{
			super();
		}
		
		override protected function addedToParent():void
		{
			addSiblingReference(Transform2D, "transform2D");
		}
		
		public function step( dt:Number ):void
		{
			if ( !transform2D ) return;
			
			transform2D.rotation += rotationSpeed * dt;
		}
	}
}

The first thing to notice is that our class extends the cadet.core.Component class. All Components your write will extend this class. It provides all the behaviour required to give the Cadet Engine its ‘plug-and-play’ quality.

We’ve got a couple of public member variables. One is a reference to the transform we want to manipulate, the other is a number we can change to affect the speed at which this behaviour rotates the transform.

We are also implementing a step() method, which is required by the ISteppableComponent interface. We’ve already seen the step() method being called on our CadetScene back in the enterFrameHandler. The CadetScene takes care of making sure step() is called on any Component implementing ISteppableComponent in its hierarchy.

We are overriding a method in the base Component class, addedToParent(). This method is called when a Component has been added to a ComponentContainer’s children. At this point we are using a method from the base Component class, addSiblingReference(). Before we explain what this method does, it’s worth explaining briefly what is meant by the term ‘sibling’.

Recall that our scene currently has the following structure:

  • CadetScene
    • RectangleEntity
      • Transform2D
      • GeometrySkin
      • RectangleGeometry

In this structure, the Transform2D, GeometrySkin and RectangleGeometry are all classed as siblings. They are all the children of the same parent.

Lets focus on the mechanism behind how the GeometrySkin class works. In order to provide the behaviour that causes our Rectangle to appear on the screen, the GeometrySkin Component needs to have easy access to one of its siblings, the RectangleGeometry. The GeometrySkin reads the geometry data and uses the Starling-Graphics-Extension's drawing API to draw the geometry.

Because of Cadet’s pluggable nature, it is possible for Components to be added and removed from their parent at any time. In order for the GeometrySkin to get a valid reference to its sibling, there are 2 possible ways this could be achieved in code.

  1. Each frame we use ComponentUtils to grab a reference to the Component we’re interested in. While not requiring much code, this approach has a couple of major drawbacks. Firstly, we only want the GeometrySkin to re-draw the Geometry if it changes. It is very inefficient to blindly re-draw each frame. Also, the ComponentUtil functions are fairly fast, but if used by a lot of Components each frame, on a large enough scene, these will begin to add their own overhead to the run-time performance of the scene.

  2. Monitor the parent Component for changes to its children, and check to see if a sibling we are interested in has been added or removed. When added we store a reference to it, when removed we make sure we remove the reference to it.

This second method is far more efficient, as we are only doing work when required. However it is a fair amount of code to have to keep on writing.

Now, back to the addSiblingReference() method in our AnimateRotationBehaviour. This function is provided by the base class, and provides the behaviour we described in method 2 above. It allows you to specify the type of sibling you’re interested in obtaining a reference to, and the name of the property on your Component you want the reference set to. The base class will take care of monitoring the parent Component, and will set the reference when an appropriate sibling exists, and will null the reference when either your Component is removed from its parent, or if the sibling is removed from its parent.

So in our example we are asking the base class to set our transform2D property with a reference to a sibling of type Transform2D whenever possible. During step() we quickly check to see if the reference exists (as our Component could be added to a ComponentContainer without a Transform2D child), if we’ve got a reference then we rotate it by an amount proportional to dt. This value means ‘delta time’, the number of seconds elapsed since step() was last called, and is usually a fraction as most games will require more that one update per second.

So now we have our own Component that we can add to any ComponentContainer we like. If that ComponentContainer has a Transform2D component as a child, then this Component will have its rotation property modified each step(). Lets add our AnimateRotationBehaviour Component to our CadetScene to see it in action.

Update the contents of your application's constructor to the following:

import cadetHelloWorld.components.behaviours.AnimateRotationBehaviour;
public function CadetHelloWorld ()
{
	cadetScene = new CadetScene();
			
	var renderer:Renderer2D = new Renderer2D();
	renderer.viewportWidth = stage.stageWidth;
	renderer.viewportHeight = stage.stageHeight;
	cadetScene.children.addItem(renderer);
	renderer.enable(this);
			
	var rectangleEntity:ComponentContainer = new ComponentContainer();
	rectangleEntity.name = "Rectangle";
			
	var transform:Transform2D = new Transform2D();
	transform.x = 250;
	transform.y = 150;
	rectangleEntity.children.addItem(transform);
	
	var rectangleGeometry:RectangleGeometry = new RectangleGeometry();
	rectangleGeometry.width = 200;
	rectangleGeometry.height = 100;
	rectangleEntity.children.addItem(rectangleGeometry);
	
	var skin:GeometrySkin = new GeometrySkin();
	rectangleEntity.children.addItem(skin);
	
	var animateRotationBehaviour:AnimateRotationBehaviour = new AnimateRotationBehaviour();
	rectangleEntity.children.addItem(animateRotationBehaviour);			
	cadetScene.children.addItem(rectangleEntity);			

	addEventListener( Event.ENTER_FRAME, enterFrameHandler );
}

Also, make sure your enterFrameHandler for your app simply says:

private function enterFrameHandler( event:Event ):void
{
	cadetScene.step();
}

Build and run, and you should see your Behaviour updating your rectangle’s transform each frame.

< Previous | Next >