Update : if you want a version with a SWC for targeting iOS, please refers to this post.

Hey ! I started to make my school game project this week. I advance quickly, it should be fine for the end of june !

For this third tutorial on the Citrus Engine, I explain better how you can use the Flash IDE as Level Editor, and I show two classes that I’ve created for my game. Here is what you will have.

Same as the last tutorial, create a fla and your MovieClips with params. This is my level :
This is my level
Adding platforms is always a difficult task, because of the rectangle’s angle. Try to don’t have corner ! A box2D platform with a rounded edge would be nice !

You can see the box2D objects on the game by opening the console with the TAB key, and write : set Box2D visible true

You should make several layers to handle the depth of field ! You can use a guide with your photoshop level to see it and set your layers. When you export your SWF, the photoshop layer will not be added. Your swf stay light ;-) This is my layers

All my decorations are on several layers. Background, Bg Elements and Grass are big PNG 1409 * 637. I put them on the top left corner. If the length of the level is higher than 4000, you can use negative coordinates. So your level max length will be approximately 7500 px !

We have already seen how you can add a view and params in your MovieClip :

var className = "objects.Roseau";
var params = {
	view: "objects/Roseau.swf"
}
 
var className = "objects.MusicalSensor"
var params = {
	song: "Si"
}

I created 4 MusicalSensor’s MovieClip, because I have 4 songs, I create one MovieClip by song. This is two new objects that I’ve created for my game. I will show their classes later.

Here is my Class Document :

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 {
 
	import com.citrusengine.core.CitrusEngine;
 
	import flash.display.Loader;
	import flash.display.MovieClip;
	import flash.events.Event;
	import flash.net.URLRequest;
	import flash.text.TextField;
 
	/**
	 * @author Aymeric
	 */
	public class Main extends CitrusEngine {
 
		public var loading:TextField;
 
		public function Main() {
 
			super();
 
			sound.addSound("Si", "sounds/si.mp3");
			sound.addSound("Do", "sounds/do.mp3");
			sound.addSound("Re", "sounds/re.mp3");
			sound.addSound("Mi", "sounds/mi.mp3");
 
			var loader:Loader = new Loader();
			loader.load(new URLRequest("levels/LevelA1.swf"));
			loader.contentLoaderInfo.addEventListener(Event.COMPLETE, handleSWFLoadComplete);
		}
 
		private function handleSWFLoadComplete(evt:Event):void {
 
			var levelObjectsMC:MovieClip = evt.target.loader.content;
			state = new GameState(levelObjectsMC, loading);
 
			evt.target.removeEventListener(Event.COMPLETE, handleSWFLoadComplete);
			evt.target.loader.unloadAndStop();
		}
	}
}

The loading TextField come from the stage (not the level stage). I add 4 sounds that I use later.

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
package {
 
	import Box2DAS.Dynamics.ContactEvent;
 
	import com.citrusengine.core.CitrusEngine;
	import com.citrusengine.core.CitrusObject;
	import com.citrusengine.core.State;
	import com.citrusengine.math.MathVector;
	import com.citrusengine.objects.CitrusSprite;
	import com.citrusengine.objects.platformer.Hero;
	import com.citrusengine.objects.platformer.Platform;
	import com.citrusengine.objects.platformer.Sensor;
	import com.citrusengine.physics.Box2D;
	import com.citrusengine.utils.ObjectMaker;
 
	import objects.MusicalSensor;
	import objects.Roseau;
 
	import flash.display.MovieClip;
	import flash.geom.Rectangle;
	import flash.text.TextField;
 
	/**
	 * @author Aymeric
	 */
	public class GameState extends State {
 
		private var _ce:CitrusEngine;
		private var _levelObjectsMC:MovieClip;
		private var _loading:TextField;
 
		public function GameState(levelObjectsMC:MovieClip, loading:TextField) {
 
			super();
 
			_ce = CitrusEngine.getInstance();
 
			_levelObjectsMC = levelObjectsMC;
			_loading = loading;
 
			var objects:Array = [Platform, Hero, CitrusSprite, Sensor, MusicalSensor, Roseau];
		}
 
		override public function initialize():void {
 
			super.initialize();
 
			var box2D:Box2D = new Box2D("Box2D");
			add(box2D);
 
			view.loadManager.onLoadComplete.addOnce(handleLoadComplete);
 
			ObjectMaker.FromMovieClip(_levelObjectsMC);
 
			var hero:Hero = Hero(getObjectByName("Hero"));
 
			view.setupCamera(hero, new MathVector(320, 240), new Rectangle(0, 0, 1409, 637), new MathVector(.25, .05));
 
			var roseaux:Vector.<CitrusObject> = getObjectsByType(Roseau);
			for each (var roseau:Roseau in roseaux) {
				roseau.onBeginContact.add(_roseauTouche);
				roseau.onEndContact.add(_roseauFin);
			}
 
			var musicalSensors:Vector.<CitrusObject> = getObjectsByType(MusicalSensor);
			for each (var musicalSensor:MusicalSensor in musicalSensors) {
				musicalSensor.onBeginContact.add(_playSound);
			}
		}
 
		override public function update(timeDelta:Number):void {
 
			super.update(timeDelta);
 
			var loaded:int = Math.round(view.loadManager.bytesLoaded / view.loadManager.bytesTotal * 100)
			if (loaded < 100)
				_loading.text = loaded.toString();
			else
				_loading.text = "";
		}
 
		private function handleLoadComplete():void {
			//remove loader
		}
 
		private function _roseauTouche(cEvt:ContactEvent):void {
 
			if (cEvt.other.GetBody().GetUserData() is Hero) {
				cEvt.fixture.GetBody().GetUserData().anim = "white";
			}
		}
 
		private function _roseauFin(cEvt:ContactEvent):void {
 
			if (cEvt.other.GetBody().GetUserData() is Hero) {
				cEvt.fixture.GetBody().GetUserData().anim = "black";
			}
		}
 
		private function _playSound(cEvt:ContactEvent):void {
 
			if (cEvt.other.GetBody().GetUserData() is Hero) {
				_ce.sound.playSound(cEvt.fixture.GetBody().GetUserData().song, 1, 0);
			}
		}
 
	}
}

Roseaux are reeds (it’s French :-) ), when my hero touches them the light change into white, otherwise it’s black. My roseau is a swf with three labels : idle (black), white (black to white) and black (white to black). To change my label into the Citrus Engine, we may use myRoseau.gotoAndPlay(“white”); but it doesn’t work ! Indeed, Citrus objects are always updated in an enter frame so it “stays” on the same frame ! To do that, we use the animation property of Citrus’ objects :

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
package objects {
 
	import com.citrusengine.objects.platformer.Sensor;
 
	/**
	 * @author Aymeric
	 */
	public class Roseau extends Sensor {
 
		private var _anim:String = "idle";
 
		public function Roseau(name:String, params:Object = null) {
			super(name, params);
		}
 
		override public function update(timeDelta:Number):void {
 
			super.update(timeDelta);
 
			updateAnimation();
		}
 
		private function updateAnimation():void {
 
			_animation = _anim;
		}
 
		public function get anim():String {
			return _anim;
		}
		public function set anim(value:String):void {
			_anim = value;
		}
 
	}
}

My roseau extends Sensor because of the contact with the hero ! And I use it easily :

private function _roseauTouche(cEvt:ContactEvent):void {
 
	if (cEvt.other.GetBody().GetUserData() is Hero) {
		cEvt.fixture.GetBody().GetUserData().anim = "white";
	}
}
 
private function _roseauFin(cEvt:ContactEvent):void {
 
	if (cEvt.other.GetBody().GetUserData() is Hero) {
		cEvt.fixture.GetBody().GetUserData().anim = "black";
	}
}

Now the musical sensor, it is really a simple sensor, I just added a param song to play :

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
package objects {
 
	import com.citrusengine.objects.platformer.Sensor;
 
	/**
	 * @author Aymeric
	 */
	public class MusicalSensor extends Sensor {
 
		public var song:String;
 
		public function MusicalSensor(name:String, params:Object = null) {
			super(name, params);
		}
	}
}

And I use it like that :

private function _playSound(cEvt:ContactEvent):void {
 
	if (cEvt.other.GetBody().GetUserData() is Hero) {
		_ce.sound.playSound(cEvt.fixture.GetBody().GetUserData().song, 1, 0);
	}
}

Instead of cutting my flowers into MovieClip, I just put my musical sensor in front of them. It’s a bit lazy, but it is faster to do :-)

You can download my zip.