Today this is a new tutorial on the great Citrus Engine framework. Before starting my school project, I wanted to try using box2D inside the Citrus Engine, so I will show you how create a breakout game !

Click here to play the game, don’t forget to click in the swf to enable keyboard.

But first of all, I would like to come back on my previous tutorial on the Citrus Engine, and add some comments:
- instead of using sensor to change the way that baddy moves (if they will fall), you can now specify a limit with leftBound and rightBound parameters. A nice feature added by Eric !
- it is possible to create cloud plateform by setting the proprety oneWay to true on a Plateform. It will enable you to jump on even if your are below.
- with the new update we can create a MovingPlatform as a new class of the core engine.
- the pause is now enable : playing = false ;-)
- a console have been added to the Citrus Engine, you can access it by pressing the TAB key. You can change properties on the fly (for example : set Box2D visible false) and add your own command :

this.console.addCommand("fullscreen", _fullscreen);
this.console.addCommand("play", _playGame);
 
private function _fullscreen():void {
	stage.displayState = "fullScreen";
}
 
private function _playGame():void {
	this.playing = !this.playing;
}

Ok, now let start the tutorial. With the Citrus Engine, we can use Flash IDE as a Level Editor. It is really easy to use it and so powerful ! You can save lot of time.

Create a fla and add MovieClip on the stage for the Plateforms, Ball, Bricks… Inside each MovieClip specify the class name :

var className = "Ball"
var params = {
	radius:15
}

You can add easily some parms. This is my stage
Our level is now created.

Now let’s start coding :
The Main 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
package {
 
	import com.citrusengine.core.CitrusEngine;
 
	import flash.display.Loader;
	import flash.display.MovieClip;
	import flash.events.Event;
	import flash.net.URLRequest;
 
	/**
	 * @author Aymeric
	 */
	public class Main extends CitrusEngine {
 
		private var _level:MovieClip;
		private var _hud:Hud;
		private var _countBricks:uint;
 
		public function Main() {
 
			super();
 
			_hud = new Hud();
			addChild(_hud);
 
			this.console.addCommand("fullscreen", _fullscreen);
			this.console.addCommand("play", _playGame);
 
			var loader:Loader = new Loader();
			loader.load(new URLRequest("LevelA.swf"));
			loader.contentLoaderInfo.addEventListener(Event.COMPLETE, _levelLoaded);
		}
 
		private function _levelLoaded(evt:Event):void {
 
			_level = evt.target.loader.content;
			_restartGame();
 
			evt.target.loader.unloadAndStop();
		}
 
		private function _fullscreen():void {
			stage.displayState = "fullScreen";
		}
 
		private function _playGame():void {
			this.playing = !this.playing;
		}
 
		private function _brickTaken(gEvt:GameEvent):void {
 
			++_countBricks;
			_hud.scoreCoin = _countBricks;
 
			if (_countBricks == GameConst.nbrCoins) {
				_restartGame();
			}
		}
 
		private function _restartGame(gEvt:GameEvent = null):void {
 
			state = new GameState(_level);
 
			_hud.scoreCoin = _countBricks = 0;
 
			state.addEventListener(GameEvent.RESTART_GAME, _restartGame);
			state.addEventListener(GameEvent.TAKE_BRICK, _brickTaken);
		}
	}
}

Compared to the last tutorial the main change are console command and the loader for our level.

Now the GameState which show how access to our MovieClip’s level.

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
package {
 
	import Box2DAS.Dynamics.ContactEvent;
 
	import com.citrusengine.core.CitrusObject;
	import com.citrusengine.core.State;
	import com.citrusengine.objects.platformer.Sensor;
	import com.citrusengine.physics.Box2D;
	import com.citrusengine.utils.ObjectMaker;
 
	import flash.display.MovieClip;
 
	/**
	 * @author Aymeric
	 */
	public class GameState extends State {
 
		private var _level:MovieClip;
 
		public function GameState(level:MovieClip) {
 
			super();
			_level = level;
		}
 
		override public function initialize():void {
 
			super.initialize();
 
			var box2D:Box2D = new Box2D("Box2D");
			add(box2D);
			box2D.visible = true;
 
			// Create objects from our level
			ObjectMaker.FromMovieClip(_level);
 
			var ball:Ball = Ball(getFirstObjectByType(Ball));
			ball.gravity = 0;
			ball.onEndContact.add(_takeBrick);
 
			var sensor:Sensor = Sensor(getFirstObjectByType(Sensor));
			sensor.onBeginContact.add(_resetLevel);
 
			var bricks:Vector.<CitrusObject> = getObjectsByType(Brick);
			for each (var brick:Brick in bricks) {
				brick.onEndContact.add(_takeBrick);
			}
		}
 
		private function _takeBrick(cEvt:ContactEvent):void {
 
			if (cEvt.other.GetBody().GetUserData() is Brick) {
				remove(cEvt.other.GetBody().GetUserData());
				this.dispatchEvent(new GameEvent(GameEvent.TAKE_BRICK));
			}
		}
 
		private function _resetLevel(cEvt:ContactEvent):void {
 
			if (cEvt.other.GetBody().GetUserData() is Ball) {
				this.dispatchEvent(new GameEvent(GameEvent.RESTART_GAME));
			}
		}
	}
}

It is really easy to access to our objects from the swf with these functions : getObjectsByType, getObjectByName, getFirstObjectByType. I put ball’s gravity to 0 to handle physics movement by myself. Nothing too complex here, let’s start to create our first Citrus Engine object : a brick !

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
package {
 
	import Box2DAS.Dynamics.ContactEvent;
 
	import com.citrusengine.objects.platformer.Platform;
 
	import org.osflash.signals.Signal;
 
	/**
	 * @author Aymeric
	 */
	public class Brick extends Platform {
 
		public var onEndContact:Signal;
 
		public function Brick(name:String, params:Object = null) {
 
			super(name, params);
			onEndContact = new Signal(ContactEvent);
		}
 
		override public function destroy():void {
 
			onEndContact.removeAll();
			_fixture.removeEventListener(ContactEvent.END_CONTACT, handleEndContact);
			super.destroy();
		}
 
		override protected function createFixture():void {
 
			super.createFixture();
			_fixture.m_reportEndContact = true;
			_fixture.addEventListener(ContactEvent.END_CONTACT, handleEndContact);
		}
 
		protected function handleEndContact(e:ContactEvent):void {
			onEndContact.dispatch(e);
		}
	}
}

A brick is a simple Plateform which reports if something collides with it.

Now the paddle :

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
package {
 
	import Box2DAS.Common.V2;
	import Box2DAS.Dynamics.b2Body;
	import Box2DAS.Dynamics.b2BodyDef;
 
	import com.citrusengine.objects.PhysicsObject;
 
	import flash.ui.Keyboard;
 
	/**
	 * @author Aymeric
	 */
	public class Paddle extends PhysicsObject {
 
		public var acceleration:Number = 1;
		public var maxVelocity:Number = 7;
 
		private var _friction:Number = 0;
		private var _playerMovingHero:Boolean = false;
 
		public function Paddle(name:String, params:Object = null) {
			super(name, params);
		}
 
		override public function update(timeDelta:Number):void {
 
			super.update(timeDelta);
 
			var velocity:V2 = _body.GetLinearVelocity();
 
			var moveKeyPressed:Boolean = false;
 
			if (_ce.input.isDown(Keyboard.RIGHT)) {
				velocity.x += (acceleration);
				moveKeyPressed = true;
			}
 
			if (_ce.input.isDown(Keyboard.LEFT)) {
				velocity.x -= (acceleration);
				moveKeyPressed = true;
			}
 
			if (moveKeyPressed && !_playerMovingHero) {
				_playerMovingHero = true;
				_fixture.SetFriction(0);
				// Take away friction so he can accelerate.
			} else if (!moveKeyPressed && _playerMovingHero) {
				_playerMovingHero = false;
				_fixture.SetFriction(_friction);
				// Add friction so that he stops running
			}
 
			// Cap velocities
			if (velocity.x > (maxVelocity))
				velocity.x = maxVelocity;
			else if (velocity.x < (-maxVelocity))
				velocity.x = -maxVelocity;
 
			// update physics with new velocity
			_body.SetLinearVelocity(velocity);
		}
 
		override protected function defineBody():void {
 
			_bodyDef = new b2BodyDef();
			_bodyDef.type = b2Body.b2_kinematicBody;
			_bodyDef.position.v2 = new V2(_x, _y);
			_bodyDef.angle = _rotation;
			_bodyDef.fixedRotation = true;
			_bodyDef.allowSleep = false;
		}
 
		override protected function defineFixture():void {
 
			super.defineFixture();
			_fixtureDef.friction = _friction;
			_fixtureDef.restitution = 1;
		}
 
		override protected function createFixture():void {
 
			super.createFixture();
			_fixture.m_reportBeginContact = true;
			_fixture.m_reportEndContact = true;
		}
	}
}

The Paddle may extends Citrus’ Hero class, but I prefer to extends PhysicsObject and copy/paste some properties and function from Hero class. It is lighter. I use a kinematic body instead of a dynamic body because of collision forces : with a dynamic body, my paddle moves to the bottom after each collision with the ball, whereas with a kinematic body it doesn’t.

And finally the ball :

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
package {
 
	import Box2DAS.Collision.Shapes.b2CircleShape;
	import Box2DAS.Common.V2;
	import Box2DAS.Dynamics.ContactEvent;
	import Box2DAS.Dynamics.b2FixtureDef;
 
	import com.citrusengine.objects.PhysicsObject;
 
	import org.osflash.signals.Signal;
 
	/**
	 * @author Aymeric
	 */
	public class Ball extends PhysicsObject {
 
		public var onBeginContact:Signal;
		public var onEndContact:Signal;
 
		private var _radius:Number;
		private var _linearVelocity:V2;
 
		private var _afterFirstBounce:Boolean = false;
 
		public function Ball(name:String, params:Object = null) {
 
			super(name, params);
 
			onBeginContact = new Signal(ContactEvent);
			onEndContact = new Signal(ContactEvent);
 
			_linearVelocity = _body.GetLinearVelocity();
			_linearVelocity.y = 5;
		}
 
		override public function destroy():void {
 
			onBeginContact.removeAll();
			onEndContact.removeAll();
			super.destroy();
		}
 
		override protected function createShape():void {
 
			_shape = new b2CircleShape();
			b2CircleShape(_shape).m_radius = _radius;
		}
 
		override public function update(timeDelta:Number):void {
 
			if (_afterFirstBounce == true) {
				_linearVelocity = _body.GetLinearVelocity();
			}
			_body.SetLinearVelocity(_linearVelocity);
		}
 
		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);
		}
 
		override protected function defineFixture():void {
 
			_fixtureDef = new b2FixtureDef();
			_fixtureDef.shape = _shape;
			_fixtureDef.density = 1;
			_fixtureDef.friction = 0;
			_fixtureDef.restitution = 1;
		}
 
		private function handleBeginContact(cEvt:ContactEvent):void {
 
			onBeginContact.dispatch(cEvt);
 
			var diff:Number;
			var ratio:Number;
			var speed:Number;
 
			if (cEvt.other.GetBody().GetUserData() is Paddle) {
				_afterFirstBounce = true;
				// get a reference to the paddle
				var paddle:Paddle = cEvt.other.GetBody().GetUserData() as Paddle;
 
				// only change collisions that are against the top surface of the paddle (let it shank off if it hits an edge).
				var collisionNormal:V2 = cEvt.getWorldManifold().normal;
				if (collisionNormal.x == 0 && collisionNormal.y == 1) {
					diff = x - paddle.x;
					// x distance between ball and paddle.
					ratio = diff / (paddle.width / 2);
 
					// distance as a ratio (between -1 and 1)
					if (ratio < -1 || ratio > 1) // it will shank.
						return;
 
					// Create a new velocity vector and set it to the ball's current speed.
					// var velocity:V2 = _body.GetLinearVelocity();
					_linearVelocity = _body.GetLinearVelocity();
					speed = _linearVelocity.length();
 
					// set the ball to be going straight up at the same speed as it came.
					_linearVelocity = new V2(0, -1);
					_linearVelocity.normalize(speed);
 
					// rotate the velocity angle between a specific range (I chose 1 radian) as a proportion ball's distance from center of paddle.
					_linearVelocity.rotate(1 * ratio);
					_body.SetLinearVelocity(_linearVelocity);
				}
			}
		}
 
		private function handleEndContact(cEvt:ContactEvent):void {
			onEndContact.dispatch(cEvt);
		}
 
		public function get radius():Number {
			return _radius * _box2D.scale;
		}
 
		public function set radius(value:Number):void {
			_radius = value / _box2D.scale;
		}
	}
}

A big thanks to Eric for helping me with the ball movement !
With a restitution set to 1, the ball bounces on each Platform.

Finally it was pretty easy to create my own object inside the Citrus Engine ! For a first try with box2D it was successful too !

By default, we specify a width and a height when we create an Object inside the Citrus Engine, but maybe the radius property should be added into the core to create circle easily.

1
2
3
4
5
6
7
8
9
protected function createShape():void {
	if (_radius == null) {
		_shape = new b2PolygonShape();
		b2PolygonShape(_shape).SetAsBox(_width / 2, _height / 2);
	} else {
		_shape = new b2CircleShape();
		b2CircleShape(_shape).m_radius = _radius;
	}
}

To conclude to this little project, I recommend to everyone interested in AS3 game development to give a try to the Citrus Engine. Moreover Eric is a likeable person who takes time to reply on the forum and help you.

My zip.
The box2D manual.