-
Notifications
You must be signed in to change notification settings - Fork 21
C2D 05 Writing your own Behaviour
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.
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
- RectangleEntity
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.
-
Each frame we use
ComponentUtils
to grab a reference to theComponent
we’re interested in. While not requiring much code, this approach has a couple of major drawbacks. Firstly, we only want theGeometrySkin
to re-draw the Geometry if it changes. It is very inefficient to blindly re-draw each frame. Also, theComponentUtil
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. -
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.