The Citrus Engine goes on Haxe NME, welcome to the CitruxEngine

1

One month ago I started to work on the CitruxEngine. I was very confident with Haxe performance on mobile and NME cross platform opportunities. And now, I can say those are awesome!

In April 14-15th, I was in Paris to assist to the Haxe conf 2012. It was really cool, Silex Labs has made a good job! Conferences were very interested and the community greatly friendly. And I had the opportunity to make a lightning talk concerning my contribution to the Citrus Engine 2D game framework and its port on Haxe NME. The presentation was a bit from scratch, but that was a good experience! I’m very happy to be the first to start the lightning talk, since there were very serious projects :D

CitruxEngine Github.
CitruxEngine Demo. Simple demo which have been tested on Flash & CPP (using left/right key and spacebar) and iOS (touch & accelerometer). There are sound, animations (idle, walk and jump) and physics.
The port is currently not finished!

I will not present some code here. If you are already familiar with the Citrus Engine, there will be no problem. Take a look on the example on Github.

HTML5
When I started the port, I would the CitruxEngine be as cross platform as possible. HTML5 is promising, and Niel Drummond the man behind Jeash has made an incredible job! However I found that the Haxe NME Box2D port has not very good performance with HTML5. So I’ve dropped the HTML5 target at the moment, but I keep an eye on Jeash!

Box2D
Thanks to Haxe NME, Box2D runs very well on mobile! This is mostly the reason why I’ve started the CitruxEngine. The Citrus Engine uses the AS3 Alchemy version of Box2D which has some differences with the original. It seems there is a bug with the Haxe NME Box2D port : the beginContact & endContact listeners are fired all the time if a dynamic body is on a static body (like a hero on a platform), whereas it fires only once if there are 2 dynamics bodies. This behavior is blocking me.

SpriteSheets
I made my test with the spritesheet haxelib which uses SpriteLoq. At the moment it works well. I’ve not made serious test with animations.

Level Editor
I love how the Citrus Engine handles Flash Pro as a Level Editor. I would like the Haxe NME version handles it as well. But at the moment we can’t read AS3 code in a SWF file (it is the way that class and properties are defined), so I need to think to an external way. Maybe it’s time to reconsider the Level Architect!

Console
The console is really a cool feature of the Citrus Engine, it will be available in the CitruxEngine too. I’ve started to implement it, but it’s not ready yet.

I will continue to work on the CitruxEngine, but now I’ve to focus on my school project using Objective-C, it’s really hard to get back when you have tested Haxe power ! In less than two months I will be graduate and looking for a job, future is exciting.

Thanks to postite & elsassph for their help!

From Box2D to Chipmunk

2

Hey there, I’ve been very busy this last weeks working hard on my 2nd school year project, a mobile game. In a few weeks, I will explain it, but for now let’s do some programming stuff!
The game is a side-scrolling 2D game. See Canabalt or Jetpack Joyride, they are great games!
Developing for iOS, I used Sparrow framework. It is awesome, and really easy to learn coming from AS3 & Starling.

Why did I develop in Objective-C since AIR 3.2 is out ? Refer to my previous post about my personal preference and the future of web development ; I decided to learn Objective-C and this project is really a good opportunity! I have followed the news about HTML5 and my opinion didn’t change… Moreover, for a long range project it is preferable to have the best workflow!

Since I used the Citrus Engine, I felt in love with physics engine and particularly Box2D. It is very useful & powerful for game development, but it has some hard constraints. With this project, I wanted to try an other physics engine. My choice was Chipmunk.

This post will not compare features, there are already a great post there, but how to move quickly from Box2D to Chipmunk. Thanks Scott Lembcke (Chipmunk’s author), for your clarifications.

QUICK OVERVIEW :
- Box2D : Box2D is an open source C++ engine for simulating rigid bodies in 2D. Box2D is developed by Erin Catto and has the zlib license. While the zlib license does not require acknowledgement, we encourage you to give credit to Box2D in your product. The manual.
- Chipmunk : Chipmunk is a fast and lightweight 2D rigid body physics library written in C. The documentation.

UNITS :
Box2D uses meter/kilogram/second (MKS), Chipmunk uses pixel. There is no units for the mass, you defined the value you want, but stay logical between objects. The time is not clearly mentioned in Chipmunk, it doesn’t express in seconds but floats. Box2D uses real world units because it has a number of tuning threshold values, and the default values are set to be sane values for life-sized objects. Chipmunk’s algorithms mostly avoid the need for tuning values so that you can use whatever arbitrary units makes most sense to you (pixels, meters, inches, etc). Likewise for time and mass.

SET UP WORLD :
Box2D uses the term “world” whereas Chipmunk uses “space”. Both defined gravity & iteration step. Body’s gravity is difficult to manage in Box2D and Chipmunk if you want your objects to have a different one. You may set up a gravity(0, 0) to your world/space and manage the gravity into each object using a variable and updating its velocity.

REGISTRATION POINT :
Both have body’s center as registration point.

RIGID BODIES :
Box2D uses two objects to define a body : body (user data, position, velocity, forces, torques, impulse…) & bodyDef (body type definitions, and init values). Chipmunk uses only one object defining mass (which is automatically calculated in Box2D) and moment which represent inertia for the body.
Box2D has 3 body types : static, kinematic, dynamic ; Chipmunk kinematic bodies are named rogue.

FIXTURES :
Box2D fixture/fixtureDef defined shape, density, friction, restitution and filters. There is no fixture in Chipmunk. Restitution is the elasticity property on shapes. It doesn’t store density on a per shape basis though. You have to calculate that into the mass manually..

SHAPES :
In Chipmunk, you can attach as many shapes to a single body as you need to in order to define a complex shape. Shapes contain the surface properties of an object such as how much friction or elasticity it has. It means than you can create a simple platform body and add all the shapes to it even if they are not close (a border bottom, a wall…).

COLLISIONS :
With Box2D you can know dynamically which is the other body you collide, in Chipmunk you may use this method too. You may also defined a collision handler’s function between the typeA and the typeB with function references defining collision start/end & pre/post solve. In Box2D you managed collision thanks to the fixture, in Chipmunk you add the “listener” to the space.

JavaScript slider made with haXe and JQuery

2

After playing with haXe and Php, it was time to try haXe and JavaScript! I used the JQuery library included in haXe. For more information on haXe JS take a look on its website.

Click here to see the slider in action. It uses keyboards and a timer. The slider centers a picture with a 50px margin, there is also a red or green filter.

The html part :

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
<!DOCTYPE html>
<html>
<head>
	<meta charset="utf-8"/>
	<title>Slider</title>
	<meta name="description" content="" />
	<link rel="stylesheet" href="style.css" type="text/css" />
 
	<script type="text/javascript" src="slider.js"></script>
</head>
 
<body>
 
	<div id="haxe:trace"></div>
 
	<div id="slider">
 
		<div id="animate">
			<ul>
				<li><img src="img/pic1.jpg" alt="img1"/></li>
				<li><img src="img/pic2.jpg" alt="img2"/></li>
				<li><img src="img/pic3.jpg" alt="img3"/></li>
				<li><img src="img/pic1.jpg" alt="img4"/></li>
			</ul>
		</div>
 
	</div>
 
</body>
</html>

A part of the CSS :

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
#slider {
  position: absolute;
  left: 50px;
  right: 50px;
  overflow: hidden;
}
 
#animate {
  position: relative;
  z-index: 5;
}
 
#animate ul {
 
  position:absolute;
  left: 1px;
 
  list-style: none;
  overflow: hidden;
  margin: 0; padding: 0;
}
 
#animate li {
  float: left;
  text-align: center;
  background-color: red;
  overflow: hidden;
}
 
#animate li:nth-child(odd) {
  background-color: green;
}
 
#animate li img {
  opacity: 0.7;
}
 
.carousel-previous {
  position: relative;
  z-index: 100;
}
 
.carousel-next {
  position: relative;
  z-index: 100;
}

And finally the haXe part :

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
package;
 
import haxe.Timer;
 
import js.Dom;
import js.JQuery;
import js.Lib;
 
class Slider {
 
	private var _timer:Timer;
	private var _timerSpeed:Int;
	private var _countTimer:Int;
	private var _timerIsRunning:Bool;
 
	private var _jqSlider:JQuery;
	private var _jqAnimate:JQuery;
	private var _jqAnimateUlLi:JQuery;
 
	private var _nbrElements:Int;
	private var _elementWidth:Int;
	private var _sliderWidth:Int;
	private var _animateWidth:Int;
	private var _margin:Int;
 
	static function main () {
 
		new JQuery (Lib.document).ready(function(evt) {
 
			new Slider();
		});
	}
 
	public function new() {
 
		_jqSlider = new JQuery('#slider');
		_jqAnimate = new JQuery('#animate');
		_jqAnimateUlLi = new JQuery('#animate ul li');
 
		// required by Chrome for the width() function
		Lib.window.onload = _onload;
 
		Lib.window.onresize = _onresize;
	}
 
	private function _onload(evt:Event):Void {
 
		_nbrElements = _jqAnimateUlLi.length;
 
		_jqSlider.css("height", Std.string(new JQuery('#slider ul li').height()) + "px");
 
		_elementWidth = _jqAnimateUlLi.width();
		_animateWidth = _elementWidth * _nbrElements;
		new JQuery("#animate ul").width(_animateWidth);
 
		_onresize();
 
		_timerSpeed = 5000;
		_countTimer = 0;
		_timer = new Timer(_timerSpeed);
		_timer.run = _tick;
		_timerIsRunning = true;
 
		Lib.document.onkeyup = _onkeypress;
 
		_jqSlider.prepend('<input class="carousel-previous" type="button" value="Previous">');
		_jqSlider.append('<input class="carousel-next" type="button" value="Next">');
 
		new JQuery('.carousel-previous').click(function(evt) {
 
			_moveLeft();
 
			if (_timerIsRunning)
				_timerIsRunning = false;
		});
 
		new JQuery('.carousel-next').click(function(evt) {
 
			_moveRight();
 
			if (_timerIsRunning)
				_timerIsRunning = false;
		});
	}
 
	private function _onresize(?evt:Event):Void {
 
		_sliderWidth = _jqSlider.width();
 
		// bitwise operator FTW !
		_margin = Std.int(_sliderWidth - _elementWidth >> 1);
 
		_jqAnimateUlLi.css("margin-left", Std.string(_margin) + "px");
	}
 
	private function _tick():Void {
 
		if (_timerIsRunning)
			_moveRight();
	}
 
	private function _onkeypress(evt:Event):Void {
 
		if (evt.keyCode == 39)
			_moveRight();
 
		if (evt.keyCode == 37)
			_moveLeft();
	}
 
	private function _moveRight():Void {
 
		//pre incrementation FTW !
		if (++_countTimer == _nbrElements) {
 
			_jqAnimate.animate({left: 0});
 
			_countTimer = 0;
 
		} else {
			_jqAnimate.animate({left: '-=' + Std.string(_elementWidth + _margin) + "px"});
		}
	}
 
	private function _moveLeft():Void {
 
		//post incrementation FTW !
		if (_countTimer-- != 0) {
 
			_jqAnimate.animate({left: '+=' + Std.string(_elementWidth + _margin) + "px"});
 
		} else {
 
			_jqAnimate.animate({left: '-=' + Std.string((_elementWidth + _margin) * (_nbrElements - 1)) + "px"});
 
			_countTimer = _nbrElements - 1;
		}
	}
}

Handle JS with haXe is easy, managing JQuery too. Once again the haXe api is your friend. Like for Php you can use external libraries and wrap them, see the documentation. Also take a look on the haXe magic.

My zip.

That’s it for the haXe experimentation to replace native languages, now I will focus on NME, Flash Stage3D, and my school project using Sparrow & Chipmunk in Objective-C. More information later !

Using native Php with haXe Php

5

Recently I had some time to dig more with haXe Php. The major question was how does it integrate with existing native Php ? I’m glad to say that it works fine!
Let’s start a quick overview of our native Php file test (Simple.class.php) :

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
<?php
function makeSimple($text) {
    return new Simple($text);
}
 
function affichText($text) {
    echo $text;
}
 
class Simple {
 
    public $text;
    public $tab;
 
    public function __construct($text) {
        $this->text = $text;
        $this->tab[0] = "index 0";
        $this->tab[1] = "index 1";
    }
 
    public function doPrint() {
        echo $this->text;
    }
 
    protected function changeText($text) {
        $this->text = $text;
    }
}
 
class Simple2 extends Simple {
 
    public function __construct($text) {
        parent::__construct($text);
    }
 
    public function makeChange($text) {
        $this->changeText($text);
    }
 
    public function associativeArray() {
 
        $tab["num1"] = "number 1";
        $tab["num2"] = "number 2";
        return $tab;
    }
}
?>

There are a simple function, some inheritance stuff and an associative array which is very common in Php.

Now the haXe part :

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
package;
 
import php.Lib;
 
class Test {
 
    static function main() {
 
    	new Test();
    }
 
    public function new() {
 
        // import php file
    	untyped __call__("require_once", "Simple.class.php");
 
        // call a php function with an arg
    	untyped __call__("affichText", "first msg </br>");
 
        // create a php object with an arg
    	var myPhpObject:Dynamic = untyped __call__('new Simple2', 'second msg </br>');
 
        // manipulate the object
    	myPhpObject.doPrint();
    	myPhpObject.makeChange("some new text </br>");
    	myPhpObject.doPrint();
 
        // print an array
        Lib.print(myPhpObject.tab);
 
        // trace the index 0 value
        trace(myPhpObject.tab[0]);
 
        // make some native php
        untyped __php__("echo '</br>php native from haXe !</br>'");
 
        // we need a Hashtable to parse an associative array from php :
        var phpAssociativeArray:Hash<String> = Lib.hashOfAssociativeArray(myPhpObject.associativeArray());
 
        // trace the key value num2
        trace(phpAssociativeArray.get("num2"));
    }
 
}

The output log :

first msg
second msg
some new text
["index 0", "index 1"]Test.hx:32: index 0
php native from haXe !
Test.hx:41: number 2

If you are using everyday libraries/tools written in Php, you may wrap them with haXe for more comfort. Take a look there : Wrapping External PHP Libraries & the haXe Magic.

Unfortunately, there isn’t lots of ressources/tutorials for haXe Php on the website, I will update this post if I go deeper in haXe Php development. It is very pleasant to write Php this way. Give it a try!
haXe API.

And because memes are fashion :

haXe workflow with Sublime Text 2, Php & nme examples

2

A good IDE is the developer’s best friend. FDT is my favorite one to code AS3 stuff, however I’m not very satisfied with haXe integration… it could be better. A good source code editor is also an amazing tool. I’ve found Sublime Text 2 some months ago, and it sounds always awesome to me. There is an extension which add Package control management to ST2 for adding new plugin, like haXe one.

Come on, download Sublime Text 2, install the Package control and haXe plugins!

That’s ok ? Now we can create a simple Php project.
Create a new file, save it as Test.hx and add this code :

package;
 
class Test {
 
    static function main() {
 
    	new Test();
    }
 
    public function new() {
 
    	var number:Float = 4;
 
        trace(number + 5);
    }
}

Then press ctrl (even if you use a mac) + shift + b. A new file called build.hxml is opened with some code generated to compile. You should just need that :

# Autogenerated build.hxml

# www
-php www
-main Test

Then press ctrl + enter. Your php files are generated. Pretty easy!

Now let’s make more stuff, a PDO connection with a simple query.

var cnx:Connection = PDO.open('mysql:host=localhost;dbname=igobelins', 'myUsr', 'myPwd');

If you press ctrl + space you have some greats auto-completion features! ctrl + i and the class is imported. If you make some mistake your code is highlighted in pink.

Our php stuff :

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;
 
import php.db.Connection;
import php.db.Mysql;
import php.db.Object;
import php.db.PDO;
import php.db.ResultSet;
 
class Test {
 
	public var cnx:Connection
 
    static function main() {
 
    	new Test();
    }
 
    public function new() {
 
      cnx = PDO.open('mysql:host=localhost;dbname=igobelins', 'myUsr', 'myPWd');
 
      var sql:String = "SELECT * FROM configs";
 
      var results:ResultSet = cnx.request(sql);
 
      for (result in results) {
       	trace(result.user);
       }
 
       cnx.close();
    }
}

According that you have a field user in your database, it will show the names.

Ok that was pretty cool, what about nme ? Go on this page and download the haXe project. In ST2 go in File/Open Folder… and select the Folder you have just unzipped. It should show the different folders & files in a left panel. Browse the Source and open SimpleBox2DExample.hx and then press ctrl + enter. It is compiled for flash and open quickly the result in the Flash Player. Ok but with NME I would like to target cpp. No problem press ctrl + shift + b then select cpp and compile. This way you can quickly change the target.
He wait, there isn’t html5 target !? I don’t know the reason but it is not offered. But you can add it (thanks to Julien Roche for the tips) : on Mac open the file Users/YourUserName/Library/Application Support/Sublime Text 2/Packages/HaXe/HaxeComplete.py and add html5 to nme_targets on line 124. Restart ST2, press ctrl + shift + b select html5 then compile. You have a new target ;-)

Sublime Text 2 and the haXe plugin are awesome, but so far it can not be as powerful as an IDE for debugging. No breakpoint for example, anyway it is already a great tool for a simple code editor!

Recently, JetBrains has released a haXe plugin for IntelliJ. We should keep an eye on it!

Air NativeProcess and bash file to compile haXe nme project

2

Last week I’ve made some test with haXe nme and box2d. The result is awesome : beautiful perf and a quick export on the target required! When I tried to run box2d from flash on a iPhone there was really bad performance : 10 dynamics objects – 5 fps. With haXe nme, more than 80 dynamics objects and 30 fps… that the power of native app. And for the fun I had an html5 box2d export, but not running smoothly.
At the moment, what I’m really missing is a powerful IDE for writing code and don’t use the console (even if it works great). FlashDevelop is the best one, but runs only on windows. Sadly, FDT supports haXe but not the nme. However it seems that JetBrains are working on a plugin for haXe and nme! But right now, I use Sublime Text 2 with the haXe plugin.

Anyway, I thought it would be cool to have a simple utilitarian app to create nmml files and compile projects with options (targets, mode, …). An Air application seemed to be an elegant way. Let’s go for a proof of concept on this last part :

The problem was how to run terminal command line with Air? The NativeProcess class provides command line integration and general launching capabilities. The NativeProcess class lets an AIR application execute native processes on the host operating system.
So it means that if I write a simple bash file, I’m able to run it with AIR!

Here we go for a simple version, my file name is nme.sh :

#!/bin/bash
nme test \"SimpleBox2DExample.nmml\" flash

Make sure to save the file with a chmod which allows you to execute it.
The Flex 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
<?xml version="1.0" encoding="utf-8"?>
<s:WindowedApplication xmlns:fx="http://ns.adobe.com/mxml/2009" 
					   xmlns:s="library://ns.adobe.com/flex/spark" 
					   xmlns:mx="library://ns.adobe.com/flex/mx">
	<fx:Declarations>
		<!-- Place non-visual elements (e.g., services, value objects) here -->	
	</fx:Declarations>
 
 
	<fx:Script>
		<![CDATA[
 
			public var process:NativeProcess;
 
			protected function compile(mEvt:MouseEvent):void
			{
				trace(NativeProcess.isSupported);
 
				var nativeProcessStartupInfo:NativeProcessStartupInfo = new NativeProcessStartupInfo();
				nativeProcessStartupInfo.workingDirectory = new File("/Users/Aymeric/Desktop/Simple Box2D Example/"); 
				nativeProcessStartupInfo.executable = new File("/Users/Aymeric/Desktop/Simple Box2D Example/nme.sh");
 
				process = new NativeProcess();
				process.start(nativeProcessStartupInfo);
				process.addEventListener(ProgressEvent.STANDARD_OUTPUT_DATA, onOutputData);
				process.addEventListener(ProgressEvent.STANDARD_ERROR_DATA, onErrorData);
				process.addEventListener(NativeProcessExitEvent.EXIT, onExit);
				process.addEventListener(IOErrorEvent.STANDARD_OUTPUT_IO_ERROR, onIOError);
				process.addEventListener(IOErrorEvent.STANDARD_ERROR_IO_ERROR, onIOError);
			}
 
			public function onOutputData(event:ProgressEvent):void {
				trace("Got: ", process.standardOutput.readUTFBytes(process.standardOutput.bytesAvailable)); 
			}
 
			public function onErrorData(event:ProgressEvent):void {
				trace("ERROR -", process.standardError.readUTFBytes(process.standardError.bytesAvailable)); 
			}
 
			public function onExit(event:NativeProcessExitEvent):void {
				trace("Process exited with ", event.exitCode);
			}
 
			public function onIOError(event:IOErrorEvent):void {
				trace(event.toString());
			}
 
		]]>
	</fx:Script>
 
	<s:Button label="Compile" click="compile(event)" />
 
</s:WindowedApplication>

If you run that script, your haXe nme project will compile from Air and open the flash player!
However this is not very dynamic… we can do better, let’s use some parameters :

#!/bin/bash
 
if [ $2 = "html5" ]
	then
	nme $1 \"SimpleBox2DExample.nmml\" $2
fi
 
nme test \"SimpleBox2DExample.nmml\" flash

That was our new bash file. $1 is the first argument given by our nativeProcessStartupInfo. The Air code :

var processArgs:Vector.<String> = new Vector.<String>();
processArgs[0] = "test";
processArgs[1] = "html5";
nativeProcessStartupInfo.arguments = processArgs;

Now if you run your app and click on the compile button, it will run your code in the browser thanks to html5 and open the flash player. You should be able to use all the nme’s Command-Line Tools.

If you’re familiar with bash script, you may be able to create a good dynamic script. I’m not.

Does it mean that we need to create ourselves the bash file? No, the app can generate one :

var file:File = File.desktopDirectory.resolvePath("test.sh");
var stream:FileStream = new FileStream();
stream.open(file, FileMode.WRITE);
stream.writeUTFBytes("#!/bin/bash\nnme test \"SimpleBox2DExample.nmml\" flash");
stream.close();

Thanks to the File API we are able to create a file, and later update it.
So everything is running fine, and I’m able to run my generated bash script? Well, there is one last problem : you can’t execute this file, so you get an error : Error #3219: The NativeProcess could not be started. ‘launch path not accessible’.

Google, my friend, show me this thread : http://forums.adobe.com/thread/721724 but it doesn’t work for me. And I can’t find other result. As far as I know, it seems to be not possible to create a file from air and change its chmod.
Anyway, if your file is already created, you could update it through Air.

That’s it for this proof of concept, an other thing for the todo list ? Maybe.

Ho! And since I was blasted by box2d mobile perf with the nme, we started to work with Dav Kert for a Citrus Engine haXe nme release : stay tuned on the github.

CitrusEngine goes Stage3D with Starling

15

Hey folks, happy new year! My previous post go back from 1st december, I’ve been really busy : adding Starling in the CitrusEngine, learning iOS and working on my 2nd year school project. And obviously some great holidays between christmas & the new year.

Today I’m very pleased to share with you the Citrus Engine working on Starling! For those who don’t know it, it’s a platformer game engine using Box2D Alchemy and Signal libraries. However, it’s not only made for platformer games, you can use it for every 2D contents even if it isn’t a game thanks to its great Box2D management. So before this update, it has supported : Box2D physics, Signals event, a Sound Manager, a Console for quick debugging & tools, Input management, lots of defined objects like Hero, Baddy, Platform, Coin, Sensor, Moving Platform…, parallax, a Loader manager, level editors thanks to its own (the Level Architect made with Air), or using Flash Pro, or Gleed2D. 2 views : the flash display list and blitting, and a layer management!

Thanks to this update, the CitrusEngine now support the great stage3d framework Starling and I’ve also added 3 new objects Cannon, Teleporter & Treadmill, a Level Manager and an abstract class to store the game’s data. This two last things are optional you may use it or not. In this blog post I will explain how I’ve adapted the CE for Starling, and my updates with some examples. For a quick getting start with the engine please refer on its website or to the tutorials on this blog.

But let’s stop chatting, it’s time to test the demo :
Click here for the demo.

The first level is really simple, some graphics a hero and a bad guy with some sensors, particles and text. The second level is just for performance test with Box2D & Starling.

Is there anything I could do before but not anymore with Starling? NO! The Starling view has not added any restriction on the engine. So you can also make this games with the Starling view : CE’s website with its demo and my 1st year school project.

TOOLS :
Ok, so before starting the explanations let’s start with tools you may use : you need to target the Flash Player 11, grab the last flex SDK and code into FDT/FlashBuilder/FlashDevelop. You can use Flash Pro CS3 and + for creating your levels, but please don’t code with that! Then there are 4 awesome tools that I use : TexturePacker to create SpriteSheets, PhysicsEditor and my template to target the CitrusEngine, and finally two others for mac only ParticleDesigner & GlyphDesigner. Also you may find useful my CE flash extension panel. All the content related with the CitrusEngine is available on its google code.

STARLING VIEW :
First of all, thanks Daniel for this awesome framework and your help! My main constraint, for this third engine view, was to keep a backward compatibility. If you have currently project made with the flash display list and you update the engine on its new version, you shouldn’t have any problem (be careful you need to compile for FP11 since it uses Stage3D due to Starling).
In the Main Class which extends CitrusEngine we used to create a new State like that :

state = new MySpriteGameState();

Now with Starling :

// 2 params available, debug mode and anti-aliasing
setUpStarling(true, 4);
state = new MyStarlingGameState();

You can set up the debugger mode there, it displays the Mr. Doob Stats class adapted for Starling by Nicolas Gans, thank you!
The starling var is protected, so you may defined it an other way. At the moment it does :

public function setUpStarling(debugMode:Boolean = false, antiAliasing:uint = 1):void {
 
	starlingDebugMode = debugMode;
 
	_starling = new Starling(RootClass, stage);
	_starling.antiAliasing = antiAliasing;
	_starling.start();
}

starlingDebugMode is a public static var. The RootClass is an internal class to the CitrusEngine class :

import starling.display.Sprite;
import starling.extensions.utils.Stats;
 
import com.citrusengine.core.CitrusEngine;
 
/**
 * RootClass is the root of Starling, it is never destroyed and only accessed through <code>_starling.stage</code>.
 * It may display a Stats class instance which contains Memory & FPS informations.
 */
internal class RootClass extends Sprite {
 
	public function RootClass() {
 
		if (CitrusEngine.starlingDebugMode)
			addChild(new Stats());
	}
}

The state var is defined as a state’s interface, IState, there are two states : State which extends flash.display.Sprite and StarlingState which extends starling.display.Sprite ; there are very similar. Finally the Starling view is very similar to the old CE Sprite view. Like the Blitting view, these 3 views extends CitrusView. The StarlingView is a clone to the SpriteView, and its Art class too. However there is one nice thing : re open the demo, press tab to open the console, and write :

set Box2D visible true

The Box2D debug view was required, it was on the top of my to-do list. But that wasn’t quite obvious : there isn’t any graphics api with Starling, so the first solution was to create a texture of this graphics and add it as an image, but textures are limited to 2048 * 2048. And in an enter frame that was a performance killer. So, the Box2D debug view is running on the flash display list :

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
package com.citrusengine.view.starlingview {
 
	import Box2DAS.Dynamics.b2DebugDraw;
 
	import starling.core.Starling;
	import starling.display.Sprite;
	import starling.events.Event;
 
	import com.citrusengine.physics.Box2D;
 
	/**
	 * This displays Box2D's debug graphics. It does so properly through Citrus Engine's view manager. Box2D by default
	 * sets visible to false, so you'll need to set the Box2D object's visible property to true in order to see the debug graphics. 
	 */
	public class Box2DDebugArt extends Sprite {
 
		private var _box2D:Box2D;
		private var _debugDrawer:b2DebugDraw;
 
		public function Box2DDebugArt() {
 
			addEventListener(Event.ADDED, handleAddedToParent);
			addEventListener(Event.ENTER_FRAME, handleEnterFrame);
			addEventListener(Event.REMOVED, destroy);
		}
 
		private function handleAddedToParent(evt:Event):void {
 
			removeEventListener(Event.ADDED, handleAddedToParent);
 
			_box2D = StarlingArt(parent).citrusObject as Box2D;
 
			_debugDrawer = new b2DebugDraw();
			Starling.current.nativeStage.addChild(_debugDrawer);
			_debugDrawer.world = _box2D.world;
			_debugDrawer.scale = _box2D.scale;
		}
 
		private function destroy(evt:Event):void {
 
			removeEventListener(Event.ADDED, handleAddedToParent);
			removeEventListener(Event.ENTER_FRAME, handleEnterFrame);
			removeEventListener(Event.REMOVED, destroy);
		}
 
		private function handleEnterFrame(evt:Event):void {
 
			_debugDrawer.Draw();
		}
	}
}

Now, let’s take a look on the most important class, the StarlingArt. It manages the art for every object. It handles many objects : png jpg gif pictures, swf (yes!), class reference, a fully qualified class name in string form (useful for a level editor!), or a Starling DisplayObject.
To create a CE object in your state class :

override public function initialize():void {
 
	super.initialize();
 
	var box2d:Box2D = new Box2D("Box2D");
	//box2d.visible = true;
	add(box2d);
 
	var baddy1:Baddy = new Baddy("Baddy1", {view:"baddy.png", x:100, y:100, width:40, height:40})
	add(bady1)
 
	var baddy2:Baddy = new Baddy("Baddy2", {view:"baddy.swf", x:400, y:100, width:40, height:40})
	add(bady2)
}

But how it works with a flash swf ? We have to thanks Emiliano Angelini which have created a Starling extension to make DynamicTextureAtlas, so your swf is transformed in a TextureAtlas “on the fly”. Awesome feature for a quick prototyping.
The StarlingArt 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
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
package com.citrusengine.view.starlingview {
 
	import Box2DAS.Dynamics.b2DebugDraw;
 
	import starling.core.Starling;
	import starling.display.DisplayObject;
	import starling.display.Image;
	import starling.display.MovieClip;
	import starling.display.Sprite;
	import starling.extensions.textureAtlas.DynamicAtlas;
	import starling.textures.Texture;
	import starling.textures.TextureAtlas;
	import starling.utils.deg2rad;
 
	import com.citrusengine.view.ISpriteView;
 
	import flash.display.Bitmap;
	import flash.display.Loader;
	import flash.events.Event;
	import flash.events.IOErrorEvent;
	import flash.net.URLRequest;
	import flash.utils.Dictionary;
	import flash.utils.getDefinitionByName;
 
	/**
	 * This is the class that all art objects use for the StarlingView state view. If you are using the StarlingView (as opposed to the blitting view, for instance),
	 * then all your graphics will be an instance of this class. There are 2 ways to manage MovieClip :
	 * - specify a "object.swf" in the view property of your object's creation.
	 * - add an AnimationSequence to your view property of your object's creation, see the AnimationSequence for more informations about it.
	 * The AnimationSequence is more optimized than the .swf which creates textures "on the fly" thanks to the DynamicAtlas class.
	 * 
	 * This class does the following things:
	 * 
	 * 1) Creates the appropriate graphic depending on your CitrusObject's view property (loader, sprite, or bitmap), and loads it if it is a non-embedded graphic.
	 * 2) Aligns the graphic with the appropriate registration (topLeft or center).
	 * 3) Calls the MovieClip's appropriate frame label based on the CitrusObject's animation property.
	 * 4) Updates the graphic's properties to be in-synch with the CitrusObject's properties once per frame.
	 * 
	 * These objects will be created by the Citrus Engine's StarlingView, so you should never make them yourself. When you use state.getArt() to gain access to your game's graphics
	 * (for adding click events, for instance), you will get an instance of this object. It extends Sprite, so you can do all the expected stuff with it, 
	 * such as add click listeners, change the alpha, etc.
	 **/
	public class StarlingArt extends Sprite {
 
		/**
		 * The content property is the actual display object that your game object is using. For graphics that are loaded at runtime
		 * (not embedded), the content property will not be available immediately. You can listen to the COMPLETE event on the loader
		 * (or rather, the loader's contentLoaderInfo) if you need to know exactly when the graphic will be loaded.
		 */
		public var content:DisplayObject;
 
		/**
		 * For objects that are loaded at runtime, this is the object that loades them. Then, once they are loaded, the content
		 * property is assigned to loader.content.
		 */
		public var loader:Loader;
 
		// properties :
 
		// determines animations playing in loop. You can add one in your state class : StarlingArt.setLoopAnimations(["walk", "climb"]);
		private static var _loopAnimation:Dictionary = new Dictionary();
 
		private var _citrusObject:ISpriteView;
		private var _registration:String;
		private var _view:*;
		private var _animation:String;
		private var _group:int;
 
		// fps for this MovieClip, it can be different between objects, to set it : view.getArt(myHero).fpsMC = 25; 
		private var _fpsMC:uint = 30;
 
		private var _texture:Texture;
		private var _textureAtlas:TextureAtlas;
 
		public function StarlingArt(object:ISpriteView) {
 
			_citrusObject = object;
 
			if (_loopAnimation["walk"] != true) {
				_loopAnimation["walk"] = true;
			}
		}
 
		public function destroy():void {
 
			if (content is MovieClip) {
				Starling.juggler.remove(content as MovieClip);
				_textureAtlas.dispose();
				content.dispose();
 
			} else if (content is AnimationSequence) {
 
				(content as AnimationSequence).destroy();
				content.dispose();
 
			} else if (content is Image) {
				_texture.dispose();
				content.dispose();
			}
 
		}
 
		/**
		 * Add a loop animation to the Dictionnary.
		 * @param tab an array with all the loop animation names.
		 */
		static public function setLoopAnimations(tab:Array):void {
 
			for each (var animation:String in tab) {
				_loopAnimation[animation] = true;
			}
		}
 
		static public function get loopAnimation():Dictionary {
			return _loopAnimation;
		}
 
		public function get registration():String {
			return _registration;
		}
 
		public function set registration(value:String):void {
 
			if (_registration == value || !content)
				return;
 
			_registration = value;
 
			if (_registration == "topLeft") {
				content.x = 0;
				content.y = 0;
			} else if (_registration == "center") {
				content.x = -content.width / 2;
				content.y = -content.height / 2;
			}
		}
 
		public function get view():* {
			return _view;
		}
 
		public function set view(value:*):void {
 
			if (_view == value)
				return;
 
			_view = value;
 
			if (_view) {
				if (_view is String) {
					// view property is a path to an image?
					var classString:String = _view;
					var suffix:String = classString.substring(classString.length - 4).toLowerCase();
					if (suffix == ".swf" || suffix == ".png" || suffix == ".gif" || suffix == ".jpg") {
						loader = new Loader();
						loader.contentLoaderInfo.addEventListener(Event.COMPLETE, handleContentLoaded);
						loader.contentLoaderInfo.addEventListener(IOErrorEvent.IO_ERROR, handleContentIOError);
						loader.load(new URLRequest(classString));
					}
					// view property is a fully qualified class name in string form. 
					else {
						var artClass:Class = getDefinitionByName(classString) as Class;
						content = new artClass();
						addChild(content);
					}
				} else if (_view is Class) {
					// view property is a class reference
					content = new citrusObject.view();
					addChild(content);
 
				} else if (_view is DisplayObject) {
					// view property is a Display Object reference
					content = _view;
					addChild(content);
				} else {
					throw new Error("SpriteArt doesn't know how to create a graphic object from the provided CitrusObject " + citrusObject);
					return;
				}
 
				if (content && content.hasOwnProperty("initialize"))
					content["initialize"](_citrusObject);
			}
		}
 
		public function get animation():String {
			return _animation;
		}
 
		public function set animation(value:String):void {
 
			if (_animation == value)
				return;
 
			_animation = value;
 
			if (_animation != null && _animation != "") {
 
				var animLoop:Boolean = _loopAnimation[_animation];
 
				if (content is MovieClip)
					(content as MovieClip).changeTextures(_textureAtlas.getTextures(_animation), _fpsMC, animLoop);
 
				if (content is AnimationSequence)
					(content as AnimationSequence).changeAnimation(_animation, _fpsMC, animLoop);
			}
		}
 
		public function get group():int {
			return _group;
		}
 
		public function set group(value:int):void {
			_group = value;
		}
 
		public function get fpsMC():uint {
			return _fpsMC;
		}
 
		public function set fpsMC(fpsMC:uint):void {
			_fpsMC = fpsMC;
		}
 
		public function get citrusObject():ISpriteView {
			return _citrusObject;
		}
 
		public function update(stateView:StarlingView):void {
 
			if (content is Box2DDebugArt) {
 
				// Box2D view is not on the Starling display list, but on the classical flash display list.
				// So we need to move its view here, not in the StarlingView.
 
				var box2dDebugArt:b2DebugDraw = (Starling.current.nativeStage.getChildAt(1) as b2DebugDraw);
 
				if (stateView.cameraTarget) {
 
					var diffX:Number = (-stateView.cameraTarget.x + stateView.cameraOffset.x) - box2dDebugArt.x;
					var diffY:Number = (-stateView.cameraTarget.y + stateView.cameraOffset.y) - box2dDebugArt.y;
					var velocityX:Number = diffX * stateView.cameraEasing.x;
					var velocityY:Number = diffY * stateView.cameraEasing.y;
					box2dDebugArt.x += velocityX;
					box2dDebugArt.y += velocityY;
 
					// Constrain to camera bounds
					if (stateView.cameraBounds) {
						if (-box2dDebugArt.x <= stateView.cameraBounds.left || stateView.cameraBounds.width < stateView.cameraLensWidth)
							box2dDebugArt.x = -stateView.cameraBounds.left;
						else if (-box2dDebugArt.x + stateView.cameraLensWidth >= stateView.cameraBounds.right)
							box2dDebugArt.x = -stateView.cameraBounds.right + stateView.cameraLensWidth;
 
						if (-box2dDebugArt.y <= stateView.cameraBounds.top || stateView.cameraBounds.height < stateView.cameraLensHeight)
							box2dDebugArt.y = -stateView.cameraBounds.top;
						else if (-box2dDebugArt.y + stateView.cameraLensHeight >= stateView.cameraBounds.bottom)
							box2dDebugArt.y = -stateView.cameraBounds.bottom + stateView.cameraLensHeight;
					}
				}
 
				box2dDebugArt.visible = _citrusObject.visible;
 
			} else {
 
				// The position = object position + (camera position * inverse parallax)
				x = _citrusObject.x + (-stateView.viewRoot.x * (1 - _citrusObject.parallax)) + _citrusObject.offsetX;
				y = _citrusObject.y + (-stateView.viewRoot.y * (1 - _citrusObject.parallax)) + _citrusObject.offsetY;
				visible = _citrusObject.visible;
				rotation = deg2rad(_citrusObject.rotation);
				scaleX = _citrusObject.inverted ? -1 : 1;
				registration = _citrusObject.registration;
				view = _citrusObject.view;
				animation = _citrusObject.animation;
				group = _citrusObject.group;
			}
		}
 
		private function handleContentLoaded(evt:Event):void {
 
			if (evt.target.loader.content is flash.display.MovieClip) {
 
				_textureAtlas = DynamicAtlas.fromMovieClipContainer(evt.target.loader.content, 1, 0, true, true);
				content = new MovieClip(_textureAtlas.getTextures(animation), _fpsMC);
				Starling.juggler.add(content as MovieClip);
			}
 
			if (evt.target.loader.content is Bitmap) {
 
				_texture = Texture.fromBitmap(evt.target.loader.content);
				content = new Image(_texture);
			}
 
			addChild(content);
		}
 
		private function handleContentIOError(evt:IOErrorEvent):void {
			throw new Error(evt.text);
		}
 
	}
}

There are three new important things :
- the static var loopAnimation dictionnary, to determine if the animation will play as a loop. Try to don’t have same animations name if one is a loop whereas the other isn’t!
- the var fpsMC, thanks to Starling each MovieClip may have a different fps, default is 30. This property is not integrated like other object’s properties (x, visible, view…) , because it is not available with the other views.
- the AnimationSequence class. With Starling there isn’t a class to manage the switch between animations, so I’ve created this one :

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
package com.citrusengine.view.starlingview {
 
	import starling.core.Starling;
	import starling.display.MovieClip;
	import starling.display.Sprite;
	import starling.textures.TextureAtlas;
 
	import flash.utils.Dictionary;
 
	/**
	 * The Animation Sequence class represents all object animations in one sprite sheet. You have to create your texture atlas in your state class.
	 * Example : var hero:Hero = new Hero("Hero", {x:400, width:60, height:130, view:new AnimationSequence(textureAtlas, ["walk", "duck", "idle", "jump"], "idle")});
	 * 
	 * @param textureAtlas : a TextureAtlas object with all your object's animations
	 * @param animations : an array with all your object's animations as a String
	 * @param firstAnimation : a string of your default animation at its creation
	 */
	public class AnimationSequence extends Sprite {
 
		private var _textureAtlas:TextureAtlas;
		private var _animations:Array;
		private var _mcSequences:Dictionary;
		private var _previousAnimation:String;
 
		public function AnimationSequence(textureAtlas:TextureAtlas, animations:Array, firstAnimation:String) {
 
			super();
 
			_textureAtlas = textureAtlas;
 
			_animations = animations;
 
			_mcSequences = new Dictionary();
 
			for each (var animation:String in animations) {
 
				if (_textureAtlas.getTextures(animation).length == 0) {
					throw new Error("One object doesn't have the " + animation + " animation in its TextureAtlas");
				}
 
				_mcSequences[animation] = new MovieClip(_textureAtlas.getTextures(animation));
 
			}
 
			addChild(_mcSequences[firstAnimation]);
			Starling.juggler.add(_mcSequences[firstAnimation]);
 
			_previousAnimation = firstAnimation;			
		}
 
		/**
		 * Called by StarlingArt, managed the MC's animations.
		 * @param animation : the MC's animation
		 * @param fps : the MC's fps
		 * @param animLoop : true if the MC is a loop
		 */
		public function changeAnimation(animation:String, fps:Number, animLoop:Boolean):void {
 
			if (!(_mcSequences[animation])) {
				throw new Error("One object doesn't have the " + animation + " animation set up in its initial array");
				return;
			}
 
			removeChild(_mcSequences[_previousAnimation]);
			Starling.juggler.remove(_mcSequences[_previousAnimation]);
 
			addChild(_mcSequences[animation]);
			Starling.juggler.add(_mcSequences[animation]);
			_mcSequences[animation].fps = fps;
			_mcSequences[animation].loop = animLoop;
 
			_previousAnimation = animation;
		}
 
		public function destroy():void {
 
			removeChild(_mcSequences[_previousAnimation]);
			Starling.juggler.remove(_mcSequences[_previousAnimation]);
 
			for each (var animation : String in _animations)
				_mcSequences[animation].dispose();
 
			_textureAtlas.dispose();
 
			_mcSequences = null;
		}
	}
}

For using it, you’ve to create a SpriteSheet. I use TexturePacker. Create all your different MovieClip in flash, each one represent a different state. They all should have the same scene width/height. Then export the swfs and import them in TexturePacker using Sparrow data format. Use the trim and enabe auto alias option, but not the crop! Export, you have your SpriteSheet with a xml.
And then embed them in your state class, and use it like that :

var bitmap:Bitmap = new _heroPng();
var texture:Texture = Texture.fromBitmap(bitmap);
var xml:XML = XML(new _heroConfig());
var sTextureAtlas:TextureAtlas = new TextureAtlas(texture, xml);
 
_hero = Hero(getFirstObjectByType(Hero));
_hero.view = new AnimationSequence(sTextureAtlas, ["walk", "duck", "idle", "jump", "hurt"], "idle");

You can find the swfs here : zip.
Maybe I will include the png & xml directly in the constructor param, so we will not have to create each time the texture altas. But if we have several objects with the same texture, it will be less optimized… Don’t hesitate to comment to tell me your preferences!

So I think this is it for the Starling view. You will find other informations in the demo code, showed later.

ABSTRACT GAME DATA
That was a request of the community : having a simple way to save game informations, datas… By the way, thanks Roger Clark, for helping lots of people this last month on the forum!!
The problem was : how to create a CE class which will not be modified by users, but will help them? An abstract dynamic class did the trick :

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
package com.citrusengine.utils {
 
	import org.osflash.signals.Signal;
 
	/**
	 * This is an (optional) abstract class to store your game's data such as lives, score, levels...
	 * You should extend this class & instantiate it into your main class using the gameData variable.
	 * You can dispatch a signal, dataChanged, if you update one of your data.
	 * For more information, watch the example below. 
	 */
	dynamic public class AGameData {
 
		public var dataChanged:Signal;
 
		protected var _lives:int = 3;
		protected var _score:int = 0;
		protected var _timeleft:int = 300;
 
		protected var _levels:Array;
 
		public function AGameData() {
 
			dataChanged = new Signal(String, Object);
		}
 
		public function get lives():int {
			return _lives;
		}
 
		public function set lives(lives:int):void {
 
			_lives = lives;
 
			dataChanged.dispatch("lives", _lives);
		}
 
		public function get score():int {
			return _score;
		}
 
		public function set score(score:int):void {
 
			_score = score;
 
			dataChanged.dispatch("score", _score);
		}
 
		public function get timeleft():int {
			return _timeleft;
		}
 
		public function set timeleft(timeleft:int):void {
 
			_timeleft = timeleft;
 
			dataChanged.dispatch("timeleft", _timeleft);
		}
 
		public function destroy():void {
 
			dataChanged.removeAll();
		}
	}
}

Finally we just create our GameData class which extends AGameData, and we use it like this :

gameData = new MyGameData();
//example in the main class :
levelManager.levels = gameData.levels;
 
//examples in the state class :
CitrusEngine.getInstance().gameData.dataChanged.add(myFunctionDataChanged);
CitrusEngine.getInstance().gameData.lives--;

LEVEL MANAGER :
The Level Manager was an other community request, so I’ve added mine. It is quite complex, but very powerful :

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
package com.citrusengine.utils {
 
	import org.osflash.signals.Signal;
 
	import flash.display.Loader;
	import flash.events.Event;
	import flash.net.URLRequest;
 
	/**
	 * The LevelManager is a complex but powerful class, you can use simple states for levels with SWC/SWF/XML.
	 * Before using it, be sure that you have good OOP knowledge. For using it, you must use an Abstract state class 
	 * that you give as constructor parameter : Alevel. 
	 * 
	 * The four ways to set up your level : 
	 * <code>levelManager.levels = [Level1, Level2];
	 * levelManager.levels = [[Level1, "level1.swf"], [level2, "level2.swf"]];
	 * levelManager.levels = [[Level1, "level1.xml"], [level2, "level2.xml"]];
	 * levelManager.levels = [[Level1, Level1_SWC], [level2, Level2_SWC]];
	 * </code>
	 * 
	 * An instanciation exemple in your Main class (you may also use the AGameData to store your levels) :
	 * <code>levelManager = new LevelManager(ALevel);
	 * levelManager.onLevelChanged.add(_onLevelChanged);
	 * levelManager.levels = [Level1, Level2];
	 * levelManager.gotoLevel();</code>
	 * 
	 * The _onLevelChanged function gives in parameter the Alevel that you associate to your state : <code>state = lvl;</code>
	 * Then you can associate other function :
	 * <code>lvl.lvlEnded.add(_nextLevel);
	 * lvl.restartLevel.add(_restartLevel);</code>
	 * And their respective actions :
	 * <code>_levelManager.nextLevel();
	 * state = _levelManager.currentLevel as IState;</code>
	 * 
	 * The ALevel class must implement public var lvlEnded & restartLevel Signals in its constructor.
	 * If you have associated a SWF or SWC file to your level, you must add a flash MovieClip as a parameter into its constructor, 
	 * or a XML if it is one!
	 */
	public class LevelManager {
 
		static private var _instance:LevelManager;
 
		public var onLevelChanged:Signal;
 
		private var _ALevel:Class;
		private var _levels:Array;
		private var _currentIndex:uint;
		private var _currentLevel:Object;
 
		public function LevelManager(ALevel:Class) {
 
			_instance = this;
 
			_ALevel = ALevel;
 
			onLevelChanged = new Signal(_ALevel);
			_currentIndex = 0;
		}
 
		static public function getInstance():LevelManager {
			return _instance;
		}
 
 
		public function destroy():void {
 
			onLevelChanged.removeAll();
 
			_currentLevel = null;
		}
 
		public function nextLevel():void {
 
			if (_currentIndex < _levels.length - 1) {
				++_currentIndex;
			}
 
			gotoLevel();
		}
 
		public function prevLevel():void {
 
			if (_currentIndex > 0) {
				--_currentIndex;
			}
 
			gotoLevel();
		}
 
		/**
		 * Call the LevelManager instance's gotoLevel() function to launch your first level, or you may specify it.
		 * @param index : the level index from 1 to ... ; different from the levels' array indexes.
		 */
		public function gotoLevel(index:int = -1):void {
 
			if (_currentLevel != null) {
				_currentLevel.lvlEnded.remove(_onLevelEnded);
			}
 
			var loader:Loader = new Loader();
 
			if (index != -1) {
				_currentIndex = index - 1;
			}
 
			// Level SWF and SWC are undefined
			if (_levels[_currentIndex][0] == undefined) {
 
				_currentLevel = _ALevel(new _levels[_currentIndex]);
				_currentLevel.lvlEnded.add(_onLevelEnded);
 
				onLevelChanged.dispatch(_currentLevel);
 
			// It's a SWC ?
			} else if (_levels[_currentIndex][1] is Class) {
 
				_currentLevel = _ALevel(new _levels[_currentIndex][0](new _levels[_currentIndex][1]()));
				_currentLevel.lvlEnded.add(_onLevelEnded);
 
				onLevelChanged.dispatch(_currentLevel);
 
			// So it's a SWF or XML, we load it 
			} else {
 
				loader.load(new URLRequest(_levels[_currentIndex][1]));
				loader.contentLoaderInfo.addEventListener(Event.COMPLETE,_levelLoaded);
			}
		}
 
		private function _levelLoaded(evt:Event):void {
 
			_currentLevel = _ALevel(new _levels[_currentIndex][0](evt.target.loader.content));
			_currentLevel.lvlEnded.add(_onLevelEnded);
 
			onLevelChanged.dispatch(_currentLevel);
 
			evt.target.removeEventListener(Event.COMPLETE, _levelLoaded);
			evt.target.loader.unloadAndStop();
		}
 
		private function _onLevelEnded():void {
 
		}
 
		public function get levels():Array {
			return _levels;
		}
 
		public function set levels(levels:Array):void {
			_levels = levels;
		}
 
		public function get currentLevel():Object {
			return _currentLevel;
		}
 
		public function set currentLevel(currentLevel:Object):void {
			_currentLevel = currentLevel;
		}
 
		public function get nameCurrentLevel():String {
			return _currentLevel.nameLevel;
		}
	}
}

Let’s see how it is used with the demo.

THE DEMO CODE :
You’re always here ? This is great :D It’s time for all the demo code :

Main :

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
package {
 
	import com.citrusengine.core.CitrusEngine;
	import com.citrusengine.core.IState;
	import com.citrusengine.utils.LevelManager;
 
	[SWF(backgroundColor="#FFFFFF", frameRate="60", width="640", height="480")]
 
	/**
	 * @author Aymeric
	 */
	public class Main extends CitrusEngine {
 
		public function Main() {
 
			setUpStarling(true);
 
			gameData = new MyGameData();
 
			levelManager = new LevelManager(ALevel);
			levelManager.onLevelChanged.add(_onLevelChanged);
			levelManager.levels = gameData.levels;
			levelManager.gotoLevel();
		}
 
		private function _onLevelChanged(lvl:ALevel):void {
 
			state = lvl;
 
			lvl.lvlEnded.add(_nextLevel);
			lvl.restartLevel.add(_restartLevel);
		}
 
		private function _nextLevel():void {
 
			levelManager.nextLevel();
		}
 
		private function _restartLevel():void {
 
			state = levelManager.currentLevel as IState;
		}
	}
}

MyGameData :

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
package {
 
	import com.citrusengine.utils.AGameData;
 
	/**
	 * @author Aymeric
	 */
	public class MyGameData extends AGameData {
 
		public function MyGameData() {
 
			super();
 
			_levels = [[Level1, "levels/A1/LevelA1.swf"], [Level2, "levels/A2/LevelA2.swf"]];
		}
 
		public function get levels():Array {
			return _levels;
		}
 
		override public function destroy():void {
 
			super.destroy();
		}
 
	}
}

ALevel:

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
package {
 
	import Box2DAS.Dynamics.ContactEvent;
 
	import starling.display.Quad;
	import starling.text.BitmapFont;
	import starling.text.TextField;
	import starling.textures.Texture;
	import starling.textures.TextureAtlas;
	import starling.utils.Color;
 
	import com.citrusengine.core.CitrusEngine;
	import com.citrusengine.core.StarlingState;
	import com.citrusengine.math.MathVector;
	import com.citrusengine.objects.CitrusSprite;
	import com.citrusengine.objects.platformer.Baddy;
	import com.citrusengine.objects.platformer.Hero;
	import com.citrusengine.objects.platformer.Platform;
	import com.citrusengine.objects.platformer.Sensor;
	import com.citrusengine.physics.Box2D;
	import com.citrusengine.utils.ObjectMaker;
	import com.citrusengine.view.starlingview.AnimationSequence;
 
	import org.osflash.signals.Signal;
 
	import flash.display.Bitmap;
	import flash.display.MovieClip;
	import flash.geom.Rectangle;
 
	/**
	 * @author Aymeric
	 */
	public class ALevel extends StarlingState {
 
		public var lvlEnded:Signal;
		public var restartLevel:Signal;
 
		protected var _ce:CitrusEngine;
		protected var _level:MovieClip;
 
		protected var _hero:Hero;
 
		[Embed(source="../embed/Hero.xml", mimeType="application/octet-stream")]
		private var _heroConfig:Class;
 
		[Embed(source="../embed/Hero.png")]
		private var _heroPng:Class;
 
		[Embed(source="../embed/ArialFont.fnt", mimeType="application/octet-stream")]
		private var _fontConfig:Class;
 
		[Embed(source="../embed/ArialFont.png")]
		private var _fontPng:Class;
 
		protected var _maskDuringLoading:Quad;
		protected var _percentTF:TextField;
 
		public function ALevel(level:MovieClip = null) {
 
			super();
 
			_ce = CitrusEngine.getInstance();
 
			_level = level;
 
			lvlEnded = new Signal();
			restartLevel = new Signal();
 
			// Useful for not forgetting to import object from the Level Editor
			var objectsUsed:Array = [Hero, Platform, Baddy, Sensor, CitrusSprite];
		}
 
		override public function initialize():void {
 
			super.initialize();
 
			var box2d:Box2D = new Box2D("Box2D");
			//box2d.visible = true;
			add(box2d);
 
			// hide objects loading in the background
			_maskDuringLoading = new Quad(stage.stageWidth, stage.stageHeight);
			_maskDuringLoading.color = 0x000000;
			_maskDuringLoading.x = (stage.stageWidth - _maskDuringLoading.width) / 2;
			_maskDuringLoading.y = (stage.stageHeight - _maskDuringLoading.height) / 2;
			addChild(_maskDuringLoading);
 
			// create a textfield to show the loading %
			var bitmap:Bitmap = new _fontPng();
			var ftTexture:Texture = Texture.fromBitmap(bitmap);
			var ftXML:XML = XML(new _fontConfig());
			TextField.registerBitmapFont(new BitmapFont(ftTexture, ftXML));
 
			_percentTF = new TextField(400, 200, "", "ArialMT");
			_percentTF.fontSize = BitmapFont.NATIVE_SIZE;
			_percentTF.color = Color.WHITE;
			_percentTF.autoScale = true;
			_percentTF.x = (stage.stageWidth - _percentTF.width) / 2;
			_percentTF.y = (stage.stageHeight - _percentTF.height) / 2;
 
			addChild(_percentTF);
 
			// when the loading is completed...
			view.loadManager.onLoadComplete.addOnce(_handleLoadComplete);
 
			// create objects from our level made with Flash Pro
			ObjectMaker.FromMovieClip(_level);
 
			// the hero view come from a sprite sheet, for the baddy that was a swf
			bitmap = new _heroPng();
			var texture:Texture = Texture.fromBitmap(bitmap);
			var xml:XML = XML(new _heroConfig());
			var sTextureAtlas:TextureAtlas = new TextureAtlas(texture, xml);
 
			_hero = Hero(getFirstObjectByType(Hero));
			_hero.view = new AnimationSequence(sTextureAtlas, ["walk", "duck", "idle", "jump", "hurt"], "idle");
			_hero.hurtDuration = 500;
 
			view.setupCamera(_hero, new MathVector(320, 240), new Rectangle(0, 0, 1550, 450), new MathVector(.25, .05));
		}
 
		protected function _changeLevel(cEvt:ContactEvent):void {
 
			if (cEvt.other.GetBody().GetUserData() is Hero) {
				lvlEnded.dispatch();
			}
		}
 
		protected function _handleLoadComplete():void {
 
			removeChild(_percentTF);
			removeChild(_maskDuringLoading);
		}
 
		override public function update(timeDelta:Number):void {
 
			super.update(timeDelta);
 
			var percent:uint = view.loadManager.bytesLoaded / view.loadManager.bytesTotal * 100;
 
			if (percent < 99) {
				_percentTF.text = percent.toString() + "%";
			}
		}
 
		override public function destroy():void {
 
			TextField.unregisterBitmapFont("ArialMT");
 
			super.destroy();
		}
	}
}

Level1 :

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
package {
 
	import Box2DAS.Dynamics.ContactEvent;
 
	import starling.core.Starling;
	import starling.extensions.particles.ParticleDesignerPS;
	import starling.extensions.particles.ParticleSystem;
	import starling.text.BitmapFont;
	import starling.text.TextField;
	import starling.textures.Texture;
	import starling.utils.Color;
 
	import com.citrusengine.objects.platformer.Hero;
	import com.citrusengine.objects.platformer.Sensor;
 
	import flash.display.MovieClip;
 
	/**
	 * @author Aymeric
	 */
	public class Level1 extends ALevel {
 
		[Embed(source="../embed/Particle.pex", mimeType="application/octet-stream")]
		private var _particleConfig:Class;
 
		[Embed(source="../embed/ParticleTexture.png")]
		private var _particlePng:Class;
 
		private var _particleSystem:ParticleSystem;
 
		private var _bmpFontTF:TextField;
 
		public function Level1(level:MovieClip = null) {
			super(level);
		}
 
		override public function initialize():void {
 
			super.initialize();
 
			var psconfig:XML = new XML(new _particleConfig());
			var psTexture:Texture = Texture.fromBitmap(new _particlePng());
 
			_particleSystem = new ParticleDesignerPS(psconfig, psTexture);
			_particleSystem.start();
			Starling.juggler.add(_particleSystem);
 
			var endLevel:Sensor = Sensor(getObjectByName("endLevel"));
			endLevel.view = _particleSystem;
 
			_bmpFontTF = new TextField(400, 200, "The Citrus Engine goes on Stage3D thanks to Starling", "ArialMT");
			_bmpFontTF.fontSize = BitmapFont.NATIVE_SIZE;
			_bmpFontTF.color = Color.WHITE;
			_bmpFontTF.autoScale = true;
			_bmpFontTF.x = (stage.stageWidth - _bmpFontTF.width) / 2;
			_bmpFontTF.y = (stage.stageHeight - _bmpFontTF.height) / 2;
 
			addChild(_bmpFontTF);
			_bmpFontTF.visible = false;
 
			var popUp:Sensor = Sensor(getObjectByName("popUp"));
 
			endLevel.onBeginContact.add(_changeLevel);
 
			popUp.onBeginContact.add(_showPopUp);
			popUp.onEndContact.add(_hidePopUp);
		}
 
		private function _showPopUp(cEvt:ContactEvent):void {
 
			if (cEvt.other.GetBody().GetUserData() is Hero) {
				_bmpFontTF.visible = true;
			}
		}
 
		private function _hidePopUp(cEvt:ContactEvent):void {
 
			if (cEvt.other.GetBody().GetUserData() is Hero) {
				_bmpFontTF.visible = false;
			}
		}
 
		override public function destroy():void {
 
			Starling.juggler.remove(_particleSystem);
			_particleSystem.stop();
			_particleSystem.dispose();
 
			removeChild(_bmpFontTF);
 
			super.destroy();
		}
 
	}
}

Level2 :

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
package {
 
	import starling.text.BitmapFont;
	import starling.text.TextField;
	import starling.utils.Color;
 
	import flash.display.MovieClip;
	import flash.events.TimerEvent;
	import flash.utils.Timer;
 
	/**
	 * @author Aymeric
	 */
	public class Level2 extends ALevel {
 
		private var _timer:Timer;
 
		private var _bmpFontTF:TextField;
 
		public function Level2(level:MovieClip = null) {
 
			super(level);
		}
 
		override public function initialize():void {
 
			super.initialize();
 
			_timer = new Timer(3000);
			_timer.addEventListener(TimerEvent.TIMER, _onTick);
 
			_bmpFontTF = new TextField(400, 200, "This is a performance test level. Box2D physics become some time unstable. You can see box2d bodies thanks to the console.", "ArialMT");
			_bmpFontTF.fontSize = BitmapFont.NATIVE_SIZE;
			_bmpFontTF.color = Color.WHITE;
			_bmpFontTF.autoScale = true;
			_bmpFontTF.x = (stage.stageWidth - _bmpFontTF.width) / 2;
			_bmpFontTF.y = (stage.stageHeight - _bmpFontTF.height) / 2;
		}
 
		override protected function _handleLoadComplete():void {
 
			super._handleLoadComplete();
 
			addChild(_bmpFontTF);
			_timer.start();
		}
 
		private function _onTick(tEvt:TimerEvent):void {
 
			if (_timer.currentCount == 2)
				removeChild(_bmpFontTF);
 
			// PhysicsEditorObjects class is created by the software PhysicsEditor and its additional CitrusEngine template.
			// Muffins are not in front of everything due to the foreground group param set to 1 in the Level Editor, default is 0.
			var muffin:PhysicsEditorObjects = new PhysicsEditorObjects("muffin", {peObject:"muffin", view:"muffin.png", registration:"topLeft", x:Math.random() * view.cameraBounds.width});
			add(muffin);
		}
 
		override public function destroy():void {
 
			_timer.removeEventListener(TimerEvent.TIMER, _onTick);
			_timer.stop();
			_timer = null;
 
			super.destroy();
		}
	}
}

And finally the PhysicsEditorObjects :

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
package {
 
	import Box2DAS.Collision.Shapes.b2PolygonShape;
	import Box2DAS.Common.V2;
 
	import com.citrusengine.objects.platformer.Crate;
 
	/**
	 * @author Aymeric
	 * <p>This is a class created by the software http://www.physicseditor.de/</p>
	 * <p>Just select the CitrusEngine template, upload your png picture, set polygons and export.</p>
	 * <p>Be careful, the registration point is topLeft !</p>
	 * @param peObject : the name of the png file
	 */
    public class PhysicsEditorObjects extends Crate {
 
		[Inspectable(defaultValue="")]
		public var peObject:String = "";
 
		private var _tab:Array;
 
		public function PhysicsEditorObjects(name:String, params:Object = null) {
 
			super(name, params);
		}
 
		override public function destroy():void {
 
			super.destroy();
		}
 
		override public function update(timeDelta:Number):void {
 
			super.update(timeDelta);
		}
 
		override protected function defineFixture():void {
 
			super.defineFixture();
 
			_createVertices();
 
			_fixtureDef.density = _getDensity();
			_fixtureDef.friction = _getFriction();
			_fixtureDef.restitution = _getRestitution();
 
			for (var i:uint = 0; i < _tab.length; ++i) {
				var polygonShape:b2PolygonShape = new b2PolygonShape();
				polygonShape.Set(_tab[i]);
				_fixtureDef.shape = polygonShape;
 
				body.CreateFixture(_fixtureDef);
			}
		}
 
        protected function _createVertices():void {
 
			_tab = [];
			var vertices:Vector.<V2> = new Vector.<V2>();
 
			switch (peObject) {
 
				case "muffin":
 
			        vertices.push(new V2(-0.5/_box2D.scale, 81.5/_box2D.scale));
					vertices.push(new V2(10.5/_box2D.scale, 59.5/_box2D.scale));
					vertices.push(new V2(46.5/_box2D.scale, 27.5/_box2D.scale));
					vertices.push(new V2(50.5/_box2D.scale, 27.5/_box2D.scale));
					vertices.push(new V2(92.5/_box2D.scale, 61.5/_box2D.scale));
					vertices.push(new V2(99.5/_box2D.scale, 79.5/_box2D.scale));
					vertices.push(new V2(59.5/_box2D.scale, 141.5/_box2D.scale));
					vertices.push(new V2(17.5/_box2D.scale, 133.5/_box2D.scale));
 
					_tab.push(vertices);
					vertices = new Vector.<V2>();
 
			        vertices.push(new V2(59.5/_box2D.scale, 141.5/_box2D.scale));
					vertices.push(new V2(99.5/_box2D.scale, 79.5/_box2D.scale));
					vertices.push(new V2(83.5/_box2D.scale, 133.5/_box2D.scale));
 
					_tab.push(vertices);
					vertices = new Vector.<V2>();
 
			        vertices.push(new V2(50.5/_box2D.scale, 27.5/_box2D.scale));
					vertices.push(new V2(46.5/_box2D.scale, 27.5/_box2D.scale));
					vertices.push(new V2(42.5/_box2D.scale, -0.5/_box2D.scale));
 
					_tab.push(vertices);
 
					break;
 
			}
		}
 
		protected function _getDensity():Number {
 
			switch (peObject) {
 
				case "muffin":
					return 1;
					break;
 
			}
 
			return 1;
		}
 
		protected function _getFriction():Number {
 
			switch (peObject) {
 
				case "muffin":
					return 0.6;
					break;
 
			}
 
			return 0.6;
		}
 
		protected function _getRestitution():Number {
 
			switch (peObject) {
 
				case "muffin":
					return 0.3;
					break;
 
			}
 
			return 0.3;
		}
	}
}

This is it! Again, everything is available on the CE’s google code. But hey, this is the CitrusEngine V3 BETA 1. What is next !?

LOOKING FOR CONTRIBUTOR
The CE is currently looking for some new contributors for more amazing features! If you’re an advanced game developer, or a simple student (like me), you can contribute!

What about the Inspectable metadata tag?
I would like to add it, but now with Starling support it means that Flash Pro must be able to target FP11 that’s a bit complicated. So not at the moment unless you ask for it!

LEVEL ARCHITECT
The Level Architect is a great tool but not quite reached. It needs always some work. Personnaly I prefer using Flash Pro, some people don’t. That would be cool if someone would contribute to it.

MOBILE
I’ll be honest : if you want to create a mobile game and you have more than 5 dynamics objects, forget the CitrusEngine for the moment. Box2D is a performance killer on mobile with Flash. However Eric started a simpler class for collision management : CitrusSolver. It is well advanced, just waiting for some more work.

ENTITY/COMPONENT SYSTEM
I haven’t include my ladder management in the Hero class. Because the Hero class would become more & more complex… what if it uses a sword, a gun, a rope… ? Extending it is not enough. In a previous post, I’ve explained why we need a simple entity system. Richard Lord has made an amazing blog post : What is an entity framework for game development?. Now I’ve fully understand how it works. But mixing it with box2d, frame animation, input management doesn’t sound easy…

CONCLUSION
If you have read everything, that’s awesome! Feel free to comment / request features for the CitrusEngine, and if you want to be a contributor you’re welcome !
For the next months, I will continue to be active for the CE’s community, always playing with Flash AS3, learning iOS, learning haXe nme, and work hard on my 2nd year school project. And finally I’m looking for job, beginning in July or later.

Oh and I will be at the World Wide haXe conf on Friday 13 – Saturday 14 April, at Paris. Hope to meet some of you there :-)

Objective-C basic interface

0

In this tutorial, we will see how to create a basic interface in Objective-C with code only. It will be one TextField and one Button. Yeah, just that and we will need 6 files! And maybe we will use some inheritance.

ActionScript 3 is really a smart language, it is very quick to create what we will do. Something like 10 lines of code… Anyway, in this tutorial I will not make comparisons between ActionScript 3 and Objective-C like the previous one unless it is really useful.

Open Xcode and create a new iOS empty application, disable everything. Many files are created, but we will use only AppDelegate.h and AppDelegate.m In the AppDelegate we will init our applications.

Objective-C is based on the MVC design pattern, so we need to implement it :
Create a new UIViewController subclass file and name it MyViewController, don’t select XIB option, and create a folder “controllers”. Your files are created in the folder but you don’t see that in Xcode. Select the files, right click and select “New Group from Selection”. Rename it in “controllers”.
Objective-C doesn’t support package!

Import your ViewController class in AppDelegate.h :

1
2
3
4
5
6
7
8
9
10
11
12
13
#import <UIKit/UIKit.h>
#import "MyViewController.h"
 
@interface AppDelegate : UIResponder <UIApplicationDelegate> {
 
    MyViewController *myViewController;
}
 
@property (nonatomic, retain) MyViewController *myViewController;
 
@property (strong, nonatomic) UIWindow *window;
 
@end

Then open its implementation file (.m) :
Synthesize the property :

@synthesize window = _window, myViewController;

Release the object in the dealloc method :

- (void)dealloc
{
    [_window release];
 
    [myViewController release];
 
    [super dealloc];
}

Now we need to add our ViewController, go inside the application:didFinishLaunchingWithOptions: method and add two lines like that :

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
    self.window = [[[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]] autorelease];
 
    // Override point for customization after application launch.
 
    self.window.backgroundColor = [UIColor whiteColor];
 
    myViewController = [[MyViewController alloc] init];
 
    [_window addSubview:myViewController.view];
 
    [self.window makeKeyAndVisible];
    return YES;
}

Now we create an UIView class, I called it MyView. Create a folder “views” and “Group” your two new files. In the header file, we create 3 properties and a new constructor :

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#import <UIKit/UIKit.h>
 
@interface MyView : UIView {
 
    NSString *myText;
    UIColor *colorText;
    UIButton *myButton;
}
 
@property (nonatomic, retain) NSString *myText;
@property (nonatomic, retain) UIColor *colorText;
@property (nonatomic, retain) UIButton *myButton;
 
- (id) initWithFrame:(CGRect)frame withText:(NSString *)text andColor:(UIColor *)color;
 
@end

Then implement your file :

@synthesize myText, colorText, myButton;

In the new constructor we create our button, and we add the textfield in an other method.

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
- (id) initWithFrame:(CGRect)frame withText:(NSString *)text andColor:(UIColor *)color {
 
    self = [super initWithFrame:frame];
    if (self) {
        // Initialization code
 
        myText = [[NSString alloc] initWithString:text];
 
        colorText = color;
 
        myButton = [UIButton buttonWithType:UIButtonTypeInfoLight];
        [myButton setFrame:CGRectMake(10, 80, 30, 30)];
    }
 
    return self;
}
 
- (void) dealloc {
 
    [myText release];
    [colorText release];
    [myButton release];
 
    [super dealloc];
}

And finally uncomment the drawRect: method which is called when a new object is created :

1
2
3
4
5
6
- (void)drawRect:(CGRect)rect
{
    // the background becomes black, don't know why...
    [colorText setFill];
    [myText drawAtPoint:CGPointMake(100, 100) withFont:[UIFont systemFontOfSize:24]];
}

Note that coordinates (0, 0) are top left on iOS app, but bottom left on mac!

So at the moment we have just set up our view with some params. Now we add our view to the view controller :

1
2
3
4
5
6
7
8
9
10
11
12
13
#import <UIKit/UIKit.h>
#import "MyView.h"
 
@interface MyViewController : UIViewController {
 
    MyView *myView;
}
 
@property (nonatomic, retain) MyView *myView;
 
- (void) wasTapped;
 
@end

We have created a method wasTapped to inform the user that the button is pressed.

@synthesize myView;

And add these methods :

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
- (void) loadView {
 
    CGRect rect = [UIScreen mainScreen].applicationFrame;
    self.view = [[UIView alloc]initWithFrame:rect];
 
    myView = [[MyView alloc] initWithFrame:rect withText:@"MyText" andColor:[UIColor greenColor]];
 
    [self.view addSubview:myView];
 
    [self.view addSubview:myView.myButton];
    [myView.myButton addTarget:self action:@selector(wasTapped) forControlEvents:UIControlEventTouchUpInside];
}
 
- (void) wasTapped {
 
    NSLog(@"Button clicked");
}
 
- (void) dealloc {
 
    [myView release];
 
    [super dealloc];
}

Now compile, you should have already your green textfield and a button which send “Button Clicked” in the console!

I think it’s time for some explanation :
In the AppDelegate file, we can’t add a view directly, we need to add its view controller thanks to these methods :

myViewController = [[MyViewController alloc] init];
[_window addSubview:myViewController.view];

Then we have added the view to our view controller thanks to the loadView method. It comes from UIViewController! Afterwards we have initialized our MyView class, giving some params, and finally added it and its button :

1
2
3
4
myView = [[MyView alloc] initWithFrame:rect withText:@"MyText" andColor:[UIColor greenColor]];
 
[self.view addSubview:myView];
[self.view addSubview:myView.myButton];

We didn’t need to add the textfield like we did with the button, because we use its method drawAtPoint which doesn’t exist on a UIButton.

And the last line of loadView method, add an event to our button :

[myView.myButton addTarget:self action:@selector(wasTapped) forControlEvents:UIControlEventTouchUpInside];

The action parameter need to be explained : Xcode is waiting for a SEL object. A SEL is simply a pointer reference to a function, we already used something like that in AS3 with listeners :

addEventListener(MouseEvent.CLICK, myFunction);
function myFunction(mEvt:MouseEvent):void { trace('ok');}

In Objective-C we used the keyword @selector(functionName).

Now it’s time to create our first object. Create a new file, MyParentObject, which extend NSObject, and group it in objects folder.

1
2
3
4
5
6
7
8
9
10
#import <Foundation/Foundation.h>
 
@interface MyParentObject : NSObject {
 
        NSString *myText;
}
 
@property (nonatomic, retain) NSString *myText;
 
@end
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
#import "MyParentObject.h"
 
@implementation MyParentObject
 
@synthesize myText;
 
- (id) init {
 
    self = [super init];
 
    if (self)  {
 
        myText = [[NSString alloc] initWithString:@"My Parent Text"];
    }
 
    return self;
}
 
- (void) dealloc {
 
    [myText release];
 
    [super dealloc];
}
 
@end

Then create a new NSObject class, MyObject which extends MyParentObject.

1
2
3
4
5
6
7
8
9
10
11
#import <Foundation/Foundation.h>
#import "MyParentObject.h"
 
@interface MyObject : MyParentObject {
 
    UIColor *myColor;
}
 
@property (nonatomic, retain) UIColor *myColor;
 
@end
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
#import "MyObject.h"
 
@implementation MyObject
 
@synthesize myColor;
 
- (id) init {
 
    self = [super init];
 
    if (self)  {
 
        myColor = [UIColor greenColor];
 
        myText = @"My overrided text";
    }
 
    return self;
}
 
- (void) dealloc {
 
    [myColor release];
 
    [super dealloc];
}
 
@end

Then we just need to init MyObject in the ViewController and added its properties to the View Constructor :

myView = [[MyView alloc] initWithFrame:rect withText:myObject.myText andColor:myObject.myColor];

You’ve to think to synthesize it, init it, and release it!

Then compile, that’s it!

MyZip.

In this tutorial we saw the 3 primary objects (NSObject, UIView and UIViewController) and how to assemble them. If you are creating an app for the iPhone & iPad, you will use Xcode’s Interface Builder with xib file. It is really smart, you just have to drag & drop interfaces and “draw line” to make connection between them and code.

On the internet, some people use NSRect NSColor objects for their view and design. I couldn’t use most of the NS object in my example… maybe there is a difference between an app on Mac and iOS. If someone could explain it, that would be great!
Finally it’s not learning a new language which is hard, it’s learning its API ;-)

I don’t think that I will made basic tutorials on creating iPhone apps, there are already lots of resources on the internet. I will focus on game engine, Cocos2D and Sparrow!

Box2D Sound Spectrum

0

This week is really challenging : my second year school project has started this week. It’s a team project with a graphic designer, a designer, a project manager and me as a developer. Our concept sounds very promising!

No matter, this short introduction explained that I’m really busy, so this experimentation is not perfect due to a lack of time…
Anyway, click here to see a sound spectrum made with Box2D and the Citrus Engine using BitmapData.

To create this effect, I used Box2D ApplyImpulse method on my Bars. I also used one Rope Joint by bar to link them all to an horizontal platform and try to not make them bounces everywhere. Uncomment the line box2D.visible = true; to see box2d objects with joints (don’t forget to comment stage bitmap addChilded).
There is a bug, sometimes objects pass between two bars and move them. I tried to change some object params like density, friction, restitution… but can’t find a good result.

Here is a part of the 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
package {
 
	import Box2DAS.Common.V2;
 
	import graphics.BarEqualizerArt;
	import graphics.ElementCircleArt;
	import graphics.ElementSquareArt;
 
	import objects.BarEqualizer;
	import objects.Element;
 
	import utils.Const;
 
	import com.citrusengine.core.CitrusEngine;
	import com.citrusengine.core.State;
	import com.citrusengine.objects.platformer.Platform;
	import com.citrusengine.physics.Box2D;
 
	import flash.display.Bitmap;
	import flash.display.BitmapData;
	import flash.media.SoundMixer;
	import flash.utils.ByteArray;
	import flash.utils.setTimeout;
 
	/**
	 * @author Aymeric
	 */
	public class SoundSpectrumState extends State {
 
		private var _ce:CitrusEngine;
 
		private var _vectBars:Vector.<BarEqualizer>;
 
		private var _ba:ByteArray;
 
		public function SoundSpectrumState() {
 
			super();
 
			_ce = CitrusEngine.getInstance();
 
			_vectBars = new Vector.<BarEqualizer>();
			_ba = new ByteArray();
		}
 
		override public function initialize():void {
 
			super.initialize();
 
			Const.bmpDStage = new BitmapData(stage.stageWidth, stage.stageHeight, true, 0);
			Const.bmpDStage.draw(stage);
 
			var bmpStage:Bitmap = new Bitmap(Const.bmpDStage);
			//addChild(bmpStage);
 
			var box2D:Box2D = new Box2D("Box2D");
			box2D.visible = true;
			add(box2D);
 
			var borderLeft:Platform = new Platform("borderLeft", {y:stage.stageHeight >> 1, height:stage.stageHeight});
			add(borderLeft);
 
			var borderRight:Platform = new Platform("borderRight", {x:stage.stageWidth, y:stage.stageHeight >> 1, height:stage.stageHeight});
			add(borderRight);
 
			var borderTop:Platform = new Platform("borderTop", {x:stage.stageWidth >> 1, width:stage.stageWidth});
			add(borderTop);
 
			var borderBottom:Platform = new Platform("borderBottom", {x:stage.stageWidth >> 1, y:350, width:stage.stageWidth});
			add(borderBottom);
 
			var barEqualizer:BarEqualizer;
			for (var i:uint = 0; i < Const.NBR_BAR; ++i) {
				barEqualizer = new BarEqualizer("barEqualizer" + i, {x:22.5 + Const.BarEqualizerWidth * i, y:stage.stageHeight, width:Const.BarEqualizerWidth, height:Const.BarEqualizerHeight, view:BarEqualizerArt});
				add(barEqualizer);
				_vectBars.push(barEqualizer);
				barEqualizer.initJoint(borderBottom, 22.5 + Const.BarEqualizerWidth * i);
			}
 
			_ce.sound.playSound("music");
 
			setTimeout(_addElements, 2500);
		}
 
		private function _addElements():void {
 
			var element:Element;
			for (var j:uint = 0; j < 12; ++j) {
				element = new Element("Element" + j, {x: 40 + 40 * j, y:50, radius:Math.random() > 0.5 ? Const.ELEMENT_RADIUS : 0});
				element.view = element.radius == Const.ELEMENT_RADIUS ? ElementCircleArt : ElementSquareArt;
				add(element);
			}
		}
 
		override public function update(timeDelta:Number):void {
 
			super.update(timeDelta);
 
			SoundMixer.computeSpectrum(_ba, true);
 
			for (var i:uint = 0; i < Const.NBR_BAR; ++i) {
 
				_vectBars[i].body.ApplyImpulse(new V2(0, -_ba.readFloat() * Const.STRENGTH), _vectBars[i].body.GetWorldCenter());
			}
		}
 
		override public function destroy():void {
 
			super.destroy();
		}
	}
}

The Box2D Bar 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
package objects{
 
	import Box2DAS.Common.V2;
	import Box2DAS.Dynamics.Joints.b2RopeJoint;
	import Box2DAS.Dynamics.Joints.b2RopeJointDef;
 
	import com.citrusengine.objects.PhysicsObject;
	import com.citrusengine.objects.platformer.Platform;
 
	/**
	 * @author Aymeric
	 */
	public class BarEqualizer extends PhysicsObject {
 
		public function BarEqualizer(name:String, params:Object = null) {
 
			super(name, params);
		}
 
		override public function destroy():void {
 
			super.destroy();
		}
 
		override public function update(timeDelta:Number):void {
 
			super.update(timeDelta);
		}
 
		override protected function createBody():void {
 
			super.createBody();
 
			_body.SetFixedRotation(true);
		}
 
		public function initJoint(platform:Platform, posX:uint):void {
 
			var jointDefPlatform:b2RopeJointDef = new b2RopeJointDef();
			jointDefPlatform.Initialize(body, platform.body, body.GetPosition(), new V2(posX / _box2D.scale, platform.body.GetPosition().y));
 
			var joint:b2RopeJoint = b2RopeJoint(_box2D.world.CreateJoint(jointDefPlatform));
			joint.m_maxLength = 3;
		}
 
	}
}

And Bar graphics art :

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
package graphics {
 
	import utils.Const;
 
	import flash.display.BitmapData;
	import flash.display.DisplayObject;
	import flash.events.Event;
	import flash.geom.Point;
	import flash.geom.Rectangle;
 
	/**
	 * @author Aymeric
	 */
	public class BarEqualizerArt extends AArt {
 
		public function BarEqualizerArt() {
 
			super();
 
			_art.graphics.clear();
			_art.graphics.beginFill(Math.random() * 0xFFFFFF);
			_art.graphics.drawRect(0, 0, Const.BarEqualizerWidth, Const.BarEqualizerHeight);
			_art.graphics.endFill();
 
			_bmpD = new BitmapData(Const.BarEqualizerWidth, Const.BarEqualizerHeight);
			_bmpD.draw(_art);
		}
 
		override protected function _ef(evt:Event):void {
 
			var point:Point = new Point((this as DisplayObject).localToGlobal(new Point()).x - Const.BarEqualizerWidth * 0.5, (this as DisplayObject).localToGlobal(new Point()).y - Const.BarEqualizerHeight * 0.5);
			Const.bmpDStage.applyFilter(_bmpD, _bmpD.rect, point, _setBitmapFilter());
			Const.bmpDStage.fillRect(new Rectangle(point.x, 0, Const.bmpDStage.width, Const.bmpDStage.height), 0);
			Const.bmpDStage.copyPixels(_bmpD, _bmpD.rect, point, null, null, true);
		}
	}
}

Objects drop (after music start) don’t use the bitmap data to render graphics but a simple Sprite. This is not optimized!
To make it properly use only one bitmap data (no sprite) and one enter frame in the stat class. Then call a render method on each object (bars and other objects). Finally think to use : bitmapData.lock() and bitmapData.unlock() (and make your bitmap data update between them) for a better control.

The zip file.

If I have some time to focus on it, I will make some improvements… but it’s not my priority. You should have enough information to make a cool Sound Spectrum with box2d ;-)

Oh and the music is an Abba cover song, made by Therion!

From AS3 to Objective-C

2

Some days after my article about what is happening to Flash developers, I have started to learn Objective-C and I already like it!
In this post, I will try to compare ActionScript 3 to Objecitive-C. I’m really new to Objective-C so if you find any errors in what I say, please add a comment to correct me.

According to Wikipedia : Objective-C is a reflective, object-oriented programming language that adds Smalltalk-style messaging to the C programming language. Today, it is used primarily on Apple’s Mac OS X and iOS. Objective-C is the primary language used for Apple’s Cocoa API.

Objective-C syntax looks like this :

- (id) initWithFrame:(CGRect)frame withText:(NSString *) text {
 
    self = [super initWithFrame:frame];
    if (self) {
        myText = [[NSString alloc] initWithString:text];
    }
    return self;
}

Dammit! What is that!? This funny syntaxe comes from Smalltalk programming language, and I find it has been simplified. Hmm, don’t run away! Objective-C is a beautiful programming language, and finally it’s syntax is easily readable.
However Objective-C has strong concepts, I recommend to learn a bit on C or Java (heir of Smalltalk too) before starting Objective-C. Personally, I’m not very familiar with these programming languages, but I learned them and it was easier to learn Objective-C thanks to their knowledge.

Ok, so now let’s start!

Memory : First of all, in Objective-C (with the last Xcode version) there is a Garbage Collector that you can activate or not! I recommend to do not activate it at first, you should know how manage memory. There are lots of keywords which reference to memory manager : alloc, dealloc, release, retain, and there are also pointers. Memory handling in Objective-C is really unique : it uses a counter to determine the number of instances of an object and when released. The object’s counter is called the retain count. Each object have one. For example if an object is used 3 times, its retain count is 3.
alloc add 1 to the retain count and release remove 1 to the retain count. If the retain count is equal to 0, dealloc method is automatically called and it killed the object. Don’t call the dealloc method yourself!

Keyword differences in AS3 some primitives keyword are :

true; false; null; this;

And in Objective-C :

YES; NO; nil; self;

Variables : Objective-C is based on C, variable declarations are identical.

//AS3 :
var myBoolean:Boolean = true;
var myInteger:int = 4;
//Objective-C :
BOOL myBoolean = YES;
int myInteger = 4;

In Objective-C there are no keywords like var and const, the type is specified before the variable.

Object declarations :

//AS3
var myObject:Object = new Object();
//Objective-C
NSObject *myObject = [[NSObject alloc] init];

In Objective-C most of the objects use NS prefix, it comes from NeXtStep. Objective-C was developed by NeXt (company that Steve Jobs founded after Apple).
You may have noticed the “*” character, yes there are pointers in Objective-C. Pointers are a special data type that points to a location in memory. When a variable is typed to a pointer, accessing that variable will redirect you to the value stored at the pointer’s memory address. More informations on Pointers. That’s mean myObject is a pointer that points to a NSObject instance somewhere in memory.
The keyword “alloc” means that you allocate memory for the object. Then the “init” is the constructor, that’s equivalent to “()”;

Methods and constructors :

//AS3
namespace function outputText():void {
    trace("Hello World");
}
//Objective-C
- (void) outputText {
   NSLog(@"Hello World");
}

Hey, it wasn’t to hard! “namespace” keyword in AS3 syntax can be replaced by public, private or protected. In Objective-C all object’s methods are public! All object’s methods start with a “-”, if we want to use a Class’ function (so a static function) we use a “+”.
If you want to use parameters :

public function paramText(newText:String):void {
   var myText:String = newText;
}
- (void) paramText:(NSSTring *)newText {
   NSString *myText = [[NSString alloc] initWithString:newText];
}

In Objective-C there isn’t default value in function’s parameters : no null, no “”… so if your function signature is :

public function paramText(newText:String = ""):void

You have to write two functions in Objective-C and call the good one :

- (void) paramText
- (void) paramText:(NSString *)newText

There is no name conflicted, because it isn’t the same signature! One is “paramText”, and the other one”paramText:”. It isn’t obvious at first… We calls this function :

paramText();
paramText(newText);
[self paramText];
[self paramText:newText];

A function with two params :

paramText(myText, 4);
public function paramText(myText:String, myNumber:uint);
[self paramText:newText withNumber:4];
- (void) paramText:(NSSTring *)newText withNumber:(uint)myNumber;

If signature name is decently named, it is really friendly to read the function!

Constructors : like for functions, you can have several constructors and call the good one.

NSMutableArray *tab = [[NSMutableArray alloc] init];
// OR :
NSMutableArray *tab = [[NSMutableArray alloc] initWithCapacity:4];

Note that in Objective-C, NSArray like NSString… are static. That means after their initialization, their value can’t change! That’s why some of them have a mutable class : NSMutableArray, NSMutableString… For example : NSMutableString extends NSString, and add a new method : “appendString:”.

Classes : Objective-C extends C (all C code is compatible with Objective-C), so it has the same structure, two files : header (.h) and implementation (.m).

A header is called an interface in Objective-C, it defines inheritance, properties and method signature. It looks like that :

#import 
 
@interface MyObject : NSObject {
 
    NSString *text;
}
 
@property (nonatomic, retain) NSString *text;
 
- (void) paramText;
 
@end

MyObject extends NSObject. I’ve defined one propertie : text. By default, it is protected. You can change this by adding (@public, @protected, @private keywords) :

@interface MyObject : NSObject {
    @public
      NSString *text;
 
    @private
      NSString *text2;
}
@end

Then the @property keyword generates getter/setter, you must precize them like I did if it is an object. But you change its options. In my example : when programm compiles it add automaticaly :

- (NSString *) text {
    return text;
}
 
- (void) setUserName:(NSString *)text_ {
    [text_ retain];
    [text release];
    text = text_;
}

If you don’t precize nonatomic, it’s atomic. Quickly : atomic is use for multi-threading it gives more security on variable accessor, whereas nonatomic doesn’t; nonatomic is faster. Better explanation here and there.
Note, even if you don’t define method in your header file you can create method in your implementation file. They will be hidden from outside (no autocompletion) like if they were private, but they are not! It can be useful if have to many functions and just would like to emphasize some of them.

Moreover adding @property allow object properties to be use like in AS3 with a “.” :

myView = new MyView(rect, myObject.myText);
 myView = [[MyView alloc] initWithFrame:rect withText:myObject.myText];

A simpler exemple of using @property :

@property BOOL myBoolean;

Now the implementation :

#import "MyObject.h"
 
@implementation MyObject
 
@synthesize text;
 
- (id) init {
 
    self = [super init];
 
    if (self) {
 
        text = [[NSString alloc] initWithString:@"my text"];
    }
 
    return self;
}
 
- (void) paramText {
    text = @"my new text";
}
 
- (void) dealloc {
 
    [text release];
 
    [super dealloc];
}
 
@end

The @synthesize refers to the @property. You must use to enable getter/setter. It is really useful :

@synthesize property1, property2, property3;

Like in AS3 we call the parent method thanks to super keyword.
In the init function, we return an id type. Objective-C is a dynamical language, you can use an object even if you don’t know its type thanks to the keyword id which is an object identifier. id is a pointer to an object.
Keyword dealloc is the descrutor, you must release your variables before calling [super dealloc];

In Objective-C like AS3 there are no multiple inheritance, no operator overloading and no templates (there are in CPP). However we can use several interfaces like in AS3 :

public class Bird extends Object implements IAnimals, IWings
@interface Bird : NSObject <IAnimals, IWings>

MVC : Objective-C is based on the MVC pattern.
When a new project start in Xcode, 2 files are created : AppDelegate.h and AppDelegate.m it is like your Application’s Main. Then there are 3 useful classes : NSObject that you already know for your Model, UIView comes from UIKit (#import ) for your View, and finally UIViewController from UIKit too for your Controller. When a new file is created from one of this class, there are already useful methods like : applicationWillResignActive, viewDidLoad, drawRect…. This will be explain in an other tutorial.

To conclude, Objective-C is an awesome programming language, very powerful (not just the language to use if you want to develop on Mac & iOS). This first approach couldn’t explain all about it, there are still lots of things to learn… in a next tutorial ;-)

Go to Top