One Game, Many Frameworks: ND2D

This is the second Molehill 2D framework I will cover. It is a bit more extensive than Starling and it offers a few more effects during rendering like blur and masks. It does not have however the intention of emulating the Flash architecture, the way Starling has. In many ways it will remind you of Cocos2D and its own hierarchy rather than Flash and its display list architecture.

I'll cover the code to accomplish the most common actions.

But first, be aware you need to set up your IDE to publish to Flash Player 11. Go back to the Starling tutorial if you don't know how to do that.

You will also need ND2D.

The git hub link is: https://github.com/nulldesign/nd2d.git

The documentation is here: http://www.nulldesign.de/nd2d/docs/

The framework blog: http://www.nulldesign.de/category/experiments/nd2d/

The Application Structure

An ND2D application starts with a World2D object. This extends the Flash Sprite, and it should be used as your document class, your entry point.

The World2D object keeps track of the current Scene2D object being displayed. So you structure your application as a series of Scene2D objects that are activated by your World2D object.

The main loop resides in the World2D object and it is already set to call a STEP method inside your active Scene2D object.

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
 
 
 
//Inside your entry point constructor, which extends World2D
 
public function MyGame() {
 
	super(Context3DRenderMode.AUTO, 60);
 
}
 
 
 
//this will register an Event, you may override it
 
override protected function addedToStage (event:Event):void {
 
	super.addedToStage(event);
 
 
 
	//set the scene you want to display as active
 
	setActiveScene(someScene2DObject);
 
 
 
	//start application
 
	start();	
 
}

So in the Frogger example, I started the application with a World2D object, and I have two Scene2D objects: The MenuScreen and the GameScreen. I still call them Screens to match the code from the previous frameworks.

Sprites and Textures

In the Frogger game I load all textures from one png file. So I'll show you first how to set up a Texture Atlas object and how to create a Sprite2DBatch object that behaves very similarly to a CCSpriteBatchNode in Cocos2D.

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
 
[Embed(source="../assets/frogger.png")]
 
protected var TextureBitmap:Class;
 
 
 
[Embed(source="../assets/frogger.plist", mimeType="application/octet-stream")]
 
protected var TextureXML:Class;
 
 
 
public var spriteBatch:Sprite2DBatch;
 
public var textureAtlas:TextureAtlas;
 
 
 
...
 
 
 
//set up sprite sheet				
 
var texture:Texture2D = Texture2D.textureFromBitmapData(new TextureBitmap().bitmapData);
 
 
 
//create a TextureAtlas
 
textureAtlas = new TextureAtlas(texture.bitmapWidth, texture.bitmapHeight, new XML(new TextureXML()), 5, false);
 
 
 
//create Sprite2DBatch container
 
spriteBatch = new Sprite2DBatch(texture);
 
spriteBatch.setSpriteSheet(textureAtlas);
 
//add it to your Scene2D container
 
addChild(spriteBatch);

Since the structure is similar to Cocos2D I chose to have the game built in the same way when I used that framework, so each Screen object in the game (which now extends the ND2D Scene2D container) has its own Sprite2DBatch container.

So notice that a Sprite2DBatch is created linking it to a source png texture. Only images from this source png file can be added to this Sprite2DBatch container. I finish the set up code adding the Sprite2DBatch to the Scene2D container. From now own, everything I create in the Scene will be added as a child to the Sprite2DBatch.

Of course a lot of what I did here has to do with the way I structured the game. You may have more than one Sprite2DBatch per Scene2D. You may have none, and create each sprite with its own individual png file for its texture. It is all up to you.

Let me show you the ways you can create a Sprite2D and texture it and show you more of the rules governing Sprite2DBatch objects:

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
 
//add a sprite to a Sprite2DBatch object
 
var logo:Sprite2D = new Sprite2D ();
 
//spriteBatch references the Sprite2DBatch object
 
spriteBatch.addChild(logo);
 
//logo.png must be inside the source png file and XML atlas you used to create the Batch
 
logo.setFrameByName("logo.png");
 
logo.x = screenWidth * 0.5;
 
logo.y = screenHeight * 0.3;
 
 
 
//add a sprite that does not belong to a sprite batch
 
[Embed(source="../assets/player.png")]
 
protected var PlayerBitmap:Class;
 
...
 
 
 
var sprite2:Sprite2D = new Sprite2D(Texture2D.textureFromBitmapData(new PlayerBitmap().bitmapData));
 
addChild(sprite2);
 
sprite2.x = 400;
 
sprite2.y = 300;
 
 
 
//create a mask
 
[Embed(source="../assets/spotlight.png")]
 
protected var SpotlightBitmap:Class;
 
 
 
...
 
 
 
var mask:Sprite2D = new Sprite2D(Texture2D.textureFromBitmapData(new SpotlightBitmap().bitmapData));
 
mask.x = 400;
 
mask.y = 300;
 
//use it to mask the player sprite
 
sprite2.setMask(mask);

If you did any of my Cocos2D tutorials you will remember the rules applied to CCSpriteBatchNode, these are very similar to the rules governing the Sprite2DBatch. Basically you can only add to it something that comes from the texture png attached to the batch node. But with ND2D these rules extend to other things, some not so obvious at first.

As you would expect, you can't draw on a SpriteBatch node. So you can't use the ND2D Quad2D object (which is similar to the one in Starling.)

And you can't create a mask inside a SpriteBatch node. Even if the mask png and the png for the masked sprite are included in the source texture for that SpriteBatch. Masks must be created on a different layer.

Sprite2DBatch objects are meant for optimization. This is their main function and they are great for that purpose.

Animations

To create animations you need a TextureAtlas object. And for the TextureAtlas object you need a source png file and an XML describing its contents. ND2D is set to load XML formatted for Cocos2D (Use TexturePacker with settings for cocos2d-0.99.4).

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
 
[Embed(source="../assets/frogger.png")]
 
protected var textureAtlasBitmap:Class;
 
 
 
[Embed(source="../assets/frogger.plist", mimeType="application/octet-stream")]
 
protected var textureAtlasXML:Class;
 
 
 
...
 
 
 
var texture:Texture2D = Texture2D.textureFromBitmapData(new textureAtlasBitmap().bitmapData);
 
var atlas:TextureAtlas = new TextureAtlas(texture.bitmapWidth, texture.bitmapHeight, new XML(new textureAtlasXML()), 5, false);
 
 
 
var spriteBatch:Sprite2DBatch = new Sprite2DBatch(texture);
 
spriteBatch.setSpriteSheet(atlas);
 
 
 
atlas.addAnimation("death", ["death_1.png", "death_2.png", "death_3.png", "death_4.png" ], false);
 
addChild(spriteBatch);

The first parameter is the name you want to give your animation. You will reference it by that name later. The remaining parameters are the frames, again referencing elements inside the TextureAtlas, and the last parameter determines whether or not the animation loops.

In this example I intend to display the animation frames inside a SpriteBatch, but you don't have to do it this way. For animations you need a TextureAtlas object, and not necessarily a Sprite2DBatch.

It is very important to understand the order things must happen: You must create the Animation before creating the Sprite that will display it.

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
 
//Playing the animation
 
 
 
mySprite = new Sprite2D();
 
spriteBatch.addChild(mySprite);
 
....
 
 
 
mySprite.spriteSheet.playAnimation("death", 0, false, true);
 
mySprite.spriteSheet.addEventListener(SpriteSheetAnimationEvent.ANIMATION_FINISHED, onDeathComplete, false, 0, true);
 
 
 
...
 
 
 
private function onDeathComplete (event:Event):void {
 
	mySprite.spriteSheet.stopCurrentAnimation();
 
	mySprite.spriteSheet.removeEventListener(SpriteSheetAnimationEvent.ANIMATION_FINISHED, onDeathComplete);
 
}

I reference the animation inside the spriteSheet property cloned with every new Sprite2D. I tell it which animation I want to play, at which frame it should start playing, if it loops and if I want an event fired once the animation is complete.

I then register to that event and trap it, stopping the animation.

And this is it for ND2D. At least as a brief introduction. I will post the main classes for Frogger so you can use them as a quick reference.