The Citrus Engine meets Away3D

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’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 & 3D Flash game engine!

I’m not familiar with 3D. Last year I had a quick look with ThreeJS and then ported it in Away3D. That was my only experience with 3D until now, so don’t hesitate to correct me and to offer some suggestions. That’s lots of new stuff to learn, and that’s pretty exciting!
Also if you have links to free arts/assets Away3D friendly, share please πŸ™‚

For the impatients, here is the demo! 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.
I use Box2D for the physics (it could be Nape), to see the Box2D debug view press tab to open the console and write :

set box2D visible true

All the source code is available on GitHub (with samples) and on GoogleCode (engine code only). The Away3D part will evolve a lot in the next month, the 2D stuff is very stable to use!

The case study : Trine & Rayman
Trine 2 is with Rayman Origins one of my favorite platformer game. Both have different styles & gameplay :


Even if Trine is a 2D platformer game, it uses 3D arts whereas Rayman doesn’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.

Why Away3D?
I’ve chosen Away3D since like Starling it is free, open source and strongly supported by Adobe! Note that it’s very easy to add an other 3D engine.

Handle 3D view in 2D physics world
Since Away3D doesn’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 project and unproject method using an OrthographicLens camera but that was a nightmare. I needed some help.
I discovered Roger Engelbert’s blog 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’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 one. 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 : 3D Coordinates from 2D Simulation. That was very kind and the article is very smart. No more project unproject method! Thank you Roger!

The demo
Please refer to the top of the article for the demo and instructions. The Perelith Knight asset come from this awesome example from Away3D’s hero Rob Bateman : Away3D multi-knight demo. I used Misfit Model 3D software to manage MD2 files changing animations name and moving a bit the jump animation.

The code
Main file

package away3dbox2d {
 
	import com.citrusengine.core.CitrusEngine;
 
	[SWF(frameRate="60")]
 
	/**
	* @author Aymeric
	*/
	public class Main extends CitrusEngine {
 
		public function Main() {
 
			state = new Away3DGameState();
		}
	}
}

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
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
package away3dbox2d {
 
	import away3d.controllers.HoverController;
	import away3d.debug.AwayStats;
	import away3d.entities.Mesh;
	import away3d.library.AssetLibrary;
	import away3d.loaders.parsers.MD2Parser;
	import away3d.materials.ColorMaterial;
	import away3d.primitives.CubeGeometry;
	import away3d.primitives.SphereGeometry;
 
	import com.citrusengine.core.State;
	import com.citrusengine.math.MathVector;
	import com.citrusengine.objects.Box2DPhysicsObject;
	import com.citrusengine.objects.CitrusSprite;
	import com.citrusengine.objects.platformer.box2d.Coin;
	import com.citrusengine.objects.platformer.box2d.Hero;
	import com.citrusengine.objects.platformer.box2d.Platform;
	import com.citrusengine.physics.Box2D;
	import com.citrusengine.view.CitrusView;
	import com.citrusengine.view.away3dview.AnimationSequence;
	import com.citrusengine.view.away3dview.Away3DArt;
	import com.citrusengine.view.away3dview.Away3DView;
 
	import flash.display.Sprite;
	import flash.events.Event;
	import flash.events.MouseEvent;
	import flash.geom.Rectangle;
	import flash.geom.Vector3D;
 
	/**
	 * @author Aymeric
	 */
	public class Away3DGameState extends State {
 
		[Embed(source="/../embed/pknight/pknight3.png")]
		public static var PKnightTexture3:Class;
 
		[Embed(source="/../embed/pknight/pknight.md2", mimeType="application/octet-stream")]
		public static var PKnightModel:Class;
 
		// navigation variables
		private var _cameraController:HoverController;
 
		private var _move:Boolean = false;
		private var _lastPanAngle:Number;
		private var _lastTiltAngle:Number;
		private var _lastMouseX:Number;
		private var _lastMouseY:Number;
		private var _lookAtPosition:Vector3D = new Vector3D();
 
		private var _heroArt:AnimationSequence;
		private var _hero:Hero;
 
		private var _clickMe:Sprite;
 
		public function Away3DGameState() {
			super();
		}
 
		override public function initialize():void {
 
			super.initialize();
 
			addChild(new AwayStats((view as Away3DView).viewRoot));
 
			var box2D:Box2D = new Box2D("box2D");
			// box2D.visible = true;
			add(box2D);
 
			AssetLibrary.enableParser(MD2Parser);
			_heroArt = new AnimationSequence(new PKnightModel(), new PKnightTexture3());
			_heroArt.scale(2);
 
			var cube1:Mesh = new Mesh(new CubeGeometry(300, 300, 0), new ColorMaterial(0x0000FF));
			var cube2:Mesh = new Mesh(new SphereGeometry(15), new ColorMaterial(0xFFFF00));
			var cube3:Mesh = new Mesh(new CubeGeometry(2500, 10, 300), new ColorMaterial(0xFFFFFF));
 
			var cloud:CitrusSprite = new CitrusSprite("cloud", {x:150, y:50, width:300, height:300, view:cube1, parallax:0.3});
			add(cloud);
			(view.getArt(cloud) as Away3DArt).z = 300;
			// equivalent to -> cube1.z = 300;
 
			add(new Platform("platformBottom", {x:stage.stageWidth / 2, y:stage.stageHeight - 30, width:2500, height:10, view:cube3}));
 
			_hero = new Hero("hero", {x:150, y:50, width:80, height:90, view:_heroArt});
			add(_hero);
 
			var coin:Coin = new Coin("coin", {x:300, y:200, width:30, height:30, view:cube2});
			add(coin);
 
			view.setupCamera(_hero, new MathVector(320, 240), new Rectangle(0, 0, 1550, 450), new MathVector(.25, .05));
			_cameraController = new HoverController((view as Away3DView).viewRoot.camera, null, 175, 20, 500);
 
			_clickMe = new Sprite();
			_clickMe.y = stage.stageHeight - 50;
			addChild(_clickMe);
			_clickMe.graphics.beginFill(0xFF0000);
			_clickMe.graphics.drawRect(10, 0, 40, 40);
			_clickMe.graphics.endFill();
			_clickMe.buttonMode = true;
 
			stage.addEventListener(MouseEvent.MOUSE_DOWN, _onMouseDown);
			stage.addEventListener(MouseEvent.MOUSE_UP, _onMouseUp);
			stage.addEventListener(Event.MOUSE_LEAVE, _onMouseUp);
			stage.addEventListener(MouseEvent.MOUSE_WHEEL, _onMouseWheel);
 
			_clickMe.addEventListener(MouseEvent.CLICK, _addBoxes);
		}
 
		// Make sure and call this override to specify Away3D view.
		override protected function createView():CitrusView {
 
			return new Away3DView(this, "2D");
		}
 
		override public function destroy():void {
 
			stage.removeEventListener(MouseEvent.MOUSE_DOWN, _onMouseDown);
			stage.removeEventListener(MouseEvent.MOUSE_UP, _onMouseUp);
			stage.removeEventListener(Event.MOUSE_LEAVE, _onMouseUp);
			stage.removeEventListener(MouseEvent.MOUSE_WHEEL, _onMouseWheel);
 
			_clickMe.addEventListener(MouseEvent.CLICK, _addBoxes);
			removeChild(_clickMe);
 
			super.destroy();
		}
 
		override public function update(timeDelta:Number):void {
 
			super.update(timeDelta);
 
			if (_move) {
				_cameraController.panAngle = 0.3 * (stage.mouseX - _lastMouseX) + _lastPanAngle;
				_cameraController.tiltAngle = 0.3 * (stage.mouseY - _lastMouseY) + _lastTiltAngle;
			}
 
			_cameraController.lookAtPosition = _lookAtPosition;
		}
 
		private function _addBoxes(mEvt:MouseEvent):void {
			add(new Box2DPhysicsObject("box" + new Date().time, {x:Math.random() * stage.stageWidth, view:new Mesh(new CubeGeometry(30, 30, 30), new ColorMaterial(Math.random() * 0xFFFFFF))}));
		}
 
		private function _onMouseDown(mEvt:MouseEvent):void {
			_lastPanAngle = _cameraController.panAngle;
			_lastTiltAngle = _cameraController.tiltAngle;
			_lastMouseX = stage.mouseX;
			_lastMouseY = stage.mouseY;
			_move = true;
		}
 
		private function _onMouseUp(evt:Event):void {
			_move = false;
		}
 
		private function _onMouseWheel(mEvt:MouseEvent):void {
 
			_cameraController.distance -= mEvt.delta * 5;
 
			if (_cameraController.distance < 100)
				_cameraController.distance = 100;
			else if (_cameraController.distance > 2000)
				_cameraController.distance = 2000;
		}
 
	}
}

The code of the demo is very easy to understand and doesn’t request strong Away3D knowledge!

Away3D is added in the Away3DView class. The Away3DArt handles the different assets. The AnimationSequence class provides animation for models.
Work is in progress on this 3 classes, there are lots of option to try and find the best!

Full 3D engine?
When 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’t know which one at the moment)! Nape was added easily in the engine, an other physics libraries will be too.

Too many libraries included?
– having lots of libraries included in the project which are never clearly used don’t have any impact on performance! It just has an impact on the final file size.
– 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’s very easy & quick to remove libs you don’t use in the engine, just follow the compiler’s indications. It takes less than 2 minutes!
– giving people choices : thanks to the Citrus Engine’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’s something important for me.

Future
From 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 πŸ˜‰
Also I’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 Prefab will do the job!

That’s it for this blog post! Don’t hesitate to comment, give your opinions and advice. Feel free to contribute!
Cheers!

16 thoughts on “The Citrus Engine meets Away3D

  1. this is really good news, I thought Away3D to make a game with the 3D view, you made the right choice for Away3D, it is open source 5 years from the beginning, a great comunity and support , it supports the highest 3D futures like dynamic shadow.
    For the physics ,Why you don’t use AwayPhysics instead Box2D https://github.com/away3d/awayphysics-core-fp11
    -For the 3D level editor, Prefab is the best right now, and I think that AwayMedia works on Game Tools ,it will be awesome .

  2. Yep, it will certainly be AwayPhysics since it seems more powerful and have better performance than JigLib. At the moment I use Box2D for my test, I’m focusing on Away3D view and later 3D physics.
    I don’t know if in Prefab we can add properties which aren’t interpreted, e.g. for the Citrus Object class name.
    That’s lot of new stuff for me πŸ˜‰

  3. WONDERFUL JOB! You are amazing!

    I would be really grateful if you coded the fast (non-adobe-speed-tax) version with NAPE.

    AwayPhysics and Box2dAlchemy are completely useless for indies looking to make browser games using stage3d since you would have to share your profits with Adobe. If you made a nape version of this demo using NAPE (or box2d without alchemy), I would be absolutely ecstatic, humble in my infinite gratitude, and would dance a jig while singing a song about how awesome you are.

    Keep up the fantastic work! =)

  4. Thank you!

    It is really quick to have it running on Nape : the demo.
    Use the console with tab key and write : set nape visible true (then press enter)

    Note that Nape platformer pre-built object need some work to have exactly the same abilities than the Box2D one.

    So how to make this demo running on Nape?
    Juste change all the box2d package into nape package, and :
    var box2D:Box2D = new Box2D(“box2D”); -> var nape:Nape = new Nape(“nape”);
    Box2DPhysicsObject becomes NapePhysicsObject
    And that’s it, 30 seconds!

    Now I’m waiting the jig πŸ˜‰ Oh and continue your amazing work too =)

  5. Hi..
    Nice work. I have a question. Do you think it is possible to make game similar to ‘trime2’ in adobe air, special for mobile?
    I am looking for any good examples to be sure it is possible.
    Regards

  6. great look good
    awayphysic is very fast and powerfull look my last demo (it’s for live editor)
    http://perso.numericable.fr/chamaslot/lab/
    i work with new version and on last player 11.5 is terrible fast. (~300 rigidbody at full speed)
    I can help you on that .
    for 3d assets the best is AWD is compresed format but you need 3dsmax

  7. Hey, I think it’s possible but you will have to limit the use of blend mode and number of particles! I will make some tests with 3D on mobile devices soon πŸ˜‰

  8. Nothing to worry if the revenue of your game is below $50,000. And these premium taxes isn’t applied to mobile games/app.

    http://www.adobe.com/devnet/flashplayer/articles/premium-features.html


    McFunkypants:

    WONDERFUL JOB! You are amazing!
    I would be really grateful if you coded the fast (non-adobe-speed-tax) version with NAPE.
    AwayPhysics and Box2dAlchemy are completely useless for indies looking to make browser games using stage3d since you would have to share your profits with Adobe. If you made a nape version of this demo using NAPE (or box2d without alchemy), I would be absolutely ecstatic, humble in my infinite gratitude, and would dance a jig while singing a song about how awesome you are.
    Keep up the fantastic work! =)

  9. Hi, been doing some experiments of my own and I’ve noticed that the model breaks when moving left. This happens regarding what model I’ve tried. Any chance somebody found a solution for this bug?

  10. Indeed, I’m not sure what is the problem : when the character goes left, it just has its scaleX value changing to -1 to save memory. Don’t know if it is the problem.

Leave a Reply

Your email address will not be published. Required fields are marked *