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.
Thanks for the post! I added it to the website. Please continue to hit me up for suggestions and questions.
Very good! Thanks! I will purchase the Citrus engine now! 🙂
very good, can you publish videos tutorials ?
Hi yasdar,
I’m sorry, I will not publish videos tutorials shortly, I don’t have time. But maybe later…
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
Hey, you’ve forgotten to import the Box2D.swc and if you are using Flash Pro for compiling, to check export swc.