Box2D

Create combination of physics objects in Citrus Engine

0

Some days ago, I said that November will be an awesome month for the Citrus Engine. So what is coming?

We’re the 2nd, November is already here and something huge is already happened to the CE : a tutorial on gotoandlearn made by Lee Brimelow, an Adobe Game Developer Evangelist! Definitely, an excellent tutorial to get start with the Citrus Engine. There’ll be other tutorials later, a new website and the forum’ll move on Starling website. Most of the future game will use Starling so that’s a good move! We’re not forgetting the 3D part, don’t worry ;)

Everything should be ready for the next Adobe Game Jam in Chicago. So that’s the plan. Now let’s go for a small tutorial.

Someone asked me what is the best way to create a combination of physics objects in CE, like this one. You have to create the Box2D physics bodies and associate arts. Firstly, don’t forget that view can lies, but never physics. For example, on the CE’s demo on the actual website the mill’s arms are one Box2D object (with two bodies) and a single picture.
Example :

The Box2D Alchemy code :

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
package
{
	import Box2DAS.Collision.Shapes.b2PolygonShape;
	import Box2DAS.Common.V2;
	import Box2DAS.Dynamics.Joints.b2RevoluteJoint;
	import Box2DAS.Dynamics.Joints.b2RevoluteJointDef;
	import Box2DAS.Dynamics.b2Body;
	import Box2DAS.Dynamics.b2BodyDef;
	import Box2DAS.Dynamics.b2Fixture;
	import Box2DAS.Dynamics.b2FixtureDef;
 
	import com.citrusengine.objects.PhysicsObject;
 
	public class WindmillArms extends PhysicsObject
	{
		private var _hingeBodyDef:b2BodyDef;
		private var _hingeBody:b2Body;
		private var _shape2:b2PolygonShape;
		private var _fixtureDef2:b2FixtureDef;
		private var _fixture2:b2Fixture;
		private var _jointDef:b2RevoluteJointDef;
		private var _joint:b2RevoluteJoint;
 
		public function WindmillArms(name:String, params:Object=null)
		{
			super(name, params);
		}
 
		override public function destroy():void
		{
			_hingeBody.destroy();
			_hingeBodyDef.destroy();
			_fixtureDef2.destroy();
			super.destroy();
		}
 
		override public function set x(value:Number):void
		{
			super.x = value;
 
			if (_hingeBody)
			{
				var pos:V2 = _hingeBody.GetPosition();
				pos.x = _x;
				_hingeBody.SetTransform(pos, _hingeBody.GetAngle());
			}
		}
 
		override public function set y(value:Number):void
		{
			super.y = value;
 
			if (_hingeBody)
			{
				var pos:V2 = _hingeBody.GetPosition();
				pos.y = _y;
				_hingeBody.SetTransform(pos, _hingeBody.GetAngle());
			}
		}
 
		override protected function defineBody():void
		{
			super.defineBody();
 
			_hingeBodyDef = new b2BodyDef();
			_hingeBodyDef.type = b2Body.b2_staticBody;
			_hingeBodyDef.position.v2 = new V2(_x, _y);
		}
 
		override protected function createBody():void
		{
			super.createBody();
 
			_hingeBody = _box2D.world.CreateBody(_hingeBodyDef);
			_hingeBody.SetUserData(this);
		}
 
		override protected function createShape():void
		{
			super.createShape();
 
			_shape2 = new b2PolygonShape();
			_shape2.SetAsBox(_width / 2, _height / 2, null, _rotation + (Math.PI / 2));
		}
 
		override protected function defineFixture():void
		{
			super.defineFixture();
 
			_fixtureDef2 = new b2FixtureDef();
			_fixtureDef2.shape = _shape2;
			_fixtureDef2.density = _fixtureDef.density;
			_fixtureDef2.friction = _fixtureDef.friction;
			_fixtureDef2.restitution = _fixtureDef.restitution;
		}
 
		override protected function createFixture():void
		{
			super.createFixture();
			_fixture2 = _body.CreateFixture(_fixtureDef2);
		}
 
		override protected function defineJoint():void
		{
			super.defineJoint();
 
			_jointDef = new b2RevoluteJointDef();
			_jointDef.bodyA = _hingeBody;
			_jointDef.bodyB = _body;
			_jointDef.enableMotor = true;
			_jointDef.maxMotorTorque = 6
			_jointDef.motorSpeed = 0;
			_jointDef.localAnchorA.v2 = new V2();
			_jointDef.localAnchorB.v2 = new V2();
		}
 
		override protected function createJoint():void
		{
			super.createJoint();
 
			_joint = _box2D.world.CreateJoint(_jointDef) as b2RevoluteJoint;
			_joint.SetUserData(this);
		}
	}
}

Easier than it sounds, right? The key is to override defined methods and add your new boddie.

Let’s go back to the first example on Emanuele’s website.
Click here for the web demo.
As usal, all the source code is available on the CE’s GitHub dedicated to examples.
Actually, you see the Box2D debug view, to see arts use the console :
Press tab key, write :

set box2D visible false

And press enter.

There are two ways to achieve it, one is not better than the other, it’s just different. You will certainly prefer one depending what you’re trying to achieve.

Create lots of CE’s object
This is the game state :

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
package complexbox2dobject{
 
	import com.citrusengine.core.State;
	import com.citrusengine.objects.platformer.box2d.Platform;
	import com.citrusengine.physics.box2d.Box2D;
 
	/**
	 * @author Aymeric
	 */
	public class ComplexBox2DObjectGameState extends State {
 
		public function ComplexBox2DObjectGameState() {
			super();
		}
 
		override public function initialize():void {
 
			super.initialize();
 
			var box2D:Box2D = new Box2D("box2D");
			box2D.visible = true;
			add(box2D);
 
			var platTop:Platform = new Platform("platTop", {x:stage.stageWidth / 2, width:stage.stageWidth, y:20});
			add(platTop);
 
			var chain:Chain;
			var vecChain:Vector.<Chain> = new Vector.<Chain>();
			for (var i:uint = 0; i < 4; ++i) {
 
				if (i == 0)
					chain = new Chain("chain" + i, {x:stage.stageWidth / 2, y:70 + i * 40, width:15, hangTo:platTop, distance:i, view:new ChainGraphic()});
				else
					chain = new Chain("chain" + i, {x:stage.stageWidth / 2, y:70 + i * 40, width:15, hangTo:vecChain[i - 1], distance:i, view:new ChainGraphic()});
 
				vecChain.push(chain);
				add(chain);
			}
 
			chain = new Chain("chain" + i, {x:2, y:70 + (i + 1) * 40, radius:15, hangTo:vecChain[i - 1], distance:i + 1, last:true, registration:"topLeft", view:new ChainGraphic(15)});
			add(chain);
			vecChain.push(chain);
		}
 
	}
}

Here we create many CE objects, with lots of similar informations. The good thing is all the arts are managed by SpriteView and SpriteArt classes.
The Box2D Chain class :

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
package complexbox2dobject{
 
	import Box2D.Common.Math.b2Vec2;
	import Box2D.Dynamics.Joints.b2RevoluteJointDef;
 
	import com.citrusengine.objects.Box2DPhysicsObject;
 
	/**
	 * @author Aymeric
	 */
	public class Chain extends Box2DPhysicsObject {
 
		public var hangTo:Box2DPhysicsObject;
		public var distance:uint;
		public var last:Boolean = false;
 
		private var _revoluteJointDef:b2RevoluteJointDef;
 
		public function Chain(name:String, params:Object) {
			super(name, params);
		}
 
		override protected function defineJoint():void {
 
			_revoluteJointDef = new b2RevoluteJointDef();
 
			if (last) {
				_revoluteJointDef.localAnchorA.Set(0, (distance + 75) / _box2D.scale);
				_revoluteJointDef.localAnchorB.Set(0, 0);
				_revoluteJointDef.bodyA = hangTo.body;
				_revoluteJointDef.bodyB = _body;
			} else
				_revoluteJointDef.Initialize(hangTo.body, _body, new b2Vec2(275 / _box2D.scale, (70 + distance * 40) / _box2D.scale));
		}
 
		override protected function createJoint():void {
 
			_box2D.world.CreateJoint(_revoluteJointDef);
		}
	}
}

Nothing too complex, we just create a joint in the right method.

And finally the ChainGraphic class :

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
package complexbox2dobject{
 
	import flash.display.Sprite;
 
	/**
	 * @author Aymeric
	 */
	public class ChainGraphic extends Sprite {
 
		public function ChainGraphic(radius:uint = 0) {
 
			this.graphics.beginFill(Math.random() * 0xFFFFFF);
 
			if (radius != 0)
				this.graphics.drawCircle(0, 0, radius);
			else
				this.graphics.drawRect(0, 0, 15, 30);
 
			this.graphics.endFill();
		}
	}
}

That’s it for the first method, the most obvious.

Only one complex Box2D and art classes

For the second method, like the mill’s arms we create several Box2D bodies in one Box2DPhysicsObject class.
The GameState :

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
package complexbox2dobject{
 
	import com.citrusengine.core.State;
	import com.citrusengine.objects.platformer.box2d.Platform;
	import com.citrusengine.physics.box2d.Box2D;
 
	/**
	 * @author Aymeric
	 */
	public class ComplexBox2DObjectGameState extends State {
 
		public function ComplexBox2DObjectGameState() {
			super();
		}
 
		override public function initialize():void {
 
			super.initialize();
 
			var box2D:Box2D = new Box2D("box2D");
			box2D.visible = true;
			add(box2D);
 
			var platTop:Platform = new Platform("platTop", {x:stage.stageWidth / 2, width:stage.stageWidth, y:20});
			add(platTop);
 
			var ropeChain:RopeChain = new RopeChain("ropeChain", {hangTo:platTop, radius:15, y:150, x:30, view:new RopeChainGraphics(), registration:"topLeft"});
			add(ropeChain);
		}
 
	}
}

The object created there is basically a circle hang to the platform top with several chains.
The RopeChain class :

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
package complexbox2dobject{
 
	import Box2D.Collision.Shapes.b2PolygonShape;
	import Box2D.Collision.Shapes.b2Shape;
	import Box2D.Common.Math.b2Vec2;
	import Box2D.Dynamics.Joints.b2RevoluteJointDef;
	import Box2D.Dynamics.b2Body;
	import Box2D.Dynamics.b2BodyDef;
	import Box2D.Dynamics.b2FixtureDef;
 
	import com.citrusengine.objects.Box2DPhysicsObject;
	import com.citrusengine.physics.PhysicsCollisionCategories;
 
	import flash.geom.Point;
 
	/**
	 * @author Aymeric
	 */
	public class RopeChain extends Box2DPhysicsObject {
 
		public var hangTo:Box2DPhysicsObject;
 
		public var numChain:uint = 5;
		public var widthChain:uint = 15;
		public var heightChain:uint = 30;
		public var attach:Point = new Point(275, 60);
		public var distance:uint = 32;
 
		private var _vecBodyDefChain:Vector.<b2BodyDef>;
		private var _vecBodyChain:Vector.<b2Body>;
		private var _vecFixtureDefChain:Vector.<b2FixtureDef>;
		private var _vecRevoluteJointDef:Vector.<b2RevoluteJointDef>;
		private var _shapeChain:b2Shape;
 
		public function RopeChain(name:String, params:Object = null) {
			super(name, params);
		}
 
		override public function initialize(poolObjectParams:Object = null):void {
			super.initialize(poolObjectParams);
 
			if (view)
				(view as RopeChainGraphics).init(numChain, widthChain, heightChain);
		}
 
		override public function update(timeDelta:Number):void {
			super.update(timeDelta);
 
			if (view)
				(view as RopeChainGraphics).update(_vecBodyChain, _box2D.scale);
		}
 
		override protected function defineBody():void {
			super.defineBody();
 
			_vecBodyDefChain = new Vector.<b2BodyDef>();
			var bodyDefChain:b2BodyDef;
 
			for (var i:uint = 0; i < numChain; ++i) {
 
				bodyDefChain = new b2BodyDef();
				bodyDefChain = new b2BodyDef();
				bodyDefChain.type = b2Body.b2_dynamicBody;
				bodyDefChain.position = new b2Vec2(attach.x / _box2D.scale, (attach.y + i * distance) / _box2D.scale);
				bodyDefChain.angle = _rotation;
 
				_vecBodyDefChain.push(bodyDefChain);
			}
		}
 
		override protected function createBody():void {
			super.createBody();
 
			_vecBodyChain = new Vector.<b2Body>();
			var bodyChain:b2Body;
 
			for each (var bodyDefChain:b2BodyDef in _vecBodyDefChain) {
 
				bodyChain = _box2D.world.CreateBody(bodyDefChain);
				bodyChain.SetUserData(this);
 
				_vecBodyChain.push(bodyChain);
			}
 
		}
 
		override protected function createShape():void {
			super.createShape();
 
			_shapeChain = new b2PolygonShape();
			b2PolygonShape(_shapeChain).SetAsBox(widthChain / 2 / _box2D.scale, heightChain / 2 / _box2D.scale);
		}
 
		override protected function defineFixture():void {
			super.defineFixture();
 
			_vecFixtureDefChain = new Vector.<b2FixtureDef>();
			var fixtureDefChain:b2FixtureDef;
 
			for (var i:uint = 0; i < numChain; ++i) {
 
				fixtureDefChain = new b2FixtureDef();
				fixtureDefChain.shape = _shapeChain;
				fixtureDefChain.density = 1;
				fixtureDefChain.friction = 0.6;
				fixtureDefChain.restitution = 0.3;
				fixtureDefChain.filter.categoryBits = PhysicsCollisionCategories.Get("Level");
				fixtureDefChain.filter.maskBits = PhysicsCollisionCategories.GetAll();
 
				_vecFixtureDefChain.push(fixtureDefChain);
			}
		}
 
		override protected function createFixture():void {
			super.createFixture();
 
			var i:uint = 0;
 
			for each (var fixtureDefChain:b2FixtureDef in _vecFixtureDefChain) {
				_vecBodyChain[i].CreateFixture(fixtureDefChain);
				++i;
			}
 
		}
 
		override protected function defineJoint():void {
 
			_vecRevoluteJointDef = new Vector.<b2RevoluteJointDef>();
			var revoluteJointDef:b2RevoluteJointDef;
 
			for (var i:uint = 0; i < numChain; ++i) {
 
				revoluteJointDef = new b2RevoluteJointDef();
 
				if (i == 0)
					revoluteJointDef.Initialize(hangTo.body, _vecBodyChain[i], new b2Vec2(attach.x / _box2D.scale, (attach.y + i * distance) / _box2D.scale));
				else
					revoluteJointDef.Initialize(_vecBodyChain[i - 1], _vecBodyChain[i], new b2Vec2(attach.x / _box2D.scale, (attach.y + i * distance) / _box2D.scale));
 
				_vecRevoluteJointDef.push(revoluteJointDef);
			}
 
 
			revoluteJointDef = new b2RevoluteJointDef();
			revoluteJointDef.localAnchorA.Set(0, distance / _box2D.scale);
			revoluteJointDef.localAnchorB.Set(0, 0);
			revoluteJointDef.bodyA = _vecBodyChain[numChain - 1];
			revoluteJointDef.bodyB = _body;
 
			_vecRevoluteJointDef.push(revoluteJointDef);
		}
 
		override protected function createJoint():void {
 
			for each (var revoluteJointDef:b2RevoluteJointDef in _vecRevoluteJointDef) {
				_box2D.world.CreateJoint(revoluteJointDef);
			}
		}
 
	}
}

We create Vectors with all our new chains’ bodies.

And finally the most complex part on this second way, the graphic class :

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
package complexbox2dobject{
 
	import Box2D.Dynamics.b2Body;
 
	import com.citrusengine.math.MathUtils;
 
	import flash.display.Sprite;
	import flash.geom.Point;
 
	/**
	 * @author Aymeric
	 */
	public class RopeChainGraphics extends Sprite {
 
		private var _numChain:uint;
		private var _vecSprites:Vector.<Sprite>;
		private var _width:uint;
		private var _height:uint;
 
		public function RopeChainGraphics() {
 
			this.graphics.beginFill(0xFF0000);
			this.graphics.drawCircle(0, 0, 15);
			this.graphics.endFill();
		}
 
		public function init(numChain:uint, width:uint, height:uint):void {
 
			_numChain = numChain;
			_width = width;
			_height = height;
 
			_vecSprites = new Vector.<Sprite>();
			var sprite:Sprite;
 
			for (var i:uint = 0; i < _numChain; ++i) {
 
				sprite = new Sprite();
				sprite.graphics.beginFill(Math.random() * 0xFFFFFF);
				sprite.graphics.drawRect(0, 0, _width, _height);
				sprite.graphics.endFill();
 
				addChild(sprite);
				_vecSprites.push(sprite);
			}
		}
 
		public function update(vecBodyChain:Vector.<b2Body>, box2DScale:Number):void {
 
			var i:uint = 0;
 
			for each (var body:b2Body in vecBodyChain) {
 
				_vecSprites[i].x = body.GetPosition().x * box2DScale - this.parent.x - _width * 0.5;
				_vecSprites[i].y = body.GetPosition().y * box2DScale - this.parent.y - _height * 0.5;
 
				MathUtils.RotateAroundExternalPoint(_vecSprites[i], new Point(_width * 0.5 + _vecSprites[i].x, _height * 0.5 + _vecSprites[i].y), body.GetAngle() * 180 / Math.PI - _vecSprites[i].rotation - this.parent.rotation);
 
				++i;
			}
		}
	}
}

Since chains’ graphics aren’t managed by SpriteView and the SpriteArt (only the circle), we have to update them manually! Note that there is a little bug on the rotation, I’m missing something here but can’t find the issue… Damn math knowledge :D Anyway it shouldn’t prevent you to use this method!

Moved from Box2D Alchemy to Box2D AS3

2

In the Citrus Engine, people knows that I’m very attached to the Box2D Alchemy version. Thanks to Alchemy we should have better performance than the pure AS3 version. Also this Alchemy version had some cool features mostly on the way you could manage collision : you don’t use a gobal contact listener like in any Box2D version, you use something very closer to AS3 event management. Example :

1
2
3
4
5
6
7
8
9
10
11
12
13
14
override protected function createFixture():void
{
	super.createFixture();
 
	_fixture.m_reportBeginContact = true;
	_fixture.addEventListener(ContactEvent.BEGIN_CONTACT, handleBeginContact);
}
 
protected function handleBeginContact(e:ContactEvent):void
{
	_contact = e.other.GetBody().GetUserData() as Box2DPhysicsObject;
	if (!e.other.IsSensor())
		explode();
}

I love this way. Anyway, many users ask me to change it… let’s go!

Box2D AS3
This is the contact listener class :

package com.citrusengine.physics.box2d {
 
	import Box2D.Dynamics.b2ContactImpulse;
	import Box2D.Collision.b2Manifold;
	import Box2D.Dynamics.Contacts.b2Contact;
	import Box2D.Dynamics.b2ContactListener;
 
	/**
	 * Used to report the contact's interaction between object.
	 */
	public class Box2DContactListener extends b2ContactListener {
 
		public function Box2DContactListener() {
		}
 
		override public function BeginContact(contact:b2Contact):void {
 
			contact.GetFixtureA().GetBody().GetUserData().handleBeginContact(contact);
			contact.GetFixtureB().GetBody().GetUserData().handleBeginContact(contact);
		}
 
		override public function EndContact(contact:b2Contact):void {
 
			contact.GetFixtureA().GetBody().GetUserData().handleEndContact(contact);
			contact.GetFixtureB().GetBody().GetUserData().handleEndContact(contact);
		}
 
		override public function PreSolve(contact:b2Contact, oldManifold:b2Manifold):void {
 
			contact.GetFixtureA().GetBody().GetUserData().handlePreSolve(contact, oldManifold);
			contact.GetFixtureB().GetBody().GetUserData().handlePreSolve(contact, oldManifold);
		}
 
		override public function PostSolve(contact:b2Contact, impulse:b2ContactImpulse):void {
 
			contact.GetFixtureA().GetBody().GetUserData().handlePostSolve(contact, impulse);
			contact.GetFixtureB().GetBody().GetUserData().handlePostSolve(contact, impulse);
		}
 
	}
}

And then I override this function handleBeginContact, handleEndContact, handlePreSolve, handlePostSolve with what I need. And I use two very useful function to work the same way that I used to do with the Alchemy version :

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
/**
 * In Box2D we are blind concerning the collision, we are never sure which body is the collider. This function should help.
 * Call this function to obtain the colliding physics object.
 * @param self : in CE's code, we give this. In your code it will be your hero, a sensor, ...
 * @param the contact
 * @return the collider
 */
static public function CollisionGetOther(self:Box2DPhysicsObject, contact:b2Contact):Box2DPhysicsObject {
	return self == contact.GetFixtureA().GetBody().GetUserData() ? contact.GetFixtureB().GetBody().GetUserData() : contact.GetFixtureA().GetBody().GetUserData();
}
 
/**
 * In Box2D we are blind concerning the collision, we are never sure which body is the collider. This function should help.
 * Call this function to obtain the collided physics object.
 * @param self : in CE's code, we give this. In your code it will be your hero, a sensor, ...
 * @param the contact
 * @return the collided
 */
static public function CollisionGetSelf(self:Box2DPhysicsObject, contact:b2Contact):Box2DPhysicsObject {
	return self == contact.GetFixtureA().GetBody().GetUserData() ? contact.GetFixtureA().GetBody().GetUserData() : contact.GetFixtureB().GetBody().GetUserData();
}

And finally an example how it is used :

1
2
3
4
5
6
7
8
9
10
11
12
13
override public function handlePreSolve(contact:b2Contact, oldManifold:b2Manifold):void {
 
	if (!_ducking)
		return;
 
	var other:Box2DPhysicsObject = Box2DPhysicsObject.CollisionGetOther(this, contact);
 
	var heroTop:Number = y;
	var objectBottom:Number = other.y + (other.height / 2);
 
	if (objectBottom < heroTop)
		contact.SetEnabled(false);
}

Pretty cool, isn’t it? I know it will be a problem to upgrade on this last version, but it will not be as hard as it sounds since 99% of the code are similar. Just take a look on this new way to handle events.

Performance
When I made the transition, I told myself : “We shouldn’t see a big difference on a browser version, and if it sucks more than Alchemy on mobile we don’t care since there is Nape”! So I made a test with my quick demo : Live4Sales game. And this is the result on a iPad3 :

Compare now to the Box2D Alchemy & Nape versions : in this previous stress test. Holy s***! Box2D AS3 is very quicker than Alchemy version, really close to Nape in fact. So much close that we should make other tests with different mobiles and other type of games. Nape is always faster, but the difference is small. No more tax, better performances : definitely a good move!
All the demos have been updated on the GitHub.

Version 3?
We’ve never been so close to the final release, it should be in November. I would like to emphasize two points :
- the 3D part (handled with Away3D & AwayPhysics) is a sandbox for me. I never pretend to offer the best 3D game engine like you can have with Unity3D, how could I compare with them?, but just a good engine where you could create quiclky some 3d physics with a character, sensors… It could be used for simple games or for interactive websites. This 3D part will evolve along the Citrus Engine V3.
- the entity/component system is not bad, but not as useful as I would. Since some time, it is in stand by. It is not the main features wished by members, so I will refocus on it after. At the moment, use it at your own risk!

The Citrus Engine meets Away3D

16

Some days ago, I was thinking that my next post would be the annoucement of the Citrus Engine V3 stable release. Since Starling and Nape are well integrated in the engine and the API very stable, I didn’t see any new big features coming. Oh in fact, I imagined one : 3D support. This one was crazy, and now it is real! Welcome to the first 2D & 3D Flash game engine!

I’m not familiar with 3D. Last year I had a quick look with ThreeJS and then ported it in Away3D. That was my only experience with 3D until now, so don’t hesitate to correct me and to offer some suggestions. That’s lots of new stuff to learn, and that’s pretty exciting!
Also if you have links to free arts/assets Away3D friendly, share please :)

For the impatients, here is the demo! Note the Away3D support needs lots of work yet. Click on the red square to add boxes (I was a bit lazy to make a correct button). Use the mouse to move the camera, and right/left/down/space keys for the Hero.
I use Box2D for the physics (it could be Nape), to see the Box2D debug view press tab to open the console and write :

set box2D visible true

All the source code is available on GitHub (with samples) and on GoogleCode (engine code only). The Away3D part will evolve a lot in the next month, the 2D stuff is very stable to use!

The case study : Trine & Rayman
Trine 2 is with Rayman Origins one of my favorite platformer game. Both have different styles & gameplay :


Even if Trine is a 2D platformer game, it uses 3D arts whereas Rayman doesn’t. The camera has nice movement which highlight the 3D assets even if it never flips on the other side. 3D arts with 2D physics / logics works like a charm, it was something to add to the Citrus Engine.

Why Away3D?
I’ve chosen Away3D since like Starling it is free, open source and strongly supported by Adobe! Note that it’s very easy to add an other 3D engine.

Handle 3D view in 2D physics world
Since Away3D doesn’t use the same coordinates (middle of the screen) than Flash (top left of the screen) it is not evident to put the view at the good object position. At first I was fighting with project and unproject method using an OrthographicLens camera but that was a nightmare. I needed some help.
I discovered Roger Engelbert’s blog some months ago. There are very good tutorials moving from one framework to an other one in an other language : Objective-C, Java, AS3. It’s definetly one blog to bookmark for developers (and artists now)! Roger has made several game prototypes using a 3D view with a 2D logic, like this one. So I asked him, what was the best way to handle a 3D view in a 2D games? He answered me with a new tutorial : 3D Coordinates from 2D Simulation. That was very kind and the article is very smart. No more project unproject method! Thank you Roger!

The demo
Please refer to the top of the article for the demo and instructions. The Perelith Knight asset come from this awesome example from Away3D’s hero Rob Bateman : Away3D multi-knight demo. I used Misfit Model 3D software to manage MD2 files changing animations name and moving a bit the jump animation.

The code
Main file

package away3dbox2d {
 
	import com.citrusengine.core.CitrusEngine;
 
	[SWF(frameRate="60")]
 
	/**
	* @author Aymeric
	*/
	public class Main extends CitrusEngine {
 
		public function Main() {
 
			state = new Away3DGameState();
		}
	}
}

The Game State:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
package away3dbox2d {
 
	import away3d.controllers.HoverController;
	import away3d.debug.AwayStats;
	import away3d.entities.Mesh;
	import away3d.library.AssetLibrary;
	import away3d.loaders.parsers.MD2Parser;
	import away3d.materials.ColorMaterial;
	import away3d.primitives.CubeGeometry;
	import away3d.primitives.SphereGeometry;
 
	import com.citrusengine.core.State;
	import com.citrusengine.math.MathVector;
	import com.citrusengine.objects.Box2DPhysicsObject;
	import com.citrusengine.objects.CitrusSprite;
	import com.citrusengine.objects.platformer.box2d.Coin;
	import com.citrusengine.objects.platformer.box2d.Hero;
	import com.citrusengine.objects.platformer.box2d.Platform;
	import com.citrusengine.physics.Box2D;
	import com.citrusengine.view.CitrusView;
	import com.citrusengine.view.away3dview.AnimationSequence;
	import com.citrusengine.view.away3dview.Away3DArt;
	import com.citrusengine.view.away3dview.Away3DView;
 
	import flash.display.Sprite;
	import flash.events.Event;
	import flash.events.MouseEvent;
	import flash.geom.Rectangle;
	import flash.geom.Vector3D;
 
	/**
	 * @author Aymeric
	 */
	public class Away3DGameState extends State {
 
		[Embed(source="/../embed/pknight/pknight3.png")]
		public static var PKnightTexture3:Class;
 
		[Embed(source="/../embed/pknight/pknight.md2", mimeType="application/octet-stream")]
		public static var PKnightModel:Class;
 
		// navigation variables
		private var _cameraController:HoverController;
 
		private var _move:Boolean = false;
		private var _lastPanAngle:Number;
		private var _lastTiltAngle:Number;
		private var _lastMouseX:Number;
		private var _lastMouseY:Number;
		private var _lookAtPosition:Vector3D = new Vector3D();
 
		private var _heroArt:AnimationSequence;
		private var _hero:Hero;
 
		private var _clickMe:Sprite;
 
		public function Away3DGameState() {
			super();
		}
 
		override public function initialize():void {
 
			super.initialize();
 
			addChild(new AwayStats((view as Away3DView).viewRoot));
 
			var box2D:Box2D = new Box2D("box2D");
			// box2D.visible = true;
			add(box2D);
 
			AssetLibrary.enableParser(MD2Parser);
			_heroArt = new AnimationSequence(new PKnightModel(), new PKnightTexture3());
			_heroArt.scale(2);
 
			var cube1:Mesh = new Mesh(new CubeGeometry(300, 300, 0), new ColorMaterial(0x0000FF));
			var cube2:Mesh = new Mesh(new SphereGeometry(15), new ColorMaterial(0xFFFF00));
			var cube3:Mesh = new Mesh(new CubeGeometry(2500, 10, 300), new ColorMaterial(0xFFFFFF));
 
			var cloud:CitrusSprite = new CitrusSprite("cloud", {x:150, y:50, width:300, height:300, view:cube1, parallax:0.3});
			add(cloud);
			(view.getArt(cloud) as Away3DArt).z = 300;
			// equivalent to -> cube1.z = 300;
 
			add(new Platform("platformBottom", {x:stage.stageWidth / 2, y:stage.stageHeight - 30, width:2500, height:10, view:cube3}));
 
			_hero = new Hero("hero", {x:150, y:50, width:80, height:90, view:_heroArt});
			add(_hero);
 
			var coin:Coin = new Coin("coin", {x:300, y:200, width:30, height:30, view:cube2});
			add(coin);
 
			view.setupCamera(_hero, new MathVector(320, 240), new Rectangle(0, 0, 1550, 450), new MathVector(.25, .05));
			_cameraController = new HoverController((view as Away3DView).viewRoot.camera, null, 175, 20, 500);
 
			_clickMe = new Sprite();
			_clickMe.y = stage.stageHeight - 50;
			addChild(_clickMe);
			_clickMe.graphics.beginFill(0xFF0000);
			_clickMe.graphics.drawRect(10, 0, 40, 40);
			_clickMe.graphics.endFill();
			_clickMe.buttonMode = true;
 
			stage.addEventListener(MouseEvent.MOUSE_DOWN, _onMouseDown);
			stage.addEventListener(MouseEvent.MOUSE_UP, _onMouseUp);
			stage.addEventListener(Event.MOUSE_LEAVE, _onMouseUp);
			stage.addEventListener(MouseEvent.MOUSE_WHEEL, _onMouseWheel);
 
			_clickMe.addEventListener(MouseEvent.CLICK, _addBoxes);
		}
 
		// Make sure and call this override to specify Away3D view.
		override protected function createView():CitrusView {
 
			return new Away3DView(this, "2D");
		}
 
		override public function destroy():void {
 
			stage.removeEventListener(MouseEvent.MOUSE_DOWN, _onMouseDown);
			stage.removeEventListener(MouseEvent.MOUSE_UP, _onMouseUp);
			stage.removeEventListener(Event.MOUSE_LEAVE, _onMouseUp);
			stage.removeEventListener(MouseEvent.MOUSE_WHEEL, _onMouseWheel);
 
			_clickMe.addEventListener(MouseEvent.CLICK, _addBoxes);
			removeChild(_clickMe);
 
			super.destroy();
		}
 
		override public function update(timeDelta:Number):void {
 
			super.update(timeDelta);
 
			if (_move) {
				_cameraController.panAngle = 0.3 * (stage.mouseX - _lastMouseX) + _lastPanAngle;
				_cameraController.tiltAngle = 0.3 * (stage.mouseY - _lastMouseY) + _lastTiltAngle;
			}
 
			_cameraController.lookAtPosition = _lookAtPosition;
		}
 
		private function _addBoxes(mEvt:MouseEvent):void {
			add(new Box2DPhysicsObject("box" + new Date().time, {x:Math.random() * stage.stageWidth, view:new Mesh(new CubeGeometry(30, 30, 30), new ColorMaterial(Math.random() * 0xFFFFFF))}));
		}
 
		private function _onMouseDown(mEvt:MouseEvent):void {
			_lastPanAngle = _cameraController.panAngle;
			_lastTiltAngle = _cameraController.tiltAngle;
			_lastMouseX = stage.mouseX;
			_lastMouseY = stage.mouseY;
			_move = true;
		}
 
		private function _onMouseUp(evt:Event):void {
			_move = false;
		}
 
		private function _onMouseWheel(mEvt:MouseEvent):void {
 
			_cameraController.distance -= mEvt.delta * 5;
 
			if (_cameraController.distance < 100)
				_cameraController.distance = 100;
			else if (_cameraController.distance > 2000)
				_cameraController.distance = 2000;
		}
 
	}
}

The code of the demo is very easy to understand and doesn’t request strong Away3D knowledge!

Away3D is added in the Away3DView class. The Away3DArt handles the different assets. The AnimationSequence class provides animation for models.
Work is in progress on this 3 classes, there are lots of option to try and find the best!

Full 3D engine?
When you create the Away3D view, we give as an argument the mode : 2D or 3D. Certainly there will be an Away3DArt2D and an Away3DArt3D classes to facilitate work on coordinates. From this point, nothing can prevent to use the Citrus Engine as a 3D engine! On my to-do list, there is the support for a 3D physics engine library (I don’t know which one at the moment)! Nape was added easily in the engine, an other physics libraries will be too.

Too many libraries included?
- having lots of libraries included in the project which are never clearly used don’t have any impact on performance! It just has an impact on the final file size.
- from what I know, most developers never upgrade libraries used when they are working on a project unless there is a new feature needed or bug fixed. So it’s very easy & quick to remove libs you don’t use in the engine, just follow the compiler’s indications. It takes less than 2 minutes!
- giving people choices : thanks to the Citrus Engine’s architecture it is very easy to customize the framework adding new views or physics libraries. Want to use Starling + Nape for your mobile project? Want to use Box2D + Away3D for a browser game? The engine is already ready for that! It’s something important for me.

Future
From 29 September to 8 October, I will be in vacation. After I will focus on the Away3D support and offer a beta 3, the last one. If a 3D physics library is added, that will be for the V3.5 ;)
Also I’m searching a software which can be used as a 3D Level Editor (like Flash Pro, Gleed or Tiled Map Editor used for 2D). Maybe Prefab will do the job!

That’s it for this blog post! Don’t hesitate to comment, give your opinions and advice. Feel free to contribute!
Cheers!

Box2D Alchemy VS Nape, performance test on iPad3

5

Edit: The Box2D AS3 pure version is quicker than Box2D Alchemy! Take a look there

Hey folks! Today I’m glad to share an important performance test for AIR mobile developers which want to use a physics engine in their games and applications.
The Box2D Alchemy version is used rather than the “simple” Box2D because it has better performance. Test on Allan Bishop’s blog.
I’ve already made a quick performance test comparing Box2D Alchemy & Nape concluding that with Nape you could create 60% more objects than Box2D and have the same FPS!

That was a simple test without different objects behaviors. So now it’s time to test in the small project game : Live4Sales, made with the Citrus Engine using Starling & AIR 3.4. In a previous post there were already project/code explanations and good practice with Box2D. I will not make code comparison with Nape since all the source are available on the CE’s GitHub.

To compile for one or the other engine just change one line in the Main :

// select Box2D Alchemy or Nape demo
state = new NapeLive4Sales();
//state = new Box2DLive4Sales();

And it will works! Most classes are common, just physics one changed. With the CE version on the repository, Nape use the same coordinates system than Box2D. It has never be so easy to switch! I’ve the same physics behaviors for Box2D & Nape in the game apart I didn’t success to cancel easily forces after collision with Nape.

The game videos :

The test is categoric, yep, Nape is blazing fast!! The Citrus Engine made a really good move adding Nape support!

In conlusion, if you want to target Web only? It doesn’t matter, both are very performant on a computer. Since the API are very differents select the one you prefer! You will find more code example with Box2D than Nape, however Nape is easier to handle. Also you have to know that Box2D Alchemy involves Adobe “Tax” if you use Starling too whereas Nape doesn’t (with the Nape’s debug view I’ve chosen). Don’t forget, if you want Adobe keeps its good work on Flash you’ve to support them!
Perhaps your game will be a mobile game? Go Nape, directly!
We are closed to the Citrus Engine V3 BETA3 which will be the last one!

Finally I’ve quickly added Nape to the CitruxEngine (Haxe NME port). It was so easy to add it from the AS3 version. This port is in stand-by at the moment : I still haven’t found an easy way to display a SpriteSheet with NME – HTML5. I’m upset!

Live4Sales, a Plant vs Zombies clone

19

Edit : Nape version

One week after the Osmos demo, I offer a new game made with the Citrus Engine based on an other popular game : Plant vs. Zombies!

If you have never played this game, try it now!

This is my clone made with the CE working as a browser game, air application, and on mobile devices :
The web demo. I didn’t make a preloader, to have the same source code between each version.
There is no end in the game to be used as a stress test!
Sources are available on the CE’s GitHub.


The story part
I was in Paris last weekend with Pierrick. We planned to make the Ludum Dare contest. The theme was evolution. We aren’t graphics designer, it’s (always) a problem. So we used graphics from a previous Pierrick’s project, the graphic designer was Lou Drouet. We thought to evolve ours shoppers into zombies to criticize brands addiction. Salers would throw later smartphones, etc… Nothing original, but funny. We didn’t finish the game because on Saturday’s night, my macbook pro died. It was baptised by beer and didn’t survive. After 4-5 years of excellent services, it was the best end that could happen to it : death on the battlefield! R.I.P. Since I need a laptop, I’ve bought a cheap Acer which do the job, I planned to buy an iMac later. No more Objective-C & Haxe NME for mobile at the moment. That’s it for my life part.

The case study
Plant vs Zombies is an interesting programming lesson since there are different ways to approach it. Some years ago
Emanuele Feronato has made several tutorials to develop this game. I really enjoy informations on his blog but don’t like his way of programming, not enough OOP. Anyway, I invite you to read his blog.

To develop Live4Sales, I used as usual the Citrus Engine. To detect collision I used Box2D physics engine ; Nape would work better but I wanted to test Box2D’s performances on mobile! Also you would probably not use a physics engine at all to make only simple collision detection. And finally I used Starling for the view renderer.

The physics engine is very useful for collision detection, and don’t forget : graphics always lie! The physic body is smaller than the graphics so we don’t have to manage/ignore layers superposition. Even if we use a physics engine, we need a grid to prevent two objects on the same case and to know if baddies are on a line. So basically we use 2 array.

Since there are lots of codes and class, I wouldn’t explain them all. We will focus on the game loop, a part of the hud (the drag & drop), the IA class and finally Box2D’s objects classes with some simple optimizations you have to take care!

Our GameState :

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
package games.live4sales {
 
	import Box2DAS.Common.V2;
 
	import games.live4sales.assets.Assets;
	import games.live4sales.characters.SalesWoman;
	import games.live4sales.events.MoneyEvent;
	import games.live4sales.objects.Block;
	import games.live4sales.objects.Cash;
	import games.live4sales.runtime.BaddiesCreation;
	import games.live4sales.runtime.CoinsCreation;
	import games.live4sales.ui.Hud;
	import games.live4sales.utils.Grid;
 
	import starling.core.Starling;
	import starling.display.Image;
 
	import com.citrusengine.core.CitrusEngine;
	import com.citrusengine.core.StarlingState;
	import com.citrusengine.objects.CitrusSprite;
	import com.citrusengine.physics.Box2D;
	import com.citrusengine.view.starlingview.AnimationSequence;
	import com.citrusengine.view.starlingview.StarlingArt;
 
	/**
	 * @author Aymeric
	 */
	public class Live4Sales extends StarlingState {
 
		private var _ce:CitrusEngine;
		private var _hud:Hud;
		private var _coinsCreation:CoinsCreation;
		private var _baddiesCreation:BaddiesCreation;
 
		public function Live4Sales() {
 
			super();
 
			_ce = CitrusEngine.getInstance();
		}
 
		override public function initialize():void {
 
			super.initialize();
 
			Assets.contentScaleFactor = Starling.current.contentScaleFactor;
 
			var box2D:Box2D = new Box2D("box2D", {gravity:new V2()});
			//box2D.visible = true;
			add(box2D);
 
			_hud = new Hud();
			addChild(_hud);
			_hud.onIconePositioned.add(_createObject);
 
			_coinsCreation = new CoinsCreation();
			addChild(_coinsCreation);
 
			StarlingArt.setLoopAnimations(["stand", "fire", "attack", "walk"]);
 
			var background:CitrusSprite = new CitrusSprite("background", {view:Image.fromBitmap(new Assets.BackgroundPng())});
			add(background);
 
			_baddiesCreation = new BaddiesCreation();
		}
 
		private function _createObject(name:String, posX:uint, posY:uint):void {
 
			if (Hud.money >= 50) {
 
				var casePositions:Array = Grid.getCaseCenter(posX, posY);
 
				if (casePositions[0] != 0 && casePositions[1] != 0) {
 
					if (name == "SalesWoman") {
 
						var saleswomanAnim:AnimationSequence = new AnimationSequence(Assets.getTextureAtlas("Objects"), ["fire", "stand"], "fire", 30, true);
						var saleswoman:SalesWoman = new SalesWoman("saleswoman", {x:casePositions[0], y:casePositions[1], group:casePositions[2], offsetY:-saleswomanAnim.height * 0.3, fireRate:1000, missileExplodeDuration:0, missileFuseDuration:3000, view:saleswomanAnim});
						add(saleswoman);
 
					} else if (name == "Block") {
 
						var blockAnimation:AnimationSequence = new AnimationSequence(Assets.getTextureAtlas("Objects"), ["block1", "block2", "block3", "blockDestroyed"], "block1");
						var block:Block = new Block("block", {x:casePositions[0], y:casePositions[1], group:casePositions[2], offsetY:-15, view:blockAnimation});
						add(block);
 
					} else if (name == "Cash") {
 
						var cash:Cash = new Cash("cash", {x:casePositions[0], y:casePositions[1], group:casePositions[2], offsetY:-15, view:new Image(Assets.getAtlasTexture("cash", "Objects"))});
						add(cash);
					}
 
					_ce.dispatchEvent(new MoneyEvent(MoneyEvent.BUY_ITEM));
 
				} else trace('case not empty');
 
			} else trace('no money');
		}
	}
}

We create the Hud, the CoinsCreation & BaddiesCreation classes to create coins/baddies at runtime and we will create there our objects if we have money and if the case is empty.

Two importants CE’s properties
- the group property is used as a z-index sorter.
- thanks to the offsetX & offsetY properties even if your graphics is registred at the center point, you can move around!

The Hud :

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
package games.live4sales.ui {
 
	import games.live4sales.events.MoneyEvent;
	import games.live4sales.assets.Assets;
	import games.live4sales.utils.Grid;
 
	import starling.display.Image;
	import starling.display.Sprite;
	import starling.events.Event;
	import starling.text.BitmapFont;
	import starling.text.TextField;
	import starling.textures.Texture;
 
	import com.citrusengine.core.CitrusEngine;
 
	import org.osflash.signals.Signal;
 
	import flash.display.Bitmap;
 
	/**
	 * @author Aymeric
	 */
	public class Hud extends Sprite {
 
		[Embed(source="../embed/ArialFont.fnt", mimeType="application/octet-stream")] private var _fontConfig:Class;
		[Embed(source="../embed/ArialFont.png")] private var _fontPng:Class;
 
		static public var money:uint = 400;
 
		public var onIconePositioned:Signal;
 
		private var _ce:CitrusEngine;
 
		private var _grid:Grid;
		private var _vectorIcon:Vector.<Icon>;
 
		private var _backgroundMenu:Image;
		private var _iconSaleswoman:Icon;
		private var _iconCash:Icon;
		private var _iconBlock:Icon;
 
		private var _score:TextField;
 
		public function Hud() {
 
			_ce = CitrusEngine.getInstance();
 
			onIconePositioned = new Signal(String, uint, uint);
 
			_grid = new Grid();
 
			_vectorIcon = new Vector.<Icon>(3, true);
 
			_backgroundMenu = new Image(Assets.getAtlasTexture("background-menu", "Menu"));
			_iconSaleswoman = new Icon(Assets.getAtlasTexture("icon-saleswoman", "Menu"));
			_iconCash = new Icon(Assets.getAtlasTexture("icon-cash", "Menu"));
			_iconBlock = new Icon(Assets.getAtlasTexture("icon-block", "Menu"));
 
			_vectorIcon[0] = _iconSaleswoman;
			_vectorIcon[1] = _iconCash;
			_vectorIcon[2] = _iconBlock;
 
			var bitmap:Bitmap = new _fontPng();
			var texture:Texture = Texture.fromBitmap(bitmap);
			var xml:XML = XML(new _fontConfig());
			TextField.registerBitmapFont(new BitmapFont(texture, xml));
			_score = new TextField(50, 20, "0", "ArialMT");
 
			addEventListener(Event.ADDED_TO_STAGE, _addedToStage);
		}
 
		public function destroy():void {
 
			onIconePositioned.removeAll();
 
			removeChild(_grid);
 
			for each (var icon:Icon in _vectorIcon) {
				icon.destroy();
				removeChild(icon);
			}
 
			_vectorIcon = null;
 
			TextField.unregisterBitmapFont("ArialMT");
			removeChild(_score);
 
			_ce.removeEventListener(MoneyEvent.BUY_ITEM, _changeMoney);
		}
 
		private function _addedToStage(evt:Event):void {
 
			removeEventListener(Event.ADDED_TO_STAGE, _addedToStage);
 
			addChild(_grid);
			_grid.visible = false;
 
			addChild(_backgroundMenu);
			_backgroundMenu.x = (480 - _backgroundMenu.width) / 2;
 
			addChild(_iconSaleswoman);
			_iconSaleswoman.name = "SalesWoman";
			_iconSaleswoman.x = (480 - _iconSaleswoman.width) / 2 - 30;
 
			addChild(_iconCash);
			_iconCash.name = "Cash";
			_iconCash.x = (480 - _iconCash.width) / 2 + 20;
 
			addChild(_iconBlock);
			_iconBlock.name = "Block";
			_iconBlock.x = (480 - _iconBlock.width) / 2 + 70;
 
			for each (var icon:Icon in _vectorIcon) {
				icon.onStartDrag.add(_showGrid);
				icon.onStopDrag.add(_hideGridAndCreateObject);
			}
 
			_score.x = 150;
			_score.y = 3;
			addChild(_score);
			_score.text = String(money);
 
			_ce.addEventListener(MoneyEvent.BUY_ITEM, _changeMoney);
			_ce.addEventListener(MoneyEvent.PICKUP_MONEY, _changeMoney);
		}
 
		private function _changeMoney(mEvt:MoneyEvent):void {
 
			if (mEvt.type == "BUY_ITEM")
				money -= 50;
			else if (mEvt.type == "PICKUP_MONEY")
				money += 50;
 
			_score.text = String(money);
		}
 
		private function _showGrid():void {
			_grid.visible = true;
		}
 
		private function _hideGridAndCreateObject(name:String, posX:uint, posY:uint):void {
			_grid.visible = false;
			onIconePositioned.dispatch(name, posX, posY);
		}
	}
}

The Icon :

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
package games.live4sales.ui {
 
	import starling.display.Image;
	import starling.events.Touch;
	import starling.events.TouchEvent;
	import starling.events.TouchPhase;
	import starling.textures.Texture;
 
	import org.osflash.signals.Signal;
 
	/**
	 * @author Aymeric
	 */
	public class Icon extends Image {
 
		public var onStartDrag:Signal;
		public var onStopDrag:Signal;
 
		private var _dragging:Boolean = false;
 
		private var _posX:uint, _posY:uint;
 
		public function Icon(texture:Texture) {
 
			super(texture);
 
			onStartDrag = new Signal();
			onStopDrag = new Signal(String, uint, uint);
 
			addEventListener(TouchEvent.TOUCH, _iconTouched);
		}
 
		public function destroy():void {
 
			removeEventListener(TouchEvent.TOUCH, _iconTouched);
 
			onStartDrag.removeAll();
			onStopDrag.removeAll();
		}
 
		private function _iconTouched(tEvt:TouchEvent):void {
 
			var touchBegan:Touch = tEvt.getTouch(this, TouchPhase.BEGAN);
			var touchMoved:Touch = tEvt.getTouch(this, TouchPhase.MOVED);
			var touchEnded:Touch = tEvt.getTouch(this, TouchPhase.ENDED);
 
			if (touchBegan) {
 
				_posX = this.x;
				_posY = this.y;
 
				_dragging = true;
 
				onStartDrag.dispatch();
 
			} else if (touchMoved && _dragging) {
 
				this.x = touchMoved.globalX - this.width * 0.5;
				this.y = touchMoved.globalY - this.height * 0.5;
 
			} else if (touchEnded && _dragging) {
 
				_dragging = false;
 
				this.x = _posX;
				this.y = _posY;
 
				onStopDrag.dispatch(name, uint(touchEnded.globalX), uint(touchEnded.globalY));
			}
		}
	}
}

In a game like Plant vs Zombies, the difficulty is progressive, there are more and more enemies. We use a master timer which will change the enemy’s speed creation after each 10s. It will be reduced of 500ms.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
package games.live4sales.runtime {
 
	import games.live4sales.assets.Assets;
	import games.live4sales.characters.ShopsWoman;
	import games.live4sales.utils.Grid;
 
	import com.citrusengine.core.CitrusEngine;
	import com.citrusengine.view.starlingview.AnimationSequence;
 
	import flash.events.TimerEvent;
	import flash.utils.Timer;
 
	/**
	 * @author Aymeric
	 */
	public class BaddiesCreation {
 
		private var _ce:CitrusEngine;
 
		private var _timerProgression:Timer;
 
		private var _timerShopsWomen:Timer;
 
		public function BaddiesCreation() {
 
			_ce = CitrusEngine.getInstance();
 
			_timerProgression = new Timer(10000);
			_timerProgression.start();
			_timerProgression.addEventListener(TimerEvent.TIMER, _progressionDifficulty);
 
			_timerShopsWomen = new Timer(4000);
			_timerShopsWomen.start();
			_timerShopsWomen.addEventListener(TimerEvent.TIMER, _tick);
		}
 
		public function destroy():void {
 
			_timerProgression.stop();
			_timerProgression.removeEventListener(TimerEvent.TIMER, _progressionDifficulty);
 
			_timerShopsWomen.stop();
			_timerShopsWomen.removeEventListener(TimerEvent.TIMER, _tick);
		}
 
		private function _progressionDifficulty(tEvt:TimerEvent):void {
 
			_timerShopsWomen.removeEventListener(TimerEvent.TIMER, _tick);
 
			var delay:uint = _timerShopsWomen.delay - 500;
			if (delay < 500)
				delay = 500;
 
			_timerShopsWomen = new Timer(delay);
			_timerShopsWomen.start();
			_timerShopsWomen.addEventListener(TimerEvent.TIMER, _tick);
		}
 
		private function _tick(tEvt:TimerEvent):void {
 
			var casePosition:Array = Grid.getBaddyPosition(0, Grid.getRandomHeight());
 
			var shopsWomanAnim:AnimationSequence = new AnimationSequence(Assets.getTextureAtlas("Objects"), ["walk", "attack"], "walk");
			var shopswoman:ShopsWoman = new ShopsWoman("shopswoman", {x:480, y:casePosition[1], group:casePosition[2],  offsetY:-shopsWomanAnim.height * 0.3, view:shopsWomanAnim});
			_ce.state.add(shopswoman);
			shopswoman.onTouchLeftSide.add(_endGame);
 
			Grid.tabBaddies[casePosition[2]] = true;
		}
 
		private function _endGame():void {
 
			trace('game over');
		}
	}
}

Now let’s start some Box2D stuff. The Citrus Engine has already lots of Box2D objects pre-built, we will extend them.
Example the SalesWoman which is just similar to a Cannon :

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
package games.live4sales.characters {
 
	import Box2DAS.Dynamics.ContactEvent;
 
	import games.live4sales.assets.Assets;
	import games.live4sales.utils.Grid;
	import games.live4sales.weapons.Bag;
 
	import starling.display.Image;
 
	import com.citrusengine.objects.platformer.box2d.Cannon;
 
	import flash.events.TimerEvent;
	import flash.utils.Timer;
 
	/**
	 * @author Aymeric
	 */
	public class SalesWoman extends Cannon {
 
		public var life:uint = 2;
 
		private var _timerHurt:Timer;
 
		public function SalesWoman(name:String, params:Object = null) {
 
			super(name, params);
 
			_timerHurt = new Timer(1000);
			_timerHurt.addEventListener(TimerEvent.TIMER, _removeLife);
		}
 
		override public function destroy():void {
 
			_fixture.removeEventListener(ContactEvent.BEGIN_CONTACT, handleBeginContact);
			_fixture.removeEventListener(ContactEvent.END_CONTACT, handleEndContact);
 
			_timerHurt.removeEventListener(TimerEvent.TIMER, _removeLife);
			_timerHurt = null;
 
			super.destroy();
		}
 
		override public function update(timeDelta:Number):void {
 
			super.update(timeDelta);
 
			if (life == 0) {
				kill = true;
				var tab:Array = Grid.getCaseId(x, y);
 
				Grid.tabObjects[tab[1]][tab[0]] = false;
			}
 
			if (Grid.tabBaddies[group])
				_firing = true;
			else
				_firing = false;
		}
 
		override protected function createFixture():void {
 
			_fixture = _body.CreateFixture(_fixtureDef);
			_fixture.m_reportBeginContact = true;
			_fixture.m_reportEndContact = true;
			_fixture.addEventListener(ContactEvent.BEGIN_CONTACT, handleBeginContact);
			_fixture.addEventListener(ContactEvent.END_CONTACT, handleEndContact);
		}
 
		protected function handleBeginContact(cEvt:ContactEvent):void {
 
			if (cEvt.other.GetBody().GetUserData() is ShopsWoman) {
 
				if (!_timerHurt.running)
					_timerHurt.start();
			}
		}
 
		protected function handleEndContact(cEvt:ContactEvent):void {
 
			if (cEvt.other.GetBody().GetUserData() is ShopsWoman) {
 
				if (_timerHurt.running)
					_timerHurt.stop();
			}
		}
 
		private function _removeLife(tEvt:TimerEvent):void {
			life--;
		}
 
		override protected function _fire(tEvt:TimerEvent):void {
 
			if (_firing) {
 
				var missile:Bag;
 
				if (startingDirection == "right")
					missile = new Bag("Missile", {x:x + width, y:y, group:group, width:missileWidth, height:missileHeight, offsetY:-30, speed:missileSpeed, angle:missileAngle, explodeDuration:missileExplodeDuration, fuseDuration:missileFuseDuration, view:new Image(Assets.getAtlasTexture("bag", "Objects"))});
				else
					missile = new Bag("Missile", {x:x - width, y:y, group:group, width:missileWidth, height:missileHeight, offsetY:-30, speed:-missileSpeed, angle:missileAngle, explodeDuration:missileExplodeDuration, fuseDuration:missileFuseDuration, view:new Image(Assets.getAtlasTexture("bag", "Objects"))});
 
				_ce.state.add(missile);
			}
		}
 
		override protected function _updateAnimation():void {
 
			if (_firing)
				_animation = "fire";
			else
				_animation = "stand";
		}
 
	}
}

Now let’s start talking about some Box2D optimizations :
A missile should only collide with an enemy, we can do it easily :

protected function handleBeginContact(cEvt:ContactEvent):void {
 
	var other:Box2DPhysicsObject = cEvt.other.GetBody().GetUserData();
 
	if (!(other is ShopsWoman))
		cEvt.contact.Disable();
}

However disabling a contact this way is expensive if we have lots of missiles & contacts. Imagine, a missile crosses through an other SalesWoman, Cash and Block… So we use categoryBits & maskBits filters! Then they will never collide! With the Citrus Engine we can set up filters easily without bits manipulation. Also ShopsWoman shoudln’t collide between them.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
package games.live4sales.characters {
 
	import Box2DAS.Common.V2;
	import Box2DAS.Dynamics.ContactEvent;
 
	import games.live4sales.objects.Block;
	import games.live4sales.objects.Cash;
	import games.live4sales.utils.Grid;
	import games.live4sales.weapons.Bag;
 
	import com.citrusengine.objects.Box2DPhysicsObject;
	import com.citrusengine.physics.Box2DCollisionCategories;
 
	import org.osflash.signals.Signal;
 
	/**
	 * @author Aymeric
	 */
	public class ShopsWoman extends Box2DPhysicsObject {
 
		public var speed:Number = 0.7;
		public var life:uint = 4;
 
		public var onTouchLeftSide:Signal;
 
		private var _fighting:Boolean = false;
 
		public function ShopsWoman(name:String, params:Object = null) {
 
			super(name, params);
 
			onTouchLeftSide = new Signal();
		}
 
		override public function destroy():void {
 
			_fixture.removeEventListener(ContactEvent.BEGIN_CONTACT, handleBeginContact);
			_fixture.removeEventListener(ContactEvent.END_CONTACT, handleEndContact);
 
			onTouchLeftSide.removeAll();
 
			super.destroy();
		}
 
		override public function update(timeDelta:Number):void {
 
			super.update(timeDelta);
 
			if (!_fighting) {
 
				var velocity:V2 = _body.GetLinearVelocity();
 
				velocity.x = -speed;
 
				_body.SetLinearVelocity(velocity);
			}
 
			if (x < 0) {
				onTouchLeftSide.dispatch();
				kill = true;
			}
 
			if (life == 0) {
				kill = true;
				Grid.tabBaddies[group] = false;
			} else {
				Grid.tabBaddies[group] = true;
			}
 
			updateAnimation();
		}
 
		override protected function defineBody():void {
 
			super.defineBody();
 
			_bodyDef.fixedRotation = true;
		}
 
		override protected function defineFixture():void {
 
			super.defineFixture();
 
			_fixtureDef.friction = 0;
			_fixtureDef.filter.categoryBits = Box2DCollisionCategories.Get("BadGuys");
			_fixtureDef.filter.maskBits = Box2DCollisionCategories.GetAllExcept("BadGuys");
		}
 
		override protected function createFixture():void {
 
			super.createFixture();
 
			_fixture.m_reportBeginContact = true;
			_fixture.m_reportEndContact = true;
			_fixture.addEventListener(ContactEvent.BEGIN_CONTACT, handleBeginContact);
			_fixture.addEventListener(ContactEvent.END_CONTACT, handleEndContact);
		}
 
		protected function handleBeginContact(cEvt:ContactEvent):void {
 
			var other:Box2DPhysicsObject = cEvt.other.GetBody().GetUserData();
 
			if (other is SalesWoman || other is Block || other is Cash)
				_fighting = true;
 
			else if (other is Bag) {
				life--;
				cEvt.contact.Disable();
			}
		}
 
		protected function handleEndContact(cEvt:ContactEvent):void {
 
			var other:Box2DPhysicsObject = cEvt.other.GetBody().GetUserData();
 
			if (other is SalesWoman || other is Block || other is Cash)
				_fighting = false;
		}
 
		protected function updateAnimation():void {
 
			_animation = _fighting ? "attack" : "walk";
		}
	}
}

Now the bags. They are just a missile with categoryBits & maskBits filters. However the CE is prebuilt as a platformer engine, it means that Missiles remove Box2D gravity this way :

override public function update(timeDelta:Number):void {
 
	var removeGravity:V2 = new V2();
	removeGravity.subtract(_box2D.world.GetGravity());
	removeGravity.multiplyN(body.GetMass());
 
	_body.ApplyForce(removeGravity, _body.GetWorldCenter());
}

In our game we don’t have gravity so we just remove this unnecessary & expensive calculation! And finally Missile are defined as bullet and use Continous Collision Detection, this is very expensive. Since our missiles aren’t fast we don’t need that.

The Bag class :

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
package games.live4sales.weapons {
 
	import Box2DAS.Common.V2;
	import Box2DAS.Dynamics.ContactEvent;
 
	import com.citrusengine.objects.platformer.box2d.Missile;
	import com.citrusengine.physics.Box2DCollisionCategories;
 
	/**
	 * @author Aymeric
	 */
	public class Bag extends Missile {
 
		public function Bag(name:String, params:Object = null) {
			super(name, params);
		}
 
		override public function update(timeDelta:Number):void {
 
			if (!_exploded)
				_body.SetLinearVelocity(_velocity);
			else
				_body.SetLinearVelocity(new V2());
 
			if (x > 480)
				kill = true;
		}
 
		override protected function handleBeginContact(cEvt:ContactEvent):void {
			explode();
		}
 
		override protected function defineBody():void {
 
			super.defineBody();
 
			_bodyDef.bullet = false;
			_bodyDef.allowSleep = true;
		}
 
		override protected function defineFixture():void {
 
			super.defineFixture();
 
			_fixtureDef.filter.categoryBits = Box2DCollisionCategories.Get("Level");
			_fixtureDef.filter.maskBits = Box2DCollisionCategories.GetAllExcept("Level");
		}
 
	}
}

I’m pretty sure that Box2D ninja user will find other optimizations, please share them.

The example is over now. Again the hardest part is to find good parameters & balance to have the best user experience. To do that you have to try your game again & again and when you think it is ok : let’s an other guy try it… then restart your balancing!

That was an other non platformer game example for the CE! Cheers!

Citrus Engine V3 Beta2

13

Hey there! On this hot summer day, I’m happy to share a new major beta for the Citrus Engine! There are lots of new features, improvements, bug fixes and a new demo for mobile devices. It is also updated on the last version of each library.

Download it on Google Code or GitHub.

Social Coding
The Citrus Engine is now on GitHub. You can easily fork it, make some pull request or add issues! The repository is built with demo contents in mind, take a look on the src package! There are differents examples for some of its features. I will built new one, if it is requested!

Only made for Platformer Games?
Some people thinks that the CE is only made for platformer games… NO!! The Citrus Engine is pre-built as a platformer engine, it means that you’ve already platformer game objects available. However you can make all type of 2D Games you want. It offers a nice way to separate logic/physics from art.

Mobile Ready
Thanks to Nape and the physics performance improvement, check this post for more details, the CE is ready to target mobile devices! It’s time for a new demo running on the web, mobile desktop, iPhone & iPad with the same source code!
The web demo. Be patient, it loads 5.7 MO. I didn’t make a preloader, to have the same source code.
Demo on mobile devices :

Graphic design are made by Pauline Oriol, from our previous Objective-C/Sparrow project : Tribus.
You will find the source on the src/mobilenapestarling package.
Particles are something very cpu & gpu greedy, it’s nice to see this demo working at 60 fps on all devices & web!

Retro-Compatibility?
It’s something very important for me to not break the compatibility with previous version! I’m glad to say that you just have to update package import concerning physics since Nape support. Not a big deal :)

Offering choices
Now the Citrus Engine offers many options, just chose which one you feel comfortable :
- the classic flash display list, or blitting or Stage3D thanks to Starling.
- Box2D, or Nape or a Simple math based collision detection. You can create easily your own logics.
- a simple way to manage object creation, and for advanced developers : an entity/component system and object pooling.
Check the different examples in the zip!

Nape
All Box2D’s objects haven’t been ported on Nape at the moment. However I would like to thanks Nick for his contribution to the CE!

The entity/component system
I was not 100% satisfied of my entity/component system, but it seems it is not bad at all! I will improve it in the future, I need a little break before getting back to it.

Object Pooling
Since the Citrus Engine dissociates view/art from logic/physics, you may use two pool objects! It works great with Nape, but it seems that Box2D Alchemy has some problems. I opened a ticket there.

Level Architect dropped, welcome the Inspectable metadata tag
The Level Artchitect was a good product, but it needs too much time to be supported. I’ve dropped its support on this version. I’ve always love how the Citrus Engine manages Flash Pro as a Level Editor. Since Flash CS6 support Stage3D, we can easily use this metadata tag. Take a look on LevelA1/LevelA2.fla to see its power! It has never be so easy to manage properties! Otherwise if your Flash Pro doesn’t support Stage3D, you can still use the old way!

Blitting & Simple math based collision
I’ve updated this two options for the CitrusEngine, take a look on their respective demo ;)

Future?
The CE is still in beta? I’ve added lots of new features in this version. I’m waiting for some feedback before releasing it as a stable version, even if it is already very stable ;) Don’t hesitate to ask for new features! Shortly I will create new game demos which are not platformer games, to show that the CE is very flexible!

Workers?
Workers can provide a huge performance boost. Take a look on this example using Nape & Starling. It’s definitely something I want to add in a future version. At the moment, Workers are not supported on mobile… it let me some times to know how I will integer them. I don’t want to force people to use it (too difficult for newbies). I’ve added lots of options/features to the Citrus Engine and tried to keep it as simple as possible. This one will be hard to add easily.

Bonus : Kinessia code open source
Kinessia was my first serious project with the Citrus Engine. Now you can access to its source code (it was build with CE V2). More information there.

I hope you will like this new version! I would like to thanks Daniel for its awesome work & support on Starling, and Nick for his contribution. Keep rockin’

An entity/component system’s attempt using Box2D

11

One year ago, I heard about the Entity/Component Model for the first time. It seemed to be awesome for game design / development. This is 3 links/resources for a good introduction : Gamasutra the Entity Component Model, Entity Systems are the future of MMOG development, Entity Systems. Then Richard Lord has made a very good blog post with code example using his own framework : Ash. And finally, Shaun Smith’s experience using Ash.

All the code example is available on the new Citrus Engine’s GitHub repository. Compile the src/entity/Main.as

During this year, entity/component system has always been something I wanted to try… but that was scary : this is not OOP, that’s a new way of programming. I wanted to add this system to the Citrus Engine as an option to handle complex object. For basic object OOP is great and easy to set up, but for complex game object which may have new abilities on the fly, you’ve quickly a Class with 700 lines of code and conditions. Not easy to maintain some weeks later. The Citrus Engine handles physics engine (Box2D & Nape) & a simple math-based collision-detection system. However it comes built-in with a “platformer” starter-kit based on Box2D, so I made my entity/component system’s experiments using Box2D. And that was not simple.

When I heard for the first time of entity/component system, I quickly understand that you can add behaviors very easily :

var heroEntity:Entity = new Entity();
heroEntity.add(New PhysicsComponent({x:200, y:270, width:40, height:60)).add(new InputComponent({right:Keyboard.RIGHT})).add(new ViewComponent({view:"hero.swf"}));

And add new ability in a magic way :

heroEntity.add(new LadderAbilityComponent());

But what happen when you want to add to your hero the ability to climb on a ladder? You’ve to specify the new key input, an animation, hero behaviors, collision, physics… it has an impact on all your previous component! Using a physics engine like Box2D doesn’t simplify that. The Asteroids game example offers with Ash is quite simple. I’ve understood how it works, but using physics engine makes it all much more complex.

Now it’s time for some code. I’ve worked on a Hero’s entity, it is able to walk, jump, duck, get hurt, give damage… with animations. That’s the hero on the Citrus Engine site. Play with it.
Its class code, programmed the “classic way” based on OOP (source on GitHub too).

package com.citrusengine.objects.platformer.box2d
{
 
	import Box2DAS.Common.V2;
	import Box2DAS.Dynamics.ContactEvent;
	import Box2DAS.Dynamics.b2Fixture;
 
	import com.citrusengine.math.MathVector;
	import com.citrusengine.objects.Box2DPhysicsObject;
	import com.citrusengine.physics.Box2DCollisionCategories;
	import com.citrusengine.utils.Box2DShapeMaker;
 
	import org.osflash.signals.Signal;
 
	import flash.display.MovieClip;
	import flash.ui.Keyboard;
	import flash.utils.clearTimeout;
	import flash.utils.getDefinitionByName;
	import flash.utils.setTimeout;
 
	/**
	 * This is a common, simple, yet solid implementation of a side-scrolling Hero. 
	 * The hero can run, jump, get hurt, and kill enemies. It dispatches signals
	 * when significant events happen. The game state's logic should listen for those signals
	 * to perform game state updates (such as increment coin collections).
	 * 
	 * Don't store data on the hero object that you will need between two or more levels (such
	 * as current coin count). The hero should be re-created each time a state is created or reset.
	 */	
	public class Hero extends Box2DPhysicsObject
	{
		//properties
		/**
		 * This is the rate at which the hero speeds up when you move him left and right. 
		 */
		[Inspectable(defaultValue="1")]
		public var acceleration:Number = 1;
 
		/**
		 * This is the fastest speed that the hero can move left or right. 
		 */
		[Inspectable(defaultValue="8")]
		public var maxVelocity:Number = 8;
 
		/**
		 * This is the initial velocity that the hero will move at when he jumps.
		 */
		[Inspectable(defaultValue="11")]
		public var jumpHeight:Number = 11;
 
		/**
		 * This is the amount of "float" that the hero has when the player holds the jump button while jumping. 
		 */
		[Inspectable(defaultValue="0.3")]
		public var jumpAcceleration:Number = 0.3;
 
		/**
		 * This is the y velocity that the hero must be travelling in order to kill a Baddy.
		 */
		[Inspectable(defaultValue="3")]
		public var killVelocity:Number = 3;
 
		/**
		 * The y velocity that the hero will spring when he kills an enemy. 
		 */
		[Inspectable(defaultValue="8")]
		public var enemySpringHeight:Number = 8;
 
		/**
		 * The y velocity that the hero will spring when he kills an enemy while pressing the jump button. 
		 */
		[Inspectable(defaultValue="9")]
		public var enemySpringJumpHeight:Number = 9;
 
		/**
		 * How long the hero is in hurt mode for. 
		 */
		[Inspectable(defaultValue="1000")]
		public var hurtDuration:Number = 1000;
 
		/**
		 * The amount of kick-back that the hero jumps when he gets hurt. 
		 */
		[Inspectable(defaultValue="6")]
		public var hurtVelocityX:Number = 6;
 
		/**
		 * The amount of kick-back that the hero jumps when he gets hurt. 
		 */
		[Inspectable(defaultValue="10")]
		public var hurtVelocityY:Number = 10;
 
		/**
		 * Determines whether or not the hero's ducking ability is enabled.
		 */
		[Inspectable(defaultValue="true")]
		public var canDuck:Boolean = true;
 
		//events
		/**
		 * Dispatched whenever the hero jumps. 
		 */
		public var onJump:Signal;
 
		/**
		 * Dispatched whenever the hero gives damage to an enemy. 
		 */		
		public var onGiveDamage:Signal;
 
		/**
		 * Dispatched whenever the hero takes damage from an enemy. 
		 */		
		public var onTakeDamage:Signal;
 
		/**
		 * Dispatched whenever the hero's animation changes. 
		 */		
		public var onAnimationChange:Signal;
 
		protected var _groundContacts:Array = [];//Used to determine if he's on ground or not.
		protected var _enemyClass:Class = Baddy;
		protected var _onGround:Boolean = false;
		protected var _springOffEnemy:Number = -1;
		protected var _hurtTimeoutID:Number;
		protected var _hurt:Boolean = false;
		protected var _friction:Number = 0.75;
		protected var _playerMovingHero:Boolean = false;
		protected var _controlsEnabled:Boolean = true;
		protected var _ducking:Boolean = false;
		protected var _combinedGroundAngle:Number = 0;
 
		public static function Make(name:String, x:Number, y:Number, width:Number, height:Number, view:* = null):Hero
		{
			if (view == null) view = MovieClip;
			return new Hero(name, { x: x, y: y, width: width, height: height, view: view } );
		}
 
		/**
		 * Creates a new hero object.
		 */		
		public function Hero(name:String, params:Object = null)
		{
			super(name, params);
 
			onJump = new Signal();
			onGiveDamage = new Signal();
			onTakeDamage = new Signal();
			onAnimationChange = new Signal();
		}
 
		override public function destroy():void
		{
			_fixture.removeEventListener(ContactEvent.PRE_SOLVE, handlePreSolve);
			_fixture.removeEventListener(ContactEvent.BEGIN_CONTACT, handleBeginContact);
			_fixture.removeEventListener(ContactEvent.END_CONTACT, handleEndContact);
			clearTimeout(_hurtTimeoutID);
			onJump.removeAll();
			onGiveDamage.removeAll();
			onTakeDamage.removeAll();
			onAnimationChange.removeAll();
			super.destroy();
		}
 
		/**
		 * Whether or not the player can move and jump with the hero. 
		 */	
		public function get controlsEnabled():Boolean
		{
			return _controlsEnabled;
		}
 
		public function set controlsEnabled(value:Boolean):void
		{
			_controlsEnabled = value;
 
			if (!_controlsEnabled)
				_fixture.SetFriction(_friction);
		}
 
		/**
		 * Returns true if the hero is on the ground and can jump. 
		 */		
		public function get onGround():Boolean
		{
			return _onGround;
		}
 
		/**
		 * The Hero uses the enemyClass parameter to know who he can kill (and who can kill him).
		 * Use this setter to to pass in which base class the hero's enemy should be, in String form
		 * or Object notation.
		 * For example, if you want to set the "Baddy" class as your hero's enemy, pass
		 * "com.citrusengine.objects.platformer.Baddy", or Baddy (with no quotes). Only String
		 * form will work when creating objects via a level editor.
		 */
		[Inspectable(defaultValue="com.citrusengine.objects.platformer.box2d.Baddy",type="String")]
		public function set enemyClass(value:*):void
		{
			if (value is String)
				_enemyClass = getDefinitionByName(value as String) as Class;
			else if (value is Class)
				_enemyClass = value;
		}
 
		/**
		 * This is the amount of friction that the hero will have. Its value is multiplied against the
		 * friction value of other physics objects.
		 */	
		public function get friction():Number
		{
			return _friction;
		}
 
		[Inspectable(defaultValue="0.75")]
		public function set friction(value:Number):void
		{
			_friction = value;
 
			if (_fixture)
			{
				_fixture.SetFriction(_friction);
			}
		}
 
		override public function update(timeDelta:Number):void
		{
			super.update(timeDelta);
 
			var velocity:V2 = _body.GetLinearVelocity();
 
			if (controlsEnabled)
			{
				var moveKeyPressed:Boolean = false;
 
				_ducking = (_ce.input.isDown(Keyboard.DOWN) &amp;&amp; _onGround &amp;&amp; canDuck);
 
				if (_ce.input.isDown(Keyboard.RIGHT) &amp;&amp; !_ducking)
				{
					velocity = V2.add(velocity, getSlopeBasedMoveAngle());
					moveKeyPressed = true;
				}
 
				if (_ce.input.isDown(Keyboard.LEFT) &amp;&amp; !_ducking)
				{
					velocity = V2.subtract(velocity, getSlopeBasedMoveAngle());
					moveKeyPressed = true;
				}
 
				//If player just started moving the hero this tick.
				if (moveKeyPressed &amp;&amp; !_playerMovingHero)
				{
					_playerMovingHero = true;
					_fixture.SetFriction(0); //Take away friction so he can accelerate.
				}
				//Player just stopped moving the hero this tick.
				else if (!moveKeyPressed &amp;&amp; _playerMovingHero)
				{
					_playerMovingHero = false;
					_fixture.SetFriction(_friction); //Add friction so that he stops running
				}
 
				if (_onGround &amp;&amp; _ce.input.justPressed(Keyboard.SPACE) &amp;&amp; !_ducking)
				{
					velocity.y = -jumpHeight;
					onJump.dispatch();
				}
 
				if (_ce.input.isDown(Keyboard.SPACE) &amp;&amp; !_onGround &amp;&amp; velocity.y &lt; 0) 				{ 					velocity.y -= jumpAcceleration; 				} 				 				if (_springOffEnemy != -1) 				{ 					if (_ce.input.isDown(Keyboard.SPACE)) 						velocity.y = -enemySpringJumpHeight; 					else 						velocity.y = -enemySpringHeight; 					_springOffEnemy = -1; 				} 				 				//Cap velocities 				if (velocity.x &gt; (maxVelocity))
					velocity.x = maxVelocity;
				else if (velocity.x &lt; (-maxVelocity))
					velocity.x = -maxVelocity;
 
				//update physics with new velocity
				_body.SetLinearVelocity(velocity);
			}
 
			updateAnimation();
		}
 
		/**
		 * Returns the absolute walking speed, taking moving platforms into account.
		 * Isn't super performance-light, so use sparingly.
		 */
		public function getWalkingSpeed():Number
		{
			var groundVelocityX:Number = 0;
			for each (var groundContact:b2Fixture in _groundContacts)
			{
				groundVelocityX += groundContact.GetBody().GetLinearVelocity().x;
			}
 
			return _body.GetLinearVelocity().x - groundVelocityX;
		}
 
		/**
		 * Hurts the hero, disables his controls for a little bit, and dispatches the onTakeDamage signal. 
		 */		
		public function hurt():void
		{
			_hurt = true;
			controlsEnabled = false;
			_hurtTimeoutID = setTimeout(endHurtState, hurtDuration);
			onTakeDamage.dispatch();
 
			//Makes sure that the hero is not frictionless while his control is disabled
			if (_playerMovingHero)
			{
				_playerMovingHero = false;
				_fixture.SetFriction(_friction);
			}
		}
 
		override protected function defineBody():void
		{
			super.defineBody();
			_bodyDef.fixedRotation = true;
			_bodyDef.allowSleep = false;
		}
 
		override protected function createShape():void
		{
			_shape = Box2DShapeMaker.BeveledRect(_width, _height, 0.1);
		}
 
		override protected function defineFixture():void
		{
			super.defineFixture();
			_fixtureDef.friction = _friction;
			_fixtureDef.restitution = 0;
			_fixtureDef.filter.categoryBits = Box2DCollisionCategories.Get("GoodGuys");
			_fixtureDef.filter.maskBits = Box2DCollisionCategories.GetAll();
		}
 
		override protected function createFixture():void
		{
			super.createFixture();
			_fixture.m_reportPreSolve = true;
			_fixture.m_reportBeginContact = true;
			_fixture.m_reportEndContact = true;
			_fixture.addEventListener(ContactEvent.PRE_SOLVE, handlePreSolve);
			_fixture.addEventListener(ContactEvent.BEGIN_CONTACT, handleBeginContact);
			_fixture.addEventListener(ContactEvent.END_CONTACT, handleEndContact);
		}
 
		protected function handlePreSolve(e:ContactEvent):void 
		{
			if (!_ducking)
				return;
 
			var other:Box2DPhysicsObject = e.other.GetBody().GetUserData() as Box2DPhysicsObject;
 
			var heroTop:Number = y;
			var objectBottom:Number = other.y + (other.height / 2);
 
			if (objectBottom &lt; heroTop)
				e.contact.Disable();
		}
 
		protected function handleBeginContact(e:ContactEvent):void
		{
			var collider:Box2DPhysicsObject = e.other.GetBody().GetUserData();
 
			if (_enemyClass &amp;&amp; collider is _enemyClass)
			{
				if (_body.GetLinearVelocity().y &lt; killVelocity &amp;&amp; !_hurt) 				{ 					hurt(); 					 					//fling the hero 					var hurtVelocity:V2 = _body.GetLinearVelocity(); 					hurtVelocity.y = -hurtVelocityY; 					hurtVelocity.x = hurtVelocityX; 					if (collider.x &gt; x)
						hurtVelocity.x = -hurtVelocityX;
					_body.SetLinearVelocity(hurtVelocity);
				}
				else
				{
					_springOffEnemy = collider.y - height;
					onGiveDamage.dispatch();
				}
			}
 
			//Collision angle
			if (e.normal) //The normal property doesn't come through all the time. I think doesn't come through against sensors.
			{
				var collisionAngle:Number = new MathVector(e.normal.x, e.normal.y).angle * 180 / Math.PI;
				if (collisionAngle &gt; 45 &amp;&amp; collisionAngle &lt; 135)
				{
					_groundContacts.push(e.other);
					_onGround = true;
					updateCombinedGroundAngle();
				}
			}
		}
 
		protected function handleEndContact(e:ContactEvent):void
		{
			//Remove from ground contacts, if it is one.
			var index:int = _groundContacts.indexOf(e.other);
			if (index != -1)
			{
				_groundContacts.splice(index, 1);
				if (_groundContacts.length == 0)
					_onGround = false;
				updateCombinedGroundAngle();
			}
		}
 
		protected function getSlopeBasedMoveAngle():V2
		{
			return new V2(acceleration, 0).rotate(_combinedGroundAngle);
		}
 
		protected function updateCombinedGroundAngle():void
		{
			_combinedGroundAngle = 0;
 
			if (_groundContacts.length == 0)
				return;
 
			for each (var contact:b2Fixture in _groundContacts)
				var angle:Number = contact.GetBody().GetAngle();
 
			var turn:Number = 45 * Math.PI / 180;
			angle = angle % turn;
			_combinedGroundAngle += angle;
			_combinedGroundAngle /= _groundContacts.length;
		}
 
		protected function endHurtState():void
		{
			_hurt = false;
			controlsEnabled = true;
		}
 
		protected function updateAnimation():void
		{
			var prevAnimation:String = _animation;
 
			var velocity:V2 = _body.GetLinearVelocity();
			if (_hurt)
			{
				_animation = "hurt";
			}
			else if (!_onGround)
			{
				_animation = "jump";
			}
			else if (_ducking)
			{
				_animation = "duck";
			}
			else
			{
				var walkingSpeed:Number = getWalkingSpeed();
				if (walkingSpeed &lt; -acceleration) 				{ 					_inverted = true; 					_animation = "walk"; 				} 				else if (walkingSpeed &gt; acceleration)
				{
					_inverted = false;
					_animation = "walk";
				}
				else
				{
					_animation = "idle";
				}
			}
 
			if (prevAnimation != _animation)
			{
				onAnimationChange.dispatch();
			}
		}
	}
}

You’ve certainly noticed that the Physics code is the most important and long part. It is very difficult to separate it into different components.

Let’s show some code of my attempt to add an entity/component system to the Citrus Engine using the Hero as an example :
The GameState class :

package entity {
 
	import com.citrusengine.core.State;
	import com.citrusengine.objects.platformer.box2d.Platform;
	import com.citrusengine.physics.Box2D;
	import com.citrusengine.system.Entity;
	import com.citrusengine.system.components.InputComponent;
	import com.citrusengine.system.components.box2d.hero.HeroCollisionComponent;
	import com.citrusengine.system.components.box2d.hero.HeroMovementComponent;
	import com.citrusengine.system.components.box2d.hero.HeroPhysicsComponent;
	import com.citrusengine.system.components.box2d.hero.HeroViewComponent;
 
	/**
	 * @author Aymeric
	 */
	public class EntityGameState extends State {
 
		private var heroEntity : Entity;
		private var input : InputComponent;
		private var _view : HeroViewComponent;
		private var physics : HeroPhysicsComponent;
 
		public function EntityGameState() {
 
			super();
		}
 
		override public function initialize():void {
 
			super.initialize();
 
			var box2d:Box2D = new Box2D("box2D");
			//box2d.visible = true;
			add(box2d);
 
			heroEntity = new Entity("heroEntity");
 
			physics = new HeroPhysicsComponent("physics", {x:200, y:270, width:40, height:60, entity:heroEntity});
			input = new InputComponent("input", {entity:heroEntity});
			var collision:HeroCollisionComponent = new HeroCollisionComponent("collision", {entity:heroEntity});
			var move:HeroMovementComponent = new HeroMovementComponent("move", {entity:heroEntity});
			_view = new HeroViewComponent("view", {view:"Patch.swf", entity:heroEntity});
 
			heroEntity.add(physics).add(input).add(collision).add(move).add(_view);
			heroEntity.initialize();
 
			addEntity(heroEntity, _view);
 
			add(new Platform("platform", {x:600, y:350, width:1800, height:20}));
		}
 
	}
}

A Hero is defined by its physics, input keys, collision management, movement, and a graphic view/object. Then it is added to the state which manages update and object destruction.

The Entity class :

package com.citrusengine.system {
 
	import com.citrusengine.core.CitrusObject;
 
	import flash.utils.Dictionary;
 
	/**
	 * A game entity is compound by components. The entity serves as a link to communicate between components.
	 * It extends the CitrusObject class to enjoy its params setter.
	 */
	public class Entity extends CitrusObject {
 
		public var components:Dictionary;
 
		public function Entity(name:String, params:Object = null) {
 
			super(name, params);
 
			components = new Dictionary();
		}
 
		/**
		 * Add a component to the entity.
		 */
		public function add(component:Component):Entity {
 
			components[component.name] = component;
 
			return this;
		}
 
		/**
		 * Remove a component from the entity.
		 */
		public function remove(component:Component):void {
 
			if (components[component.name]) {
				component.destroy();
				delete components[component.name];
			}
		}
 
		/**
		 * After all the components have been added call this function to perform an init on them.
		 * Mostly used if you want to access to other components through the entity.
		 */
		public function initialize():void {
 
			for each (var component:Component in components) {
				component.initialize();
			}
		}
 
		/**
		 * Destroy the entity and its components.
		 */
		override public function destroy():void {
 
			for each (var component:Component in components) {
				component.destroy();
			}
 
			components = null;
 
			super.destroy();
		}
 
		/**
		 * Perform an update on all entity's components.
		 */
		override public function update(timeDelta:Number):void {
 
			for each (var component:Component in components) {
				component.update(timeDelta);
			}
		}
	}
}

Using dictionary and for each loop is not very optimized, but it’s ok for this entity/component system’s test.
The Component class :

package com.citrusengine.system {
 
	import com.citrusengine.core.CitrusEngine;
	import com.citrusengine.core.CitrusObject;
 
	/**
	 * A component is an object dedicate to a (single) task for an entity : physics, collision, inputs, view, movement... management.
	 * You will use an entity when your object become too much complex to manage into a single class.
	 * Preferably if you use a physics engine, create at first the entity's physics component.
	 * It extends the CitrusObject class to enjoy its params setter.
	 */
	public class Component extends CitrusObject {
 
		public var entity:Entity;
 
		protected var _ce:CitrusEngine;
 
		public function Component(name:String, params:Object = null) {
 
			super(name, params);
 
			_ce = CitrusEngine.getInstance();
		}
 
		/**
		 * Register other components in your component class in this function.
		 * It should be call after all components have been added to an entity.
		 */
		public function initialize():void {
 
		}
 
		/**
		 * Destroy the component, most of the time called by its entity.
		 */
		override public function destroy():void {
 
			super.destroy();
		}
 
		/**
		 * Perform an update on the component, called by its entity.
		 */
		override public function update(timeDelta:Number):void {
		}
 
	}
}

Wait! In Ash, a component only stores data. It doesn’t have logic, but System does. At the moment, I’ve chosen to add logic in components. I may separate them later.

The Input & View components (don’t depend directly of Starling, flash display list, Box2D, Nape…) :

package com.citrusengine.system.components {
 
	import com.citrusengine.system.Component;
 
	import flash.ui.Keyboard;
 
	/**
	 * An input component, it will inform if the key is down, just pressed or just released.
	 */
	public class InputComponent extends Component {
 
		public var rightKeyIsDown:Boolean = false;
		public var leftKeyIsDown:Boolean = false;
		public var downKeyIsDown:Boolean = false;
		public var spaceKeyIsDown:Boolean = false;
		public var spaceKeyJustPressed:Boolean = false;
 
		public function InputComponent(name:String, params:Object = null) {
			super(name, params);
		}
 
		override public function update(timeDelta:Number):void {
 
			super.update(timeDelta);
 
			rightKeyIsDown = _ce.input.isDown(Keyboard.RIGHT);
			leftKeyIsDown = _ce.input.isDown(Keyboard.LEFT);
			downKeyIsDown = _ce.input.isDown(Keyboard.DOWN);
			spaceKeyIsDown = _ce.input.isDown(Keyboard.SPACE);
			spaceKeyJustPressed = _ce.input.justPressed(Keyboard.SPACE);
		}
	}
}
package com.citrusengine.system.components {
 
	import com.citrusengine.system.Component;
	import com.citrusengine.view.ISpriteView;
 
	import org.osflash.signals.Signal;
 
	import flash.display.MovieClip;
 
	/**
	 * The view component, it manages everything to set the view.
	 * Extend it to handle animation.
	 */
	public class ViewComponent extends Component implements ISpriteView {
 
		public var onAnimationChange:Signal;
 
		protected var _x:Number = 0;
		protected var _y:Number = 0;
		protected var _rotation:Number = 0;
		protected var _inverted:Boolean = false;
		protected var _parallax:Number = 1;
		protected var _animation:String = "";
		protected var _visible:Boolean = true;
		protected var _view:* = MovieClip;
 
		private var _group:Number = 0;
		private var _offsetX:Number = 0;
		private var _offsetY:Number = 0;
		private var _registration:String = "center";
 
		public function ViewComponent(name:String, params:Object = null) {
			super(name, params);
 
			onAnimationChange = new Signal();
		}
 
		override public function update(timeDelta:Number):void {
 
			super.update(timeDelta);
		}
 
		override public function destroy():void {
 
			onAnimationChange.removeAll();
 
			super.destroy();
		}
 
		public function get x():Number {
			return _x;
		}
 
		public function set x(value:Number):void {
			_x = value;
		}
 
		public function get y():Number {
			return _y;
		}
 
		public function set y(value:Number):void {
			_y = value;
		}
 
		public function get rotation():Number {
			return _rotation;
		}
 
		public function set rotation(value:Number):void {
			_rotation = value;
		}
 
		public function get parallax():Number {
			return _parallax;
		}
 
		public function set parallax(value:Number):void {
			_parallax = value;
		}
 
		public function get group():Number
		{
			return _group;
		}
 
		public function set group(value:Number):void
		{
			_group = value;
		}
 
		public function get visible():Boolean
		{
			return _visible;
		}
 
		public function set visible(value:Boolean):void
		{
			_visible = value;
		}
 
		public function get view():*
		{
			return _view;
		}
 
		public function set view(value:*):void
		{
			_view = value;
		}
 
		public function get animation():String
		{
			return _animation;
		}
 
		public function get inverted():Boolean
		{
			return _inverted;
		}
 
		public function get offsetX():Number
		{
			return _offsetX;
		}
 
		public function set offsetX(value:Number):void
		{
			_offsetX = value;
		}
 
		public function get offsetY():Number
		{
			return _offsetY;
		}
 
		public function set offsetY(value:Number):void
		{
			_offsetY = value;
		}
 
		public function get registration():String
		{
			return _registration;
		}
 
		public function set registration(value:String):void
		{
			_registration = value;
		}
	}
}

Now some Box2D components : Box2D physics, collision handler, movement :

package com.citrusengine.system.components.box2d {
 
	import Box2DAS.Collision.Shapes.b2CircleShape;
	import Box2DAS.Collision.Shapes.b2PolygonShape;
	import Box2DAS.Collision.Shapes.b2Shape;
	import Box2DAS.Common.V2;
	import Box2DAS.Dynamics.b2Body;
	import Box2DAS.Dynamics.b2BodyDef;
	import Box2DAS.Dynamics.b2Fixture;
	import Box2DAS.Dynamics.b2FixtureDef;
 
	import com.citrusengine.core.CitrusEngine;
	import com.citrusengine.physics.Box2D;
	import com.citrusengine.physics.Box2DCollisionCategories;
	import com.citrusengine.system.Component;
 
	/**
	 * The base's physics Box2D Component. Manage (just) the physics creation.
	 */
	public class Box2DComponent extends Component {
 
		protected var _box2D:Box2D;
		protected var _bodyDef:b2BodyDef;
		protected var _body:b2Body;
		protected var _shape:b2Shape;
		protected var _fixtureDef:b2FixtureDef;
		protected var _fixture:b2Fixture;
 
		protected var _x:Number = 0;
		protected var _y:Number = 0;
		protected var _rotation:Number = 0;
		protected var _width:Number = 1;
		protected var _height:Number = 1;
		protected var _radius:Number = 0;
 
		public function Box2DComponent(name:String, params:Object = null) {
 
			_box2D = CitrusEngine.getInstance().state.getFirstObjectByType(Box2D) as Box2D;
 
			super(name, params);
 
			defineBody();
			createBody();
			createShape();
			defineFixture();
			createFixture();
			defineJoint();
			createJoint();
		}
 
		override public function destroy():void {
 
			_body.destroy();
			_fixtureDef.destroy();
			_shape.destroy();
			_bodyDef.destroy();
 
			super.destroy();
		}
 
		public function get x():Number
		{
			if (_body)
				return _body.GetPosition().x * _box2D.scale;
			else
				return _x * _box2D.scale;
		}
 
		public function set x(value:Number):void
		{
			_x = value / _box2D.scale;
 
			if (_body)
			{
				var pos:V2 = _body.GetPosition();
				pos.x = _x;
				_body.SetTransform(pos, _body.GetAngle());
			}
		}
 
		public function get y():Number
		{
			if (_body)
				return _body.GetPosition().y * _box2D.scale;
			else
				return _y * _box2D.scale;
		}
 
		public function set y(value:Number):void
		{
			_y = value / _box2D.scale;
 
			if (_body)
			{
				var pos:V2 = _body.GetPosition();
				pos.y = _y;
				_body.SetTransform(pos, _body.GetAngle());
			}
		}
 
		public function get rotation():Number
		{
			if (_body)
				return _body.GetAngle() * 180 / Math.PI;
			else
				return _rotation * 180 / Math.PI;
		}
 
		public function set rotation(value:Number):void
		{
			_rotation = value * Math.PI / 180;
 
			if (_body)
				_body.SetTransform(_body.GetPosition(), _rotation); 
		}
 
		/**
		 * This can only be set in the constructor parameters. 
		 */		
		public function get width():Number
		{
			return _width * _box2D.scale;
		}
 
		public function set width(value:Number):void
		{
			_width = value / _box2D.scale;
 
			if (_initialized)
			{
				trace("Warning: You cannot set " + this + " width after it has been created. Please set it in the constructor.");
			}
		}
 
		/**
		 * This can only be set in the constructor parameters. 
		 */	
		public function get height():Number
		{
			return _height * _box2D.scale;
		}
 
		public function set height(value:Number):void
		{
			_height = value / _box2D.scale;
 
			if (_initialized)
			{
				trace("Warning: You cannot set " + this + " height after it has been created. Please set it in the constructor.");
			}
		}
 
		/**
		 * This can only be set in the constructor parameters. 
		 */	
		public function get radius():Number
		{
			return _radius * _box2D.scale;
		}
 
		/**
		 * The object has a radius or a width &amp; height. It can't have both.
		 */
		public function set radius(value:Number):void
		{
			_radius = value / _box2D.scale;
 
			if (_initialized)
			{
				trace("Warning: You cannot set " + this + " radius after it has been created. Please set it in the constructor.");
			}
		}
 
		/**
		 * A direction reference to the Box2D body associated with this object.
		 */
		public function get body():b2Body
		{
			return _body;
		}
 
		/**
		 * This method will often need to be overriden to provide additional definition to the Box2D body object. 
		 */		
		protected function defineBody():void
		{
			_bodyDef = new b2BodyDef();
			_bodyDef.type = b2Body.b2_dynamicBody;
			_bodyDef.position.v2 = new V2(_x, _y);
			_bodyDef.angle = _rotation;
		}
 
		/**
		 * This method will often need to be overriden to customize the Box2D body object. 
		 */	
		protected function createBody():void
		{
			_body = _box2D.world.CreateBody(_bodyDef);
			_body.SetUserData(this);
		}
 
		/**
		 * This method will often need to be overriden to customize the Box2D shape object.
		 * The PhysicsObject creates a rectangle by default if the radius it not defined, but you can replace this method's
		 * definition and instead create a custom shape, such as a line or circle.
		 */	
		protected function createShape():void
		{
			if (_radius != 0) {
				_shape = new b2CircleShape();
				b2CircleShape(_shape).m_radius = _radius;
			} else {
				_shape = new b2PolygonShape();
				b2PolygonShape(_shape).SetAsBox(_width / 2, _height / 2);
			}
		}
 
		/**
		 * This method will often need to be overriden to provide additional definition to the Box2D fixture object. 
		 */	
		protected function defineFixture():void
		{
			_fixtureDef = new b2FixtureDef();
			_fixtureDef.shape = _shape;
			_fixtureDef.density = 1;
			_fixtureDef.friction = 0.6;
			_fixtureDef.restitution = 0.3;
			_fixtureDef.filter.categoryBits = Box2DCollisionCategories.Get("Level");
			_fixtureDef.filter.maskBits = Box2DCollisionCategories.GetAll();
		}
 
		/**
		 * This method will often need to be overriden to customize the Box2D fixture object. 
		 */	
		protected function createFixture():void
		{
			_fixture = _body.CreateFixture(_fixtureDef);
		}
 
		/**
		 * This method will often need to be overriden to provide additional definition to the Box2D joint object.
		 * A joint is not automatically created, because joints require two bodies. Therefore, if you need to create a joint,
		 * you will also need to create additional bodies, fixtures and shapes, and then also instantiate a new b2JointDef
		 * and b2Joint object.
		 */	
		protected function defineJoint():void
		{
 
		}
 
		/**
		 * This method will often need to be overriden to customize the Box2D joint object. 
		 * A joint is not automatically created, because joints require two bodies. Therefore, if you need to create a joint,
		 * you will also need to create additional bodies, fixtures and shapes, and then also instantiate a new b2JointDef
		 * and b2Joint object.
		 */		
		protected function createJoint():void
		{
 
		}
	}
}
package com.citrusengine.system.components.box2d {
 
	import Box2DAS.Dynamics.ContactEvent;
 
	import com.citrusengine.system.Component;
 
	/**
	 * The Box2D collision component, extends it to handle collision.
	 */
	public class CollisionComponent extends Component {
 
		public function CollisionComponent(name:String, params:Object = null) {
 
			super(name, params);
		}
 
		public function handlePreSolve(e:ContactEvent):void {
 
		}
 
		public function handleBeginContact(e:ContactEvent):void {
 
		}
 
		public function handleEndContact(e:ContactEvent):void {
 
		}
	}
}
package com.citrusengine.system.components.box2d {
 
	import Box2DAS.Common.V2;
 
	import com.citrusengine.system.Component;
 
	/**
	 * The Box2D movement component, we've to know the Box2D physics component to be able to move it.
	 */
	public class MovementComponent extends Component {
 
		protected var _physicsComponent:Box2DComponent;
 
		protected var _velocity:V2;
 
		public function MovementComponent(name:String, params:Object = null) {
			super(name, params);
		}
 
		override public function initialize():void {
 
			super.initialize();
 
			_physicsComponent = entity.components["physics"];
		}
 
		override public function update(timeDelta:Number):void {
 
			super.update(timeDelta);
 
			if (_physicsComponent) {
 
				_velocity = _physicsComponent.body.GetLinearVelocity();
			}
		}
 
	}
}

You can already see that there are directly some interactions between them. If you would put those somewhere else, you will have a class managing all the logic. Not very different from our first single class Hero base.

Now the Hero specific components. Hé wait!!! OOP!? Yep. Most of the physics properties are already defined in our Box2DComponent class, but our Hero has specific physics! It must handle collision as well, animations, movement… So its physics, movement, view, collision components class :

package com.citrusengine.system.components.box2d.hero {
 
	import Box2DAS.Dynamics.ContactEvent;
 
	import com.citrusengine.physics.Box2DCollisionCategories;
	import com.citrusengine.system.components.box2d.Box2DComponent;
	import com.citrusengine.system.components.box2d.CollisionComponent;
	import com.citrusengine.utils.Box2DShapeMaker;
 
	/**
	 * The Box2D Hero physics component add the fixture listener, change its friction, restitution...
	 */
	public class HeroPhysicsComponent extends Box2DComponent {
 
		protected var _collisionComponent:CollisionComponent;
 
		protected var _friction:Number = 0.75;
 
		public function HeroPhysicsComponent(name:String, params:Object = null) {
			super(name, params);
		}
 
		override public function destroy():void {
 
			_fixture.removeEventListener(ContactEvent.PRE_SOLVE, _collisionComponent.handlePreSolve);
			_fixture.removeEventListener(ContactEvent.BEGIN_CONTACT, _collisionComponent.handleBeginContact);
			_fixture.removeEventListener(ContactEvent.END_CONTACT, _collisionComponent.handleEndContact);
 
			super.destroy();
		}
 
		override public function initialize():void {
 
			_collisionComponent = entity.components["collision"];
 
			_fixture.addEventListener(ContactEvent.PRE_SOLVE, _collisionComponent.handlePreSolve);
			_fixture.addEventListener(ContactEvent.BEGIN_CONTACT, _collisionComponent.handleBeginContact);
			_fixture.addEventListener(ContactEvent.END_CONTACT, _collisionComponent.handleEndContact);
		}
 
		override protected function defineBody():void {
 
			super.defineBody();
 
			_bodyDef.fixedRotation = true;
			_bodyDef.allowSleep = false;
		}
 
		override protected function createShape():void {
 
			_shape = Box2DShapeMaker.BeveledRect(_width, _height, 0.1);
		}
 
		override protected function defineFixture():void {
 
			super.defineFixture();
 
			_fixtureDef.friction = _friction;
			_fixtureDef.restitution = 0;
			_fixtureDef.filter.categoryBits = Box2DCollisionCategories.Get("GoodGuys");
			_fixtureDef.filter.maskBits = Box2DCollisionCategories.GetAll();
		}
 
		override protected function createFixture():void {
 
			super.createFixture();
 
			_fixture.m_reportPreSolve = true;
			_fixture.m_reportBeginContact = true;
			_fixture.m_reportEndContact = true;
		}
 
		public function changeFixtureToZero():void {
			_fixture.SetFriction(0);
		}
 
		public function changeFixtureToItsInitialValue():void {
			_fixture.SetFriction(_friction);
		}
	}
}
package com.citrusengine.system.components.box2d.hero {
 
	import Box2DAS.Common.V2;
 
	import com.citrusengine.objects.Box2DPhysicsObject;
	import com.citrusengine.system.components.InputComponent;
	import com.citrusengine.system.components.box2d.MovementComponent;
 
	import org.osflash.signals.Signal;
 
	import flash.utils.clearTimeout;
	import flash.utils.setTimeout;
 
	/**
	 * The Box2D Hero movement component. Most of its properties are here. It uses a lot of informations from the input component &amp; 
	 * some from the Box2D Hero collision component.
	 */
	public class HeroMovementComponent extends MovementComponent {
 
		//properties
		/**
		 * This is the rate at which the hero speeds up when you move him left and right. 
		 */
		public var acceleration:Number = 1;
 
		/**
		 * This is the fastest speed that the hero can move left or right. 
		 */
		public var maxVelocity:Number = 8;
 
		/**
		 * This is the initial velocity that the hero will move at when he jumps.
		 */
		public var jumpHeight:Number = 11;
 
		/**
		 * This is the amount of "float" that the hero has when the player holds the jump button while jumping. 
		 */
		public var jumpAcceleration:Number = 0.3;
 
		/**
		 * This is the y velocity that the hero must be travelling in order to kill a Baddy.
		 */
		public var killVelocity:Number = 3;
 
		/**
		 * The y velocity that the hero will spring when he kills an enemy. 
		 */
		public var enemySpringHeight:Number = 8;
 
		/**
		 * The y velocity that the hero will spring when he kills an enemy while pressing the jump button. 
		 */
		public var enemySpringJumpHeight:Number = 9;
 
		/**
		 * How long the hero is in hurt mode for. 
		 */
		public var hurtDuration:Number = 1000;
 
		/**
		 * The amount of kick-back that the hero jumps when he gets hurt. 
		 */
		public var hurtVelocityX:Number = 6;
 
		/**
		 * The amount of kick-back that the hero jumps when he gets hurt. 
		 */
		public var hurtVelocityY:Number = 10;
 
		/**
		 * Determines whether or not the hero's ducking ability is enabled.
		 */
		public var canDuck:Boolean = true;
 
		//events
		/**
		 * Dispatched whenever the hero jumps. 
		 */
		public var onJump:Signal;
 
		/**
		 * Dispatched whenever the hero gives damage to an enemy. 
		 */		
		public var onGiveDamage:Signal;
 
		/**
		 * Dispatched whenever the hero takes damage from an enemy. 
		 */		
		public var onTakeDamage:Signal;
 
		protected var _inputComponent:InputComponent;
		protected var _collisionComponent:HeroCollisionComponent;
 
		protected var _onGround:Boolean = false;
		protected var _springOffEnemy:Number = -1;
		protected var _hurtTimeoutID:Number;
		protected var _isHurt:Boolean = false;
		protected var _controlsEnabled:Boolean = true;
		protected var _playerMovingHero:Boolean = false;
		protected var _ducking:Boolean = false;
 
		public function HeroMovementComponent(name:String, params:Object = null) {
 
			super(name, params);
 
			onJump = new Signal();
			onGiveDamage = new Signal();
			onTakeDamage = new Signal();
		}
 
		override public function initialize():void {
 
			super.initialize();
 
			_inputComponent = entity.components["input"];
			_collisionComponent = entity.components["collision"];
		}
 
		override public function destroy():void {
 
			onJump.removeAll();
			onGiveDamage.removeAll();
			onTakeDamage.removeAll();
 
			clearTimeout(_hurtTimeoutID);
 
			super.destroy();
		}
 
		override public function update(timeDelta:Number):void {
 
			super.update(timeDelta);
 
			if (controlsEnabled &amp;&amp; _physicsComponent) {
 
				var moveKeyPressed:Boolean = false;
 
				_ducking = (_inputComponent.downKeyIsDown &amp;&amp; _onGround &amp;&amp; canDuck);
 
				if (_inputComponent.rightKeyIsDown &amp;&amp; !_ducking) {
					_velocity = V2.add(_velocity, getSlopeBasedMoveAngle());
					moveKeyPressed = true;
				}
 
				if (_inputComponent.leftKeyIsDown &amp;&amp; !_ducking) {					
					_velocity = V2.subtract(_velocity, getSlopeBasedMoveAngle());
					moveKeyPressed = true;
				}
 
				//If player just started moving the hero this tick.
				if (moveKeyPressed &amp;&amp; !_playerMovingHero) {
					_playerMovingHero = true;
					(_physicsComponent as HeroPhysicsComponent).changeFixtureToZero(); //Take away friction so he can accelerate.
				}
				//Player just stopped moving the hero this tick.
				else if (!moveKeyPressed &amp;&amp; _playerMovingHero) {
					_playerMovingHero = false;
					(_physicsComponent as HeroPhysicsComponent).changeFixtureToItsInitialValue(); //Add friction so that he stops running
				}
 
				if (_onGround &amp;&amp; _inputComponent.spaceKeyJustPressed &amp;&amp; !_ducking) {
					_velocity.y = -jumpHeight;
					onJump.dispatch();
				}
 
				if (_inputComponent.spaceKeyIsDown &amp;&amp; !_onGround &amp;&amp; _velocity.y &lt; 0) 					_velocity.y -= jumpAcceleration; 					 				if (_springOffEnemy != -1) { 					if (_inputComponent.spaceKeyIsDown) 						_velocity.y = -enemySpringJumpHeight; 					else 						_velocity.y = -enemySpringHeight; 					_springOffEnemy = -1; 				} 				 				//Cap velocities 				if (_velocity.x &gt; maxVelocity)
					_velocity.x = maxVelocity;
				else if (_velocity.x &lt; -maxVelocity) 					_velocity.x = -maxVelocity; 				 				_physicsComponent.body.SetLinearVelocity(_velocity); 			} 		} 		 		/** 		 * The hero gives damage 		 */ 		  		public function giveDamage(collider:Box2DPhysicsObject):void { 			 			_springOffEnemy = collider.y - _physicsComponent.height; 			onGiveDamage.dispatch(); 		} 		 		/** 		 * Hurts and fling the hero, disables his controls for a little bit, and dispatches the onTakeDamage signal.  		 */		 		public function hurt(collider:Box2DPhysicsObject):void { 			 			_isHurt = true; 			controlsEnabled = false; 			_hurtTimeoutID = setTimeout(endHurtState, hurtDuration); 			onTakeDamage.dispatch(); 			 			var hurtVelocity:V2 = _physicsComponent.body.GetLinearVelocity(); 			hurtVelocity.y = -hurtVelocityY; 			hurtVelocity.x = hurtVelocityX; 			if (collider.x &gt; _physicsComponent.x)
				hurtVelocity.x = -hurtVelocityX;
			_physicsComponent.body.SetLinearVelocity(hurtVelocity);
 
			//Makes sure that the hero is not frictionless while his control is disabled
			if (_playerMovingHero) {
				_playerMovingHero = false;
				(_physicsComponent as HeroPhysicsComponent).changeFixtureToItsInitialValue();
			}
		}
 
		protected function endHurtState():void {
 
			_isHurt = false;
			controlsEnabled = true;
		}
 
		protected function getSlopeBasedMoveAngle():V2 {
			return new V2(acceleration, 0).rotate(_collisionComponent.combinedGroundAngle);
		}
 
		/**
		 * Whether or not the player can move and jump with the hero. 
		 */	
		public function get controlsEnabled():Boolean
		{
			return _controlsEnabled;
		}
 
		public function set controlsEnabled(value:Boolean):void
		{
			_controlsEnabled = value;
 
			if (!_controlsEnabled)
				(_physicsComponent as HeroPhysicsComponent).changeFixtureToItsInitialValue();
		}
 
		/**
		 * Returns true if the hero is on the ground and can jump. 
		 */		
		public function get onGround():Boolean {
			return _onGround;
		}
 
		public function set onGround(value:Boolean):void {
			_onGround = value;
		}
 
		public function get ducking():Boolean {
			return _ducking;
		}
 
		public function get isHurt():Boolean {
			return _isHurt;
		}
	}
}
package com.citrusengine.system.components.box2d.hero {
 
	import Box2DAS.Dynamics.b2Fixture;
 
	import com.citrusengine.system.components.ViewComponent;
 
	/**
	 * The Box2D Hero view component. It manages the hero's view based on its physics component and depending its movement.
	 */
	public class HeroViewComponent extends ViewComponent {
 
		public var groundContacts:Array = [];//Used to determine if he's on ground or not.
 
		protected var _physicsComponent:HeroPhysicsComponent;
		protected var _movementComponent:HeroMovementComponent;
 
		public function HeroViewComponent(name:String, params:Object = null) {
 
			super(name, params);
		}
 
		override public function initialize():void {
 
			super.initialize();
 
			_physicsComponent = entity.components["physics"];
			_movementComponent = entity.components["move"];
		}
 
		override public function update(timeDelta:Number):void {
 
			super.update(timeDelta);
 
			var prevAnimation:String = _animation;
 
			if (_physicsComponent &amp;&amp; _movementComponent) {
 
				if (_movementComponent.isHurt)
					_animation = "hurt";
				else if (!_movementComponent.onGround)
					_animation = "jump";
				else if (_movementComponent.ducking)
					_animation = "duck";
				else {
 
					var walkingSpeed:Number = getWalkingSpeed();
 
					if (walkingSpeed &lt; -_movementComponent.acceleration) { 						_inverted = true; 						_animation = "walk"; 					} else if (walkingSpeed &gt; _movementComponent.acceleration) {
						_inverted = false;
						_animation = "walk";
					} else {
						_animation = "idle";
					}
				}				
			}
 
			if (prevAnimation != _animation)
				onAnimationChange.dispatch();
		}
 
		/**
		 * Returns the absolute walking speed, taking moving platforms into account.
		 * Isn't super performance-light, so use sparingly.
		 */
		public function getWalkingSpeed():Number {
 
			var groundVelocityX:Number = 0;
			for each (var groundContact:b2Fixture in groundContacts) {
				groundVelocityX += groundContact.GetBody().GetLinearVelocity().x;
			}
 
			return _physicsComponent.body.GetLinearVelocity().x - groundVelocityX;
		}
	}
}
package com.citrusengine.system.components.box2d.hero {
 
	import Box2DAS.Dynamics.ContactEvent;
	import Box2DAS.Dynamics.b2Fixture;
 
	import com.citrusengine.math.MathVector;
	import com.citrusengine.objects.Box2DPhysicsObject;
	import com.citrusengine.objects.platformer.box2d.Baddy;
	import com.citrusengine.system.components.box2d.CollisionComponent;
 
	/**
	 * The Box2D Hero collision component. We need to access informations of the hero view &amp; movement &amp; physics component.
	 */
	public class HeroCollisionComponent extends CollisionComponent {
 
		protected var _viewComponent:HeroViewComponent;
		protected var _movementComponent:HeroMovementComponent;
		protected var _physicsComponent:HeroPhysicsComponent;
 
		protected var _enemyClass:Class = Baddy;
		protected var _combinedGroundAngle:Number = 0;
 
		public function HeroCollisionComponent(name:String, params:Object = null) {
 
			super(name, params);
		}
 
		override public function initialize():void {
 
			super.initialize();
 
			_viewComponent = entity.components["view"];
			_movementComponent = entity.components["move"];
			_physicsComponent = entity.components["physics"];
		}
 
		override public function destroy():void {
 
			super.destroy();
		}
 
		override public function handlePreSolve(e:ContactEvent):void {
 
			super.handlePreSolve(e);
 
			if (!_movementComponent.ducking)
				return;
 
			var other:Box2DPhysicsObject = e.other.GetBody().GetUserData() as Box2DPhysicsObject;
 
			var heroTop:Number = _physicsComponent.y;
			var objectBottom:Number = other.y + (other.height / 2);
 
			if (objectBottom &lt; heroTop)
				e.contact.Disable();
		}
 
		override public function handleBeginContact(e:ContactEvent):void {
 
			super.handleBeginContact(e);
 
			var collider:Box2DPhysicsObject = e.other.GetBody().GetUserData();
 
			if (_enemyClass &amp;&amp; collider is _enemyClass) {
 
				if (_physicsComponent.body.GetLinearVelocity().y &lt; _movementComponent.killVelocity &amp;&amp; !_movementComponent.isHurt) 					_movementComponent.hurt(collider); 				else 					_movementComponent.giveDamage(collider); 			} 			 			//Collision angle 			if (e.normal) //The normal property doesn't come through all the time. I think doesn't come through against sensors. 			{ 				var collisionAngle:Number = new MathVector(e.normal.x, e.normal.y).angle * 180 / Math.PI; 				if (collisionAngle &gt; 45 &amp;&amp; collisionAngle &lt; 135)
				{
					_viewComponent.groundContacts.push(e.other);
					_movementComponent.onGround = true;
					updateCombinedGroundAngle();
				}
			}
		}
 
		override public function handleEndContact(e:ContactEvent):void {
 
			super.handleEndContact(e);
 
			//Remove from ground contacts, if it is one.
			var index:int = _viewComponent.groundContacts.indexOf(e.other);
			if (index != -1)
			{
				_viewComponent.groundContacts.splice(index, 1);
				if (_viewComponent.groundContacts.length == 0)
					_movementComponent.onGround = false;
				updateCombinedGroundAngle();
			}
		}
 
		protected function updateCombinedGroundAngle():void {
 
			_combinedGroundAngle = 0;
 
			if (_viewComponent.groundContacts.length == 0)
				return;
 
			for each (var contact:b2Fixture in _viewComponent.groundContacts)
				var angle:Number = contact.GetBody().GetAngle();
 
			var turn:Number = 45 * Math.PI / 180;
			angle = angle % turn;
			_combinedGroundAngle += angle;
			_combinedGroundAngle /= _viewComponent.groundContacts.length;
		}
 
		public function get combinedGroundAngle():Number {
			return _combinedGroundAngle;
		}
	}
}

There are interactions between components, here the rubber hits the road. We don’t have separate components as it would be and data’s component may be changed from an other component, but you can easily add a new functionality (climb a ladder for example) and handle it. Easier to manage but, that’s just OOP, isn’t it?

Oh and wait! An entity/component system is designed to be modular. It means that you can add functionalities on the fly, and not programming everything into 4 – 6 components with lots of conditions if the ability/behavior is added. Yep, I know. I failed. I didn’t find an easy way to do that with all this Box2D stuff.

To conclude, this experiment was very rewarding even if I didn’t success to achieve it. Don’t hesitate to comment to put me on the right direction!!

Concerning the Citrus Engine, we are close to the 2nd beta for the V3 : lots of bug fixes, performance enhancement, Nape support, Flash Pro support as level editor is even better and this attempt of entity/component system which might be useful… or not.

Citrus Engine on Nape and physics performance improvement

15

Hi folks! Since my studies are over and my new portfolio online, I have time to focus on personal projects. Yep, it was time to contribute again to the Citrus Engine. I’ve worked 3 days at full time focusing on its big issue : mobile performances. I’m glad to say that now they are just an old memories!

6 months ago, I’ve made the CE compatible with Stage3D thanks to Starling and added some cool stuff. CitrusEngineV3 BETA 1 has been downloaded 3047 in 6 months, that’s not bad! However it didn’t see lots of Stage3D game, because it was missing the point : people wants to make mobile games.

You will find all the sources at the end.

Nape
Nape is Luca Deltodesco‘s open-source physics engine written in haXe with the help of his preprocessor caXe. It is written with AVM2 in mind for performance, and utilising another part of his build chain provides a seamless API between AS3 and haXe though it does compile with hxcpp with other targets unknown. The idea behind Nape is to provide a high-performance, powerful and moreover friendly and safe physics engine.

Nape is faster than Box2D, and easier to handle. Adding it in the CE wasn’t hard, and we use it the same way than Box2D. Using Box2D (Alchemy version) & Stage3D with Starling in the CE you must take a look on Adobe Flash Player Premium announcement if you are targetting Browser games. With Nape, you are no more related to the “premium features license” from Adobe.

A NapeStarlingGameState Class exemple :

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
package  
{
	import com.citrusengine.core.StarlingState;
	import com.citrusengine.objects.NapePhysicsObject;
	import com.citrusengine.objects.platformer.nape.Platform;
	import com.citrusengine.physics.Nape;
 
	import starling.display.Image;
	import starling.events.Touch;
	import starling.events.TouchEvent;
	import starling.events.TouchPhase;
	import starling.textures.Texture;
 
	/**
	 * ...
	 * @author Aymeric
	 */
	public class NapeStarlingGameState extends StarlingState 
	{
		[Embed(source="../bin/small_crate.png")]
		private var _cratePng:Class;
 
		public function NapeStarlingGameState() 
		{
			super();
		}
 
		override public function initialize():void {
 
			super.initialize();
 
			var nape:Nape = new Nape("nape");
			//nape.visible = true; -> to see the debug view!
			add(nape);
 
			add(new Platform("borderBottom", { x:0, y:stage.stageHeight - 10, width:stage.stageWidth, height:10 } ));
			add(new Platform("borderLeft", { x:0, y:0, width:10, height:stage.stageHeight } ));
			add(new Platform("borderRight", { x:stage.stageWidth - 10, y:0, width:10, height:stage.stageHeight } ));
 
			stage.addEventListener(TouchEvent.TOUCH, _addObject);
		}
 
		private function _addObject(tEvt:TouchEvent):void {
 
			var touch:Touch = tEvt.getTouch(stage, TouchPhase.BEGAN);
 
			if (touch) {
 
				var image:Image = new Image(Texture.fromBitmap(new _cratePng()));
 
				var physicObject:NapePhysicsObject = new NapePhysicsObject("physicobject", { x:touch.getLocation(this).x, y:touch.getLocation(this).y, width:35, height:38, view:image} );
				add(physicObject);
			}
 
		}
 
	}
 
}

There wouldn’t be any CE’s API difference if you used Box2D (except for package/class names).

Bad mobile performances’ end
So Nape was amazing powerful in the CE? Yes and no. The performance was better than Box2D, but not as good as I’d like. Troubled, I started thinking there was a bad “architecture” in the Citrus Engine… and there was one. The solution came from my Haxe NME port, or more precisely a simple Box2D’s Haxe NME project on my desktop. This one had better performance than my port, on very simple test. The Box2D’s debug view changed object’s color after some time ; not on mine! Sleeping object… something prevented objects to sleep. After some investigations, it was the way how gravity was settled : no gravity in World/Space but one applied on each dynamic objects :

/**
 * You should override this method to extend the functionality of your physics object. This is where you will 
 * want to do any velocity/force logic. By default, this method also updates the gravitatonal effect on the object.
 * I have chosen to implement gravity in each individual object instead of globally via Box2D so that it is easy
 * to create objects that defy gravity (like birds or bullets). This is difficult to do naturally in Box2D. Instead,
 * you can simply set your PhysicsObject's gravity property to 0, and baddabing: no gravity. 
 */		
override public function update(timeDelta:Number):void
{
	if (_bodyDef.type == b2Body.b2_dynamicBody)
	{
		var velocity:V2 = _body.GetLinearVelocity();
		velocity.y += gravity;
		_body.SetLinearVelocity(velocity);
	}
}

Easier to manage different objects’ gravity, but no sleeping objects. Now the gravity is set in the World/Space creation and this update function is empty. That was it!

Performance tests
Ok now everything is correct, it’s time to give some numbers! Don’t hesitate to give yours! The tests are based on the code above and use an iPhone 4S.
Citrus Engine with Starling & Box2D : 50 dynamic objects = 50FPS.
CitruxEngine with Box2D : 67 dynamic objects = 50FPS.
Citrus Engine with Starling & Nape : 80 dynamic objects = 50FPS.
I’ve not implemented Nape on the CitruxEngine yet.
Note that I was compiling for 60FPS, targetting 50 FPS just to be sure that performance decrease. Important, the 50FPS is stable when objects are sleeping. Since they are all in contact each other, the framerate drop fast.

Physics engines cohabitation
The Citrus Engine is built with a “platformer” starter-kit using Box2D, which you can use to easily make awesome 2D sidescrolling games. I’ve started to port some platformer objects on Nape. That would be really stupid to delete Box2D support since Nape is more powerful. Remember the Citrus Engine with Box2D is performing very well on Desktop & Browser games. So it’s time for a cohabitation! There is a (little) file size difference using 2 physics engines instead of one (250 Ko for a SWF, 700 Ko for an IPA), but primarily I didn’t detect a performance drop! For the final release, you may remove the other engine stuff, it takes less than 2 minutes.
At the moment, Nape class name have “Nape” in front of them or are in a Nape package (platformer objects). I will do the same with Box2D objects.

Future
I will port some Box2D platformer objects into Nape, and later offer the Citrus Engine V3 BETA 2. The idea is to have the same behavior with both physics engines. Community help is always greatly appreciated ;)
The CitruxEngine will drop Box2D support to use only Nape since I had an endless bug with it. Remember, this Haxe NME port is not ready for production but don’t be shy, get involved :D
An optional entity/component system is always something jumping in my head, I hope to be able to offer one soon.

Sources & demo
Citrus Engine with Nape & Starling ; Browser live demo
Citrus Engine with Box2D & Starling ; Browser live demo
CitruxEngine with Box2D ; Browser live demo

Grab the last Citrus Engine update on its Google Code!

The Citrus Engine goes on Haxe NME, welcome to the CitruxEngine

3

One month ago I started to work on the CitruxEngine. I was very confident with Haxe performance on mobile and NME cross platform opportunities. And now, I can say those are awesome!

In April 14-15th, I was in Paris to assist to the Haxe conf 2012. It was really cool, Silex Labs has made a good job! Conferences were very interested and the community greatly friendly. And I had the opportunity to make a lightning talk concerning my contribution to the Citrus Engine 2D game framework and its port on Haxe NME. The presentation was a bit from scratch, but that was a good experience! I’m very happy to be the first to start the lightning talk, since there were very serious projects :D

CitruxEngine Github.
CitruxEngine Demo. Simple demo which have been tested on Flash & CPP (using left/right key and spacebar) and iOS (touch & accelerometer). There are sound, animations (idle, walk and jump) and physics.
The port is currently not finished!

I will not present some code here. If you are already familiar with the Citrus Engine, there will be no problem. Take a look on the example on Github.

HTML5
When I started the port, I would the CitruxEngine be as cross platform as possible. HTML5 is promising, and Niel Drummond the man behind Jeash has made an incredible job! However I found that the Haxe NME Box2D port has not very good performance with HTML5. So I’ve dropped the HTML5 target at the moment, but I keep an eye on Jeash!

Box2D
Thanks to Haxe NME, Box2D runs very well on mobile! This is mostly the reason why I’ve started the CitruxEngine. The Citrus Engine uses the AS3 Alchemy version of Box2D which has some differences with the original. It seems there is a bug with the Haxe NME Box2D port : the beginContact & endContact listeners are fired all the time if a dynamic body is on a static body (like a hero on a platform), whereas it fires only once if there are 2 dynamics bodies. This behavior is blocking me.

SpriteSheets
I made my test with the spritesheet haxelib which uses SpriteLoq. At the moment it works well. I’ve not made serious test with animations.

Level Editor
I love how the Citrus Engine handles Flash Pro as a Level Editor. I would like the Haxe NME version handles it as well. But at the moment we can’t read AS3 code in a SWF file (it is the way that class and properties are defined), so I need to think to an external way. Maybe it’s time to reconsider the Level Architect!

Console
The console is really a cool feature of the Citrus Engine, it will be available in the CitruxEngine too. I’ve started to implement it, but it’s not ready yet.

I will continue to work on the CitruxEngine, but now I’ve to focus on my school project using Objective-C, it’s really hard to get back when you have tested Haxe power ! In less than two months I will be graduate and looking for a job, future is exciting.

Thanks to postite & elsassph for their help!

From Box2D to Chipmunk

2

Hey there, I’ve been very busy this last weeks working hard on my 2nd school year project, a mobile game. In a few weeks, I will explain it, but for now let’s do some programming stuff!
The game is a side-scrolling 2D game. See Canabalt or Jetpack Joyride, they are great games!
Developing for iOS, I used Sparrow framework. It is awesome, and really easy to learn coming from AS3 & Starling.

Why did I develop in Objective-C since AIR 3.2 is out ? Refer to my previous post about my personal preference and the future of web development ; I decided to learn Objective-C and this project is really a good opportunity! I have followed the news about HTML5 and my opinion didn’t change… Moreover, for a long range project it is preferable to have the best workflow!

Since I used the Citrus Engine, I felt in love with physics engine and particularly Box2D. It is very useful & powerful for game development, but it has some hard constraints. With this project, I wanted to try an other physics engine. My choice was Chipmunk.

This post will not compare features, there are already a great post there, but how to move quickly from Box2D to Chipmunk. Thanks Scott Lembcke (Chipmunk’s author), for your clarifications.

QUICK OVERVIEW :
- Box2D : Box2D is an open source C++ engine for simulating rigid bodies in 2D. Box2D is developed by Erin Catto and has the zlib license. While the zlib license does not require acknowledgement, we encourage you to give credit to Box2D in your product. The manual.
- Chipmunk : Chipmunk is a fast and lightweight 2D rigid body physics library written in C. The documentation.

UNITS :
Box2D uses meter/kilogram/second (MKS), Chipmunk uses pixel. There is no units for the mass, you defined the value you want, but stay logical between objects. The time is not clearly mentioned in Chipmunk, it doesn’t express in seconds but floats. Box2D uses real world units because it has a number of tuning threshold values, and the default values are set to be sane values for life-sized objects. Chipmunk’s algorithms mostly avoid the need for tuning values so that you can use whatever arbitrary units makes most sense to you (pixels, meters, inches, etc). Likewise for time and mass.

SET UP WORLD :
Box2D uses the term “world” whereas Chipmunk uses “space”. Both defined gravity & iteration step. Body’s gravity is difficult to manage in Box2D and Chipmunk if you want your objects to have a different one. You may set up a gravity(0, 0) to your world/space and manage the gravity into each object using a variable and updating its velocity.

REGISTRATION POINT :
Both have body’s center as registration point.

RIGID BODIES :
Box2D uses two objects to define a body : body (user data, position, velocity, forces, torques, impulse…) & bodyDef (body type definitions, and init values). Chipmunk uses only one object defining mass (which is automatically calculated in Box2D) and moment which represent inertia for the body.
Box2D has 3 body types : static, kinematic, dynamic ; Chipmunk kinematic bodies are named rogue.

FIXTURES :
Box2D fixture/fixtureDef defined shape, density, friction, restitution and filters. There is no fixture in Chipmunk. Restitution is the elasticity property on shapes. It doesn’t store density on a per shape basis though. You have to calculate that into the mass manually..

SHAPES :
In Chipmunk, you can attach as many shapes to a single body as you need to in order to define a complex shape. Shapes contain the surface properties of an object such as how much friction or elasticity it has. It means than you can create a simple platform body and add all the shapes to it even if they are not close (a border bottom, a wall…).

COLLISIONS :
With Box2D you can know dynamically which is the other body you collide, in Chipmunk you may use this method too. You may also defined a collision handler’s function between the typeA and the typeB with function references defining collision start/end & pre/post solve. In Box2D you managed collision thanks to the fixture, in Chipmunk you add the “listener” to the space.

Go to Top