Archive for May, 2010


An update to this post has been published.

The problem

A few weeks ago I showed you the (very) beginnings of a tile based flash game using a physics engine called Box2d. The advantage of a tile based game is how easy it can be, once the game engine is complete, to edit and create levels. I would even like people who are not familiar with AS3 and Flash to be able to make levels. With this in mind, it is obviously no good having the level data all tied up in the Flash files.

Level overlaid with XML data

Level overlaid with XML data

The solution?

Store the level data in an external XML file!

For those of you not too interested in the AS3 code and just want to play around with making your own levels, download this zip file and open the XML file in any text editor program to make your own level layout.

A look at the code

The first important thing to note is that we now have another AS3 file called Levels.as. We are going to create an instance of this object which we can then use to access any data it collects from the XML file. All we need to know for now is how to use this Level object. Lets take a look at the start of the Levels.as file.

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 flash.events.*;
	import flash.net.URLRequest;
	import flash.net.URLLoader;
 
	public class Levels extends EventDispatcher 
	// Extends Event Dispatcher so it can fire an event after loading
	{
 
		/**** vars ****/
		private var url:String;
		public var levels:Array;
 
		public function Levels(u:String) {
			url = u;
		}
 
		public function loadLevels():void
		{
			var urlReq:URLRequest = new URLRequest(url);
			var loader:URLLoader = new URLLoader();
			loader.addEventListener(Event.COMPLETE, getLevels);
			loader.addEventListener(IOErrorEvent.IO_ERROR, IOError);
			loader.addEventListener(SecurityErrorEvent.SECURITY_ERROR, SError);
			loader.load(urlReq);
		}

This class extends EventDispatcher, this allows the object to fire an Event when it is finished loading the XML file into an array. We can also see that we need to send through a URL variable to direct the object to the right XML file. Next is the getLevels() function

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
private function getLevels(e:Event):void
{
	if ( e.target.data )
	{
		levels = new Array();
		var levelsXML:XML = new XML(e.target.data);
		var levelsList:XMLList = levelsXML.children();
 
		for each (var levelInfo:XML in levelsList) 
		{
			if (levelInfo.name() == "level") 
			{
				var levelContent:XMLList = levelInfo.children();
				var obj:Object = new Object();
				for each (var levelPart:XML in levelContent)
				{
					if(levelPart.name() == "title")
					{
                                               //This is for later when we want to name levels
						obj.title = levelPart.text();
					}
					if(levelPart.name() == "rows")
					{
                                               //This is for retrieving the number of rows
						obj.rows = levelPart.text();
					}
					if(levelPart.name() == "data")
					{
						//Regular expression to decode the level data
						var myPattern:RegExp = /[A-Z]|[a-z]|[0-9]/g;  
						var str:String = levelPart.text();
						var sArray:Array = str.match(myPattern);
						obj.data = new Array();
						obj.data = sArray;
					}
				}
				levels.push(obj);
			}
		}
		//Event to tell the game that the levels have loaded
		this.dispatchEvent(new Event("xmlloaded"));
	}
}

This function retrieves the XML and parses it into an array of objects. Each object in the array has a title, a number of rows and then an array containing the level data. Once the function finishes parsing the XML, it dispatches an event so that the main class knows it can continue.

Meanwhile, back in the main class

First we need to create an instance of the Levels object.

46
47
// The level object which will hold all our level data
private var L:Levels = new Levels("levels.xml");

Next we need to listen for the “xmlloaded” event that gets thrown by the Levels object so we can trigger the createLvl() function. This gets added to the Levels object we just created. The listener is placed in with the other event listeners for the game. With the listener ready, we can call the function that loads and parses the XML.

49
50
51
52
53
54
public function TileGame()
{
	stage.addEventListener(KeyboardEvent.KEY_DOWN, key_pressed);
	stage.addEventListener(KeyboardEvent.KEY_UP, key_released);
	L.addEventListener("xmlloaded", createLvl);
        L.loadLevels();

Now that we have a Levels object and it contains all of the levels data, we can make use of that data to build the levels. When we get the level data from the object, note that there is a variable called lvlCurrent. This will be used later on when we want to be able to progress from level to level.

71
72
73
74
75
76
77
private function createLvl(e:Event):void
{
	//Retrieve the level data and put it into a more manageable array.
	//Notice the "lvlCurrent" variable which will be used later for changing levels.
	var lvlArray:Array = L.levels[lvlCurrent-1].data; 
	var lvlRows:int = L.levels[lvlCurrent-1].rows;
	var lvlColumns:int = Math.ceil((lvlArray.length)/lvlRows);

With the level data successfully retrieved and stored in an easy to use array, we can now build the level based on that data. Using a switch statement, each value in the array will trigger a different object to be built with the physics engine.

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
for(var i:int = 0;i<lvlArray.length;i++)
{
        /*If i is a multiple of lvlColumns, then the 
        row number needs to be increased.*/
	if((i/lvlColumns) is int)
	{
		row++;
	}
	column = lvlColumns + (i - row*lvlColumns)+1;
 
	switch (lvlArray[i]){
		case "1":						
			//Create Solid Block
			var wallFd:b2FixtureDef = new b2FixtureDef();
			var wallSd:b2PolygonShape= new b2PolygonShape();						
			var wallBd:b2BodyDef = new b2BodyDef();
			wallBd.position.Set(column/ m_physScale*30, row/ m_physScale*30);
			wallBd.type = b2Body.b2_staticBody;
			wallFd.friction = .9;
			wallFd.restitution = .05;
			wallFd.shape = wallSd;
			wallSd.SetAsBox(15 / m_physScale, 15 / m_physScale);						
			levelB = m_world.CreateBody(wallBd);
			levelB.CreateFixture(wallFd);						
		break;
		case "2":
			//Create Ramp A
			var rampAFd:b2FixtureDef = new b2FixtureDef();
			var rampASd:b2PolygonShape = new b2PolygonShape();
			var rampABd:b2BodyDef = new b2BodyDef();
			rampAFd.friction = 1.0;
			rampAFd.restitution = .05;
			rampAFd.shape = rampASd;
			var vxsA:Array = [new b2Vec2(15 / m_physScale, 15 / m_physScale), 
							 new b2Vec2(-(15 / m_physScale), 15 / m_physScale), 
							 new b2Vec2(15 / m_physScale, -(15 / m_physScale))];
			rampASd.SetAsArray(vxsA, vxsA.length);
			rampABd.type = b2Body.b2_staticBody;
			rampABd.userData = "ramp";
			rampABd.position.Set(column/ m_physScale*30, row/ m_physScale*30);
			levelB = m_world.CreateBody(rampABd);
			levelB.CreateFixture(rampAFd);				
		break;
		case "3":
			//Create Ramp B
			var rampBFd:b2FixtureDef = new b2FixtureDef();
			var rampBSd:b2PolygonShape = new b2PolygonShape();
			var rampBBd:b2BodyDef = new b2BodyDef();
			rampBFd.friction = 1.0;
			rampBFd.restitution = .05;
			rampBFd.shape = rampBSd;
			var vxsB:Array = [new b2Vec2(15 / m_physScale, 15 / m_physScale), 
							 new b2Vec2(-(15 / m_physScale), 15 / m_physScale), 
							 new b2Vec2(-(15 / m_physScale), -(15 / m_physScale))];
			rampBSd.SetAsArray(vxsB, vxsB.length);
			rampBBd.type = b2Body.b2_staticBody;
			rampBBd.userData = "ramp";
			rampBBd.position.Set(column/ m_physScale*30, row/ m_physScale*30);
			levelB = m_world.CreateBody(rampBBd);
			levelB.CreateFixture(rampBFd);				
		break;
		case "4":
			//Create Loose block
			var bodyDef:b2BodyDef = new b2BodyDef();
			bodyDef.type = b2Body.b2_dynamicBody;
			var boxShape:b2PolygonShape = new b2PolygonShape();
			fixtureDef.shape = boxShape
			fixtureDef.density = .2;
			fixtureDef.friction = 0.9;
			fixtureDef.restitution = 0.1;
			boxShape.SetAsBox(15 / m_physScale, 15 / m_physScale);
			bodyDef.position.Set(column/ m_physScale*30, row/ m_physScale*30);
			bodyDef.angle = 0;
			levelB = m_world.CreateBody(bodyDef);
			levelB.CreateFixture(fixtureDef);
		break;
		case "5":
			//Create Player
			var bodyDefC:b2BodyDef = new b2BodyDef();
			bodyDefC.type = b2Body.b2_dynamicBody;
			var playerShape:b2PolygonShape = new b2PolygonShape();
			playerShape.SetAsBox(10 / m_physScale, 10 / m_physScale);
			fixtureDef.shape = playerShape;
			fixtureDef.density = 5;
			fixtureDef.friction = .3;
			fixtureDef.restitution = 0.3;
			bodyDefC.position.Set(column/ m_physScale*30, row/ m_physScale*30);
			bodyDefC.angle = 0;
			player = m_world.CreateBody(bodyDefC);
			player.CreateFixture(fixtureDef);
		break;
	}
}
//Once the level is built, begin running the physics engine.
addEventListener(Event.ENTER_FRAME, run);

As you can see from the code, 1 is a solid block, 2 and 3 are ramp blocks created with the box2d polygon function, 4 is a dynamic block and 5 is our player block. All objects in box2d have a friction value and a restitution value, which is fancy talk for bounciness. Only dynamic or movable objects have density, which is weight. Once the loop is finished and all the physics objects are built, we can add an ENTER_FRAME event listener which runs a step of the physics world for every frame that passes.

If you would like to mess around with the engine, download the full source here.

The result

Get Adobe Flash player

I just found something cool and wanted to quickly share it. I have noticed for a while that many blogs have perfect colours on their AS3, PHP, XML etc in their posts. I assumed they were images, but found them to be fully selectable text. After a quick search I found a WordPress Widget called Geshi. It is installed like any other widget and will use a combination of CSS and script to change any type of code into tagged html with code colours, line numbers and special characters built in.

Basically, with the addition of a lang="actionscript3" attribute to your pre tag, you code goes from this;

private var lvlArray1:Array = new Array(
    1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,
    1,4,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,
    1,0,0,0,0,0,0,0,0,0,3,0,0,0,0,0,0,1,
    1,0,0,0,0,0,0,0,0,0,3,0,0,0,0,0,0,1,
    1,0,0,0,0,0,0,2,0,0,3,0,0,0,0,0,2,1,
    1,0,0,0,0,0,2,0,0,0,3,0,0,0,0,2,1,1,
    1,0,0,0,0,2,0,0,0,0,3,0,0,0,2,1,1,1,
    1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1
);

To this;

private var lvlArray1:Array = new Array(
    1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,
    1,4,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,
    1,0,0,0,0,0,0,0,0,0,3,0,0,0,0,0,0,1,
    1,0,0,0,0,0,0,0,0,0,3,0,0,0,0,0,0,1,
    1,0,0,0,0,0,0,2,0,0,3,0,0,0,0,0,2,1,
    1,0,0,0,0,0,2,0,0,0,3,0,0,0,0,2,1,1,
    1,0,0,0,0,2,0,0,0,0,3,0,0,0,2,1,1,1,
    1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1
);

You can also use the line=1 attribute to put the line numbers on the side like this.

1
2
3
4
5
6
7
8
9
10
private var lvlArray1:Array = new Array(
    1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,
    1,4,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,
    1,0,0,0,0,0,0,0,0,0,3,0,0,0,0,0,0,1,
    1,0,0,0,0,0,0,0,0,0,3,0,0,0,0,0,0,1,
    1,0,0,0,0,0,0,2,0,0,3,0,0,0,0,0,2,1,
    1,0,0,0,0,0,2,0,0,0,3,0,0,0,0,2,1,1,
    1,0,0,0,0,2,0,0,0,0,3,0,0,0,2,1,1,1,
    1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1
);

Or start the line attribute at any other number to break up a long piece of code into parts.

1
2
3
4
5
private var lvlArray1:Array = new Array(
    1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,
    1,4,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,
    1,0,0,0,0,0,0,0,0,0,3,0,0,0,0,0,0,1,
    1,0,0,0,0,0,0,0,0,0,3,0,0,0,0,0,0,1,
6
7
8
9
10
    1,0,0,0,0,0,0,2,0,0,3,0,0,0,0,0,2,1,
    1,0,0,0,0,0,2,0,0,0,3,0,0,0,0,2,1,1,
    1,0,0,0,0,2,0,0,0,0,3,0,0,0,2,1,1,1,
    1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1			
);

Tweet Dreams

Screengrab of the flash Twitter dream feed.

Click image to view flash.

The idea

An empty night sky becomes filled with shooting stars generated by people dreams. Flash pulls the dreams from an RSS feed generated by the Twitter search term #dreamt. All of the Tweet Dreams are parsed by Flash and one by one shot out across the sky where they reveal their message before settling back into the sky. The file can be setup to start at any size, although cannot be re-sized whilst running.

What’s next?

Currently no thought has been put into design, so that is obviously the next step. I would like to get some particle effects for the stars as they whiz by and create a proper scene (maybe a little silhouetted town) to replace the grass down below. It also needs an introduction and a loader. I was thinking of starting with some famous quotes about dreams, “To sleep, perchance to dream” – Shakespeare.

Tile-Based Box2d game

A snap shot from the tile based physics game.

Screen grab of the game.

G’day all, and welcome to my first post. For the past month or so I have been playing around with the Flash physics engine called Box2d. Originally a C+ code library, it has been ported to AS3. Having always been a fan of old school tile based games, I decided to attempt to make one using the Box2d objects for my tiles. Firstly though I would like to give a little credit to Emanuele Feronato for his many AS3 blog posts and to this Mr Sun Studios post on storing level data.

The level data

Below you will see a ARRAY containing a series of characters. Each character is going to represent a different type of tile in our little game world. A ’1′ represents a solid block, a ’2′ is a ramp, a ’3′ is a ramp and a ’4′ is our player. Although it is just a 1 dimensional array, it has been laid out so that it appears 2 dimensional. This way, it can easily be edited by sight.

1
2
3
4
5
6
7
8
9
10
private var lvlArray:Array = new Array(
    1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,
    1,4,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,
    1,0,0,0,0,0,0,0,0,0,3,0,0,0,0,0,0,1,
    1,0,0,0,0,0,0,0,0,0,3,0,0,0,0,0,0,1,
    1,0,0,0,0,0,0,2,0,0,3,0,0,0,0,0,2,1,
    1,0,0,0,0,0,2,0,0,0,3,0,0,0,0,2,1,1,
    1,0,0,0,0,2,0,0,0,0,3,0,0,0,2,1,1,1,
    1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1
);

Unfortunately, we do not yet have enough information to extract our level data. We need to know how many rows and columns the level is supposed to have. I know there will be a better way to do this with maybe a 2D array (maybe something to look at for the next post) but for now we are going to hard code the number of rows and from that work out the number of columns.

1
2
var lvlRows:int = 8;
var lvlColumns:int = Math.ceil((lvlArray.length)/lvlRows);

Building the level

With our level data contained in a single array, and our rows and columns worked out, we can now loop through the array and use a SWITCH statement to decide what to do given each individual value.

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
var row:int = 0;
var column:int = 0;
for(var i:int = 0; i<lvlArray.length; i++)
{
    if((i/lvlColumns) is int)
    {
        row++;
    }
    column = lvlColumns + (i - row*lvlColumns)+1;
    switch (lvlArray[i])
    {
        case 1:
            //Create a fixed block
        break;
        case 2:
            //Create a ramp sloping from
        break;
        case 3:
            //Create a dynamic block or loose block
        break;
        case 4:
            //Create the player
        break;
    }
}

In the code above there is only a comment in each case. This is just to show where each object is created. In future posts I will describe how each of the objects is created. For now, however, I have included the full working source file for exploration. I will also cover the player movement and the camera follow code in a later post.

The result

Use arrow keys to move the player.

Download the full source files.

Powered by WordPress. Theme: Motion by 85ideas.