Tag Archives: flash

Silly Family available on the Mac App Store

Hey, I’m glad to share with you that Silly Family is now available on the Mac App Store! Don’t hear anything about Silly Family? Have a look there.

Thanks to Adobe AIR technology, with exactly the same code base we released the game on iOS, Mac, Android & Amazon. We could also release it to PC, but not sure where to sell it… I hope AIR would work on Windows 8 modern UI mode and so sell it via Windows store, but that’s an other story.

The first time you’re looking for putting your AS3/AIR game on the iOS store, you will find lots of tuts. However for the Mac App Store, that’s an other story. This one is the most concrete that I found.

Here is my bash script, FromAirAppToMacAppStore, working for AIR 13:

#!/bin/sh
#!/usr/bin/env bash
#$ -N $2
 
DIR="$( cd "$( dirname "$0" )" && pwd )"
 
cp $DIR/Info.plist $DIR/"$1".app/Contents
cp $DIR/Icon.icns $DIR/"$1".app/Contents/Resources
 
rm $DIR/"$1".app/Contents/Frameworks/Adobe\ AIR.framework/Versions/Current/Resources/WebKit.dylib
rm -rf $DIR/"$1".app/Contents/Frameworks/Adobe\ AIR.framework/Versions/1.0/Resources/AdobeCP15.plugin
rm -rf $DIR/"$1".app/Contents/Frameworks/Adobe\ AIR.framework/Versions/1.0/Resources/Flash\ Player.plugin
rm -rf $DIR/"$1".app/Contents/Frameworks/Adobe\ AIR.framework/Versions/1.0/Resources/adobecp.plugin
 
chmod -R 777 $DIR/"$1".app/
 
#codesign -f -v -s "$2" $DIR/"$1".app/Contents/Frameworks/Adobe\ AIR.framework/Versions/1.0/Resources/AdobeCP15.plugin
#codesign -f -v -s "$2" $DIR/"$1".app/Contents/Frameworks/Adobe\ AIR.framework/Versions/1.0/Resources/Flash\ Player.plugin
#codesign -f -v -s "$2" $DIR/"$1".app/Contents/Frameworks/Adobe\ AIR.framework/Versions/1.0/Resources/adobecp.plugin
codesign -f -v -s "$2" $DIR/"$1".app/Contents/Frameworks/Adobe\ AIR.framework/Versions/1.0
codesign -f -v -s "$2" $DIR/"$1".app/Contents/Frameworks/Adobe\ AIR.framework
codesign -f -v -s "$2" --entitlements $DIR/entitlements.plist $DIR/"$1".app
 
codesign --display --entitlements - $DIR/"$1".app
 
productbuild --component $DIR/"$1".app /Applications $DIR/"$1".pkg --sign "$3"
 
codesign -v --verify $DIR/"$1".app
 
exit 0
 
#sh FromAirToMacAppStore.sh "SillyFamilyDesktop" "3rd Party Mac Developer Application: DaVikingCode (XXXXXXXXXX)" "3rd Party Mac Developer Installer: DaVikingCode (XXXXXXXXXX)"

For more instructions (and latest update) have a look on my Github’s repository. Note that I’m moving some repositories to Da Viking Code‘s Github account, the Citrus will move too ;)

Meet Sophie la Girafe

Being a freelancer is awesome. You’re free to work for everyone, from everywhere, on a huge variety of project!
Today I’m glad to share a new one: an iOS Application for the famous Sophie la Girafe! It has been made with love using the Citrus Engine.

Sophie la Girafe
sophieFrench icon and true friend of babies for more than 50 years, the very famous Sophie la girafe is now coming on your iPad and iPhone with a brand new edutaining app that will get you to her mythical world.

Sophie la girafe and her friends Lazare the cat, Margot the turtle, Gabin the bear, Joséphine the mouse and Kiwi the bird will stimulate the five senses of your child thanks to lots of entertaining, funny and educational games. The different stories will help him/her to enter the famous world of Sophie. In addition, soft and catchy melodies complete the application.

-Stimulation of the five senses of your child thanks to ten games
-Several difficulty levels to adapt to your child’s needs and desires
-A imagination oriented app thanks to beautiful visual effects and catchy melodies
-Switch from English to French! Have fun spotting the differences, and have your child accustomed to a foreign language
-A super-easy gameplay, accessible to young kids.

Technology?
bainWhen launching the app, did you see there is a very short loading time compared to many applications? Does there are other loading time inside some part of the app? No, there isn’t. We used Flash technology with GPU mode and the traditional display list!

Nowadays many people are thinking that applications should be made using strong libraries dealing with the GPU using OpenGL. In the Flash community, Starling has the wind in its sails. It’s an excellent framework, giving awesome performances: a 2D shooter games with particle effects, many missiles and enemies will run at 60FPS! But for an app like Sophie la Girafe, it would failed. Why?

Animations are the key of every application! Let’s take a look on them, they are huge (big animations and very long). Don’t forget that using an OpenGL based framework, you will be limited to use SpriteSheet (4096 * 4096 PNG for iPad retina) or video. Video are very heavy, and it isn’t always easy to add some interaction. For example, if you’ve some layer management, it’ll be a nightmare. Obviously you can combine SpriteSheets together but don’t forget that PNG needs to be uploaded on the GPU, and its long process! Also RAM memory is not the same than GPU memory, this last one is much more limited. Making this application with Starling meant to upload new SpriteSheets before each long animaiton and then dispose them. This would result into a waste of time for the final user.

Also using Vector graphic saves lots of application weight compared to PNGs. Ok, so why we shoulnd’t always use Vector animation? Because it’s very heavy to be rendered. If you plan to target a game at 60 FPS with many objects, forget it. In Sophie la Girafe, we targeted 25 FPS, we really didn’t need more, so vector are ok, even on low devices. However if you plan to make faster animations with more objects, rasterization would be a very benificial process! And keep Blitting technical somewhere in your mind!

Why using the Citrus Engine?
As the lead developer of the CE, I knew why I should use it and how to make this app quickly and easily. The CE is positioned as a game engine, but don’t forget that a Rich Mobile Application has many features shared with a game! Let see what was beneficial using the Citrus Engine in this app:
– state management: in this application we often change part of the application. Moving from one state to an other one. You’ve to be sure that everything will be cleaned and destroyed before moving to an other state, or you will have some memory leak. Thanks to its structure and state management, the CE helps you to avoid this! Also if you combine the app navigation with an external xml, you’re on the good road ;)
– sounds: playing music is much more needed in an app, it’s crucial to control every sounds and be able to loop them, apply tween, dispatch Signal when a specific sound is completed, etc. By its built-in classes, the CE provides a very elegant sound manager.
– scrolling camera: when you need some scrolling content, it’s important to have an easy to set up camera with parallax effect. The Citrus Engine will provide many options for a camera: a target, zoom, rotation, parallax objects, etc.
– also don’t forget the physics part!

Give a try to Sophie la Girafe and the Citrus Engine!

Android 4.2.2. resolve problem with AIR mobile deployment

Google has recently pushed a new Android version: 4.2.2. Concerning the new features, this update brings about a new security feature in regards to USB debugging: now there is a gatekeeper on your phone! You will have to accept the RSA key before being able to deploy on your device. More information concerning this update there.

If you have made this update on your device, you won’t be able to deploy your application, even if you’ve accepted the RSA key. Your IDE won’t success to connect to the device and push on it.
When I tried to resolve the problem, it quickly appears it wasn’t related to the IDE but directly from the AIR SDK. Then I tried to deploy on my tablet with a Unity project: same problem. Using Unity, you work directly with the Android SDK. I just needed to download its latest version and replace the previous one, and it works!

Concerning AIR, we also use the Android SDK but not with its original structure. You need to replace some files on this folder: YourSDK/lib/android/bin. Replace those files: aapt, adb and dx.jar with files coming from the latest Android SDK: sdk/platform-tools aapt, adb and its lib folder with the dx.jar file. Using Windows you will also need to replace the dll files.

That was easy! I hope Adobe will quickly deploy an AIR SDK update for everyone.

Take photo with AIR on iOS

Hey guys,

Recently, I’ve worked on a mobile project and camera use drives me crazy about performances. On iOS, after taking a photo and validate it, it freezes the application during one minute to be able to encode it.
I’ve made lots of tests with several libraries, using Alchemy ones, and finally the fastest way (less than 5 seconds) is to use the new BitmapData encode method, available since Flash Player 11.3 and AIR 3.3.

Here is the code (lots of code come from this excellent post) :

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
if (CameraUI.isSupported) {
	viewsAC.addItem({label:"Take Photo", icon:cameraIcon});
	myCam = new CameraUI();
	myCam.addEventListener(MediaEvent.COMPLETE, onCameraComplete);
}
 
protected function onCameraComplete(evt:MediaEvent):void
{
	sqlStatement = new SQLStatement();
	sqlStatement.sqlConnection = model2.connection;
	sqlStatement.text =
		"INSERT INTO albumItems (album, photoFile, dateAdded) " +
		"VALUES (:album, :photoFile, :dateAdded)";
	sqlStatement.parameters[":album"] = model2.selectedAlbum;
 
	var mediaPromise:MediaPromise = evt.data;
	if (mediaPromise.file == null) {
 
		// For iOS we need to load with a Loader first
		_loader = new Loader();
		_loader.contentLoaderInfo.addEventListener(Event.COMPLETE, imageloader_complete, false, 0, true);
		_loader.loadFilePromise(mediaPromise);
 
		return;
	} else {
 
		// Android & BlackBerry
		registerPhoto(mediaPromise.file.url)
	}
}
 
private function imageloader_complete(event:Event):void {
 
	_loader.contentLoaderInfo.removeEventListener(Event.COMPLETE, imageloader_complete);
 
	var loaderInfo:LoaderInfo = event.target as LoaderInfo;
 
	if (CameraRoll.supportsAddBitmapData) {
 
		var bitmapData:BitmapData = new BitmapData(loaderInfo.width, loaderInfo.height);
		bitmapData.draw(loaderInfo.loader);
		var file:File = File.applicationStorageDirectory.resolvePath("image" + new Date().time + ".jpg");
		var stream:FileStream = new FileStream()
		stream.open(file, FileMode.WRITE);
		var bytes:ByteArray = bitmapData.encode(bitmapData.rect, new flash.display.JPEGEncoderOptions());
		stream.writeBytes(bytes, 0, bytes.bytesAvailable);
		stream.close();
 
	}
	registerPhoto(file.url)
 
}
 
private function registerPhoto(url:String):void {
 
	sqlStatement.parameters[":photoFile"] = url;
 
	sqlStatement.parameters[":dateAdded"] = new Date();
	sqlStatement.execute();
 
	viewsList.selectedIndex = -1;
}

Citrus Engine V3 and a new website!

Hey there,

I’m glad to announce the V3 release and a new website for the Citrus Engine! It has been a long and hard work. Most of the new tutorials and news will be displayed on this new website. Don’t know how I will manage this blog on Citrus Engine subject due to the new website, I will certainly replicate some contents.

Anyway I let you explore the new website :) Don’t hesitate to give some feedback.

Playing with Cadet Editor 3D and AwayPhysics

The 3D part is a bit in stand-by this last weeks on the Citrus Engine, because I’m polishing the engine for the V3 which should be out this week!

I’ve never clearly introduced AwayPhysics in the Citrus Engine, so it’s the day! The idea is to have a similar pre-built platformer objects that we already have with Box2D and Nape but with AwayPhysics this time for 3D stuff! This work and Away3D support will evolve during the V3.

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) :D 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 :

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
<cadet:CadetScene x:id="0" framerate="30" name="Component" timeScale="1" xmlns:cadet="www.unwrong.com/cadet/1.0.0" xmlns:x="org.bonesframework.core.serialization.Serializer" xmlns:bones="www.bonesframwork.org/bones/1.0.0" xmlns:cadetAway3D4="www.unwrong.com/cadetAway3D4/1.0.0" 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"/>
      </ns0:CameraComponent>
    </cadetAway3D4:RendererAway3D>
    <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"/>
      </bones:ArrayCollection>
      <x:Ref x:name="geometryComponent" x:id="8"/>
    </cadetAway3D4:MeshComponent>
    <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"/>
    </ns3:DirectionalLightComponent>
    <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"/>
      </bones:ArrayCollection>
      <x:Ref x:name="geometryComponent" x:id="14"/>
    </cadetAway3D4:MeshComponent>
    <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"/>
      </bones:ArrayCollection>
      <x:Ref x:name="geometryComponent" x:id="18"/>
    </cadetAway3D4:MeshComponent>
    <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"/>
      </bones:ArrayCollection>
      <x:Ref x:name="geometryComponent" x:id="21"/>
    </cadetAway3D4:MeshComponent>
    <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"/>
      </bones:ArrayCollection>
      <x:Ref x:name="geometryComponent" x:id="24"/>
    </cadetAway3D4:MeshComponent>
    <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"/>
      </bones:ArrayCollection>
      <x:Ref x:name="geometryComponent" x:id="27"/>
    </cadetAway3D4:MeshComponent>
    <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"/>
      </bones:ArrayCollection>
      <x:Ref x:name="geometryComponent" x:id="30"/>
    </cadetAway3D4:MeshComponent>
    <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"/>
      </bones:ArrayCollection>
      <x:Ref x:name="geometryComponent" x:id="33"/>
    </cadetAway3D4:MeshComponent>
    <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"/>
      </bones:ArrayCollection>
      <x:Ref x:name="geometryComponent" x:id="36"/>
    </cadetAway3D4:MeshComponent>
    <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"/>
      </bones:ArrayCollection>
      <x:Ref x:name="geometryComponent" x:id="39"/>
    </cadetAway3D4:MeshComponent>
    <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"/>
      </bones:ArrayCollection>
      <x:Ref x:name="geometryComponent" x:id="42"/>
    </cadetAway3D4:MeshComponent>
    <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"/>
      </bones:ArrayCollection>
      <x:Ref x:name="geometryComponent" x:id="45"/>
    </cadetAway3D4:MeshComponent>
    <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"/>
      </bones:ArrayCollection>
      <x:Ref x:name="geometryComponent" x:id="48"/>
    </cadetAway3D4:MeshComponent>
    <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"/>
      </bones:ArrayCollection>
      <x:Ref x:name="geometryComponent" x:id="51"/>
    </cadetAway3D4:MeshComponent>
    <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"/>
      </bones:ArrayCollection>
      <x:Ref x:name="geometryComponent" x:id="54"/>
    </cadetAway3D4:MeshComponent>
    <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"/>
      </bones:ArrayCollection>
      <x:Ref x:name="geometryComponent" x:id="57"/>
    </cadetAway3D4:MeshComponent>
    <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"/>
      </bones:ArrayCollection>
      <x:Ref x:name="geometryComponent" x:id="60"/>
    </cadetAway3D4:MeshComponent>
    <x:Ref x:name="20" x:id="16"/>
    <x:Ref x:name="21" x:id="6"/>
    <x:Ref x:name="22" x:id="12"/>
  </bones:ArrayCollection>
  <Object x:name="userData" x:id="61"/>
  <bones:DependencyManager x:name="dependencyManager" x:id="62">
    <bones:ArrayCollection x:name="dependencyNodes" x:id="63"/>
  </bones:DependencyManager>
</cadet:CadetScene>

This format isn’t easy to handle! Cadet should really export to XML or JSON format.
Anyway this what I’ve done : demo. 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 :

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
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.events.Event;
	import flash.events.MouseEvent;
	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() {
 
			super();
		}
 
		override public function initialize():void {
 
			super.initialize();
 
			addChild(new AwayStats((view as Away3DView).viewRoot));
 
			var awayPhysics:AwayPhysics = new AwayPhysics("awayPhysics");
			awayPhysics.visible = true;
			add(awayPhysics);
 
			ObjectMaker3D.FromCadetEditor3D(XML(new _CADET_LEVEL()));
 
			_cameraController = new HoverController((view as Away3DView).viewRoot.camera, 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);
 
			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 _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;
		}
 
	}
}

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

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
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);
							}
						}
 
						objects.push(object);
					}
				}
 
			}
		}
	}
 
	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!

I will continue my experiments and improve Cadet 3D support. I really hope that some of my requests will be added to Cadet Editor and then it will be an awesome 3D level editor for the CE!

Create combination of physics objects in Citrus Engine

Some days ago, I said that November will be an awesome month for the Citrus Engine. So what is coming?

We’re the 2nd, November is already here and something huge is already happened to the CE : a tutorial on gotoandlearn made by Lee Brimelow, an Adobe Game Developer Evangelist! Definitely, an excellent tutorial to get start with the Citrus Engine. There’ll be other tutorials later, a new website and the forum’ll move on Starling website. Most of the future game will use Starling so that’s a good move! We’re not forgetting the 3D part, don’t worry ;)

Everything should be ready for the next Adobe Game Jam in Chicago. So that’s the plan. Now let’s go for a small tutorial.

Someone asked me what is the best way to create a combination of physics objects in CE, like this one. You have to create the Box2D physics bodies and associate arts. Firstly, don’t forget that view can lies, but never physics. For example, on the CE’s demo on the actual website the mill’s arms are one Box2D object (with two bodies) and a single picture.
Example :

The Box2D Alchemy code :

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
package
{
	import Box2DAS.Collision.Shapes.b2PolygonShape;
	import Box2DAS.Common.V2;
	import Box2DAS.Dynamics.Joints.b2RevoluteJoint;
	import Box2DAS.Dynamics.Joints.b2RevoluteJointDef;
	import Box2DAS.Dynamics.b2Body;
	import Box2DAS.Dynamics.b2BodyDef;
	import Box2DAS.Dynamics.b2Fixture;
	import Box2DAS.Dynamics.b2FixtureDef;
 
	import com.citrusengine.objects.PhysicsObject;
 
	public class WindmillArms extends PhysicsObject
	{
		private var _hingeBodyDef:b2BodyDef;
		private var _hingeBody:b2Body;
		private var _shape2:b2PolygonShape;
		private var _fixtureDef2:b2FixtureDef;
		private var _fixture2:b2Fixture;
		private var _jointDef:b2RevoluteJointDef;
		private var _joint:b2RevoluteJoint;
 
		public function WindmillArms(name:String, params:Object=null)
		{
			super(name, params);
		}
 
		override public function destroy():void
		{
			_hingeBody.destroy();
			_hingeBodyDef.destroy();
			_fixtureDef2.destroy();
			super.destroy();
		}
 
		override public function set x(value:Number):void
		{
			super.x = value;
 
			if (_hingeBody)
			{
				var pos:V2 = _hingeBody.GetPosition();
				pos.x = _x;
				_hingeBody.SetTransform(pos, _hingeBody.GetAngle());
			}
		}
 
		override public function set y(value:Number):void
		{
			super.y = value;
 
			if (_hingeBody)
			{
				var pos:V2 = _hingeBody.GetPosition();
				pos.y = _y;
				_hingeBody.SetTransform(pos, _hingeBody.GetAngle());
			}
		}
 
		override protected function defineBody():void
		{
			super.defineBody();
 
			_hingeBodyDef = new b2BodyDef();
			_hingeBodyDef.type = b2Body.b2_staticBody;
			_hingeBodyDef.position.v2 = new V2(_x, _y);
		}
 
		override protected function createBody():void
		{
			super.createBody();
 
			_hingeBody = _box2D.world.CreateBody(_hingeBodyDef);
			_hingeBody.SetUserData(this);
		}
 
		override protected function createShape():void
		{
			super.createShape();
 
			_shape2 = new b2PolygonShape();
			_shape2.SetAsBox(_width / 2, _height / 2, null, _rotation + (Math.PI / 2));
		}
 
		override protected function defineFixture():void
		{
			super.defineFixture();
 
			_fixtureDef2 = new b2FixtureDef();
			_fixtureDef2.shape = _shape2;
			_fixtureDef2.density = _fixtureDef.density;
			_fixtureDef2.friction = _fixtureDef.friction;
			_fixtureDef2.restitution = _fixtureDef.restitution;
		}
 
		override protected function createFixture():void
		{
			super.createFixture();
			_fixture2 = _body.CreateFixture(_fixtureDef2);
		}
 
		override protected function defineJoint():void
		{
			super.defineJoint();
 
			_jointDef = new b2RevoluteJointDef();
			_jointDef.bodyA = _hingeBody;
			_jointDef.bodyB = _body;
			_jointDef.enableMotor = true;
			_jointDef.maxMotorTorque = 6
			_jointDef.motorSpeed = 0;
			_jointDef.localAnchorA.v2 = new V2();
			_jointDef.localAnchorB.v2 = new V2();
		}
 
		override protected function createJoint():void
		{
			super.createJoint();
 
			_joint = _box2D.world.CreateJoint(_jointDef) as b2RevoluteJoint;
			_joint.SetUserData(this);
		}
	}
}

Easier than it sounds, right? The key is to override defined methods and add your new boddie.

Let’s go back to the first example on Emanuele’s website.
Click here for the web demo.
As usal, all the source code is available on the CE’s GitHub dedicated to examples.
Actually, you see the Box2D debug view, to see arts use the console :
Press tab key, write :

set box2D visible false

And press enter.

There are two ways to achieve it, one is not better than the other, it’s just different. You will certainly prefer one depending what you’re trying to achieve.

Create lots of CE’s object
This is 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
package complexbox2dobject{
 
	import com.citrusengine.core.State;
	import com.citrusengine.objects.platformer.box2d.Platform;
	import com.citrusengine.physics.box2d.Box2D;
 
	/**
	 * @author Aymeric
	 */
	public class ComplexBox2DObjectGameState extends State {
 
		public function ComplexBox2DObjectGameState() {
			super();
		}
 
		override public function initialize():void {
 
			super.initialize();
 
			var box2D:Box2D = new Box2D("box2D");
			box2D.visible = true;
			add(box2D);
 
			var platTop:Platform = new Platform("platTop", {x:stage.stageWidth / 2, width:stage.stageWidth, y:20});
			add(platTop);
 
			var chain:Chain;
			var vecChain:Vector.<Chain> = new Vector.<Chain>();
			for (var i:uint = 0; i < 4; ++i) {
 
				if (i == 0)
					chain = new Chain("chain" + i, {x:stage.stageWidth / 2, y:70 + i * 40, width:15, hangTo:platTop, distance:i, view:new ChainGraphic()});
				else
					chain = new Chain("chain" + i, {x:stage.stageWidth / 2, y:70 + i * 40, width:15, hangTo:vecChain[i - 1], distance:i, view:new ChainGraphic()});
 
				vecChain.push(chain);
				add(chain);
			}
 
			chain = new Chain("chain" + i, {x:2, y:70 + (i + 1) * 40, radius:15, hangTo:vecChain[i - 1], distance:i + 1, last:true, registration:"topLeft", view:new ChainGraphic(15)});
			add(chain);
			vecChain.push(chain);
		}
 
	}
}

Here we create many CE objects, with lots of similar informations. The good thing is all the arts are managed by SpriteView and SpriteArt classes.
The Box2D Chain class :

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
package complexbox2dobject{
 
	import Box2D.Common.Math.b2Vec2;
	import Box2D.Dynamics.Joints.b2RevoluteJointDef;
 
	import com.citrusengine.objects.Box2DPhysicsObject;
 
	/**
	 * @author Aymeric
	 */
	public class Chain extends Box2DPhysicsObject {
 
		public var hangTo:Box2DPhysicsObject;
		public var distance:uint;
		public var last:Boolean = false;
 
		private var _revoluteJointDef:b2RevoluteJointDef;
 
		public function Chain(name:String, params:Object) {
			super(name, params);
		}
 
		override protected function defineJoint():void {
 
			_revoluteJointDef = new b2RevoluteJointDef();
 
			if (last) {
				_revoluteJointDef.localAnchorA.Set(0, (distance + 75) / _box2D.scale);
				_revoluteJointDef.localAnchorB.Set(0, 0);
				_revoluteJointDef.bodyA = hangTo.body;
				_revoluteJointDef.bodyB = _body;
			} else
				_revoluteJointDef.Initialize(hangTo.body, _body, new b2Vec2(275 / _box2D.scale, (70 + distance * 40) / _box2D.scale));
		}
 
		override protected function createJoint():void {
 
			_box2D.world.CreateJoint(_revoluteJointDef);
		}
	}
}

Nothing too complex, we just create a joint in the right method.

And finally the ChainGraphic class :

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
package complexbox2dobject{
 
	import flash.display.Sprite;
 
	/**
	 * @author Aymeric
	 */
	public class ChainGraphic extends Sprite {
 
		public function ChainGraphic(radius:uint = 0) {
 
			this.graphics.beginFill(Math.random() * 0xFFFFFF);
 
			if (radius != 0)
				this.graphics.drawCircle(0, 0, radius);
			else
				this.graphics.drawRect(0, 0, 15, 30);
 
			this.graphics.endFill();
		}
	}
}

That’s it for the first method, the most obvious.

Only one complex Box2D and art classes

For the second method, like the mill’s arms we create several Box2D bodies in one Box2DPhysicsObject class.
The GameState :

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
package complexbox2dobject{
 
	import com.citrusengine.core.State;
	import com.citrusengine.objects.platformer.box2d.Platform;
	import com.citrusengine.physics.box2d.Box2D;
 
	/**
	 * @author Aymeric
	 */
	public class ComplexBox2DObjectGameState extends State {
 
		public function ComplexBox2DObjectGameState() {
			super();
		}
 
		override public function initialize():void {
 
			super.initialize();
 
			var box2D:Box2D = new Box2D("box2D");
			box2D.visible = true;
			add(box2D);
 
			var platTop:Platform = new Platform("platTop", {x:stage.stageWidth / 2, width:stage.stageWidth, y:20});
			add(platTop);
 
			var ropeChain:RopeChain = new RopeChain("ropeChain", {hangTo:platTop, radius:15, y:150, x:30, view:new RopeChainGraphics(), registration:"topLeft"});
			add(ropeChain);
		}
 
	}
}

The object created there is basically a circle hang to the platform top with several chains.
The RopeChain class :

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
package complexbox2dobject{
 
	import Box2D.Collision.Shapes.b2PolygonShape;
	import Box2D.Collision.Shapes.b2Shape;
	import Box2D.Common.Math.b2Vec2;
	import Box2D.Dynamics.Joints.b2RevoluteJointDef;
	import Box2D.Dynamics.b2Body;
	import Box2D.Dynamics.b2BodyDef;
	import Box2D.Dynamics.b2FixtureDef;
 
	import com.citrusengine.objects.Box2DPhysicsObject;
	import com.citrusengine.physics.PhysicsCollisionCategories;
 
	import flash.geom.Point;
 
	/**
	 * @author Aymeric
	 */
	public class RopeChain extends Box2DPhysicsObject {
 
		public var hangTo:Box2DPhysicsObject;
 
		public var numChain:uint = 5;
		public var widthChain:uint = 15;
		public var heightChain:uint = 30;
		public var attach:Point = new Point(275, 60);
		public var distance:uint = 32;
 
		private var _vecBodyDefChain:Vector.<b2BodyDef>;
		private var _vecBodyChain:Vector.<b2Body>;
		private var _vecFixtureDefChain:Vector.<b2FixtureDef>;
		private var _vecRevoluteJointDef:Vector.<b2RevoluteJointDef>;
		private var _shapeChain:b2Shape;
 
		public function RopeChain(name:String, params:Object = null) {
			super(name, params);
		}
 
		override public function initialize(poolObjectParams:Object = null):void {
			super.initialize(poolObjectParams);
 
			if (view)
				(view as RopeChainGraphics).init(numChain, widthChain, heightChain);
		}
 
		override public function update(timeDelta:Number):void {
			super.update(timeDelta);
 
			if (view)
				(view as RopeChainGraphics).update(_vecBodyChain, _box2D.scale);
		}
 
		override protected function defineBody():void {
			super.defineBody();
 
			_vecBodyDefChain = new Vector.<b2BodyDef>();
			var bodyDefChain:b2BodyDef;
 
			for (var i:uint = 0; i < numChain; ++i) {
 
				bodyDefChain = new b2BodyDef();
				bodyDefChain = new b2BodyDef();
				bodyDefChain.type = b2Body.b2_dynamicBody;
				bodyDefChain.position = new b2Vec2(attach.x / _box2D.scale, (attach.y + i * distance) / _box2D.scale);
				bodyDefChain.angle = _rotation;
 
				_vecBodyDefChain.push(bodyDefChain);
			}
		}
 
		override protected function createBody():void {
			super.createBody();
 
			_vecBodyChain = new Vector.<b2Body>();
			var bodyChain:b2Body;
 
			for each (var bodyDefChain:b2BodyDef in _vecBodyDefChain) {
 
				bodyChain = _box2D.world.CreateBody(bodyDefChain);
				bodyChain.SetUserData(this);
 
				_vecBodyChain.push(bodyChain);
			}
 
		}
 
		override protected function createShape():void {
			super.createShape();
 
			_shapeChain = new b2PolygonShape();
			b2PolygonShape(_shapeChain).SetAsBox(widthChain / 2 / _box2D.scale, heightChain / 2 / _box2D.scale);
		}
 
		override protected function defineFixture():void {
			super.defineFixture();
 
			_vecFixtureDefChain = new Vector.<b2FixtureDef>();
			var fixtureDefChain:b2FixtureDef;
 
			for (var i:uint = 0; i < numChain; ++i) {
 
				fixtureDefChain = new b2FixtureDef();
				fixtureDefChain.shape = _shapeChain;
				fixtureDefChain.density = 1;
				fixtureDefChain.friction = 0.6;
				fixtureDefChain.restitution = 0.3;
				fixtureDefChain.filter.categoryBits = PhysicsCollisionCategories.Get("Level");
				fixtureDefChain.filter.maskBits = PhysicsCollisionCategories.GetAll();
 
				_vecFixtureDefChain.push(fixtureDefChain);
			}
		}
 
		override protected function createFixture():void {
			super.createFixture();
 
			var i:uint = 0;
 
			for each (var fixtureDefChain:b2FixtureDef in _vecFixtureDefChain) {
				_vecBodyChain[i].CreateFixture(fixtureDefChain);
				++i;
			}
 
		}
 
		override protected function defineJoint():void {
 
			_vecRevoluteJointDef = new Vector.<b2RevoluteJointDef>();
			var revoluteJointDef:b2RevoluteJointDef;
 
			for (var i:uint = 0; i < numChain; ++i) {
 
				revoluteJointDef = new b2RevoluteJointDef();
 
				if (i == 0)
					revoluteJointDef.Initialize(hangTo.body, _vecBodyChain[i], new b2Vec2(attach.x / _box2D.scale, (attach.y + i * distance) / _box2D.scale));
				else
					revoluteJointDef.Initialize(_vecBodyChain[i - 1], _vecBodyChain[i], new b2Vec2(attach.x / _box2D.scale, (attach.y + i * distance) / _box2D.scale));
 
				_vecRevoluteJointDef.push(revoluteJointDef);
			}
 
 
			revoluteJointDef = new b2RevoluteJointDef();
			revoluteJointDef.localAnchorA.Set(0, distance / _box2D.scale);
			revoluteJointDef.localAnchorB.Set(0, 0);
			revoluteJointDef.bodyA = _vecBodyChain[numChain - 1];
			revoluteJointDef.bodyB = _body;
 
			_vecRevoluteJointDef.push(revoluteJointDef);
		}
 
		override protected function createJoint():void {
 
			for each (var revoluteJointDef:b2RevoluteJointDef in _vecRevoluteJointDef) {
				_box2D.world.CreateJoint(revoluteJointDef);
			}
		}
 
	}
}

We create Vectors with all our new chains’ bodies.

And finally the most complex part on this second way, the graphic class :

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
package complexbox2dobject{
 
	import Box2D.Dynamics.b2Body;
 
	import com.citrusengine.math.MathUtils;
 
	import flash.display.Sprite;
	import flash.geom.Point;
 
	/**
	 * @author Aymeric
	 */
	public class RopeChainGraphics extends Sprite {
 
		private var _numChain:uint;
		private var _vecSprites:Vector.<Sprite>;
		private var _width:uint;
		private var _height:uint;
 
		public function RopeChainGraphics() {
 
			this.graphics.beginFill(0xFF0000);
			this.graphics.drawCircle(0, 0, 15);
			this.graphics.endFill();
		}
 
		public function init(numChain:uint, width:uint, height:uint):void {
 
			_numChain = numChain;
			_width = width;
			_height = height;
 
			_vecSprites = new Vector.<Sprite>();
			var sprite:Sprite;
 
			for (var i:uint = 0; i < _numChain; ++i) {
 
				sprite = new Sprite();
				sprite.graphics.beginFill(Math.random() * 0xFFFFFF);
				sprite.graphics.drawRect(0, 0, _width, _height);
				sprite.graphics.endFill();
 
				addChild(sprite);
				_vecSprites.push(sprite);
			}
		}
 
		public function update(vecBodyChain:Vector.<b2Body>, box2DScale:Number):void {
 
			var i:uint = 0;
 
			for each (var body:b2Body in vecBodyChain) {
 
				_vecSprites[i].x = body.GetPosition().x * box2DScale - this.parent.x - _width * 0.5;
				_vecSprites[i].y = body.GetPosition().y * box2DScale - this.parent.y - _height * 0.5;
 
				MathUtils.RotateAroundExternalPoint(_vecSprites[i], new Point(_width * 0.5 + _vecSprites[i].x, _height * 0.5 + _vecSprites[i].y), body.GetAngle() * 180 / Math.PI - _vecSprites[i].rotation - this.parent.rotation);
 
				++i;
			}
		}
	}
}

Since chains’ graphics aren’t managed by SpriteView and the SpriteArt (only the circle), we have to update them manually! Note that there is a little bug on the rotation, I’m missing something here but can’t find the issue… Damn math knowledge :D Anyway it shouldn’t prevent you to use this method!

Create a game like Tiny Wings

Tiny Wings is really a cute game and very fun to play thanks to physics content! It uses Box2D. If you’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’ll find cool tutorials on Emanuele Feronato’s website using Box2D and an other on Lorenzo Nuvoletta’s website using Nape.

Someone asks me if it was possible to create this type of game with the Citrus Engine, absolutely! So let’s go for a quick tutorial.

Firstly, all hails to Lorenzo! The physics hills and the sprites creation come from his algorithm.
This is what you will get : demo.
To see the physics debug view, open the console with the key tab, then write :

set nape visible true

And press enter. You will see Nape’s debug view and the hero. You can jump with a click.
All the source code is accessible on the new CE’s GitHub for examples.

I won’t explain the algorithm. If you have any questions on it, ask Lorenzo. He will certainly answer better than me. However I’ll explain how it has been added to the Citrus Engine.

This the Main class :

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
package games.tinywings {
 
	import com.citrusengine.core.StarlingCitrusEngine;
 
	/**
	 * @author Aymeric
	 */
	public class Main extends StarlingCitrusEngine {
 
		public function Main() {
 
			setUpStarling(true);
 
			state = new TinyWingsGameState();
		}
	}
}

The GameState. Here you init your physics world, and create the hero and the hills :

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
package games.tinywings {
 
	import com.citrusengine.core.StarlingState;
	import com.citrusengine.math.MathVector;
	import com.citrusengine.physics.nape.Nape;
 
	import flash.geom.Rectangle;
 
	/**
	 * @author Aymeric
	 */
	public class TinyWingsGameState extends StarlingState {
 
		private var _nape:Nape;
		private var _hero:BirdHero;
 
		private var _hillsTexture:HillsTexture;
 
		public function TinyWingsGameState() {
			super();
		}
 
		override public function initialize():void {
 
			super.initialize();
 
			_nape = new Nape("nape");
			//_nape.visible = true;
			add(_nape);
 
			_hero = new BirdHero("hero", {radius:20});
			add(_hero);
 
			_hillsTexture = new HillsTexture();
 
			var hills:HillsManagingGraphics = new HillsManagingGraphics("hills", {sliceHeight:200, sliceWidth:70, currentYPoint:350, registration:"topLeft", view:_hillsTexture});
			add(hills);
 
			view.setupCamera(_hero, new MathVector(stage.stageWidth /2, stage.stageHeight / 2), new Rectangle(0, 0, int.MAX_VALUE, int.MAX_VALUE), new MathVector(.25, .05));
		}
 
		override public function update(timeDelta:Number):void {
			super.update(timeDelta);
 
			// update the hills here to remove the displacement made by StarlingArt. Called after all operations done.
			_hillsTexture.update();
		}
	}
}

Now let’s focus on the Hills creation. Hills is a new Nape’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’t want to manage graphics in this core class. So we remove all the content related to graphics :

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
package com.citrusengine.objects.platformer.nape {
 
	import nape.geom.Vec2;
	import nape.phys.Body;
	import nape.phys.BodyType;
	import nape.shape.Polygon;
 
	import com.citrusengine.objects.NapePhysicsObject;
 
	/**
	 * This class creates perpetual hills like the games Tiny Wings, Ski Safari...
	 * Write a class to manage graphics, and extends this one to call graphics function.
	 * For more information, check out CE's Tiny Wings example.
	 * Thanks to <a href="http://www.lorenzonuvoletta.com/create-an-infinite-scrolling-world-with-starling-and-nape/">Lorenzo Nuvoletta</a>.
	 */
	public class Hills extends NapePhysicsObject {
 
		/**
		 * This is the height of a slice. 
		 */
		public var sliceHeight:uint = 600;
 
		/**
		 * This is the width of a slice. 
		 */
		public var sliceWidth:uint = 30;
 
		/**
		 * This is the height of the first point.
		 */
		public var currentYPoint:Number = 200;
 
		/**
		 * This is the width of the hills visible. Most of the time your stage width. 
		 */
		public var widthHills:Number = 550;
 
		/**
		 * This is the physics object from which the Hills read its position and create/delete hills. 
		 */
		public var rider:NapePhysicsObject;
 
		protected var _slicesCreated:uint;
		protected var _currentAmplitude:Number;
		protected var _nextYPoint:Number;
		protected var _slicesInCurrentHill:uint;
		protected var _indexSliceInCurrentHill:uint;
		protected var _slices:Vector.<Body>;
		protected var _sliceVectorConstructor:Vector.<Vec2>;
 
		public function Hills(name:String, params:Object = null) {
			super(name, params);
		}
 
		override public function initialize(poolObjectParams:Object = null):void {
 
			super.initialize(poolObjectParams);
 
			_prepareSlices();
		}
 
		protected function _prepareSlices():void {
 
			_slices = new Vector.<Body>();
 
			// Generate a rectangle made of Vec2
			_sliceVectorConstructor = new Vector.<Vec2>();
			_sliceVectorConstructor.push(new Vec2(0, sliceHeight));
			_sliceVectorConstructor.push(new Vec2(0, 0));
			_sliceVectorConstructor.push(new Vec2(sliceWidth, 0));
			_sliceVectorConstructor.push(new Vec2(sliceWidth, sliceHeight));
 
			// fill the stage with slices of hills
			for (var i:uint = 0; i < widthHills / sliceWidth * 1.2; ++i) {
				_createSlice();
			}
		}
 
		protected function _createSlice():void {
 
			// Every time a new hill has to be created this algorithm predicts where the slices will be positioned
			if (_indexSliceInCurrentHill >= _slicesInCurrentHill) {
				_slicesInCurrentHill = Math.random() * 40 + 10;
				_currentAmplitude = Math.random() * 60 - 20;
				_indexSliceInCurrentHill = 0;
			}
			// Calculate the position of the next slice
			_nextYPoint = currentYPoint + (Math.sin(((Math.PI / _slicesInCurrentHill) * _indexSliceInCurrentHill)) * _currentAmplitude);
			_sliceVectorConstructor[2].y = _nextYPoint - currentYPoint;
			var slicePolygon:Polygon = new Polygon(_sliceVectorConstructor);
			_body = new Body(BodyType.STATIC);
			_body.shapes.add(slicePolygon);
			_body.position.x = _slicesCreated * sliceWidth;
			_body.position.y = currentYPoint;
			_body.space = _nape.space;
 
			_pushHill();
		}
 
		protected function _pushHill():void {
 
			_slicesCreated++;
			_indexSliceInCurrentHill++;
			currentYPoint = _nextYPoint;
 
			 _slices.push(_body);
		}
 
		protected function _checkHills():void {
 
			if (!rider)
				rider = _ce.state.getFirstObjectByType(Hero) as Hero;
 
			var length:uint = _slices.length;
 
			for (var i:uint = 0; i < length; ++i) {
 
				if (rider.body.position.x - _slices[i].position.x > widthHills * 0.5 + 100) {
 
					_deleteHill(i);
					--i;
					_createSlice();
 
				} else
					break;
			}
		}
 
		protected function _deleteHill(index:uint):void {
 
			_nape.space.bodies.remove(_slices[index]);
			_slices.splice(index, 1);
		}
 
		override public function update(timeDelta:Number):void {
 
			super.update(timeDelta);
 
			_checkHills();
		}
 
		/**
		 * Bodies are generated automatically, those functions aren't needed.
		 */
		override protected function defineBody():void {
		}
 
		override protected function createBody():void {
		}
 
		override protected function createMaterial():void {
		}
 
		override protected function createShape():void {
		}
 
		override protected function createFilter():void {
		}
 
		override protected function createConstraint():void {
		}
	}
}

We’ve cutted the algorithm in several methods to be able to manage graphics from a subclass.
This is the class which manage graphics :

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
package games.tinywings {
 
	import com.citrusengine.objects.platformer.nape.Hills;
 
	/**
	 * @author Aymeric
	 */
	public class HillsManagingGraphics extends Hills {
 
		public function HillsManagingGraphics(name:String, params:Object = null) {
			super(name, params);
		}
 
		override protected function _prepareSlices():void {
 
			if (view)
				(view as HillsTexture).init(sliceWidth, sliceHeight);
 
			super._prepareSlices();
		}
 
		override protected function _pushHill():void {
 
			if (view)
				(view as HillsTexture).createSlice(_body, _nextYPoint, currentYPoint);
 
			super._pushHill();
		}
 
		override protected function _deleteHill(index:uint):void {
 
			(view as HillsTexture).deleteHill(index);
 
			super._deleteHill(index);
		}
 
 
	}
}

We just make some calls to view’s functions. And so finally, the view class :

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
package games.tinywings {
 
	import nape.phys.Body;
 
	import starling.display.Image;
	import starling.display.Sprite;
	import starling.events.Event;
	import starling.textures.Texture;
 
	import flash.display.BitmapData;
	import flash.geom.Matrix;
 
	/**
	 * @author Aymeric
	 */
	public class HillsTexture extends Sprite {
 
		private var _groundTexture:Texture;
		private var _sliceWidth:uint;
		private var _sliceHeight:uint;
 
		private var _images:Vector.<Image>;
 
		private var _flagAdded:Boolean = false;
 
		public function HillsTexture() {
		}
 
		public function init(sliceWidth:uint, sliceHeight:uint):void {
 
			_sliceWidth = sliceWidth;
			_sliceHeight = sliceHeight;
 
			_groundTexture = Texture.fromBitmapData(new BitmapData(_sliceWidth, _sliceHeight, false, 0xffaa33));
 
			_images = new Vector.<Image>();
 
			addEventListener(Event.ADDED, _added);
		}
 
		private function _added(evt:Event):void {
 
			_flagAdded = true;
 
			removeEventListener(Event.ADDED_TO_STAGE, _added);
		}
 
		public function update():void {
 
			// we don't want to move the parent like StarlingArt does!
			if (_flagAdded)
				this.parent.x = this.parent.y = 0;
		}
 
		public function createSlice(rider:Body, nextYPoint:uint, currentYPoint:uint):void {
 
			var image:Image = new Image(_groundTexture);
			addChild(image);
 
			_images.push(image);
 
			var matrix:Matrix = image.transformationMatrix;
            matrix.translate(rider.position.x, rider.position.y);
            matrix.a = 1.04;
            matrix.b = (nextYPoint - currentYPoint) / _sliceWidth;
            image.transformationMatrix.copyFrom(matrix); 
		}
 
		public function deleteHill(index:uint):void {
 
			removeChild(_images[index], true);
			_images.slice(index, 1);
		}
	}
}

And that’s it! Our scrollable world with hills is working!

To have exactly the same feeling that Tiny Wings there is always lots of work to achieve :
– make the terrain more rolling.
– don’t apply a linear velocity to your hero, just a basic force and then change its mass between screen touched or not.
– create nice graphics using procedural palettes algorithms.

When I’ve some time, I will work on the Box2D version thanks to Emanuele’s tutorial and polish this version.

Moved from Box2D Alchemy to Box2D AS3

In the Citrus Engine, people knows that I’m very attached to the Box2D Alchemy version. Thanks to Alchemy we should have better performance than the pure AS3 version. Also this Alchemy version had some cool features mostly on the way you could manage collision : you don’t use a gobal contact listener like in any Box2D version, you use something very closer to AS3 event management. Example :

1
2
3
4
5
6
7
8
9
10
11
12
13
14
override protected function createFixture():void
{
	super.createFixture();
 
	_fixture.m_reportBeginContact = true;
	_fixture.addEventListener(ContactEvent.BEGIN_CONTACT, handleBeginContact);
}
 
protected function handleBeginContact(e:ContactEvent):void
{
	_contact = e.other.GetBody().GetUserData() as Box2DPhysicsObject;
	if (!e.other.IsSensor())
		explode();
}

I love this way. Anyway, many users ask me to change it… let’s go!

Box2D AS3
This is the contact listener class :

package com.citrusengine.physics.box2d {
 
	import Box2D.Dynamics.b2ContactImpulse;
	import Box2D.Collision.b2Manifold;
	import Box2D.Dynamics.Contacts.b2Contact;
	import Box2D.Dynamics.b2ContactListener;
 
	/**
	 * Used to report the contact's interaction between object.
	 */
	public class Box2DContactListener extends b2ContactListener {
 
		public function Box2DContactListener() {
		}
 
		override public function BeginContact(contact:b2Contact):void {
 
			contact.GetFixtureA().GetBody().GetUserData().handleBeginContact(contact);
			contact.GetFixtureB().GetBody().GetUserData().handleBeginContact(contact);
		}
 
		override public function EndContact(contact:b2Contact):void {
 
			contact.GetFixtureA().GetBody().GetUserData().handleEndContact(contact);
			contact.GetFixtureB().GetBody().GetUserData().handleEndContact(contact);
		}
 
		override public function PreSolve(contact:b2Contact, oldManifold:b2Manifold):void {
 
			contact.GetFixtureA().GetBody().GetUserData().handlePreSolve(contact, oldManifold);
			contact.GetFixtureB().GetBody().GetUserData().handlePreSolve(contact, oldManifold);
		}
 
		override public function PostSolve(contact:b2Contact, impulse:b2ContactImpulse):void {
 
			contact.GetFixtureA().GetBody().GetUserData().handlePostSolve(contact, impulse);
			contact.GetFixtureB().GetBody().GetUserData().handlePostSolve(contact, impulse);
		}
 
	}
}

And then I override this function handleBeginContact, handleEndContact, handlePreSolve, handlePostSolve with what I need. And I use two very useful function to work the same way that I used to do with the Alchemy version :

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
/**
 * In Box2D we are blind concerning the collision, we are never sure which body is the collider. This function should help.
 * Call this function to obtain the colliding physics object.
 * @param self : in CE's code, we give this. In your code it will be your hero, a sensor, ...
 * @param the contact
 * @return the collider
 */
static public function CollisionGetOther(self:Box2DPhysicsObject, contact:b2Contact):Box2DPhysicsObject {
	return self == contact.GetFixtureA().GetBody().GetUserData() ? contact.GetFixtureB().GetBody().GetUserData() : contact.GetFixtureA().GetBody().GetUserData();
}
 
/**
 * In Box2D we are blind concerning the collision, we are never sure which body is the collider. This function should help.
 * Call this function to obtain the collided physics object.
 * @param self : in CE's code, we give this. In your code it will be your hero, a sensor, ...
 * @param the contact
 * @return the collided
 */
static public function CollisionGetSelf(self:Box2DPhysicsObject, contact:b2Contact):Box2DPhysicsObject {
	return self == contact.GetFixtureA().GetBody().GetUserData() ? contact.GetFixtureA().GetBody().GetUserData() : contact.GetFixtureB().GetBody().GetUserData();
}

And finally an example how it is used :

1
2
3
4
5
6
7
8
9
10
11
12
13
override public function handlePreSolve(contact:b2Contact, oldManifold:b2Manifold):void {
 
	if (!_ducking)
		return;
 
	var other:Box2DPhysicsObject = Box2DPhysicsObject.CollisionGetOther(this, contact);
 
	var heroTop:Number = y;
	var objectBottom:Number = other.y + (other.height / 2);
 
	if (objectBottom < heroTop)
		contact.SetEnabled(false);
}

Pretty cool, isn’t it? I know it will be a problem to upgrade on this last version, but it will not be as hard as it sounds since 99% of the code are similar. Just take a look on this new way to handle events.

Performance
When I made the transition, I told myself : “We shouldn’t see a big difference on a browser version, and if it sucks more than Alchemy on mobile we don’t care since there is Nape”! So I made a test with my quick demo : Live4Sales game. And this is the result on a iPad3 :

Compare now to the Box2D Alchemy & Nape versions : in this previous stress test. Holy s***! Box2D AS3 is very quicker than Alchemy version, really close to Nape in fact. So much close that we should make other tests with different mobiles and other type of games. Nape is always faster, but the difference is small. No more tax, better performances : definitely a good move!
All the demos have been updated on the GitHub.

Version 3?
We’ve never been so close to the final release, it should be in November. I would like to emphasize two points :
– the 3D part (handled with Away3D & AwayPhysics) is a sandbox for me. I never pretend to offer the best 3D game engine like you can have with Unity3D, how could I compare with them?, but just a good engine where you could create quiclky some 3d physics with a character, sensors… It could be used for simple games or for interactive websites. This 3D part will evolve along the Citrus Engine V3.
– the entity/component system is not bad, but not as useful as I would. Since some time, it is in stand by. It is not the main features wished by members, so I will refocus on it after. At the moment, use it at your own risk!

Citrus Engine’s structure updated, camera movement example

This is a quick blog post to share a small tutorial on how you can handle a camera in the Citrus Engine and having feedbacks for the new CE’s structure.

Project dependencies
Let’s start for a very interessant & complex subject : refactor & separate an engine which has many libraries dependencies. Now that the Citrus Engine handles 3D and 3D physics thanks to Away3D & AwayPhysics it becomes too heavy. There is no problem with performances but a simple hello world is far too heavy. At the moment, any project needs to have Starling, Box2D, Nape, Away3D & AwayPhysics included. Even if you just need, for example, Starling & Box2D. We have to find a solution. We can’t just remove libraries that we don’t need manually and follow compiler instructions to remove them from the engine too. We have to find the best solution for final users & contributors.
It would be great to have swc as well for each dependency, e.g. ce.swc, ce_starling.swc & ce_starling_box2d.swc or ce.swc, cd_away3d.swc & ce_away3d_nape.swc. It means lots of swcs but it would be really easy to pick up those needed.

That was the point. And I’m glad to say that we find the best solution for everyone! And it’s up on GitHub! Also I would like to thanks Johann Barbie who helped me a lot on this. Now everything in the engine is separated, it means simply that in the view package we never care about which physics engine we use. Concerning the physics debug view, we have a class for each physics engine and then we use some wrapper to handle them in Starling or Away3D.

What changed?
I try to do my best to not break backward compatibility. If you use a modern IDE (if not, you should!!), just update the package import. It should works fine.
If you use Starling, you Main class must extends StarlingCitrusEngine. And that’s it!
All examples have been updated, and the final SWF size is really smaller!

Ant
Now I’m working with Ant to provide a swc for each build which only contain CE’s code. My main problem is I don’t success to remove external libraries on the final build. I would like to include them to compile my swc, but not include them in the final swc. Any advice would be very much appreciated!

Box2D AS3
Some people request me to move on Box2D AS3 version instead of the Alchemy version. It is a good idea if you don’t want to depend of the new Adobe tax. However this AS3 version is harder to handle since there isn’t a simple event system to manage collision like the Alchemy one. If you are familiar with the AS3 version and would like to provide your help, please contact me.

Camera demo
The demo.
Use the mouse wheel to zoom/dezoom. Press R to rotate the content.

The source code is very simple :

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
package cameramovement {
 
	import com.citrusengine.core.CitrusEngine;
	import com.citrusengine.core.State;
	import com.citrusengine.math.MathUtils;
	import com.citrusengine.math.MathVector;
	import com.citrusengine.objects.platformer.box2d.Hero;
	import com.citrusengine.objects.platformer.box2d.Platform;
	import com.citrusengine.physics.box2d.Box2D;
 
	import flash.events.MouseEvent;
	import flash.geom.Point;
	import flash.geom.Rectangle;
	import flash.ui.Keyboard;
 
	/**
	 * @author Aymeric
	 */
	public class CameraMovement extends State {
 
		public function CameraMovement() {
			super();
		}
 
		override public function initialize():void {
 
			super.initialize();
 
			var box2d:Box2D = new Box2D("box2D", {visible:true});
			box2d.group = 0; // box2d debug view will be behind graphics, default is 1 : can't work on Starling, since the debug view is on the display list
			add(box2d);
 
			var hero:Hero = new Hero("hero", {x:100, view:"PatchSpriteArt.swf", width:60, height:120, offsetY:15});
			add(hero);
 
			add(new Platform("platBot", {x:stage.stageWidth / 2, y:stage.stageHeight, width:3000}));
			add(new Platform("cloud", {x:450, y:250, width:200, oneWay:true}));
 
			view.setupCamera(hero, new MathVector(stage.stageWidth / 2, stage.stageHeight / 2), new Rectangle(0, 0, 1550, 450), new MathVector(.25, .05));
 
			stage.addEventListener(MouseEvent.MOUSE_WHEEL, _mouseWheel);
		}
 
		override public function destroy():void {
 
			stage.removeEventListener(MouseEvent.MOUSE_DOWN, _mouseWheel);
 
			super.destroy();
		}
 
		override public function update(timeDelta:Number):void {
 
			super.update(timeDelta);
 
			if (CitrusEngine.getInstance().input.isDown(Keyboard.R)) {
 
				// if you use Starling, just move the pivot point!
 
				MathUtils.RotateAroundInternalPoint(this, new Point(stage.stageWidth / 2, stage.stageHeight / 2), 1);
 
				view.cameraOffset = new MathVector(stage.stageWidth / 2, stage.stageHeight / 2);
				view.cameraBounds = new Rectangle(0, 0, 1550, 450);
			}
		}
 
		private function _mouseWheel(mEvt:MouseEvent):void {
 
			scaleX = mEvt.delta > 0 ? scaleX + 0.03 : scaleX - 0.03;
			scaleY = scaleX;
 
			view.cameraOffset = new MathVector(stage.stageWidth / 2 / scaleX, stage.stageHeight / 2 / scaleY);
			view.cameraBounds = new Rectangle(0, 0, 1550 * scaleX, 450 * scaleY);
		}
	}
}

In a physics world, you will never rotate the world! You just have to rotate its view, then manage the gravity like you want ;)

November should be an awesome month for the Citrus Engine, so stay tuned!