Starting with CitrusEngine 2

Today I will introduce the CitrusEngine framework to make Flash Game. CitrusEngine is really great to create platform games with physical engine, it uses box2D with alchemy for better performance !

What I did in less than 2 hours : the game. Don’t forget to click in to enable keyboard.

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
package {
 
	import com.citrusengine.core.CitrusEngine;
 
	/**
	 * @author Aymeric
	 */
	public class Main extends CitrusEngine {
 
		private var _hud:Hud;
 
		private var _countCoins:uint;
		private var _countLifes:uint;
 
		public function Main() {
 
			super();
 
			_hud = new Hud();
			addChild(_hud);
 
			_restartGame(); //launch the game
		}
 
		private function _lifeLost(gEvt:GameEvent):void {
 
			--_countLifes;
			_hud.scoreLife = _countLifes;
 
			if (_countLifes == 0) {
				_restartGame();
			}
		}
 
		private function _coinTaken(gEvt:GameEvent):void {
 
			++_countCoins;
			_hud.scoreCoin = _countCoins;
 
			if (_countCoins == GameConst.nbrCoins) {
				_restartGame();
			}
		}
 
		private function _restartGame(gEvt:GameEvent = null):void {
 
			state = new GameState(); //specify the level
 
			_countCoins = 0;
			_hud.scoreCoin = _countCoins;
 
			_countLifes = GameConst.nbrLifes;
			_hud.scoreLife = _countLifes;
 
			state.addEventListener(GameEvent.RESTART_GAME, _restartGame);
			state.addEventListener(GameEvent.TAKE_COIN, _coinTaken);
			state.addEventListener(GameEvent.LOSE_LIFE, _lifeLost);
		}
	}
}

Nothing too hard into this Document class. If we had many levels, we should create several GameState and specify the state; in the _restartGame function it would be a parameter for the 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
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
package {
 
	import Box2DAS.Dynamics.ContactEvent;
 
	import com.citrusengine.core.State;
	import com.citrusengine.math.MathVector;
	import com.citrusengine.objects.CitrusSprite;
	import com.citrusengine.objects.platformer.Baddy;
	import com.citrusengine.objects.platformer.Coin;
	import com.citrusengine.objects.platformer.Hero;
	import com.citrusengine.objects.platformer.Platform;
	import com.citrusengine.objects.platformer.Sensor;
	import com.citrusengine.physics.Box2D;
 
	/**
	 * @author Aymeric
	 */
	public class GameState extends State {
 
		private var _hero:Hero;
		private var _ennemi:Baddy;
 
		public function GameState() {
			super();
		}
 
		override public function initialize():void {
 
			var box2D:Box2D = new Box2D("Box2D");
			add(box2D);
			box2D.visible = true; //Display Box2D object
 
			_hero = new Hero("Hero");
			add(_hero);
			_hero.x = 200;
			_hero.y = 200;
			_hero.onTakeDamage.add(_hurt);
 
			_ennemi = new Baddy("Ennemi");
			add(_ennemi);
			_ennemi.x = 400;
			_ennemi.y = 200;
 
			var background:CitrusSprite = new CitrusSprite("Background", {view:BackgroundArt, parallax:0.5});
			add(background);
 
			var platform1:Platform = new Platform("Platform1", {width:500, height:20});
			add(platform1);
			platform1.x = 100;
			platform1.y = 300;
 
			var platform2:Platform = new Platform("Platform2", {width:500, height:20});
			add(platform2);
			platform2.x = 500;
			platform2.y = 200;
 
			var sensor:Sensor = new Sensor("Reset Sensor", {width:2000, height:20});
			add(sensor);
			sensor.onBeginContact.add(_resetLevel);
			sensor.x = 0;
			sensor.y = 400;
 
			var coin:Coin;
			for (var i:uint; i < GameConst.nbrCoins; ++i) {
				coin = new Coin("Coin" + i, {x:i * 50, y:150, width:15, height:15});
				add(coin);
				coin.onBeginContact.add(_takeCoin);
			}
 
			view.cameraTarget = _hero;
			view.cameraOffset = new MathVector(200, 200);
			view.cameraEasing.y = 0.05;
		}
 
		private function _hurt():void {
			this.dispatchEvent(new GameEvent(GameEvent.LOSE_LIFE));
		}
 
		private function _takeCoin(cEvt:ContactEvent):void {
			this.dispatchEvent(new GameEvent(GameEvent.TAKE_COIN));
		}
 
		private function _resetLevel(cEvt:ContactEvent):void {
 
			if (cEvt.other.GetBody().GetUserData() is Hero) {
 
				this.dispatchEvent(new GameEvent(GameEvent.RESTART_GAME));
			}
		}
	}
}

As usual, it’s the box2D line the most complicated :

if (cEvt.other.GetBody().GetUserData() is Hero)

Otherwise it’s really easy to create a simple level with many platforms. The sensor can be used for several other things : for example if you don’t want the baddy falls, you add one sensor on each corner and change its direction. It can be also used to display a pop up like the game on the CitrusEngine’s website.
For adding some graphics to a shape, we just need to mention it with the key word view. For instance :

var background:CitrusSprite = new CitrusSprite("Background", {view:BackgroundArt, parallax:0.5});

The BackroundArt is a class, it doesn’t need to be instantiated. I’m sure that you have noticed the parallax effect too 😉

CitrusEngine uses also Signal’s library of Robert Penner. It has many nice features, however I never use it before. It seems to be really cool, so I will dig in it too 🙂

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
package {
 
	import flash.display.Shape;
 
	/**
	 * @author Aymeric
	 */
	public class BackgroundArt extends Shape {
 
		public function BackgroundArt() {
 
			this.graphics.clear();
			this.graphics.beginFill(0x0000FF, 0.5);
			this.graphics.drawRect(50, 50, 100, 50);
			this.graphics.drawRect(250, 50, 70, 20);
			this.graphics.drawRect(460, 20, 140, 70);
			this.graphics.endFill();
		}
	}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
package {
	/**
	 * @author Aymeric
	 */
	public class GameConst {
 
		private static const _NBR_COINS:uint = 5;
 
		private static const _NBR_LIFES:uint = 3;
 
		public static function get nbrCoins():uint {
			return _NBR_COINS;
		}
 
		public static function get nbrLifes():uint {
			return _NBR_LIFES;
		}
 
	}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
package {
 
	import flash.events.Event;
 
	/**
	 * @author Aymeric
	 */
	public class GameEvent extends Event {
 
		public static const RESTART_GAME:String = "RESTART_GAME";
		public static const LOSE_LIFE:String = "LOSE_LIFE";
		public static const TAKE_COIN:String = "TAKE_COIN";
 
		public function GameEvent(type:String, bubbles:Boolean = false, cancelable:Boolean = false) {
			super(type, bubbles, cancelable);
		}
	}
}
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
package {
 
	import flash.display.Sprite;
	import flash.text.TextField;
 
	/**
	 * @author Aymeric
	 */
	public class Hud extends Sprite {
 
		private var _nbrLife:TextField;
		private var _nbrCoin:TextField;
 
		public function Hud() {
 
			var life:TextField = new TextField();
			addChild(life);
			life.text = "Life :";
 
			_nbrLife = new TextField();
			addChild(_nbrLife);
			_nbrLife.x = 30;
 
			var coin:TextField = new TextField();
			addChild(coin);
			coin.text = "Coin :";
			coin.x = 500;
 
			_nbrCoin = new TextField();
			addChild(_nbrCoin);
			_nbrCoin.x = 530;
		}
 
		public function set scoreLife($value:uint):void {
			_nbrLife.text = String($value);
		}
 
		public function set scoreCoin($value:uint):void {
			_nbrCoin.text = String($value);
		}
	}
}

That’s all for this first try with the new CitrusEngine, it is very promising. Yet I’ve noticed that one feature is missing : to stop and resume the game. It is usefull if we manage an inventory for example.

state.pause();
// OR
CitrusEngine.getInstance().pause();
// Would be welcome :-)

In a future tutorial, I will add graphics, made animation and start my game school project… finally 😉

P.S. You can download the zip.

6 thoughts on “Starting with CitrusEngine 2

  1. Great Tutorial
    but how to fix this error ?

    C:\Users\Cholid\Downloads\CitrusArt\CitrusArt\src\Box2DAS\Collision\b2Distance.as, Line 10 1172: Definition cmodule.Box2D could not be found.

    Thanks

Leave a Reply

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