{"id":785,"date":"2012-10-29T14:58:02","date_gmt":"2012-10-29T13:58:02","guid":{"rendered":"http:\/\/www.aymericlamboley.fr\/blog\/?p=785"},"modified":"2014-11-01T14:48:21","modified_gmt":"2014-11-01T13:48:21","slug":"create-a-game-like-tiny-wings","status":"publish","type":"post","link":"http:\/\/www.aymericlamboley.fr\/blog\/create-a-game-like-tiny-wings\/","title":{"rendered":"Create a game like Tiny Wings"},"content":{"rendered":"<p>Tiny Wings is really a cute game and very fun to play thanks to physics content! It uses Box2D. If you&#8217;ve never played the game, give it a quick look :<br \/>\n<iframe loading=\"lazy\" width=\"420\" height=\"315\" src=\"http:\/\/www.youtube.com\/embed\/x6pT_2E5xI0\" frameborder=\"0\" allowfullscreen><\/iframe><br \/>\nGenerate those type of hills with a physics engine is complicated for a novice. You&#8217;ll find cool tutorials on <a href=\"http:\/\/www.emanueleferonato.com\/2011\/10\/04\/create-a-terrain-like-the-one-in-tiny-wings-with-flash-and-box2d-%E2%80%93-adding-more-bumps\/\" target=\"_blank\">Emanuele Feronato&#8217;s website<\/a> using Box2D and an other on <a href=\"http:\/\/www.lorenzonuvoletta.com\/create-an-infinite-scrolling-world-with-starling-and-nape\/\" target=\"_blank\">Lorenzo Nuvoletta&#8217;s website<\/a> using Nape.<\/p>\n<p>Someone asks me if it was possible to create this type of game with <a href=\"http:\/\/citrusengine.com\/\" target=\"_blank\">the Citrus Engine<\/a>, absolutely! So let&#8217;s go for a quick tutorial.<\/p>\n<p><!--more--><\/p>\n<p>Firstly, all hails to Lorenzo! The physics hills and the sprites creation come from his algorithm.<br \/>\nThis is what you will get : <a href=\"http:\/\/www.aymericlamboley.fr\/blog\/wp-content\/uploads\/2012\/10\/tiny-wings.html\" target=\"_blank\">demo<\/a>.<br \/>\nTo see the physics debug view, open the console with the key tab, then write :<\/p>\n<pre>set nape visible true<\/pre>\n<p>And press enter. You will see Nape&#8217;s debug view and the hero. You can jump with a click.<br \/>\nAll the source code is accessible on the new <a href=\"https:\/\/github.com\/alamboley\/Citrus-Engine-Examples\/tree\/master\/src\/games\" target=\"_blank\">CE&#8217;s GitHub for examples<\/a>.<\/p>\n<p>I won&#8217;t explain the algorithm. If you have any questions on it, ask Lorenzo. He will certainly answer better than me. However I&#8217;ll explain how it has been added to the Citrus Engine.<\/p>\n<p>This the Main class :<\/p>\n<pre lang=\"actionscript3\" line=\"1\">package games.tinywings {\r\n\r\n\timport com.citrusengine.core.StarlingCitrusEngine;\r\n\r\n\t\/**\r\n\t * @author Aymeric\r\n\t *\/\r\n\tpublic class Main extends StarlingCitrusEngine {\r\n\r\n\t\tpublic function Main() {\r\n\t\t\t\r\n\t\t\tsetUpStarling(true);\r\n\t\t\t\r\n\t\t\tstate = new TinyWingsGameState();\r\n\t\t}\r\n\t}\r\n}<\/pre>\n<p>The GameState. Here you init your physics world, and create the hero and the hills :<\/p>\n<pre lang=\"actionscript3\" line=\"1\">package games.tinywings {\r\n\r\n\timport com.citrusengine.core.StarlingState;\r\n\timport com.citrusengine.math.MathVector;\r\n\timport com.citrusengine.physics.nape.Nape;\r\n\r\n\timport flash.geom.Rectangle;\r\n\r\n\t\/**\r\n\t * @author Aymeric\r\n\t *\/\r\n\tpublic class TinyWingsGameState extends StarlingState {\r\n\t\t\r\n\t\tprivate var _nape:Nape;\r\n\t\tprivate var _hero:BirdHero;\r\n\t\t\r\n\t\tprivate var _hillsTexture:HillsTexture;\r\n\r\n\t\tpublic function TinyWingsGameState() {\r\n\t\t\tsuper();\r\n\t\t}\r\n\r\n\t\toverride public function initialize():void {\r\n\t\t\t\r\n\t\t\tsuper.initialize();\r\n\r\n\t\t\t_nape = new Nape(\"nape\");\r\n\t\t\t\/\/_nape.visible = true;\r\n\t\t\tadd(_nape);\r\n\t\t\t\r\n\t\t\t_hero = new BirdHero(\"hero\", {radius:20});\r\n\t\t\tadd(_hero);\r\n\t\t\t\r\n\t\t\t_hillsTexture = new HillsTexture();\r\n\r\n\t\t\tvar hills:HillsManagingGraphics = new HillsManagingGraphics(\"hills\", {sliceHeight:200, sliceWidth:70, currentYPoint:350, registration:\"topLeft\", view:_hillsTexture});\r\n\t\t\tadd(hills);\r\n\r\n\t\t\tview.setupCamera(_hero, new MathVector(stage.stageWidth \/2, stage.stageHeight \/ 2), new Rectangle(0, 0, int.MAX_VALUE, int.MAX_VALUE), new MathVector(.25, .05));\r\n\t\t}\r\n\t\t\t\r\n\t\toverride public function update(timeDelta:Number):void {\r\n\t\t\tsuper.update(timeDelta);\r\n\t\t\t\r\n\t\t\t\/\/ update the hills here to remove the displacement made by StarlingArt. Called after all operations done.\r\n\t\t\t_hillsTexture.update();\r\n\t\t}\r\n\t}\r\n}<\/pre>\n<p>Now let&#8217;s focus on the Hills creation. Hills is a new Nape&#8217;s object added to the Core of the Citrus Engine. It extends NapePhysicsObject. Since the Citrus Engine separates logic\/physics from view\/art, we don&#8217;t want to manage graphics in this core class. So we remove all the content related to graphics :<\/p>\n<pre lang=\"actionscript3\" line=\"1\">package com.citrusengine.objects.platformer.nape {\r\n\r\n\timport nape.geom.Vec2;\r\n\timport nape.phys.Body;\r\n\timport nape.phys.BodyType;\r\n\timport nape.shape.Polygon;\r\n\r\n\timport com.citrusengine.objects.NapePhysicsObject;\r\n\r\n\t\/**\r\n\t * This class creates perpetual hills like the games Tiny Wings, Ski Safari...\r\n\t * Write a class to manage graphics, and extends this one to call graphics function.\r\n\t * For more information, check out CE's Tiny Wings example.\r\n\t * Thanks to <a href=\"http:\/\/www.lorenzonuvoletta.com\/create-an-infinite-scrolling-world-with-starling-and-nape\/\">Lorenzo Nuvoletta<\/a>.\r\n\t *\/\r\n\tpublic class Hills extends NapePhysicsObject {\r\n\t\t\r\n\t\t\/**\r\n\t\t * This is the height of a slice. \r\n\t\t *\/\r\n\t\tpublic var sliceHeight:uint = 600;\r\n\t\t\r\n\t\t\/**\r\n\t\t * This is the width of a slice. \r\n\t\t *\/\r\n\t\tpublic var sliceWidth:uint = 30;\r\n\t\t\r\n\t\t\/**\r\n\t\t * This is the height of the first point.\r\n\t\t *\/\r\n\t\tpublic var currentYPoint:Number = 200;\r\n\t\t\r\n\t\t\/**\r\n\t\t * This is the width of the hills visible. Most of the time your stage width. \r\n\t\t *\/\r\n\t\tpublic var widthHills:Number = 550;\r\n\t\t\r\n\t\t\/**\r\n\t\t * This is the physics object from which the Hills read its position and create\/delete hills. \r\n\t\t *\/\r\n\t\tpublic var rider:NapePhysicsObject;\r\n\t\t\r\n\t\tprotected var _slicesCreated:uint;\r\n\t\tprotected var _currentAmplitude:Number;\r\n\t\tprotected var _nextYPoint:Number;\r\n\t\tprotected var _slicesInCurrentHill:uint;\r\n\t\tprotected var _indexSliceInCurrentHill:uint;\r\n\t\tprotected var _slices:Vector.<Body>;\r\n\t\tprotected var _sliceVectorConstructor:Vector.<Vec2>;\r\n\r\n\t\tpublic function Hills(name:String, params:Object = null) {\r\n\t\t\tsuper(name, params);\r\n\t\t}\r\n\t\t\t\r\n\t\toverride public function initialize(poolObjectParams:Object = null):void {\r\n\t\t\t\r\n\t\t\tsuper.initialize(poolObjectParams);\r\n\t\t\t\r\n\t\t\t_prepareSlices();\r\n\t\t}\r\n\t\t\r\n\t\tprotected function _prepareSlices():void {\r\n\t\t\t\r\n\t\t\t_slices = new Vector.<Body>();\r\n\r\n\t\t\t\/\/ Generate a rectangle made of Vec2\r\n\t\t\t_sliceVectorConstructor = new Vector.<Vec2>();\r\n\t\t\t_sliceVectorConstructor.push(new Vec2(0, sliceHeight));\r\n\t\t\t_sliceVectorConstructor.push(new Vec2(0, 0));\r\n\t\t\t_sliceVectorConstructor.push(new Vec2(sliceWidth, 0));\r\n\t\t\t_sliceVectorConstructor.push(new Vec2(sliceWidth, sliceHeight));\r\n\t\t\t\r\n\t\t\t\/\/ fill the stage with slices of hills\r\n\t\t\tfor (var i:uint = 0; i < widthHills \/ sliceWidth * 1.2; ++i) {\r\n\t\t\t\t_createSlice();\r\n\t\t\t}\r\n\t\t}\r\n\t\t\r\n\t\tprotected function _createSlice():void {\r\n\t\t\t\r\n\t\t\t\/\/ Every time a new hill has to be created this algorithm predicts where the slices will be positioned\r\n\t\t\tif (_indexSliceInCurrentHill >= _slicesInCurrentHill) {\r\n\t\t\t\t_slicesInCurrentHill = Math.random() * 40 + 10;\r\n\t\t\t\t_currentAmplitude = Math.random() * 60 - 20;\r\n\t\t\t\t_indexSliceInCurrentHill = 0;\r\n\t\t\t}\r\n\t\t\t\/\/ Calculate the position of the next slice\r\n\t\t\t_nextYPoint = currentYPoint + (Math.sin(((Math.PI \/ _slicesInCurrentHill) * _indexSliceInCurrentHill)) * _currentAmplitude);\r\n\t\t\t_sliceVectorConstructor[2].y = _nextYPoint - currentYPoint;\r\n\t\t\tvar slicePolygon:Polygon = new Polygon(_sliceVectorConstructor);\r\n\t\t\t_body = new Body(BodyType.STATIC);\r\n\t\t\t_body.shapes.add(slicePolygon);\r\n\t\t\t_body.position.x = _slicesCreated * sliceWidth;\r\n\t\t\t_body.position.y = currentYPoint;\r\n\t\t\t_body.space = _nape.space;\r\n\t\t\t\r\n\t\t\t_pushHill();\r\n\t\t}\r\n\t\t\r\n\t\tprotected function _pushHill():void {\r\n\t\t\t\r\n\t\t\t_slicesCreated++;\r\n\t\t\t_indexSliceInCurrentHill++;\r\n\t\t\tcurrentYPoint = _nextYPoint;\r\n\t\t\t\r\n\t\t\t _slices.push(_body);\r\n\t\t}\r\n\t\t\r\n\t\tprotected function _checkHills():void {\r\n\t\t\t\r\n\t\t\tif (!rider)\r\n\t\t\t\trider = _ce.state.getFirstObjectByType(Hero) as Hero;\r\n\t\t\t\r\n\t\t\tvar length:uint = _slices.length;\r\n\t\t\t\r\n\t\t\tfor (var i:uint = 0; i < length; ++i) {\r\n\t\t\t\t\r\n\t\t\t\tif (rider.body.position.x - _slices[i].position.x > widthHills * 0.5 + 100) {\r\n\t\t\t\t\t\r\n\t\t\t\t\t_deleteHill(i);\r\n\t\t\t\t\t--i;\r\n\t\t\t\t\t_createSlice();\r\n\t\t\t\t\t\r\n\t\t\t\t} else\r\n\t\t\t\t\tbreak;\r\n\t\t\t}\r\n\t\t}\r\n\t\t\r\n\t\tprotected function _deleteHill(index:uint):void {\r\n\t\t\t\r\n\t\t\t_nape.space.bodies.remove(_slices[index]);\r\n\t\t\t_slices.splice(index, 1);\r\n\t\t}\r\n\t\t\t\r\n\t\toverride public function update(timeDelta:Number):void {\r\n\t\t\t\r\n\t\t\tsuper.update(timeDelta);\r\n\t\t\t\r\n\t\t\t_checkHills();\r\n\t\t}\r\n\t\t\r\n\t\t\/**\r\n\t\t * Bodies are generated automatically, those functions aren't needed.\r\n\t\t *\/\r\n\t\toverride protected function defineBody():void {\r\n\t\t}\r\n\t\t\r\n\t\toverride protected function createBody():void {\r\n\t\t}\r\n\t\t\r\n\t\toverride protected function createMaterial():void {\r\n\t\t}\r\n\t\t\r\n\t\toverride protected function createShape():void {\r\n\t\t}\r\n\t\t\r\n\t\toverride protected function createFilter():void {\r\n\t\t}\r\n\t\t\r\n\t\toverride protected function createConstraint():void {\r\n\t\t}\r\n\t}\r\n}<\/pre>\n<p>We&#8217;ve cutted the algorithm in several methods to be able to manage graphics from a subclass.<br \/>\nThis is the class which manage graphics :<\/p>\n<pre lang=\"actionscript3\" line=\"1\">package games.tinywings {\r\n\r\n\timport com.citrusengine.objects.platformer.nape.Hills;\r\n\r\n\t\/**\r\n\t * @author Aymeric\r\n\t *\/\r\n\tpublic class HillsManagingGraphics extends Hills {\r\n\r\n\t\tpublic function HillsManagingGraphics(name:String, params:Object = null) {\r\n\t\t\tsuper(name, params);\r\n\t\t}\r\n\t\r\n\t\toverride protected function _prepareSlices():void {\r\n\r\n\t\t\tif (view)\r\n\t\t\t\t(view as HillsTexture).init(sliceWidth, sliceHeight);\r\n\t\t\t\t\r\n\t\t\tsuper._prepareSlices();\r\n\t\t}\r\n\r\n\t\toverride protected function _pushHill():void {\r\n\t\t\t\r\n\t\t\tif (view)\r\n\t\t\t\t(view as HillsTexture).createSlice(_body, _nextYPoint, currentYPoint);\r\n\r\n\t\t\tsuper._pushHill();\r\n\t\t}\r\n\r\n\t\toverride protected function _deleteHill(index:uint):void {\r\n\t\t\t\r\n\t\t\t(view as HillsTexture).deleteHill(index);\r\n\t\t\t\r\n\t\t\tsuper._deleteHill(index);\r\n\t\t}\r\n\r\n\r\n\t}\r\n}<\/pre>\n<p>We just make some calls to view&#8217;s functions. And so finally, the view class :<\/p>\n<pre lang=\"actionscript3\" line=\"1\">package games.tinywings {\r\n\r\n\timport nape.phys.Body;\r\n\r\n\timport starling.display.Image;\r\n\timport starling.display.Sprite;\r\n\timport starling.events.Event;\r\n\timport starling.textures.Texture;\r\n\r\n\timport flash.display.BitmapData;\r\n\timport flash.geom.Matrix;\r\n\r\n\t\/**\r\n\t * @author Aymeric\r\n\t *\/\r\n\tpublic class HillsTexture extends Sprite {\r\n\t\t\r\n\t\tprivate var _groundTexture:Texture;\r\n\t\tprivate var _sliceWidth:uint;\r\n\t\tprivate var _sliceHeight:uint;\r\n\t\t\r\n\t\tprivate var _images:Vector.<Image>;\r\n\t\t\r\n\t\tprivate var _flagAdded:Boolean = false;\r\n\r\n\t\tpublic function HillsTexture() {\r\n\t\t}\r\n\t\t\r\n\t\tpublic function init(sliceWidth:uint, sliceHeight:uint):void {\r\n\t\t\t\r\n\t\t\t_sliceWidth = sliceWidth;\r\n\t\t\t_sliceHeight = sliceHeight;\r\n\t\t\t\r\n\t\t\t_groundTexture = Texture.fromBitmapData(new BitmapData(_sliceWidth, _sliceHeight, false, 0xffaa33));\r\n\t\t\t\r\n\t\t\t_images = new Vector.<Image>();\r\n\t\t\t\r\n\t\t\taddEventListener(Event.ADDED, _added);\r\n\t\t}\r\n\r\n\t\tprivate function _added(evt:Event):void {\r\n\t\t\t\r\n\t\t\t_flagAdded = true;\r\n\t\t\t\r\n\t\t\tremoveEventListener(Event.ADDED_TO_STAGE, _added);\r\n\t\t}\r\n\t\t\r\n\t\tpublic function update():void {\r\n\t\t\t\r\n\t\t\t\/\/ we don't want to move the parent like StarlingArt does!\r\n\t\t\tif (_flagAdded)\r\n\t\t\t\tthis.parent.x = this.parent.y = 0;\r\n\t\t}\r\n\t\t\r\n\t\tpublic function createSlice(rider:Body, nextYPoint:uint, currentYPoint:uint):void {\r\n\t\t\t\r\n\t\t\tvar image:Image = new Image(_groundTexture);\r\n\t\t\taddChild(image);\r\n\t\t\t\r\n\t\t\t_images.push(image);\r\n\t\t\t\r\n\t\t\tvar matrix:Matrix = image.transformationMatrix;\r\n            matrix.translate(rider.position.x, rider.position.y);\r\n            matrix.a = 1.04;\r\n            matrix.b = (nextYPoint - currentYPoint) \/ _sliceWidth;\r\n            image.transformationMatrix.copyFrom(matrix); \r\n\t\t}\r\n\t\t\r\n\t\tpublic function deleteHill(index:uint):void {\r\n\t\t\t\r\n\t\t\tremoveChild(_images[index], true);\r\n\t\t\t_images.slice(index, 1);\r\n\t\t}\r\n\t}\r\n}<\/pre>\n<p>And that&#8217;s it! Our scrollable world with hills is working!<\/p>\n<p>To have exactly the same feeling that Tiny Wings there is always lots of work to achieve :<br \/>\n&#8211; make the terrain more rolling.<br \/>\n&#8211; don&#8217;t apply a linear velocity to your hero, just a basic force and then change its mass between screen touched or not.<br \/>\n&#8211; create nice graphics using <a href=\"http:\/\/devmag.org.za\/2012\/07\/29\/how-to-choose-colours-procedurally-algorithms\/\" target=\"_blank\">procedural palettes algorithms<\/a>.<\/p>\n<p>When I&#8217;ve some time, I will work on the Box2D version thanks to Emanuele&#8217;s tutorial and polish this version.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Tiny Wings is really a cute game and very fun to play thanks to physics content! It uses Box2D. If you&#8217;ve never played the game, give it a quick look : Generate those type of hills with a physics engine is complicated for a novice. You&#8217;ll find cool tutorials on Emanuele Feronato&#8217;s website using Box2D &hellip; <a href=\"http:\/\/www.aymericlamboley.fr\/blog\/create-a-game-like-tiny-wings\/\" class=\"more-link\">Continue reading <span class=\"screen-reader-text\">Create a game like Tiny Wings<\/span> <span class=\"meta-nav\">&rarr;<\/span><\/a><\/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,51,33,11,145,114,6,90],"tags":[15,50,34,26,146,117,91],"_links":{"self":[{"href":"http:\/\/www.aymericlamboley.fr\/blog\/wp-json\/wp\/v2\/posts\/785"}],"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=785"}],"version-history":[{"count":8,"href":"http:\/\/www.aymericlamboley.fr\/blog\/wp-json\/wp\/v2\/posts\/785\/revisions"}],"predecessor-version":[{"id":1263,"href":"http:\/\/www.aymericlamboley.fr\/blog\/wp-json\/wp\/v2\/posts\/785\/revisions\/1263"}],"wp:attachment":[{"href":"http:\/\/www.aymericlamboley.fr\/blog\/wp-json\/wp\/v2\/media?parent=785"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"http:\/\/www.aymericlamboley.fr\/blog\/wp-json\/wp\/v2\/categories?post=785"},{"taxonomy":"post_tag","embeddable":true,"href":"http:\/\/www.aymericlamboley.fr\/blog\/wp-json\/wp\/v2\/tags?post=785"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}