2-Weeks In.. Mobile Game Progress Report

I’m now two weeks into my original 3-week-deadline mobile game project. You can checkout the progress thus far by playing the game above (‘up’ to jump, ‘left’ and ‘right’ to control the player).

As you can see there has been some progress since my last update a week ago. I was hoping to have gotten a little further by this point but my personal life has been somewhat hectic the past week reducing the amount of game-development time to a few measly hours. In the few hours we have had, we have managed to get a general story / theme for the game and the rudiments of for the first ‘stage’.

So the story is that our furry marsupial protagonist has ambitions of breaking free of his earth-bound cage to become the first hamster in space. The game is about his adventure to realise this dream.

The plan is to break the game levels in into ‘stages’ like other mobile puzzle games such as cut the rope or angry birds:

Each stage will have its own theme, giving the player a refreshed experience per stage. The first of these is going to be set within the cage itself so the tileset and background should hopefully represent that.

The bulk of my work this week however has been spent trying to get the game controls working just right. It needs to be fun to play, not too frustrating but not too easy. This is easier said than done. Im not sure its quite there yet, but play the game above and let me know what you think, im keen to hear your opinion.

During the last week I also ran into a few nasty issues with the physics which I thought I had nailed in the previous post. The problem was that the issue only occurs at low frame rates, this was really noticeable in the last post when I tried the game out on my iPhone 3G, the player was extremely jerky and unplayable.

To test the problem quickly (without needing to do an iPhone build every time) I used the handy “fps” attribute in the NME .nmml file. Setting the fps to 60 then having the player jump then setting the fps to 10 and having the player do the same jump there was a noticeable problem. On the 10fps build the player couldn’t jump anywhere nearly as high as in the 60fps build.

I knew that the reason for this problem lay somewhere in my update function in my Player class. My usual method of running update loops is to work out the time between two frames (delta) in milliseconds, pass that into the update loop for a game object then have the game modulate its update based on that delta. So as a very simple example:

[codesyntax lang=”actionscript3″ lines=”normal”]

class Player
{
	public var position : Vector2;
	public var velocity : Vector2;
	
	...
	
	public function update(delta:Int)
	{
		position.x += velocity.x * delta;
		position.y += velocity.y * delta;	
	}
	
	...
	
}

[/codesyntax]

This is pretty standard stuff and should be familiar to any game programmer. In theory it shouldn’t matter what frame rate the game is running at, the player movement should be consistent because the velocity is being modulated by the time between the previous frame and now.

The code for the Player class however is quite abit more complicated than the simple example given above and something in the logic was causing problems. I had my suspicions but no matter what I tweaked or changed I couldn’t track it down.

I had however read of a different technique documented by deWiTTERS that involved a different way of writing your game loop that didnt take into account of frame delta but was still able to provide a consistent gameplay at differing framerates.

I wont detail the process here as dewitter explains it very well in his post. The result is a greatly simplified update loop for my Player by removing all the delta time factors. My resulting update loop now looks like:

[codesyntax lang=”actionscript3″ lines=”normal”]

class Game
{
	public static var TICKS_PER_SECOND : Float = 50;
    public static var SKIP_TICKS : Float = 1000 / TICKS_PER_SECOND;
    public static var MAX_FRAMESKIP : Float = 10;

	...

	private function onEnterFrame(e:Event):Void 
	{
		var loops : Int = 0;
        while( Lib.getTimer() > nextFrameTime && loops < MAX_FRAMESKIP) {
            update();
            nextFrameTime += SKIP_TICKS;
            loops++;
        }
        render();
	}	
	
	private function update() 
	{
		Ctrl.instance.update();
		objects.update();
		camera.update();
		bg.update();
	}
	
	private function render() 
	{
		tiles.render();
	}
	
	...
	
}

[/codesyntax]

The result of doing this is that the game now runs consistently at 60fps or 10fps. I haven't tried it out on my 3G yet but I have high hopes for the technique.

The moral of that story, if you bang your head against a problem for a while, just take a breath, think outside the box and attack it from a different angle 😉

Well that's about it for this week. The original intention was that is was going to be my last week of working on this game. (Un)fortunately my artist partner has gone on holiday this week so he is unable to work on the game, the result being I have given us an extra week to complete the game, yey! 🙂

Oh BTW, the title for the game is hidden in the demo above, see if you can work out what it is 😉

Day 6 – Mobile Game Progress

Its been a few more days now and quite a bit of progress has been made so I thought I would update. First, have a go at the game in its current form above (use the keyboard to control).

Since last time I have concentrated on the parts I was less technically confident about such as the correct rendering method of animated sprites and how I was going to handle the rotation of the world.

First I tackled rendering. In my previous update the tiles in the world rendered simply by drawing vector squares to Sprite.graphics, then shifting their container to move them. When I tried to run this on an actual mobile device however the performance was extremely poor, less than 1FPS. The reason for this was because drawing vectors on mobile devices doesn’t really fit well into their GPU-orientated graphics pipeline. To get decent frame rates on mobile I needed to find a way to take advantage of the mobile GPU.

Fortunately for me those clever guys who wrote NME were way ahead of me and developed the “Tilesheet.drawTiles()” API:

[codesyntax lang=”actionscript3″ lines=”normal”]

extern class Tilesheet
{
	static var TILE_SCALE:Int;
	static var TILE_ROTATION:Int;
	static var TILE_RGB:Int;
	static var TILE_ALPHA:Int;
	static var TILE_TRANS_2x2:Int;

	static var TILE_BLEND_NORMAL:Int;
	static var TILE_BLEND_ADD:Int;

	function new(inImage:BitmapData):Void;
	function addTileRect(rectangle:Rectangle, centerPoint:Point = null):Void;

	/**
	 * Fast method to draw a batch of tiles using a Tilesheet
	 * 
	 * The input array accepts the x, y and tile ID for each tile you wish to draw.
	 * For example, an array of [ 0, 0, 0, 10, 10, 1 ] would draw tile 0 to (0, 0) and
	 * tile 1 to (10, 10)
	 * 
	 * You can also set flags for TILE_SCALE, TILE_ROTATION, TILE_RGB and
	 * TILE_ALPHA.
	 * 
	 * Depending on which flags are active, this is the full order of the array:
	 * 
	 * [ x, y, tile ID, scale, rotation, red, green, blue, alpha, x, y ... ]
	 * 
	 * @param	graphics		The nme.display.Graphics object to use for drawing
	 * @param	tileData		An array of all position, ID and optional values for use in drawing
	 * @param	smooth		(Optional) Whether drawn tiles should be smoothed (Default: false)
	 * @param	flags		(Optional) Flags to enable scale, rotation, RGB and/or alpha when drawing (Default: 0)
	 */
	function drawTiles (graphics:Graphics, tileData:Array<Float>, smooth:Bool = false, flags:Int = 0):Void;
}

[/codesyntax]

With it you pass an array of tile data with a number of optional properties such as Rotation, Alpha and Scale. It then does the heavy lifting behind the scenes of building a vertex buffer and sending it to the GPU for rendering with your Tilesheet texture. This results in two big wins for performance; 1) you can render a great many sprites on the GPU in the same render, 2) you dont perform any expensive texture switches if all your sprites are on the same texture.

As for the tilesheet itself. At first I was planning on using a manual method of putting the sprites onto the tilesheet such that each sprite was the same size and we arranged in a very simplistic “one row per animation” fashion:

The problem with this however was that it was going to be a fairly inefficient way of organising the assets and it was going to be tricky for Moh (the artist) to build these animations frame by frame and put them on there while keeping them looking good.

Fortunately for me this problem has already been encountered by those before me. Philippe Elsass (of Flash Develop fame) has written a library called TileLayer that solves two problems. Firstly it abstracts out the some of the hard work of using the low-level drawTiles() API into a more friendly parent-child-like syntax which should be very familiar to any flash developer. Secondly it provides a parser for “Sparrow” spritesheets.

Sparrow is a library for game development in pure Objective-C. Part of that library is a method for loading tightly packed, animated spritesheets. These spritesheets can be generated by a number of tools but the best one I found was Texture Packer (by Lee Brimelow Andreas Löw):

Using the tool you can take a number of input’s such as SWFs or other images and output a compact spritesheet and an XML document containing the data NME will use to render the sprites on the screen.

One problem I encountered with using Texture Packer with SWFs however was that for it to work you must do all your animating on the timeline, it wasn’t smart enough to populate named sprites from the library. Another thing to remember is that you must arrange your animations from the top left of the stage. If you put the animation in the middle of the stage it will be offset when it is rendered in the game. Not big problems but it did cause some head-scratching for a while before I realised what was going on.

With my sprites now loading and animating correctly in flash I decided to have a go at getting the game to run on mobile devices. Thanks to the way NME works it was a pretty simple process to generate a template project “nme update application.nmml ios” then run the generated xcode project file. And it runs! The only problems was that the frame rate was around 15FPS on my iPhone 4, which was far lower than I was hoping for.

So I started thinking about what could be causing a slow down. In flash when you try to render objects that are off-screen, flash automatically works out if it needs to be rendered and if not then it skips the render and hence improves the performance. I had a suspicion that Tilelayer / NME couldnt perform this operation so all the tiles that weren’t on the screen were being unnecessarily drawn to an off-screen buffer and hence lowering the performance.

Because in this game the player is always in the centre of the screen I knew that a crude way to calculate whether a tile was on screen was to simply check the distance of the tile from the player, if it was greater than the width + height of the screen (because the world rotates) then I knew it would be off-screen and thus didn’t need to be rendered:

As suspected this had a marked improvement in FPS, the game was now running on my iPhone at about 30FPS. An improvement but still short of what I was hoping for.

After some more experimentation I discovered another problem. In the code for checking whether to render a tile or not I was doing the following:

[codesyntax lang=”actionscript3″ lines=”normal”]

public function update(delta:Int) : Void
{				
	var dx = x - game.player.x;
	var dy = y - game.player.y;
	visible = animated = (dx * dx) + (dy * dy) < game.root.stage.stageWidth * game.root.stage.stageHeight;
}

[/codesyntax]

The problem is that apparently the call to “stage.stageWidth” and “stage.stageHeight” is very expensive. I presume behind the scenes NME is making an expensive call to the device for width and height information.

Once I took out those calls (cacheing them in the game object instead) and tested it again on the device the FPS was now up to more what I was hoping for at 60FPS 🙂

The final technical hurdle was to rotate the world. I was worried about this one as I was contemplating all the mathematical calculations that would be needed to work out the rotation for each tile in the game. Fortunately however there was a better solution. Because NME is based on the flash API I was able to put the world within a container Sprite, then offset the container by the player’s position – screenH/2 and screenW/2 then rotate the container thus giving the illusion of the world rotating.

To my relief this idea actually worked and even more incredibly it didn’t seem to affect the frame rate! See the video below of it running one my iPhone 4, iPad 3 and my old iPhone 3G.

The 3G will need some more work to make it run at acceptable framerates, but im impressed it even runs on that thing, you cant do this with Adobe’s Stage3D!

Well that enough of me talking about this for now, I need to get on with making the damn thing! Next up is some more game-play elements and perhaps some menu structure.

3 Days into the 3-Week Challenge

.. and we have progress! See above. Use the keyboard to control the ‘player’.

The first thing you will noticed is that there is a world populated with solid blocks and some orange circle things that may look a little like collectables 😛 To generate this level I decided to take a page out of Notch’s book and build the levels at the pixel level in Paint.NET. That way the image editing software becomes the level editor. So as an example, the data that builds the level for above looks like:

That’s a 50×50 pixel image which represents a world 50×50 tiles wide. Each pixel in the image has a colour which corresponds to a tile type in the game. Black is a solid wall, red is the player spawn point and orange is a ‘collectable’. As the game develops we will be adding more tile types and hence colours.

At runtime all I do is load the level data image and loop through the pixels, grab the colour value and populate the world with the appropriate object. A problem I soon encountered however is that for some reason on different platforms Haxe reads the colour value different. This made things problematic so instead what I have done is make another 8×8 image as a “key”:

Loading this key first I can then determine what colour the platform will be recognising a particular tile type as. So to generate the level the code looks like:

[codesyntax lang=”actionscript3″]

class LevelManager 
{
	public static var TYPES : Hash<Class<Dynamic>>;	

	public var gridW : Int;
	public var gridH : Int;
	public var tiles : Array<BaseObject>;

	public function new() 
	{
		if (TYPES == null)
		{
			var objectTypes = [null, SolidBlock, SpawnPoint, Ring];			
			TYPES = new Hash<Class<Dynamic>>();
			var bmd =  Assets.getBitmapData("assets/levels/key.png");
			if (bmd == null) throw new Error("key png is null for some reason!");
			var i = 0;
			for (y in 0...bmd.height) for (x in 0...bmd.width) if(i<objectTypes.length) TYPES.set("" + StringTools.hex(bmd.getPixel(x, y), 6), objectTypes[i++]);
		}
	}

	public function loadLevel(stageIndex:Int, levelIndex:Int) 
	{
		var bmd =  Assets.getBitmapData("assets/levels/s" + stageIndex + "_l" + levelIndex+"/world.png");
		gridW = bmd.width;
		gridH = bmd.height;
		tiles = [];

		for (y in 0...gridH)
		{
			for (x in 0...gridW)
			{
				var c = StringTools.hex(bmd.getPixel(x, y), 6);
				var t = TYPES.get(c + "");
				if (t == null) { continue; }			
				var o : BaseObject = Type.createInstance(t, []);
				o.x = x * Game.GRID_SIZE;
				o.y = y * Game.GRID_SIZE;
				Game.I.addObject(o);
				tiles[y * gridW + x] = o;
			}			
		}
	}
...

[/codesyntax]

Once I had the level populating I started getting the basics of the physics sorted. At first I thought it was going to be a nightmare as in the original version of the game it appeared as if the whole world rotated about the player (see video for reminder), I worried about how I was going to handle the complex physics of a grid at odd angles while continually rotating. After a while however I realised that what was actually going on was that the world was standing still and all that was happening was that the camera was rotating at same rate at which the gravity vectyor was changing, thus giving the illusion of a rotating world, eg:

Once I realised this fact it made my life a whole lot easier. Calculating the physics for the world should now just be a matter of solving a circle against a static grid without rotations. I decided to go with my own physics solution rather one of the existing solutions such as Box2D or Nape as I thought that it should be pretty simple to calculate and I knew from a previous project that using Box2D or Nape would have issues at the joins between tiles.

The solution it turns out took a little longer than I thought but I eventually cracked it. The key was to use the Separating Axis Theorem with Voroni Regions, there is a great tutorial on it over at magnet software, they have a handy SWF that demonstrates the concept really well:

As can be seen from above that all you need do is split the problem up into a grid, then in turn check each of the 8 neighbouring cells from the current cell. The north, east, south and west cells can be classed as one type and only need to have their relevant axis checked against the radius of the player wheres the corner cells need to be checked against the distance from the closest point. In code this looks something like:

[codesyntax lang=”actionscript3″]

// From Player.hx

override public function update(delta:Int) : Void
{
	#if !mobile
	if (Ctrl.instance.isDown("up")) vel.y -= 1;
	if (Ctrl.instance.isDown("left")) vel.x -= 1;
	if (Ctrl.instance.isDown("right")) vel.x += 1;
	//if (Ctrl.instance.isDown("down")) vel.y += 1;
	#end

	if (Ctrl.instance.mouseDown) vel.y -= 1;

	var d = delta * 0.01;
	vel.x += gravity.x * d;
	vel.y += gravity.y * d;		

	var newPos = new Vec2(x + vel.x * d, y + vel.y * d);
	var ntx : Int = Std.int(newPos.x / Game.GRID_SIZE);
	var nty : Int = Std.int(newPos.y / Game.GRID_SIZE);				

	checkTileCollide(ntx, nty, ntx - 1, nty + 1, newPos, vel);
	checkTileCollide(ntx, nty, ntx + 1, nty + 1, newPos, vel);
	checkTileCollide(ntx, nty, ntx - 1, nty - 1, newPos, vel);
	checkTileCollide(ntx, nty, ntx + 1, nty - 1, newPos, vel);
	checkTileCollide(ntx, nty, ntx, nty + 1, newPos, vel);
	checkTileCollide(ntx, nty, ntx, nty - 1, newPos, vel);
	checkTileCollide(ntx, nty, ntx + 1, nty , newPos, vel);
	checkTileCollide(ntx, nty, ntx - 1, nty , newPos, vel);	

	x = newPos.x;
	y = newPos.y;
}

private function checkTileCollide(fromTX:Int, fromTY:Int, toTX:Int, toTY:Int, pos:Vec2, vel:Vec2) : Bool
{
	var tile = game.level.getTile(toTX, toTY);
	var dTX = fromTX - toTX;
	var dTY = fromTY - toTY;
	if (tile != null && tile.is(SolidBlock))
	{				
		if (dTX == 0)
		{
			var d =  Math.abs(pos.y-((toTY - fromTY) > 0?toTY * Game.GRID_SIZE:fromTY * Game.GRID_SIZE));
			if (d < radius)
			{
				pos.y += dTY * (radius - d);
				vel.y = 0;
				return true;
			}
		}
		if (dTY == 0)
		{
			var d =  Math.abs(pos.x-((toTX - fromTX) > 0?toTX * Game.GRID_SIZE:fromTX * Game.GRID_SIZE));
			if (d < radius)
			{
				pos.x += dTX * (radius - d);
				vel.x = 0;
				return true;
			}
		}			
		else
		{	
			var tp = new Vec2((dTX>0?fromTX:toTX)*Game.GRID_SIZE, (dTY>0?fromTY:toTY)*Game.GRID_SIZE);				
			var vToCorner = new Vec2(tp.x - pos.x, tp.y - pos.y);			
			if (vToCorner.lengthSqr() < radius * radius)
			{					
				var ang = Math.atan2(vToCorner.y, vToCorner.x);							
				pos.x = tp.x - Math.cos(ang) * radius;
				pos.y = tp.y - Math.sin(ang) * radius;				
				//vel.x = vel.y = 0;
				return true;
			}			
		}
	}
	return false;
}

[/codesyntax]

Its not 100% perfect, there is some oddness when the player hits a corner but will do for now.

On the art side of the project Moh has been making good progress coming up with themes for the game. We have been playing around with the idea that the player is a Hamster lost in space, which I really like the idea of. To test this idea he made little mock-up, which looks great:

You may have noticed that currently the game is in Flash. That’s because with NME you can target Flash as one of your outputs. This makes developing and testing the game alot easyier (a lot faster to compile and run). I have however been very aware of the problems I could cause myself if I developed the whole game solely in flash and only testing on mobile right at the end. Trying to track down an obscure problem in a fully written game would be a nightmare. So I have been making progress with getting the game to run on my iPhone 4.

One of the problems I faced (and I banged my against the wall for a while on this one) was that for some reason when the level was populating from the PNG, certain tiles weren’t being built. I couldn’t for the life of me work out why. To cut a long story short, apparently when building for iOS in Haxe you MUST put the super call in the constructor BEFORE any other call, else the code before the super call in the constructor wont be executed:

[codesyntax lang=”actionscript3″]

class Player extends BaseObject
{	

	public function new() 
	{		
		trace("This will not be executed when built for iOS but WILL be executed when built for flash");	
		super();
		trace("This will be execute on flash AND iOS");	
	}

...

[/codesyntax]

A small thing to remember but quite a gotcha for the NME Haxe newbie!

Another issue I have run into is the fact that my iPhone currently has iOS 5.1 on it, this means that to use it as a testing platform I had to upgrade my Macbook to OSX Lion which meant I have to leave it running over night and this morning downloading and installing. Not a biggie as I have been meaning to upgrade for a while anyways, but an inconvenience when you want to sit down to test your shiny new game out!

We have quite a way to go, but im happy with the progress we have made in 3 days thus far 🙂