Playing with Citrus, Cadet Editor 3D and AwayPhysics

Exactly a month ago I showed you the new Citrus engine which now supports Away3D. On the other hand, Away3D supports its own Physics engine called awayphysics, and I showed an example of what you can do with with a quick 3D physics with Away3D 4.1 and Bullet Physics Engine demonstration.

Aymeric Lamboley, the mind (and fingers) behind the Citrus Engine, pushed the thing further and gave me the permission to publish his way to quickly generate 3D physics scenes with Citrus Engine and Away3D + awayphysics.

« Now that 3D views and physics are supported, it’s time to give a look on which tool we can use as a 3D game Level Editor. At first, I thought to Prefab. This is the best tool to create a scene with Away3D, importing assets, add lights… But too complex for a simple level design, I mean : hey the Citrus Engine is not a concurrent to Unity3D (which I started to learn thanks to this awesome tutorial). Its 3D physics part is just here to create basic 3D game / puzzle. Also the level editor has to support object creation (physics object), this isn’t obvious with Prefab.
Then I gave a look to Cadet Editor, the 3D editor is very easy to handle. You can create quickly sphere, cube and plane objects, add lights… That’s the right tool to see what can be done!

I really enjoy the design of Cadet Editor, however it should manage right click with camera rotation instead of displaying a simple menu with a link to Away3D.
In Cadet3D project are saved into a format close to MXML. See our examples and its code:

<cadet:CadetScene x:id="0" framerate="30" name="Component" timeScale="1" xmlns:cadet="" xmlns:x="org.bonesframework.core.serialization.Serializer" xmlns:bones="" xmlns:cadetAway3D4="" xmlns:ns0="cadetAway3D4.components.cameras" xmlns:ns1="cadetAway3D4.components.materials" xmlns:ns2="cadetAway3D4.components.geom" xmlns:ns3="cadetAway3D4.components.lights">
  <bones:ArrayCollection x:name="children" x:id="1">
    <cadetAway3D4:RendererAway3D x:name="0" x:id="2" name="Away3D 4 Renderer">
      <ns0:CameraComponent x:name="cameraComponent" x:id="3" transform="0.9833181500434875,-8.754431490842762e-8,-0.18189382553100586,0,0.12057413905858994,0.7487242221832275,0.6518235206604004,0,0.13618825376033783,-0.66288161277771,0.7362341284751892,0,-191.17271423339844,590.8668212890625,-854.3065185546875,1" name="Camera">
        <bones:ArrayCollection x:name="children" x:id="4"/>
    <cadetAway3D4:MeshComponent x:name="1" x:id="5" transform="1,0,0,0,0,1,0,0,0,0,1,0,0,0,0,1" name="Plane">
      <ns1:ColorMaterialComponent x:name="materialComponent" x:id="6" gloss="50" specularStrength="1" color="333722" name="Blue Material" depthCompareMode="less"/>
      <bones:ArrayCollection x:name="children" x:id="7">
        <ns2:PlaneGeometryComponent x:name="0" x:id="8" height="1000" segmentsH="1" segmentsW="1" name="a platform" width="1000"/>
      <x:Ref x:name="geometryComponent" x:id="8"/>
    <ns3:DirectionalLightComponent x:name="2" x:id="9" color="16777215" specular="1" ambient="0.1" transform="0.9063077569007874,0,-0.4226183295249939,0,0.3830223083496094,0.42261824011802673,0.8213937878608704,0,0.17860621213912964,-0.9063078165054321,0.3830221891403198,0,0,0,0,1" ambientColor="16777215" diffuse="1" name="Directional Light">
      <bones:ArrayCollection x:name="children" x:id="10"/>
    <x:Ref x:name="3" x:id="3"/>
    <cadetAway3D4:MeshComponent x:name="4" x:id="11" transform="1,0,0,0,0,1,0,0,0,0,1,0,-58.91594696044922,146.62379455566406,-32.241188049316406,1" name="Cube">
      <ns1:ColorMaterialComponent x:name="materialComponent" x:id="12" gloss="50" specularStrength="1" color="489736" name="Green Material" depthCompareMode="less"/>
      <bones:ArrayCollection x:name="children" x:id="13">
        <ns2:CubeGeometryComponent x:name="0" x:id="14" height="180.85" segmentsH="1" tile6="1" depth="180.85" segmentsW="1" segmentsD="1" name="a cube" width="180.85"/>
      <x:Ref x:name="geometryComponent" x:id="14"/>
    <cadetAway3D4:MeshComponent x:name="5" x:id="15" transform="1,0,0,0,0,1,0,0,0,0,1,0,-140.8660888671875,340.4476318359375,-54.04615783691406,1" name="Sphere">
      <ns1:ColorMaterialComponent x:name="materialComponent" x:id="16" gloss="50" specularStrength="1" color="12320768" name="Red Material" depthCompareMode="less"/>
      <bones:ArrayCollection x:name="children" x:id="17">
        <ns2:SphereGeometryComponent x:name="0" x:id="18" segmentsH="12" segmentsW="16" radius="84.13898408877864" name="a sphere"/>
      <x:Ref x:name="geometryComponent" x:id="18"/>
    <cadetAway3D4:MeshComponent x:name="6" x:id="19" transform="1,0,0,0,0,1,0,0,0,0,1,0,151.02638244628906,146.50900268554688,127.5979995727539,1" name="Sphere">
      <x:Ref x:name="materialComponent" x:id="16"/>
      <bones:ArrayCollection x:name="children" x:id="20">
        <ns2:SphereGeometryComponent x:name="0" x:id="21" segmentsH="12" segmentsW="16" radius="52.35" name="Sphere Geometry"/>
      <x:Ref x:name="geometryComponent" x:id="21"/>
    <cadetAway3D4:MeshComponent x:name="7" x:id="22" transform="1,0,0,0,0,1,0,0,0,0,1,0,164.13088989257813,348.98486328125,102.31562805175781,1" name="Cube">
      <x:Ref x:name="materialComponent" x:id="12"/>
      <bones:ArrayCollection x:name="children" x:id="23">
        <ns2:CubeGeometryComponent x:name="0" x:id="24" height="76.87" segmentsH="1" tile6="1" depth="76.87" segmentsW="1" segmentsD="1" name="Cube Geometry" width="76.87064711138314"/>
      <x:Ref x:name="geometryComponent" x:id="24"/>
    <cadetAway3D4:MeshComponent x:name="8" x:id="25" transform="1,0,0,0,0,1,0,0,0,0,1,0,-161.5980987548828,37.646751403808594,291.46575927734375,1" name="Cube">
      <x:Ref x:name="materialComponent" x:id="12"/>
      <bones:ArrayCollection x:name="children" x:id="26">
        <ns2:CubeGeometryComponent x:name="0" x:id="27" height="75.29" segmentsH="1" tile6="1" depth="75.29" segmentsW="1" segmentsD="1" name="Cube Geometry" width="75.29350445797198"/>
      <x:Ref x:name="geometryComponent" x:id="27"/>
    <cadetAway3D4:MeshComponent x:name="9" x:id="28" transform="1,0,0,0,0,1,0,0,0,0,1,0,-10.505144119262695,181.75933837890625,69.79844665527344,1" name="Cube">
      <x:Ref x:name="materialComponent" x:id="12"/>
      <bones:ArrayCollection x:name="children" x:id="29">
        <ns2:CubeGeometryComponent x:name="0" x:id="30" height="66.29" segmentsH="1" tile6="1" depth="66.29" segmentsW="1" segmentsD="1" name="Cube Geometry" width="66.29740645095558"/>
      <x:Ref x:name="geometryComponent" x:id="30"/>
    <cadetAway3D4:MeshComponent x:name="10" x:id="31" transform="1,0,0,0,0,1,0,0,0,0,1,0,-393.7727966308594,113.91818237304688,-41.75886917114258,1" name="Cube">
      <x:Ref x:name="materialComponent" x:id="12"/>
      <bones:ArrayCollection x:name="children" x:id="32">
        <ns2:CubeGeometryComponent x:name="0" x:id="33" height="84.85" segmentsH="1" tile6="1" depth="84.85" segmentsW="1" segmentsD="1" name="Cube Geometry" width="84.85060333420417"/>
      <x:Ref x:name="geometryComponent" x:id="33"/>
    <cadetAway3D4:MeshComponent x:name="11" x:id="34" transform="1,0,0,0,0,1,0,0,0,0,1,0,110.92161560058594,25.127405166625977,230.1426239013672,1" name="Cube">
      <x:Ref x:name="materialComponent" x:id="12"/>
      <bones:ArrayCollection x:name="children" x:id="35">
        <ns2:CubeGeometryComponent x:name="0" x:id="36" height="50.25" segmentsH="1" tile6="1" depth="50.25" segmentsW="1" segmentsD="1" name="Cube Geometry" width="50.25481013821924"/>
      <x:Ref x:name="geometryComponent" x:id="36"/>
    <cadetAway3D4:MeshComponent x:name="12" x:id="37" transform="1,0,0,0,0,1,0,0,0,0,1,0,156.6885223388672,99.52238464355469,51.9654655456543,1" name="Cube">
      <x:Ref x:name="materialComponent" x:id="12"/>
      <bones:ArrayCollection x:name="children" x:id="38">
        <ns2:CubeGeometryComponent x:name="0" x:id="39" height="49.76" segmentsH="1" tile6="1" depth="49.76" segmentsW="1" segmentsD="1" name="Cube Geometry" width="49.76054139091827"/>
      <x:Ref x:name="geometryComponent" x:id="39"/>
    <cadetAway3D4:MeshComponent x:name="13" x:id="40" transform="1,0,0,0,0,1,0,0,0,0,1,0,-241.41665649414063,106.39049530029297,-15.305731773376465,1" name="Sphere">
      <x:Ref x:name="materialComponent" x:id="16"/>
      <bones:ArrayCollection x:name="children" x:id="41">
        <ns2:SphereGeometryComponent x:name="0" x:id="42" segmentsH="12" segmentsW="16" radius="81.08778158664182" name="Sphere Geometry"/>
      <x:Ref x:name="geometryComponent" x:id="42"/>
    <cadetAway3D4:MeshComponent x:name="14" x:id="43" transform="1,0,0,0,0,1,0,0,0,0,1,0,240.3887176513672,66.32697296142578,28.3767147064209,1" name="Sphere">
      <x:Ref x:name="materialComponent" x:id="16"/>
      <bones:ArrayCollection x:name="children" x:id="44">
        <ns2:SphereGeometryComponent x:name="0" x:id="45" segmentsH="12" segmentsW="16" radius="42.73389288130251" name="Sphere Geometry"/>
      <x:Ref x:name="geometryComponent" x:id="45"/>
    <cadetAway3D4:MeshComponent x:name="15" x:id="46" transform="1,0,0,0,0,1,0,0,0,0,1,0,77.10282897949219,82.25804901123047,-173.88815307617188,1" name="Sphere">
      <x:Ref x:name="materialComponent" x:id="16"/>
      <bones:ArrayCollection x:name="children" x:id="47">
        <ns2:SphereGeometryComponent x:name="0" x:id="48" segmentsH="12" segmentsW="16" radius="44.53863475379832" name="Sphere Geometry"/>
      <x:Ref x:name="geometryComponent" x:id="48"/>
    <cadetAway3D4:MeshComponent x:name="16" x:id="49" transform="1,0,0,0,0,1,0,0,0,0,1,0,-8.887575149536133,77.42305755615234,325.1551513671875,1" name="Sphere">
      <x:Ref x:name="materialComponent" x:id="16"/>
      <bones:ArrayCollection x:name="children" x:id="50">
        <ns2:SphereGeometryComponent x:name="0" x:id="51" segmentsH="12" segmentsW="16" radius="26.87288819730761" name="Sphere Geometry"/>
      <x:Ref x:name="geometryComponent" x:id="51"/>
    <cadetAway3D4:MeshComponent x:name="17" x:id="52" transform="1,0,0,0,0,1,0,0,0,0,1,0,-111.85294342041016,150.3166961669922,314.55419921875,1" name="Sphere">
      <x:Ref x:name="materialComponent" x:id="16"/>
      <bones:ArrayCollection x:name="children" x:id="53">
        <ns2:SphereGeometryComponent x:name="0" x:id="54" segmentsH="12" segmentsW="16" radius="36.59617847826625" name="Sphere Geometry"/>
      <x:Ref x:name="geometryComponent" x:id="54"/>
    <cadetAway3D4:MeshComponent x:name="18" x:id="55" transform="1,0,0,0,0,1,0,0,0,0,1,0,194.72483825683594,66.20823669433594,249.91229248046875,1" name="Sphere">
      <x:Ref x:name="materialComponent" x:id="16"/>
      <bones:ArrayCollection x:name="children" x:id="56">
        <ns2:SphereGeometryComponent x:name="0" x:id="57" segmentsH="12" segmentsW="16" radius="34.06102262376309" name="Sphere Geometry"/>
      <x:Ref x:name="geometryComponent" x:id="57"/>
    <cadetAway3D4:MeshComponent x:name="19" x:id="58" transform="1,0,0,0,0,1,0,0,0,0,1,0,153.58782958984375,59.29662322998047,139.8775634765625,1" name="Sphere">
      <x:Ref x:name="materialComponent" x:id="16"/>
      <bones:ArrayCollection x:name="children" x:id="59">
        <ns2:SphereGeometryComponent x:name="0" x:id="60" segmentsH="12" segmentsW="16" radius="27.704758141615848" name="Sphere Geometry"/>
      <x:Ref x:name="geometryComponent" x:id="60"/>
    <x:Ref x:name="20" x:id="16"/>
    <x:Ref x:name="21" x:id="6"/>
    <x:Ref x:name="22" x:id="12"/>
  <Object x:name="userData" x:id="61"/>
  <bones:DependencyManager x:name="dependencyManager" x:id="62">
    <bones:ArrayCollection x:name="dependencyNodes" x:id="63"/>

This format isn’t easy to handle! Cadet should really export to XML or JSON format.
Anyway this what I’ve done:

You can remove the AwayPhysics debug view via the console:

set awayPhysics visible false

As usual all the source code is available on the GitHub.

This is the GameState:

package awayphysics.cadet3d {
	import away3d.controllers.HoverController;
	import away3d.debug.AwayStats;
	import com.citrusengine.core.State;
	import com.citrusengine.physics.awayphysics.AwayPhysics;
	import com.citrusengine.utils.ObjectMaker3D;
	import com.citrusengine.view.CitrusView;
	import com.citrusengine.view.away3dview.Away3DView;
	import flash.geom.Vector3D;
	 * @author Aymeric
	public class Cadet3DGameState extends State {
		[Embed(source="/../embed/3D/simpletest.away3d4", mimeType="application/octet-stream")]
		private const _CADET_LEVEL: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();
		public function Cadet3DGameState() {
		override public function initialize():void {
			addChild(new AwayStats((view as Away3DView).viewRoot));
			var awayPhysics:AwayPhysics = new AwayPhysics("awayPhysics");
			awayPhysics.visible = true;
			ObjectMaker3D.FromCadetEditor3D(XML(new _CADET_LEVEL()));
			_cameraController = new HoverController((view as Away3DView), null, 175, 20, 1000);
			stage.addEventListener(MouseEvent.MOUSE_DOWN, _onMouseDown);
			stage.addEventListener(MouseEvent.MOUSE_UP, _onMouseUp);
			stage.addEventListener(Event.MOUSE_LEAVE, _onMouseUp);
			stage.addEventListener(MouseEvent.MOUSE_WHEEL, _onMouseWheel);
		// Make sure and call this override to specify Away3D view.
		override protected function createView():CitrusView {
			return new Away3DView(this);
		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);
		override public function update(timeDelta:Number):void {
			if (_move) {
				_cameraController.panAngle = 0.3 * (stage.mouseX - _lastMouseX) + _lastPanAngle;
				_cameraController.tiltAngle = 0.3 * (stage.mouseY - _lastMouseY) + _lastTiltAngle;
			_cameraController.lookAtPosition = _lookAtPosition;
		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 -= * 5;
			if (_cameraController.distance < 100)
				_cameraController.distance = 100;
			else if (_cameraController.distance > 2000)
				_cameraController.distance = 2000;

Like Flash as Level Editor or Tiled Map Editor, we use the ObjectMaker class. This is the template for Cadet Editor 3D:

public static function FromCadetEditor3D(levelData:XML, addToCurrentState:Boolean = true):Array {
	var ce:CitrusEngine = CitrusEngine.getInstance();
	var params:Object;
	var objects:Array = [];
	var type:String;
	var radius:Number;
	var object:AwayPhysicsObject;
	for each (var root:XML in levelData.children()) {
		for each (var parent:XML in root.children()) {
			type = parent.@name;
			if (type == "Cube" || type == "Plane" || type == "Sphere") {
				var transform:Array = parent.@transform.split(",");
				params = {};
				params.x = transform[12];
				params.y = transform[13];
				params.z = transform[14];
				for each (var child:XML in parent.children()) {
					for each (var finalElement:XML in child.children()) {
						params.width = finalElement.@width;
						params.height = finalElement.@height;
						params.depth = finalElement.@depth;
						radius = finalElement.@radius;
						if (radius)
							params.radius = finalElement.@radius;
						if (type == "Plane") {
							// the plane seems to use the height as the depth
							params.depth = params.height;
							params.height = 0;
							params.view = new Mesh(new CubeGeometry(params.width, params.height, params.depth), new ColorMaterial(0xFF0000));
							object = new Platform("plane", params);
						} else {
							if (params.radius) {
								params.view = new Mesh(new SphereGeometry(params.radius), new ColorMaterial(0x00FF00));
								object = new AwayPhysicsObject("sphere", params);
							} else {
								params.view = new Mesh(new CubeGeometry(params.width, params.height, params.depth), new ColorMaterial(0x0000FF));
								object = new AwayPhysicsObject("cube", params);
	if (addToCurrentState)
		for each (object in objects) ce.state.add(object);
	return objects;

As you can notice, the object maker uses CE prebuilt objects for AwayPhysics but not in a dynamic way.

This is some points that Cadet Editor should allow to be an excellent level editor :
– quickly most of the points are in Tiled Map Editor, checkout Citrus Engine support there.
– an easy to parse exporter format (xml or json).
– adding personal properties so we can precize type, gravity, view for example directly in the level editor. A must have! »

Follow Aymeric Lamboley blog for the latest news.

Get the most popular Phaser 3 book

Through 202 pages, 32 source code examples and an Android Studio project you will learn how to build cross platform HTML5 games and create a complete game along the way.

Get the book

// 1+2=3
// 100 rounds
// 10000000
// 2 Cars
// 2048
// A Blocky Christmas
// A Jumping Block
// A Life of Logic
// Angry Birds
// Angry Birds Space
// Artillery
// Astro-PANIC!
// Avoider
// Back to Square One
// Ball Game
// Ball vs Ball
// Ball: Revamped
// Balloon Invasion
// BallPusher
// Ballz
// Bar Balance
// Bejeweled
// Biggification
// Block it
// Blockage
// Bloons
// Boids
// Bombuzal
// Boom Dots
// Bouncing Ball
// Bouncing Ball 2
// Bouncy Light
// BoxHead
// Breakout
// Bricks
// Bubble Chaos
// Bubbles 2
// Card Game
// Castle Ramble
// Chronotron
// Circle Chain
// Circle Path
// Circle Race
// Circular endless runner
// Cirplosion
// CLOCKS - The Game
// Color Hit
// Color Jump
// ColorFill
// Columns
// Concentration
// Crossy Road
// Crush the Castle
// Cube Jump
// CubesOut
// Dash N Blast
// Dashy Panda
// Deflection
// Diamond Digger Saga
// Don't touch the spikes
// Dots
// Down The Mountain
// Drag and Match
// Draw Game
// Drop Wizard
// DROP'd
// Dudeski
// Dungeon Raid
// Educational Game
// Elasticity
// Endless Runner
// Erase Box
// Eskiv
// Farm Heroes Saga
// Filler
// Flappy Bird
// Fling
// Flipping Legend
// Floaty Light
// Fuse Ballz
// GearTaker
// Gem Sweeper
// Globe
// Goat Rider
// Gold Miner
// Grindstone
// GuessNext
// Helicopter
// Hero Emblems
// Hero Slide
// Hexagonal Tiles
// HookPod
// Hop Hop Hop Underwater
// Horizontal Endless Runner
// Hundreds
// Hungry Hero
// Hurry it's Christmas
// InkTd
// Iromeku
// Jet Set Willy
// Jigsaw Game
// Knife Hit
// Knightfall
// Legends of Runeterra
// Lep's World
// Line Rider
// Lumines
// Magick
// MagOrMin
// Mass Attack
// Math Game
// Maze
// Meeblings
// Memdot
// Metro Siberia Underground
// Mike Dangers
// Mikey Hooks
// Nano War
// Nodes
// o:anquan
// One Button Game
// One Tap RPG
// Ononmin
// Pacco
// Perfect Square!
// Perfectionism
// Phyballs
// Pixel Purge
// PixelField
// Planet Revenge
// Plants Vs Zombies
// Platform
// Platform game
// Plus+Plus
// Pocket Snap
// Poker
// Pool
// Pop the Lock
// Pop to Save
// Poux
// Pudi
// Pumpkin Story
// Puppet Bird
// Pyramids of Ra
// qomp
// Quick Switch
// Racing
// Radical
// Rebuild Chile
// Renju
// Rise Above
// Risky Road
// Roguelike
// Roly Poly
// Run Around
// Rush Hour
// SameGame
// SamePhysics
// Save the Totem
// Security
// Serious Scramblers
// Shrink it
// Sling
// Slingy
// Snowflakes
// Sokoban
// Space Checkers
// Space is Key
// Spellfall
// Spinny Gun
// Splitter
// Spring Ninja
// Sproing
// Stabilize!
// Stack
// Stick Hero
// String Avoider
// Stringy
// Sudoku
// Super Mario Bros
// Surfingers
// Survival Horror
// Talesworth Adventure
// Tetris
// The Impossible Line
// The Moops - Combos of Joy
// The Next Arrow
// Threes
// Tic Tac Toe
// Timberman
// Tiny Wings
// Tipsy Tower
// Toony
// Totem Destroyer
// Tower Defense
// Trick Shot
// Tunnelball
// Turn
// Turnellio
// TwinSpin
// vvvvvv
// Warp Shift
// Way of an Idea
// Whack a Creep
// Wheel of Fortune
// Where's my Water
// Wish Upon a Star
// Word Game
// Wordle
// Worms
// Yanga
// Yeah Bunny
// Zhed
// zNumbers