Posts tagged as3

I finally made a serious game

0

After several years in the gaming industry making mostly casual games, it was about time to give a try to serious game, right?

mef-gameMon Exploitation Forestière, only available in French at the moment, offers to the player to grow a forest. He will have to manage different tree species having their own properties (life duration, units collected, …) and cope with natural elements (fire, storm, …). The client wanted to responsibility players about the fact that a forest need to be maintain.
As cool features to develop there were saving players game in database, facebook connect and tiles’ OOP management.

Let’s start for some explanations with the latest one:
Each tile is designed into Flash Pro and inherit from a class with its properties (tree or empty tile for example). This way it’s very easy to modify it in real time, switch or even save it!
For saving the game, I’ve basically create a JSON with each tiles informations (+ score etc.), since everything is OOP it’s very easy to read its data and save it. For loading a save game it’s the same process. Load the JSON with all tiles informations and create objects.
Concerning the Facebook connect, I didn’t use at all the AS3 SDK but the php one. It’s very useful since you may use it for UI combined with JavaScript SDK. And using the php one in the backend is very safe ;)

AIR ANEs

0

Hey guys! Time flies, February is already finished.

It’s time for a very short blog post! I made many AIR Native Extensions this last months, playing with ads, analytics, sounds… it’s awesome the number of amazing SDKs you find on mobile. Unfortunately all my ANEs are private due to NDAs.

Here are some links to learn how to write your own:
- never played with ANEs? Get started with an Adobe tutorial.
- before coding your first ANE, have a look on the ones provided by StickSports. Richard Lord’s made an excellent work! He provides the best template ;)
- developing your ANE, be sure to read his tips!
- and yes, obviously you can add an Objective-C view on top of your Flash View but you unfortunately you can’t do the reverse.

Hope this links will help!

Working with multi-resolutions assets

0

When we’re making mobile apps we have to support lots of resolution from 480 x 320 to 2048 x 1536 if you only target iOS. Adding Android support you have to extend it to 2560 x 1600 (for the Nexus 10)!

If your graphic designer made its design into a vector format, it will be easy to fit exactly to your screen size, and you will only fight with the different ratios format.
However if your design is made with bitmaps, you have to, firstly, be sure they have been made for the highest resolution you would like to support! When this is done, you will use a SpriteSheet to have all of your assets in one (or a little more) place.

In this blog post we will see how to manage multi-resolutions assets based on a Starling app.

When you’re building a game with Starling you have several way to handle the multi-resolution management and the Citrus Engine offers more. In this example, we will just fit Starling’ stage to always have the size of the device. And we will use our own scale factor!

I will override the CitrusEngine findScaleFactor with my custom one. Note it should be more or less the same even if you don’t use the Citrus Engine.

override protected function findScaleFactor(assetSizes:Array):Number {
 
	var minValue:Number = Math.min(screenWidth, screenHeight);
 
	if (minValue < 400) // iPhone3GS
		return 1; // 0.25 in TexturePacker
	else if (minValue < 640) // Lots of Android devices
		return 1.5; // 0.375 in TexturePacker
	else if (minValue < 1536) // iPhone4, iPhone5, iPad non retina
		return 2; // 0.5 in TexturePacker
	else  // iPad retina
		return 4; // 1 in TexturePacker
}

In my app I will support 4 sets of texture, we don’t want to create a lots more because it will request extra space. Also I don’t extend it to the Nexus 10, since the one provided by the iPad retina should be ok.

Now that I’ve my scaleFactor, I will load the correct set using Starling’s AssetManager:

Assets.assets.enqueue(File.applicationDirectory.resolvePath(formatString("assets/{0}x", scaleFactor)));
 
Assets.assets.loadQueue(function(ratio:Number):void {
	if (ratio == 1) {
		state = new GameState();
	}
});

Bonus: you can easily combine it with DragonBones! Here you should export your design into the awesome .dbswf format, it will save you lots of space! Also if you’ve designed your DragonBones assets at the maximum resolution you will supported, it’s perfect you can easily resize the future spritesheet:

Assets.factoryDB = new StarlingFactory();
Assets.factoryDB.scaleForTexture = scaleFactor / 4; // models designed for 4X.
 
Assets.factoryDB.parseData(new Assets.DragonBonesAssets());

Ok, so this is great but how could I automate my way to generate SpriteSheets?
Firstly, you have to use the best SpriteSheets generator on the market: TexturePacker. Put all your bitmap assets at one place, and drag and drop them in TexturePacker. When you’ve selected the different options save it as a .tps file!
You could manually generate SpriteSheets one by one just changing the scale and it will do the job. But it will be very annoying to do it each time you change one asset.
TexturePacker provides a command line tool, so let’s use it with a bash script:

#!/bin/sh
#!/usr/bin/env bash
 
DIR="$( cd "$( dirname "$0" )" && pwd )"
 
for i in 1 1.5 2 4
do
        TexturePacker --sheet "$DIR"/../bin/assets/"$i"x/assets.png --data "$DIR"/../bin/assets/"$i"x/assets.xml --scale $(echo "scale=3; $i/4" | bc) --format sparrow "$DIR"/assets.tps
        echo "pngquant image" "$i"x
        pngquant -f --ext .png "$DIR"/../bin/assets/"$i"x/assets.png
done
 
echo "TexturePacker and pngquant tasks done."
 
exit 0

Latest script version and instruction to run it.

It will generate you all the resolutions needed and applied the awesome pngquant! So it will greatly improve your final SpriteSheets weight!

Bonus: you will probably also use BitmapFonts into your game. I used to deal with the great GlyphDesigner, however I’m facing a strong issue: I’m not able to export a downscale BitmapFonts spritesheets (updated: in fact it’s only available via the command line). They all have characters to a different place. And I would enjoy to add this png generated to my main SpriteSheets… so how could I do that?

I switched to bmGlyph, and it enables me to downscale a BitmapFonts SpriteSheets. So thanks to this downscale, I generate them all (you will certainly don’t have to update this bitmap fonts often), add the 4x sizes to my bitmap assets folder, and remove all the others generated pngs. And then in the same repository than the future global SpriteSheet, I add the .fnt file. Now when Starling parse the SpriteSheet, it will also add the bitmap font. Powerful, isn’t it?

Monsieur Bear’s ABC and the myth of not being able to run SWF vector graphics on mobile

0

I’m glad to present a new application I’ve developed: Monsieur Bear’s ABC. Available for iOS and Android.

Monsieur Bear’s ABC
mb1A funny animated alphabet book designed by the talented Virgo!

Virginie Aracil aka Virgo is back in the app world for a new adventure! Now, she comes with an alphabet book, Monsieur Bear’s ABC! This cute little bear will help your child discover the letters, the sounds and words in an original and acidulous universe. With hilarious animations, cartoon sound effets and a French Touch style, this application will give hours of fun to your child!

- An alphabet book completely animated !
- Cute drawings !
- Discover Monsieur Bear, the bear made by Virgo !
- Original animations and fun sound effets !
- Available in French and English !

Technology?
mb2Like Sophie la Girafe, Monsieur Bear’s ABC is built using AS3/AIR technology with the Citrus Engine framework on top of the display list.
I won’t explain the choice of the display list and the gpu mode, it’s well described in Sophie la girafe’s article (here we have the same problematic).
However behind this catchy title, it reveals a strong reality: no one think we’re able to run correctly swf on mobile. Please have a look on this thread on Starling‘s forum. I know it’s Starling forum, it’s normal that everyone recommend the Starling’s way. But common’ guys, if you tried this app how the hell would you be able to make it running with Starling? The DragonBones framework, would be a way… but it won’t correspond to many animations, and it will be so long to do all the animations for it!
Here no one think it would be possible to make this project on the display list. At first, I tried to just run the swf animations but it was crap then tried using the rasterize process but it wasn’t good too. I was in a blind alley. Then I remember that I’ve already been able to run many SWFs correctly on the display list (again the goal isn’t to target 60 fps with particles, blur etc), so why I’m not able to run correctly this swf? I contacted the graphic designer: each model is made in Illustrator. I grabbed ond of them, it has so many points: 500 000! Everything became obvious… There are optimizations in code, there are also optimizations on graphics!! And obviously it should be done before code optimization ;)

Using directly SWF format, I was able to rescale everything to the perfect screen dimensions and it’s priceless! Also the final weight for all the animations is 7Mo. This is just impossible to achieve using the spritesheet way.

AddFrameScript magic
It’s very a common way to make vectors animation with Flash Pro. For example Tiny Thief is a beautiful game using vector graphics (that’s very easy to rescale everything!!). However they used Scaleform technology to export on mobile, and not Adobe AIR.
With iOS (using AIR) we’re not able to reload SWF which has code in them (a simple stop() on the timeline is considered as ABC code). It means if a SWF has AS3 code, you won’t be able to dispose it and reload it. It will always stay in memory. When you’ve more than 300 SWFs that’s a big deal :D So obviously the solution is to not add any code in this SWF, but how are we able to stop animations easily or do other operations? Using the undocumented addFrameScript method:

_currentAnim.addFrameScript(_currentAnim.totalFrames - 1, _animCompleted);
 
function _animCompleted():void {
      _currentAnim.stopAllMovieClips();
}

That was a very simple example. Using this method you’re able to inject code to a specific frame of a MovieClip. And it won’t cause any problem to be unloaded and reloaded! Obviously it is a bit harder to manage because the code isn’t directly in the swf, and so if you change your animations don’t forget to change the addFrameScript code too. But if your code is well organized, it shouldn’t be a big deal!

Rdrct.it
Now let’s follow this link: http://rdrct.it/monsieurbearsabc. Depending on your OS you will be redirected:
- on a website for all the computers.
- on Google Play for Android.
- on the App Store for iOS.

That’s a very convenient way to share a link, isn’t it? There is also a powerful feature, if you open it on your mobile and you already have the application installed, it will open it! Don’t wait anymore, give a try to rdrct.it too!

Live Coding in AS3 using COLT

0

Hey guys,

I hope many of you have seen this amazing video made by Bret Victor: Inventing on Principle. This is one of the best introduction to live coding if you never heard about it.

Recently, Code Orchestra, released an amazing tool named COLT. It enables live coding in AS3, you will find many great video examples on their page. You can use the tool with a free trial during 15 days, don’t hesitate! It comes with many examples to help you understand the basis, oh and there is one running with the Citrus Engine ;)

Think different
At first, if you never tried live coding, it’s a very strange feeling: you think that you will be able to change everything at runtime, like building from scratch your application in one single shot, but it isn’t possible at all.
Examples: you want to change position of an object? You may create functions which edit object properties, but you have to think it before compiling. The easiest way is to call a live function from your constructor which will destroy all the existing objects at first (when you will live edit this part of code it will be performed again and so you won’t have two times your objects) and then instanciate objects with the new positions.
We know that programming user interface for the iPad retina is a pain because we don’t have a screen which is able to display entirely its screen. Here live coding is an incredible gift!
Also when you’re trying to balance a gameplay it may save you a lot of time, especially on mobile where compiling time and launch time is long, because you will be able to modify quickly some values and test it again and again.

I invite you to give a look to COLT documentation to know all of its features, and know how to enable them.

Adding COLT in your workflow
COLT is an external tool, there are some plugins to enable it in your IDE but there isn’t one for all of them. Personnaly, I use FDT. So for me it means I’m not able to use it with my usual workflow which is already a bit complex: I add referenced projects (like the Citrus Engine) to my working/current project. This way, I’m sure to work all the time with its last version and benefit the last changes (bug fixed, new features). It may also rise compatibility issues, but that’s another story.

You can always code in your favorite IDE and have all its advantages, but you will have to re setup your project into COLT and compile there.
So let’s take an example: see how the Citrus Engine repository is setup, it’s shared in my current project, Silly Family, which is exactly configured the same way. I’ve added a colt directory at the root of my project, and saved colt project there.
COLT0This is how the project paths are set up into COLT. Note that I had some issues at first. It may have a priority for the paths, but I’m not sure… I’ve tried several configurations. There are many source folder so I’m sure to be able to edit whatever I want and some SWCs for third part libraries.

COLT1Now this is the Compiler Settings. Note that COLT is provided with an old SDK (I don’t know why), mine is AIR 3.8 (still in beta). The outpout path is in the bin directory, so I share the same assets (at the same position) than my IDE.

Except for the issue when setting projects paths (I also had a strange issue saying that flash.display.MovieClip wasn’t found) it isn’t hard to setup your project. If you’ve some problem when you compile it, just change the order when you added projects paths.

COLT issues & improvements
When I worked with COLT I encountered strong issues. It seems that many guys haven’t them so it may be a problem on my side, this part should not prevent you to give a try to COLT! This is mainly a feedback for COLT’s developers.
- problems at the compilation that I’ve solved by deleting and recreating the whole project. I may have change the project paths, but not the Live Settings or Compiler Settings. Sounds strange.
- Livecoding doesn’t work anymore after a while, sometimes my content is no more refreshed. It works several times, then it is frozen. It isn’t updated anymore even if I’m just changing some positions (tried on Flash player and on mobile). I have to recompile.
- the log window isn’t easy to use. It isn’t really clear, and in the connection window it doesn’t provide the actual time when it’s started which is less useful IMHO.
- the trace I’ve when I launch the application (so not in a LiveCodeUpdateListener method) are not displayed.
- it seems we’re not able to target AIR without using a mobile, that’s annoying.
- when adding new connections it freezes some of them.
- when generating script for mobile we are not able to select an existing *-app.xml, it may overwrite the one added by our IDE if we’ve chosen the same name for the project. Here we would just need to be able to select an existing @-app.xml or create a new one.
- on Android it doesn’t automatically uninstall the previous application and it doesn’t install the recent one, we have to do uninstall it ourselves and recompile. I’m not sure for iOS.
- a good feature would be to be able to make several connections (in an AIR project) based on different resolutions, and be able to make changes at the same time everywhere. It would make mobile development very easy!

Many of this issue may be resolved using a plugin directly in our IDE, which would be a top features!

COLT is a young tool, they already achieve the impossible: live coding in AS3. With a bit more maturity, it will become the most useful tool for AS3 developer! On my side I’ve no doubt about it, I bought my license yesterday. It’s time to refresh our brain and our code. We’re no more in a static way of testing our app when it is compiled.

Alef is out!

4

Hey there,

It has been a long time since I wrote my first game, Kinessia, with the Citrus Engine (and start contributing), one and a half year. Less than one year ago, I made an other one, Tribus, using a Citrus Engine’s port in Objective-C. That was my first project in Objective-C so the source code of the port shall not be very clean, that’s why I’ve never released it officially.

We all know the problem making a framework / library and never use in real context project. That’s why I started my carreer as a freelancer 4 months ago, to be able to make applications / games for companies and show what can be done with the Citrus Engine. And now I’m proud to add a new reference to the engine: Alef.

Alef
Alef2Alef is a free app that teaches young children the letters of the alphabet and the construction of simple words in 5 different languages. The app encourages learning through simple interaction and is adapted for young kids. Children are taught to identify letters by dragging and dropping colorful puzzle pieces together. Parents can select a language including English, German, French, Arabic or Spanish and challenge their kids to construct words using the puzzle pieces! The app also provides word categories, for example animals, which give children visual clues about the meaning of the word they are trying to spell. Parents can customize the words themselves, opening the door to a world of possibilities: names, places, household items, etc. When the word has been put together correctly, a jingle will celebrate the child’s success. Hooray!

The beginning
Alef is similar to Spell Cubes in its first approach. Drag & drop letters at the right place with letter’s sound, like many other games did. When we start working on Alef, we considerate it as an internal project: AJ Consulting is one of the company that I enjoy to work with. They have tons of fresh ideas. They were looking for a Flash framework which would help them to make edutainment project, so I convinced them to use the Citrus Engine and it was a blast! In two days, we already had a working prototype! Then we’ve started to experiment: how to enhance user experience? Children love animals, they enjoy funny sounds too. Both has been added in this first official release. And we have many more awesome features coming! We’ve released the app now for having lots of feedback.

Technologies
We use the Citrus Engine library including Starling, Feathers and Nape.
Why did we use the CE for this app? The obvious answer is for the physics engine support, then comes very useful class to work with Starling: setting a Starling object very quickly and manage its z-sorting easily. And finally for the state transition between each part of the app. The CE makes them easy to clean and create!
We have also added Parse to store data in the cloud. If we want to add new animals, we just need to add it in our Parse database, send a push notification via Parse, people will update the list via the update button and they will get the last animals!
Also I’ve wrote an algorithm for the arabic alphabet which is a bit complex to manage due to the different states of a letter: isolated, initial, medial and final according where the letter is situated in the word. And obviously there are exceptions :D Since I’d no knowledge in arabic that was very challenging!
And finally, it was funny to replicate the iOS keyboard behavior managing accents!

CitrusAJ*
As you may have noticed, it’s mentioned the name of CitrusAJ* in the information of the application. It’s a private (at the moment) fork of the Citrus Engine (which has a MIT licence) including stuff which aren’t directly requested in a game engine. It has some helper for Parse, languages, SQL database, etc.
It’s always hard to determine what should or should not be included in the CE.

CitrusAJ* Builder
There isn’t official level editor / app builder for the CE. We use existing tools (Flash Pro, Gleed2D, Tiled Map Editor), it works great but we’re not able to manage them as we would. On the other side, writing a level editor takes lots of time and I don’t have enough time to do it. The CitrusAJ* Builder will be a commercial product developed by AJ Consulting to make an app without writing code. It would be a mix of a game level editor and the storyboard mode in Xcode.

Citrus Engine
No worries, the CE will always be free, open-source and strongly supported. Since we don’t have any kind of financial support, it’s important to take opportunities to work with companies on future product using the engine. This way we make sure it will stay the best AS3 game (and app!) engine!

AIR and iOS app options (cutsom url scheme, hidden)

4

Each day we discover new options and features giving the developer life more exciting!

Today I’m sharing some options for iOS development. Let say you have a main application including links to several games. You don’t want to include all this games into the main application because there are more like independant mini-games, or you don’t want to clean everything as it should when you leave your game (the lazy way), or there are several developers involved in this different games and they don’t use the same technology!

Anyway, you want to create a main application able to navigate to an other one. In other terms: communicate with other apps (Apple references check Communicating with Other Apps part and below).

In your *-app.xml document, add your url scheme for your main app inside

<iPhone><InfoAdditions><![CDATA[
<array>
<dict>
	<key>CFBundleURLSchemes</key>
	<array>
		<string>aymeric</string>
	</array>
	<key>CFBundleURLName</key>
	<string>games</string>
</dict>
</array>

From any other games on your iOS device, you can call this main application this way:

navigateToURL(new URLRequest("aymeric:games"));

Now let say we have a pirate game. Defines its url scheme:

<array>
<dict>
	<key>CFBundleURLSchemes</key>
	<array>
		<string>pirate</string>
	</array>
</dict>
</array>

You will be able to call it fropm your main app:

navigateToURL(new URLRequest("pirate:"));

Why not using aymeric:pirate url scheme? Well it seems there is an issue… it didn’t work on my side.
For more informations on custom url scheme, take a look there and there.

It’s pretty cool to be able to open an app from an other one, but I already see them on the iOS “desktop”. If you have one hundred of mini-games, it may be embarassing… So let’s hide them from the “desktop” and the “dock”!

<key>SBAppTags</key>
<array>
       <string>hidden</string>
</array>
<key>SBIsRevealable</key>
<false />

Now you won’t see them anymore! Also if you’re launching games from a main application, it sounds more logical to exit them when you go back to this application, if you go back on them they will be relaunched from start.

<key>UIApplicationExitsOnSuspend</key>
<true />

That’s it!

Update: following this article, I’ve been contacted by a company which has just launched a new very interesting product. Here is the mail copy/paste:

It is called rdrct.it (pronounced redirect.it), and enables mobile developers (across iOS, Android, Windows Phone & BB10) to easily enhance the functionality of their “Custom URL Schemes” in cases where the user who clicks the link might not already have the app installed.

It has two main (related) functions. The first, which we’re calling the “Universal URL” is where an app developer (or marketer) can create one URL that directs people to the correct app store for their device e.g. iOS users to the App Store, and Android users to Google Play – this makes it much easier to advertise your app on social media by just sending out one link instead of many. We also show you which types of device are visiting the link so that you can see if your app would be popular on a device that you don’t currently support.

The second function (and the function more relevant to your article) is the support of Custom URL Schemes (as they’re called in iOS – I’m an iOS developer too, so that’s my main interest).

If my app shares my custom URL with the world, but people who click the URL don’t have the app installed, then nothing happens. With rdrct.it, if the app is installed, then it will be opened as usual (including with the query string etc). If the app is not installed, then the user is again directed to the appropriate app store for their device. This means that with one click, users are directed to the right place, and they don’t have to go and lookup the correct app to open the URL (an extra step that a lot of potential users would not undertake).

Take photo with AIR on iOS

3

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!

2

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

6

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!

Go to Top