{"id":253,"date":"2011-05-07T13:56:42","date_gmt":"2011-05-07T12:56:42","guid":{"rendered":"http:\/\/www.aymericlamboley.fr\/blog\/?p=253"},"modified":"2014-11-01T15:09:13","modified_gmt":"2014-11-01T14:09:13","slug":"create-a-breakout-game-with-the-citrus-engine","status":"publish","type":"post","link":"http:\/\/www.aymericlamboley.fr\/blog\/create-a-breakout-game-with-the-citrus-engine\/","title":{"rendered":"Create a breakout game with the Citrus Engine"},"content":{"rendered":"<p>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 !<\/p>\n<p><a target=\"_blank\" href=\"http:\/\/www.aymericlamboley.fr\/blog\/wp-content\/uploads\/2011\/05\/index.html\"><strong>Click here to play the game<\/strong><\/a>, don&#8217;t forget to click in the swf to enable keyboard.<\/p>\n<p><!--more--><\/p>\n<p>But first of all, I would like to come back on my previous tutorial on the Citrus Engine, and add some comments:<br \/>\n&#8211; instead of using sensor to change the way that baddy moves (if they will fall), you can now specify a limit with <em>leftBound<\/em> and <em>rightBound<\/em> parameters. A nice feature added by Eric !<br \/>\n&#8211; it is possible to create cloud plateform by setting the proprety <em>oneWay<\/em> to true on a Plateform. It will enable you to jump on even if your are below.<br \/>\n&#8211; with the new update we can create a <em>MovingPlatform<\/em> as a new class of the core engine.<br \/>\n&#8211; the pause is now enable : <em>playing = false<\/em> \ud83d\ude09<br \/>\n&#8211; a console have been added to the Citrus Engine, you can access it by pressing the <em>TAB<\/em> key. You can change properties on the fly (for example : set Box2D visible false) and add your own command :<\/p>\n<pre lang=\"actionscript3\">this.console.addCommand(\"fullscreen\", _fullscreen);\r\nthis.console.addCommand(\"play\", _playGame);\r\n\r\nprivate function _fullscreen():void {\r\n\tstage.displayState = \"fullScreen\";\r\n}\r\n\r\nprivate function _playGame():void {\r\n\tthis.playing = !this.playing;\r\n}<\/pre>\n<p>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.<\/p>\n<p>Create a fla and add MovieClip on the stage for the Plateforms, Ball, Bricks&#8230; Inside each MovieClip specify the class name :<\/p>\n<pre lang=\"actionscript3\">var className = \"Ball\"\r\nvar params = {\r\n\tradius:15\r\n}<\/pre>\n<p>You can add easily some parms. <img decoding=\"async\" src=\"http:\/\/www.aymericlamboley.fr\/blog\/wp-content\/uploads\/2011\/05\/screen-capture.png\" alt=\"This is my stage\" \/><br \/>\nOur level is now created.<\/p>\n<p>Now let&#8217;s start coding :<br \/>\nThe Main class :<\/p>\n<pre lang=\"actionscript3\" line=\"1\">package {\r\n\r\n\timport com.citrusengine.core.CitrusEngine;\r\n\r\n\timport flash.display.Loader;\r\n\timport flash.display.MovieClip;\r\n\timport flash.events.Event;\r\n\timport flash.net.URLRequest;\r\n\r\n\t\/**\r\n\t * @author Aymeric\r\n\t *\/\r\n\tpublic class Main extends CitrusEngine {\r\n\t\t\r\n\t\tprivate var _level:MovieClip;\r\n\t\tprivate var _hud:Hud;\r\n\t\tprivate var _countBricks:uint;\r\n\r\n\t\tpublic function Main() {\r\n\r\n\t\t\tsuper();\r\n\r\n\t\t\t_hud = new Hud();\r\n\t\t\taddChild(_hud);\r\n\r\n\t\t\tthis.console.addCommand(\"fullscreen\", _fullscreen);\r\n\t\t\tthis.console.addCommand(\"play\", _playGame);\r\n\r\n\t\t\tvar loader:Loader = new Loader();\r\n\t\t\tloader.load(new URLRequest(\"LevelA.swf\"));\r\n\t\t\tloader.contentLoaderInfo.addEventListener(Event.COMPLETE, _levelLoaded);\r\n\t\t}\r\n\r\n\t\tprivate function _levelLoaded(evt:Event):void {\r\n\r\n\t\t\t_level = evt.target.loader.content;\r\n\t\t\t_restartGame();\r\n\t\t\t\r\n\t\t\tevt.target.loader.unloadAndStop();\r\n\t\t}\r\n\r\n\t\tprivate function _fullscreen():void {\r\n\t\t\tstage.displayState = \"fullScreen\";\r\n\t\t}\r\n\r\n\t\tprivate function _playGame():void {\r\n\t\t\tthis.playing = !this.playing;\r\n\t\t}\r\n\r\n\t\tprivate function _brickTaken(gEvt:GameEvent):void {\r\n\r\n\t\t\t++_countBricks;\r\n\t\t\t_hud.scoreCoin = _countBricks;\r\n\r\n\t\t\tif (_countBricks == GameConst.nbrCoins) {\r\n\t\t\t\t_restartGame();\r\n\t\t\t}\r\n\t\t}\r\n\r\n\t\tprivate function _restartGame(gEvt:GameEvent = null):void {\r\n\r\n\t\t\tstate = new GameState(_level);\r\n\r\n\t\t\t_hud.scoreCoin = _countBricks = 0;\r\n\r\n\t\t\tstate.addEventListener(GameEvent.RESTART_GAME, _restartGame);\r\n\t\t\tstate.addEventListener(GameEvent.TAKE_BRICK, _brickTaken);\r\n\t\t}\r\n\t}\r\n}<\/pre>\n<p>Compared to the last tutorial the main change are console command and the loader for our level.<\/p>\n<p>Now the GameState which show how access to our MovieClip&#8217;s level.<\/p>\n<pre lang=\"actionscript3\" line=\"1\">package {\r\n\r\n\timport Box2DAS.Dynamics.ContactEvent;\r\n\r\n\timport com.citrusengine.core.CitrusObject;\r\n\timport com.citrusengine.core.State;\r\n\timport com.citrusengine.objects.platformer.Sensor;\r\n\timport com.citrusengine.physics.Box2D;\r\n\timport com.citrusengine.utils.ObjectMaker;\r\n\r\n\timport flash.display.MovieClip;\r\n\r\n\t\/**\r\n\t * @author Aymeric\r\n\t *\/\r\n\tpublic class GameState extends State {\r\n\r\n\t\tprivate var _level:MovieClip;\r\n\r\n\t\tpublic function GameState(level:MovieClip) {\r\n\r\n\t\t\tsuper();\r\n\t\t\t_level = level;\r\n\t\t}\r\n\r\n\t\toverride public function initialize():void {\r\n\r\n\t\t\tsuper.initialize();\r\n\r\n\t\t\tvar box2D:Box2D = new Box2D(\"Box2D\");\r\n\t\t\tadd(box2D);\r\n\t\t\tbox2D.visible = true;\r\n\t\t\t\r\n\t\t\t\/\/ Create objects from our level\r\n\t\t\tObjectMaker.FromMovieClip(_level);\r\n\r\n\t\t\tvar ball:Ball = Ball(getFirstObjectByType(Ball));\r\n\t\t\tball.gravity = 0;\r\n\t\t\tball.onEndContact.add(_takeBrick);\r\n\r\n\t\t\tvar sensor:Sensor = Sensor(getFirstObjectByType(Sensor));\r\n\t\t\tsensor.onBeginContact.add(_resetLevel);\r\n\r\n\t\t\tvar bricks:Vector.<CitrusObject> = getObjectsByType(Brick);\r\n\t\t\tfor each (var brick:Brick in bricks) {\r\n\t\t\t\tbrick.onEndContact.add(_takeBrick);\r\n\t\t\t}\r\n\t\t}\r\n\r\n\t\tprivate function _takeBrick(cEvt:ContactEvent):void {\r\n\r\n\t\t\tif (cEvt.other.GetBody().GetUserData() is Brick) {\r\n\t\t\t\tremove(cEvt.other.GetBody().GetUserData());\r\n\t\t\t\tthis.dispatchEvent(new GameEvent(GameEvent.TAKE_BRICK));\r\n\t\t\t}\r\n\t\t}\r\n\r\n\t\tprivate function _resetLevel(cEvt:ContactEvent):void {\r\n\r\n\t\t\tif (cEvt.other.GetBody().GetUserData() is Ball) {\r\n\t\t\t\tthis.dispatchEvent(new GameEvent(GameEvent.RESTART_GAME));\r\n\t\t\t}\r\n\t\t}\r\n\t}\r\n}<\/pre>\n<p> It is really easy to access to our objects from the swf with these functions : getObjectsByType, getObjectByName, getFirstObjectByType. I put ball&#8217;s gravity to 0 to handle physics movement by myself. Nothing too complex here, let&#8217;s start to create our first Citrus Engine object : a brick !<\/p>\n<pre lang=\"actionscript3\" line=\"1\">package {\r\n\r\n\timport Box2DAS.Dynamics.ContactEvent;\r\n\r\n\timport com.citrusengine.objects.platformer.Platform;\r\n\r\n\timport org.osflash.signals.Signal;\r\n\r\n\t\/**\r\n\t * @author Aymeric\r\n\t *\/\r\n\tpublic class Brick extends Platform {\r\n\r\n\t\tpublic var onEndContact:Signal;\r\n\r\n\t\tpublic function Brick(name:String, params:Object = null) {\r\n\r\n\t\t\tsuper(name, params);\r\n\t\t\tonEndContact = new Signal(ContactEvent);\r\n\t\t}\r\n\r\n\t\toverride public function destroy():void {\r\n\r\n\t\t\tonEndContact.removeAll();\r\n\t\t\t_fixture.removeEventListener(ContactEvent.END_CONTACT, handleEndContact);\r\n\t\t\tsuper.destroy();\r\n\t\t}\r\n\r\n\t\toverride protected function createFixture():void {\r\n\t\t\t\r\n\t\t\tsuper.createFixture();\r\n\t\t\t_fixture.m_reportEndContact = true;\r\n\t\t\t_fixture.addEventListener(ContactEvent.END_CONTACT, handleEndContact);\r\n\t\t}\r\n\r\n\t\tprotected function handleEndContact(e:ContactEvent):void {\r\n\t\t\tonEndContact.dispatch(e);\r\n\t\t}\r\n\t}\r\n}<\/pre>\n<p> A brick is a simple Plateform which reports if something collides with it.<\/p>\n<p>Now the paddle :<\/p>\n<pre lang=\"actionscript3\" line=\"1\">package {\r\n\r\n\timport Box2DAS.Common.V2;\r\n\timport Box2DAS.Dynamics.b2Body;\r\n\timport Box2DAS.Dynamics.b2BodyDef;\r\n\r\n\timport com.citrusengine.objects.PhysicsObject;\r\n\r\n\timport flash.ui.Keyboard;\r\n\r\n\t\/**\r\n\t * @author Aymeric\r\n\t *\/\r\n\tpublic class Paddle extends PhysicsObject {\r\n\r\n\t\tpublic var acceleration:Number = 1;\r\n\t\tpublic var maxVelocity:Number = 7;\r\n\r\n\t\tprivate var _friction:Number = 0;\r\n\t\tprivate var _playerMovingHero:Boolean = false;\r\n\r\n\t\tpublic function Paddle(name:String, params:Object = null) {\r\n\t\t\tsuper(name, params);\r\n\t\t}\r\n\r\n\t\toverride public function update(timeDelta:Number):void {\r\n\t\t\t\r\n\t\t\tsuper.update(timeDelta);\r\n\r\n\t\t\tvar velocity:V2 = _body.GetLinearVelocity();\r\n\r\n\t\t\tvar moveKeyPressed:Boolean = false;\r\n\r\n\t\t\tif (_ce.input.isDown(Keyboard.RIGHT)) {\r\n\t\t\t\tvelocity.x += (acceleration);\r\n\t\t\t\tmoveKeyPressed = true;\r\n\t\t\t}\r\n\r\n\t\t\tif (_ce.input.isDown(Keyboard.LEFT)) {\r\n\t\t\t\tvelocity.x -= (acceleration);\r\n\t\t\t\tmoveKeyPressed = true;\r\n\t\t\t}\r\n\r\n\t\t\tif (moveKeyPressed && !_playerMovingHero) {\r\n\t\t\t\t_playerMovingHero = true;\r\n\t\t\t\t_fixture.SetFriction(0);\r\n\t\t\t\t\/\/ Take away friction so he can accelerate.\r\n\t\t\t} else if (!moveKeyPressed && _playerMovingHero) {\r\n\t\t\t\t_playerMovingHero = false;\r\n\t\t\t\t_fixture.SetFriction(_friction);\r\n\t\t\t\t\/\/ Add friction so that he stops running\r\n\t\t\t}\r\n\r\n\t\t\t\/\/ Cap velocities\r\n\t\t\tif (velocity.x > (maxVelocity))\r\n\t\t\t\tvelocity.x = maxVelocity;\r\n\t\t\telse if (velocity.x < (-maxVelocity))\r\n\t\t\t\tvelocity.x = -maxVelocity;\r\n\r\n\t\t\t\/\/ update physics with new velocity\r\n\t\t\t_body.SetLinearVelocity(velocity);\r\n\t\t}\r\n\r\n\t\toverride protected function defineBody():void {\r\n\t\t\t\r\n\t\t\t_bodyDef = new b2BodyDef();\r\n\t\t\t_bodyDef.type = b2Body.b2_kinematicBody;\r\n\t\t\t_bodyDef.position.v2 = new V2(_x, _y);\r\n\t\t\t_bodyDef.angle = _rotation;\r\n\t\t\t_bodyDef.fixedRotation = true;\r\n\t\t\t_bodyDef.allowSleep = false;\r\n\t\t}\r\n\r\n\t\toverride protected function defineFixture():void {\r\n\t\t\t\r\n\t\t\tsuper.defineFixture();\r\n\t\t\t_fixtureDef.friction = _friction;\r\n\t\t\t_fixtureDef.restitution = 1;\r\n\t\t}\r\n\r\n\t\toverride protected function createFixture():void {\r\n\t\t\t\r\n\t\t\tsuper.createFixture();\r\n\t\t\t_fixture.m_reportBeginContact = true;\r\n\t\t\t_fixture.m_reportEndContact = true;\r\n\t\t}\r\n\t}\r\n}<\/pre>\n<p>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.<\/p>\n<p>And finally the ball :<\/p>\n<pre lang=\"actionscript3\" line=\"1\">package {\r\n\r\n\timport Box2DAS.Collision.Shapes.b2CircleShape;\r\n\timport Box2DAS.Common.V2;\r\n\timport Box2DAS.Dynamics.ContactEvent;\r\n\timport Box2DAS.Dynamics.b2FixtureDef;\r\n\r\n\timport com.citrusengine.objects.PhysicsObject;\r\n\r\n\timport org.osflash.signals.Signal;\r\n\r\n\t\/**\r\n\t * @author Aymeric\r\n\t *\/\r\n\tpublic class Ball extends PhysicsObject {\r\n\r\n\t\tpublic var onBeginContact:Signal;\r\n\t\tpublic var onEndContact:Signal;\r\n\r\n\t\tprivate var _radius:Number;\r\n\t\tprivate var _linearVelocity:V2;\r\n\r\n\t\tprivate var _afterFirstBounce:Boolean = false;\r\n\r\n\t\tpublic function Ball(name:String, params:Object = null) {\r\n\r\n\t\t\tsuper(name, params);\r\n\r\n\t\t\tonBeginContact = new Signal(ContactEvent);\r\n\t\t\tonEndContact = new Signal(ContactEvent);\r\n\r\n\t\t\t_linearVelocity = _body.GetLinearVelocity();\r\n\t\t\t_linearVelocity.y = 5;\r\n\t\t}\r\n\r\n\t\toverride public function destroy():void {\r\n\r\n\t\t\tonBeginContact.removeAll();\r\n\t\t\tonEndContact.removeAll();\r\n\t\t\tsuper.destroy();\r\n\t\t}\r\n\r\n\t\toverride protected function createShape():void {\r\n\r\n\t\t\t_shape = new b2CircleShape();\r\n\t\t\tb2CircleShape(_shape).m_radius = _radius;\r\n\t\t}\r\n\r\n\t\toverride public function update(timeDelta:Number):void {\r\n\r\n\t\t\tif (_afterFirstBounce == true) {\r\n\t\t\t\t_linearVelocity = _body.GetLinearVelocity();\r\n\t\t\t}\r\n\t\t\t_body.SetLinearVelocity(_linearVelocity);\r\n\t\t}\r\n\r\n\t\toverride protected function createFixture():void {\r\n\t\t\t\r\n\t\t\tsuper.createFixture();\r\n\t\t\t_fixture.m_reportBeginContact = true;\r\n\t\t\t_fixture.m_reportEndContact = true;\r\n\t\t\t_fixture.addEventListener(ContactEvent.BEGIN_CONTACT, handleBeginContact);\r\n\t\t\t_fixture.addEventListener(ContactEvent.END_CONTACT, handleEndContact);\r\n\t\t}\r\n\r\n\t\toverride protected function defineFixture():void {\r\n\t\t\t\r\n\t\t\t_fixtureDef = new b2FixtureDef();\r\n\t\t\t_fixtureDef.shape = _shape;\r\n\t\t\t_fixtureDef.density = 1;\r\n\t\t\t_fixtureDef.friction = 0;\r\n\t\t\t_fixtureDef.restitution = 1;\r\n\t\t}\r\n\r\n\t\tprivate function handleBeginContact(cEvt:ContactEvent):void {\r\n\r\n\t\t\tonBeginContact.dispatch(cEvt);\r\n\r\n\t\t\tvar diff:Number;\r\n\t\t\tvar ratio:Number;\r\n\t\t\tvar speed:Number;\r\n\r\n\t\t\tif (cEvt.other.GetBody().GetUserData() is Paddle) {\r\n\t\t\t\t_afterFirstBounce = true;\r\n\t\t\t\t\/\/ get a reference to the paddle\r\n\t\t\t\tvar paddle:Paddle = cEvt.other.GetBody().GetUserData() as Paddle;\r\n\r\n\t\t\t\t\/\/ only change collisions that are against the top surface of the paddle (let it shank off if it hits an edge).\r\n\t\t\t\tvar collisionNormal:V2 = cEvt.getWorldManifold().normal;\r\n\t\t\t\tif (collisionNormal.x == 0 && collisionNormal.y == 1) {\r\n\t\t\t\t\tdiff = x - paddle.x;\r\n\t\t\t\t\t\/\/ x distance between ball and paddle.\r\n\t\t\t\t\tratio = diff \/ (paddle.width \/ 2);\r\n\r\n\t\t\t\t\t\/\/ distance as a ratio (between -1 and 1)\r\n\t\t\t\t\tif (ratio < -1 || ratio > 1) \/\/ it will shank.\r\n\t\t\t\t\t\treturn;\r\n\r\n\t\t\t\t\t\/\/ Create a new velocity vector and set it to the ball's current speed.\r\n\t\t\t\t\t\/\/ var velocity:V2 = _body.GetLinearVelocity();\r\n\t\t\t\t\t_linearVelocity = _body.GetLinearVelocity();\r\n\t\t\t\t\tspeed = _linearVelocity.length();\r\n\r\n\t\t\t\t\t\/\/ set the ball to be going straight up at the same speed as it came.\r\n\t\t\t\t\t_linearVelocity = new V2(0, -1);\r\n\t\t\t\t\t_linearVelocity.normalize(speed);\r\n\r\n\t\t\t\t\t\/\/ rotate the velocity angle between a specific range (I chose 1 radian) as a proportion ball's distance from center of paddle.\r\n\t\t\t\t\t_linearVelocity.rotate(1 * ratio);\r\n\t\t\t\t\t_body.SetLinearVelocity(_linearVelocity);\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t}\r\n\r\n\t\tprivate function handleEndContact(cEvt:ContactEvent):void {\r\n\t\t\tonEndContact.dispatch(cEvt);\r\n\t\t}\r\n\r\n\t\tpublic function get radius():Number {\r\n\t\t\treturn _radius * _box2D.scale;\r\n\t\t}\r\n\r\n\t\tpublic function set radius(value:Number):void {\r\n\t\t\t_radius = value \/ _box2D.scale;\r\n\t\t}\r\n\t}\r\n}<\/pre>\n<p>A big thanks to Eric for helping me with the ball movement !<br \/>\nWith a restitution set to 1, the ball bounces on each Platform.<\/p>\n<p>Finally it was pretty easy to create my own object inside the Citrus Engine ! For a first try with box2D it was successful too !<\/p>\n<p>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.<\/p>\n<pre lang=\"actionscript3\" line=\"1\">protected function createShape():void {\r\n\tif (_radius == null) {\r\n\t\t_shape = new b2PolygonShape();\r\n\t\tb2PolygonShape(_shape).SetAsBox(_width \/ 2, _height \/ 2);\r\n\t} else {\r\n\t\t_shape = new b2CircleShape();\r\n\t\tb2CircleShape(_shape).m_radius = _radius;\r\n\t}\r\n}<\/pre>\n<p>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.<\/p>\n<p>My <a href=\"http:\/\/www.aymericlamboley.fr\/blog\/wp-content\/uploads\/2011\/05\/Breakout.zip\"><strong>zip<\/strong><\/a>.<br \/>\nThe box2D <a target=\"_blank\" href=\"http:\/\/www.box2d.org\/manual.html\">manual<\/a>.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>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&#8217;t forget to click in the swf to enable keyboard.<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"_monsterinsights_skip_tracking":false,"_monsterinsights_sitenote_active":false,"_monsterinsights_sitenote_note":"","_monsterinsights_sitenote_category":0},"categories":[4,115,51,33,11,114,6],"tags":[20,15,27,53,52,34,54],"_links":{"self":[{"href":"http:\/\/www.aymericlamboley.fr\/blog\/wp-json\/wp\/v2\/posts\/253"}],"collection":[{"href":"http:\/\/www.aymericlamboley.fr\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"http:\/\/www.aymericlamboley.fr\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"http:\/\/www.aymericlamboley.fr\/blog\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"http:\/\/www.aymericlamboley.fr\/blog\/wp-json\/wp\/v2\/comments?post=253"}],"version-history":[{"count":11,"href":"http:\/\/www.aymericlamboley.fr\/blog\/wp-json\/wp\/v2\/posts\/253\/revisions"}],"predecessor-version":[{"id":1302,"href":"http:\/\/www.aymericlamboley.fr\/blog\/wp-json\/wp\/v2\/posts\/253\/revisions\/1302"}],"wp:attachment":[{"href":"http:\/\/www.aymericlamboley.fr\/blog\/wp-json\/wp\/v2\/media?parent=253"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"http:\/\/www.aymericlamboley.fr\/blog\/wp-json\/wp\/v2\/categories?post=253"},{"taxonomy":"post_tag","embeddable":true,"href":"http:\/\/www.aymericlamboley.fr\/blog\/wp-json\/wp\/v2\/tags?post=253"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}