{"id":750,"date":"2012-09-27T12:42:15","date_gmt":"2012-09-27T11:42:15","guid":{"rendered":"http:\/\/www.aymericlamboley.fr\/blog\/?p=750"},"modified":"2014-11-01T14:50:13","modified_gmt":"2014-11-01T13:50:13","slug":"the-citrus-engine-meets-away3d","status":"publish","type":"post","link":"http:\/\/www.aymericlamboley.fr\/blog\/the-citrus-engine-meets-away3d\/","title":{"rendered":"The Citrus Engine meets Away3D"},"content":{"rendered":"<p>Some days ago, I was thinking that my next post would be the annoucement of the <a href=\"http:\/\/citrusengine.com\/\" target=\"_blank\">Citrus Engine<\/a> V3 stable release. Since Starling and Nape are well integrated in the engine and the API very stable, I didn&#8217;t see any new big features coming. Oh in fact, I imagined one : 3D support. This one was crazy, and now it is real! Welcome to the first 2D &#038; 3D Flash game engine!<\/p>\n<p>I&#8217;m not familiar with 3D. Last year I had a quick look with <a href=\"http:\/\/www.aymericlamboley.fr\/blog\/quick-look-into-threejs\/\" target=\"_blank\">ThreeJS<\/a> and then ported it in <a href=\"http:\/\/www.aymericlamboley.fr\/blog\/getting-started-with-away3d-for-fp11\/\" target=\"_blank\">Away3D<\/a>. That was my only experience with 3D until now, so don&#8217;t hesitate to correct me and to offer some suggestions. That&#8217;s lots of new stuff to learn, and that&#8217;s pretty exciting!<br \/>\nAlso if you have links to free arts\/assets Away3D friendly, share please \ud83d\ude42<\/p>\n<p>For the impatients, <a href=\"http:\/\/www.aymericlamboley.fr\/blog\/wp-content\/uploads\/2012\/09\/Citrus-Engine-Away3D.html\" target=\"_blank\">here is the demo<\/a>! Note the Away3D support needs lots of work yet. Click on the red square to add boxes (I was a bit lazy to make a correct button). Use the mouse to move the camera, and right\/left\/down\/space keys for the Hero.<br \/>\nI use Box2D for the physics (it could be Nape), to see the Box2D debug view <em>press tab to open the console and write :<\/em><\/p>\n<pre>set box2D visible true<\/pre>\n<p><em>All the source code is available on <a href=\"https:\/\/github.com\/alamboley\/Citrus-Engine\" target=\"_blank\">GitHub<\/a> (with samples) and on <a href=\"http:\/\/code.google.com\/p\/citrus-engine\/\" target=\"_blank\">GoogleCode<\/a> (engine code only). The Away3D part will evolve a lot in the next month, <strong>the 2D stuff is very stable to use!<\/strong><\/em><\/p>\n<p><!--more--><\/p>\n<p><strong>The case study : Trine &#038; Rayman<\/strong><br \/>\n<a href=\"http:\/\/trine-thegame.com\/\" target=\"_blank\">Trine 2<\/a> is with <a href=\"http:\/\/raymanorigins.us.ubi.com\/\" target=\"_blank\">Rayman Origins<\/a> one of my favorite platformer game. Both have different styles &#038; gameplay :<br \/>\n<iframe loading=\"lazy\" width=\"640\" height=\"360\" src=\"https:\/\/www.youtube.com\/embed\/uWJNgG3PFVc?feature=player_detailpage\" frameborder=\"0\" allowfullscreen><\/iframe><br \/>\n<iframe loading=\"lazy\" width=\"640\" height=\"360\" src=\"https:\/\/www.youtube.com\/embed\/OfafUEVb0R4?feature=player_detailpage\" frameborder=\"0\" allowfullscreen><\/iframe><br \/>\nEven if Trine is a 2D platformer game, it uses 3D arts whereas Rayman doesn&#8217;t. The camera has nice movement which highlight the 3D assets even if it never flips on the other side. 3D arts with 2D physics \/ logics works like a charm, it was something to add to the Citrus Engine.<\/p>\n<p><strong>Why Away3D?<\/strong><br \/>\nI&#8217;ve chosen <a href=\"http:\/\/away3d.com\/\" target=\"_blank\">Away3D<\/a> since like <a href=\"http:\/\/gamua.com\/starling\/\" target=\"_blank\">Starling<\/a> it is free, open source and strongly supported by Adobe! Note that it&#8217;s very easy to add an other 3D engine.<\/p>\n<p><strong>Handle 3D view in 2D physics world<\/strong><br \/>\nSince Away3D doesn&#8217;t use the same coordinates (middle of the screen) than Flash (top left of the screen) it is not evident to put the view at the good object position. At first I was fighting with <em>project<\/em> and <em>unproject<\/em> method using an OrthographicLens camera but that was a nightmare. I needed some help.<br \/>\nI discovered <a href=\"http:\/\/rengelbert.com\/blog\/\" target=\"_blank\">Roger Engelbert&#8217;s blog<\/a> some months ago. There are very good tutorials moving from one framework to an other one in an other language : Objective-C, Java, AS3. It&#8217;s definetly one blog to bookmark for developers (and artists now)! Roger has made several game prototypes using a 3D view with a 2D logic, like this <a href=\"http:\/\/rengelbert.com\/blog\/chopper-3d-endless-terrain\/\" target=\"_blank\">one<\/a>. So I asked him, what was the best way to handle a 3D view in a 2D games? He answered me with a new tutorial : <a href=\"http:\/\/rengelbert.com\/blog\/3d-coordinates-from-2d-simulation\/\" target=\"_blank\">3D Coordinates from 2D Simulation<\/a>. That was very kind and the article is very smart. No more <em>project unproject<\/em> method! Thank you Roger!<\/p>\n<p><strong>The demo<\/strong><br \/>\nPlease refer to the top of the article for the demo and instructions. The Perelith Knight asset come from this awesome example from Away3D&#8217;s hero Rob Bateman : <a href=\"http:\/\/www.infiniteturtles.co.uk\/blog\/away3d-multi-knight-demo\" target=\"_blank\">Away3D multi-knight demo<\/a>. I used <a href=\"http:\/\/www.misfitcode.com\/misfitmodel3d\/\" target=\"_blank\">Misfit Model 3D software<\/a> to manage MD2 files changing animations name and moving a bit the jump animation.<\/p>\n<p><strong>The code<\/strong><br \/>\nMain file<\/p>\n<pre lang=\"actionscript3\">package away3dbox2d {\r\n\r\n\timport com.citrusengine.core.CitrusEngine;\r\n\r\n\t[SWF(frameRate=\"60\")]\r\n\r\n\t\/**\r\n\t* @author Aymeric\r\n\t*\/\r\n\tpublic class Main extends CitrusEngine {\r\n\r\n\t\tpublic function Main() {\r\n\r\n\t\t\tstate = new Away3DGameState();\r\n\t\t}\r\n\t}\r\n}<\/pre>\n<p>The Game State:<\/p>\n<pre lang=\"actionscript3\" line=\"1\">package away3dbox2d {\r\n\r\n\timport away3d.controllers.HoverController;\r\n\timport away3d.debug.AwayStats;\r\n\timport away3d.entities.Mesh;\r\n\timport away3d.library.AssetLibrary;\r\n\timport away3d.loaders.parsers.MD2Parser;\r\n\timport away3d.materials.ColorMaterial;\r\n\timport away3d.primitives.CubeGeometry;\r\n\timport away3d.primitives.SphereGeometry;\r\n\r\n\timport com.citrusengine.core.State;\r\n\timport com.citrusengine.math.MathVector;\r\n\timport com.citrusengine.objects.Box2DPhysicsObject;\r\n\timport com.citrusengine.objects.CitrusSprite;\r\n\timport com.citrusengine.objects.platformer.box2d.Coin;\r\n\timport com.citrusengine.objects.platformer.box2d.Hero;\r\n\timport com.citrusengine.objects.platformer.box2d.Platform;\r\n\timport com.citrusengine.physics.Box2D;\r\n\timport com.citrusengine.view.CitrusView;\r\n\timport com.citrusengine.view.away3dview.AnimationSequence;\r\n\timport com.citrusengine.view.away3dview.Away3DArt;\r\n\timport com.citrusengine.view.away3dview.Away3DView;\r\n\r\n\timport flash.display.Sprite;\r\n\timport flash.events.Event;\r\n\timport flash.events.MouseEvent;\r\n\timport flash.geom.Rectangle;\r\n\timport flash.geom.Vector3D;\r\n\r\n\t\/**\r\n\t * @author Aymeric\r\n\t *\/\r\n\tpublic class Away3DGameState extends State {\r\n\r\n\t\t[Embed(source=\"\/..\/embed\/pknight\/pknight3.png\")]\r\n\t\tpublic static var PKnightTexture3:Class;\r\n\r\n\t\t[Embed(source=\"\/..\/embed\/pknight\/pknight.md2\", mimeType=\"application\/octet-stream\")]\r\n\t\tpublic static var PKnightModel:Class;\r\n\r\n\t\t\/\/ navigation variables\r\n\t\tprivate var _cameraController:HoverController;\r\n\r\n\t\tprivate var _move:Boolean = false;\r\n\t\tprivate var _lastPanAngle:Number;\r\n\t\tprivate var _lastTiltAngle:Number;\r\n\t\tprivate var _lastMouseX:Number;\r\n\t\tprivate var _lastMouseY:Number;\r\n\t\tprivate var _lookAtPosition:Vector3D = new Vector3D();\r\n\r\n\t\tprivate var _heroArt:AnimationSequence;\r\n\t\tprivate var _hero:Hero;\r\n\r\n\t\tprivate var _clickMe:Sprite;\r\n\r\n\t\tpublic function Away3DGameState() {\r\n\t\t\tsuper();\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\taddChild(new AwayStats((view as Away3DView).viewRoot));\r\n\r\n\t\t\tvar box2D:Box2D = new Box2D(\"box2D\");\r\n\t\t\t\/\/ box2D.visible = true;\r\n\t\t\tadd(box2D);\r\n\r\n\t\t\tAssetLibrary.enableParser(MD2Parser);\r\n\t\t\t_heroArt = new AnimationSequence(new PKnightModel(), new PKnightTexture3());\r\n\t\t\t_heroArt.scale(2);\r\n\r\n\t\t\tvar cube1:Mesh = new Mesh(new CubeGeometry(300, 300, 0), new ColorMaterial(0x0000FF));\r\n\t\t\tvar cube2:Mesh = new Mesh(new SphereGeometry(15), new ColorMaterial(0xFFFF00));\r\n\t\t\tvar cube3:Mesh = new Mesh(new CubeGeometry(2500, 10, 300), new ColorMaterial(0xFFFFFF));\r\n\r\n\t\t\tvar cloud:CitrusSprite = new CitrusSprite(\"cloud\", {x:150, y:50, width:300, height:300, view:cube1, parallax:0.3});\r\n\t\t\tadd(cloud);\r\n\t\t\t(view.getArt(cloud) as Away3DArt).z = 300;\r\n\t\t\t\/\/ equivalent to -> cube1.z = 300;\r\n\r\n\t\t\tadd(new Platform(\"platformBottom\", {x:stage.stageWidth \/ 2, y:stage.stageHeight - 30, width:2500, height:10, view:cube3}));\r\n\r\n\t\t\t_hero = new Hero(\"hero\", {x:150, y:50, width:80, height:90, view:_heroArt});\r\n\t\t\tadd(_hero);\r\n\r\n\t\t\tvar coin:Coin = new Coin(\"coin\", {x:300, y:200, width:30, height:30, view:cube2});\r\n\t\t\tadd(coin);\r\n\r\n\t\t\tview.setupCamera(_hero, new MathVector(320, 240), new Rectangle(0, 0, 1550, 450), new MathVector(.25, .05));\r\n\t\t\t_cameraController = new HoverController((view as Away3DView).viewRoot.camera, null, 175, 20, 500);\r\n\r\n\t\t\t_clickMe = new Sprite();\r\n\t\t\t_clickMe.y = stage.stageHeight - 50;\r\n\t\t\taddChild(_clickMe);\r\n\t\t\t_clickMe.graphics.beginFill(0xFF0000);\r\n\t\t\t_clickMe.graphics.drawRect(10, 0, 40, 40);\r\n\t\t\t_clickMe.graphics.endFill();\r\n\t\t\t_clickMe.buttonMode = true;\r\n\r\n\t\t\tstage.addEventListener(MouseEvent.MOUSE_DOWN, _onMouseDown);\r\n\t\t\tstage.addEventListener(MouseEvent.MOUSE_UP, _onMouseUp);\r\n\t\t\tstage.addEventListener(Event.MOUSE_LEAVE, _onMouseUp);\r\n\t\t\tstage.addEventListener(MouseEvent.MOUSE_WHEEL, _onMouseWheel);\r\n\r\n\t\t\t_clickMe.addEventListener(MouseEvent.CLICK, _addBoxes);\r\n\t\t}\r\n\r\n\t\t\/\/ Make sure and call this override to specify Away3D view.\r\n\t\toverride protected function createView():CitrusView {\r\n\r\n\t\t\treturn new Away3DView(this, \"2D\");\r\n\t\t}\r\n\r\n\t\toverride public function destroy():void {\r\n\r\n\t\t\tstage.removeEventListener(MouseEvent.MOUSE_DOWN, _onMouseDown);\r\n\t\t\tstage.removeEventListener(MouseEvent.MOUSE_UP, _onMouseUp);\r\n\t\t\tstage.removeEventListener(Event.MOUSE_LEAVE, _onMouseUp);\r\n\t\t\tstage.removeEventListener(MouseEvent.MOUSE_WHEEL, _onMouseWheel);\r\n\r\n\t\t\t_clickMe.addEventListener(MouseEvent.CLICK, _addBoxes);\r\n\t\t\tremoveChild(_clickMe);\r\n\r\n\t\t\tsuper.destroy();\r\n\t\t}\r\n\r\n\t\toverride public function update(timeDelta:Number):void {\r\n\r\n\t\t\tsuper.update(timeDelta);\r\n\r\n\t\t\tif (_move) {\r\n\t\t\t\t_cameraController.panAngle = 0.3 * (stage.mouseX - _lastMouseX) + _lastPanAngle;\r\n\t\t\t\t_cameraController.tiltAngle = 0.3 * (stage.mouseY - _lastMouseY) + _lastTiltAngle;\r\n\t\t\t}\r\n\r\n\t\t\t_cameraController.lookAtPosition = _lookAtPosition;\r\n\t\t}\r\n\r\n\t\tprivate function _addBoxes(mEvt:MouseEvent):void {\r\n\t\t\tadd(new Box2DPhysicsObject(\"box\" + new Date().time, {x:Math.random() * stage.stageWidth, view:new Mesh(new CubeGeometry(30, 30, 30), new ColorMaterial(Math.random() * 0xFFFFFF))}));\r\n\t\t}\r\n\r\n\t\tprivate function _onMouseDown(mEvt:MouseEvent):void {\r\n\t\t\t_lastPanAngle = _cameraController.panAngle;\r\n\t\t\t_lastTiltAngle = _cameraController.tiltAngle;\r\n\t\t\t_lastMouseX = stage.mouseX;\r\n\t\t\t_lastMouseY = stage.mouseY;\r\n\t\t\t_move = true;\r\n\t\t}\r\n\r\n\t\tprivate function _onMouseUp(evt:Event):void {\r\n\t\t\t_move = false;\r\n\t\t}\r\n\r\n\t\tprivate function _onMouseWheel(mEvt:MouseEvent):void {\r\n\r\n\t\t\t_cameraController.distance -= mEvt.delta * 5;\r\n\r\n\t\t\tif (_cameraController.distance < 100)\r\n\t\t\t\t_cameraController.distance = 100;\r\n\t\t\telse if (_cameraController.distance > 2000)\r\n\t\t\t\t_cameraController.distance = 2000;\r\n\t\t}\r\n\r\n\t}\r\n}<\/pre>\n<p>The code of the demo is very easy to understand and doesn&#8217;t request strong Away3D knowledge!<\/p>\n<p>Away3D is added in the <a href=\"https:\/\/github.com\/alamboley\/Citrus-Engine\/blob\/master\/libs\/com\/citrusengine\/view\/away3dview\/Away3DView.as\" target=\"_blank\">Away3DView<\/a> class. The <a href=\"https:\/\/github.com\/alamboley\/Citrus-Engine\/blob\/master\/libs\/com\/citrusengine\/view\/away3dview\/Away3DArt.as\" target=\"_blank\">Away3DArt<\/a> handles the different assets. The <a href=\"https:\/\/github.com\/alamboley\/Citrus-Engine\/blob\/master\/libs\/com\/citrusengine\/view\/away3dview\/AnimationSequence.as\" target=\"_blank\">AnimationSequence<\/a> class provides animation for models.<br \/>\nWork is in progress on this 3 classes, there are lots of option to try and find the best!<\/p>\n<p><strong>Full 3D engine?<\/strong><br \/>\nWhen you create the Away3D view, we give as an argument the mode : 2D or 3D. Certainly there will be an Away3DArt2D and an Away3DArt3D classes to facilitate work on coordinates. From this point, nothing can prevent to use the Citrus Engine as a 3D engine! On my to-do list, there is the support for a 3D physics engine library (I don&#8217;t know which one at the moment)! Nape was added easily in the engine, an other physics libraries will be too.<\/p>\n<p><strong>Too many libraries included?<\/strong><br \/>\n&#8211; having lots of libraries included in the project which are never clearly used don&#8217;t have any impact on performance! It just has an impact on the final file size.<br \/>\n&#8211; from what I know, most developers never upgrade libraries used when they are working on a project unless there is a new feature needed or bug fixed. So it&#8217;s very easy &#038; quick to remove libs you don&#8217;t use in the engine, just follow the compiler&#8217;s indications. It takes less than 2 minutes!<br \/>\n&#8211; giving people choices : thanks to the Citrus Engine&#8217;s architecture it is very easy to customize the framework adding new views or physics libraries. Want to use Starling + Nape for your mobile project? Want to use Box2D + Away3D for a browser game? The engine is already ready for that! It&#8217;s something important for me.<\/p>\n<p><strong>Future<\/strong><br \/>\nFrom 29 September to 8 October, I will be in vacation. After I will focus on the Away3D support and offer a beta 3, the last one. If a 3D physics library is added, that will be for the V3.5 \ud83d\ude09<br \/>\nAlso I&#8217;m searching a software which can be used as a 3D Level Editor (like Flash Pro, Gleed or Tiled Map Editor used for 2D). Maybe <a href=\"http:\/\/www.closier.nl\/prefab\/\" target=\"_blank\">Prefab<\/a> will do the job!<\/p>\n<p>That&#8217;s it for this blog post! Don&#8217;t hesitate to comment, give your opinions and advice. Feel free to contribute!<br \/>\nCheers!<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Some days ago, I was thinking that my next post would be the annoucement of the Citrus Engine V3 stable release. Since Starling and Nape are well integrated in the engine and the API very stable, I didn&#8217;t see any new big features coming. Oh in fact, I imagined one : 3D support. This one &hellip; <a href=\"http:\/\/www.aymericlamboley.fr\/blog\/the-citrus-engine-meets-away3d\/\" class=\"more-link\">Continue reading <span class=\"screen-reader-text\">The Citrus Engine meets Away3D<\/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,92,115,51,33,11,114],"tags":[15,93,154,50,34,26],"_links":{"self":[{"href":"http:\/\/www.aymericlamboley.fr\/blog\/wp-json\/wp\/v2\/posts\/750"}],"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=750"}],"version-history":[{"count":12,"href":"http:\/\/www.aymericlamboley.fr\/blog\/wp-json\/wp\/v2\/posts\/750\/revisions"}],"predecessor-version":[{"id":1269,"href":"http:\/\/www.aymericlamboley.fr\/blog\/wp-json\/wp\/v2\/posts\/750\/revisions\/1269"}],"wp:attachment":[{"href":"http:\/\/www.aymericlamboley.fr\/blog\/wp-json\/wp\/v2\/media?parent=750"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"http:\/\/www.aymericlamboley.fr\/blog\/wp-json\/wp\/v2\/categories?post=750"},{"taxonomy":"post_tag","embeddable":true,"href":"http:\/\/www.aymericlamboley.fr\/blog\/wp-json\/wp\/v2\/tags?post=750"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}