Skip to content

C2D 06 Writing your own Skin

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

So far in these tutorials we have been using the GeometrySkin class to provide the look of our ComponentContainer. This is a very versatile class as you are able to specify the fill color, fill alpha, line color and line alpha to get a fairly customized look.

Your Own Skin screenshot

In this tutorial we are going to add a new ComponentContainer to our scene. This time we are going to use a CircleGeometry, and then write our own Skin to draw a circle with a shaded style.Copy and paste the following code over your current CadetHelloWorld class:

package
{
	import flash.display.Sprite;
	import flash.events.Event;
	
	import cadet.core.CadetScene;
	import cadet.core.ComponentContainer;
	
	import cadet2D.components.geom.CircleGeometry;
	import cadet2D.components.geom.RectangleGeometry;
	import cadet2D.components.renderers.Renderer2D;
	import cadet2D.components.skins.GeometrySkin;
	import cadet2D.components.transforms.Transform2D;
	
	import cadetHelloWorld.components.behaviours.AnimateRotationBehaviour;
	import cadetHelloWorld.components.skins.ShadedCircleSkin;
	
	[SWF( width="700", height="400", backgroundColor="0x002135", frameRate="60" )]
	public class CadetHelloWorld extends Sprite
	{
		private var cadetScene:CadetScene;
		
		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);
			
			addEventListener( Event.ENTER_FRAME, enterFrameHandler );
			
			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);
			
			var circleEntity:ComponentContainer = new ComponentContainer();
			circleEntity.name = "Circle";
			cadetScene.children.addItem(circleEntity);
			
			transform = new Transform2D();
			transform.x = 550;
			transform.y = 150;
			circleEntity.children.addItem(transform);
			
			var circleGeometry:CircleGeometry = new CircleGeometry(50);
			circleEntity.children.addItem(circleGeometry);
			
			skin = new GeometrySkin();
			circleEntity.children.addItem(skin);	
		}
		
		private function enterFrameHandler( event:Event ):void
		{
			cadetScene.step();
		}
	}
}

You'll see we still have our rectangleEntity from the previous examples and a new circleEntity.

Build and run to see your circle in the scene.

Now that we’ve got our circle appearing in the scene, let’s work on replacing the GeometrySkin with our own Skin.

Create the following class:

package cadetHelloWorld.components.skins
{
	import flash.display.GradientType;
	
	import cadet.events.ValidationEvent;
	
	import cadet2D.components.geom.CircleGeometry;
	import cadet2D.components.skins.AbstractSkin2D;
	
	import starling.core.Starling;
	import starling.display.Shape;
	import starling.textures.GradientTexture;
	import starling.textures.Texture;
	
	public class ShadedCircleSkin extends AbstractSkin2D
	{
		private var _circle		:CircleGeometry;
		private var _shape		:Shape;
		
		private const DISPLAY	:String = "display";
		
		public function ShadedCircleSkin()
		{
			_displayObject = new Shape();
			_shape = Shape(_displayObject);
		}
		
		override protected function addedToParent():void
		{
			super.addedToParent();
			addSiblingReference(CircleGeometry, "circle");
		}
		
		public function set circle( value:CircleGeometry ):void
		{
			if ( _circle )
			{
				_circle.removeEventListener(ValidationEvent.INVALIDATE, invalidateCircleHandler);
			}
			
			_circle = value;
			
			if ( _circle )
			{
				_circle.addEventListener(ValidationEvent.INVALIDATE, invalidateCircleHandler);
			}
			
			invalidate(DISPLAY);
		}
		public function get circle():CircleGeometry{ return _circle; }
		
		
		private function invalidateCircleHandler( event:ValidationEvent ):void
		{
			invalidate(DISPLAY);
		}
		
		override public function validateNow():void
		{
			if ( isInvalid(DISPLAY) )
			{
				validateDisplay();
			}
			super.validateNow()
		}
		
		private function validateDisplay():void
		{
			_shape.graphics.clear();
			if ( !_circle ) return;
			
			var colors:Array = [0xFFFFFF, 0x909090];
			var ratios:Array = [0,255];
			var alphas:Array = [1,1];
			
			// Don't attempt to create the gradientTexture if the Starling.context is unavailable,
			// as Texture.fromBitmapData() will throw a missing context error
			if (!Starling.context) return;
			
			var gradientTexture:Texture = GradientTexture.create(128, 128, GradientType.RADIAL, colors, alphas, ratios );
			_shape.graphics.beginTextureFill(gradientTexture);
			_shape.graphics.drawCircle(circle.x,circle.y,circle.radius);
			_shape.graphics.endFill();
		}
	}
}

The first thing you’ll notice is that we’re extending a base class, AbstractSkin2D. This class implements IRenderable (which in turn extends IComponent). This base class provides a useful starting point for writing a 2D skin, and removes the need to provide a concrete implementation of the IRenderable interface.

Our Skin keeps a reference to one of its siblings – a Geometry Component of type CircleGeometry. It does this using the method we used in the previous tutorial, addSiblingReference.

In the setter method for the circle, we are removing an event listener if we already have a circle reference set, and adding a new listener if the reference is not null. This handler simply calls a function invalidate(), and passes a string “display”. What’s going on here?


Invalidation mechanism

The Cadet Component Framework employs a invalidate/validate mechanism very similar to the one used by the Flex framework. This mechanism serves to reduce the amount of work carried out by delaying any work required to the next ‘validation cycle’. A validation cycle is initiated by calling validateNow() on any Component. If the Component is a ComponentContainer, it will also call validateNow() on all its children. This process continues recursively down the tree.

We’ve already explained how the cadetScene.step() method steps all ISteppable Components, then calls validateNow().

The CadetScene is a ComponentContainer, so this line causes validateNow() to be called on every Component in the hierarchy.

Calling invalidate(“display”) simply puts a flag against the string “display” that can later be queried by the isInvalid() method. You can use whatever string identifier you like – the convention is to use static consts to avoid spelling errors, and a slight performance gain.

In our ShadedCircleSkin we use this mechanism to delay a call to validateDisplay until it is absolutely necessary. This means the circle property could be set a million times between validation cycles, but validateDisplay() would still only be called once.

Components dispatch InvalidationEvents when their invalidate() method is called. This allows other Components to detect when siblings they depend upon have been changed in some way. In our example, we are making sure we invalidate “display” whenever our CircleGeometry is changed (for example, by having its radius property changed).


Adding our skin to the scene

To see our new skin in all its shaded glory, we need to replace the following code in our CadetHelloWorld app's initScene() method:

skin = new GeometrySkin(1,0xFFFFFF,1,0xd89d1f,0.5);
circleEntity.children.addItem(skin);

with:

var shadedCircleSkin:ShadedCircleSkin = new ShadedCircleSkin();
circleEntity.children.addItem(shadedCircleSkin);

Build and run to see your Circle being rendered with your new skin.

< Previous | Next >