Create combination of physics objects in Citrus Engine

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 😀 Anyway it shouldn’t prevent you to use this method!

Leave a Reply

Your email address will not be published. Required fields are marked *