Subscribe to GameFromScratch on YouTube Support GameFromScratch on Patreon Join the GFS Discord Server!
24. April 2013

In the past we covered basic program structure as well as using graphics using Haxe and NME.  In this section we are going to look at handling input.  A number of these samples, such as multi touch, will only work on a device, although we will briefly look at how to create device specific code that still compiles on other platforms.  Alright, let's jump right in.

 

First lets look at the basics of handling touch and mouse click events.

 

Mouse click and finger touch event handling

 package gfs;

import nme.Assets;
import nme.display.Bitmap;
import nme.display.Sprite;
import nme.events.Event;
import nme.events.TouchEvent;
import nme.events.MouseEvent;
import nme.Lib;


class Main extends Sprite
{
	private var sprite:Sprite;
	
	public function new()
	{
		super();
		
		var img = new Bitmap(Assets.getBitmapData("img/mechwarriorFrameScaled.png"));
		
		//Bitmap unfortunately cannot receive events, so we wrap it in a sprite
		sprite = new Sprite();
		sprite.addChild(img);
		
		sprite.x = Lib.current.stage.stageWidth / 2 - sprite.width / 2;
		sprite.y = Lib.current.stage.stageHeight / 2 - sprite.height / 2;
		
		sprite.addEventListener(MouseEvent.CLICK, mechClicked);
		Lib.current.stage.addEventListener(TouchEvent.TOUCH_TAP, onTouch);
		Lib.current.stage.addEventListener(MouseEvent.CLICK, onClick);

		Lib.current.stage.addChild(sprite);
	}
	
	public function mechClicked(event: MouseEvent)
	{
		sprite.x = Lib.current.stage.stageWidth / 2 - sprite.width / 2;
		sprite.y = Lib.current.stage.stageHeight / 2 - sprite.height / 2;
		event.stopImmediatePropagation();
	}
	
	public function onTouch(event: TouchEvent)
	{
		sprite.x = event.stageX - sprite.width / 2;
		sprite.y = event.stageY - sprite.height / 2;
	}
	
	public function onClick(event: MouseEvent)
	{
		sprite.x = event.stageX - sprite.width / 2;
		sprite.y = event.stageY - sprite.height / 2;
	}
	
} 

When you run this code you should see:

1

The mech will move wherever you click or touch.  If you click on the mech sprite, it will move back to the centre of the screen.

 

Now back to the code. I was informed that I didn't need to create a static main function when working with NME, so in my prior examples… ignore that bit. :)

Therefore we now start of in our constructor new.  First we create an image, a small version of our now familiar mech image.  There is a catch though, even though it has the method addEventListener, a Bitmap object can't actually receive events.  Therefore we are creating a Sprite object to hold our Bitmap.  Next up we centre the sprite to the screen.  Then we wire up sprite to receive MouseEvent.CLICK events, which will causer it to call the function mechClicked.  We also wire up TouchEvent.TOUCHT_TAP and MouseEvent.CLICK event handlers to the actual stage.

It's important to realize the order things are handled when a click or touch occurs.  It will ultimately be the object that is touched that get's first crack at the event, then it's parent, then it's parent, etc…  this is called 'bubbling'.  It's a very important thing to get your head around, as consider the current situation…  we are setting up a click handler in our mech sprite that handles the click, causing the sprite to be re-entered back to the middle of the screen if clicked.  Then the stage handles the event and moves the sprite to where the user clicked, effectively overwriting the actions the Sprite's event handler took.  In this case we want to stop handling the event  once the mech is clicked, so it's parent's handler won't run.  This is accomplished by calling event.stopImmediatePropagation().  The only other thing to notice is the different event types passed in to the various event handlers.

There is however one current gotcha in the above code… for CPP targets it doesn't actually work.  The call to stopImmediatePropagation() doesn't actually do what it says.  I reported this on the forums and most impressively, they already fixed the bug!  Colour me impressed on that one.  So, depending on when you are reading this, mechClicked() may not appear to be working if run on one of the CPP targets.  It does however work just fine on Flash and HTML5.

If you aren't working from NME sources, you can work around with this simple fix:

class Main extends Sprite
{
	private var sprite:Sprite;
	var clickHandled = false;
	
	public function new()
	{
		super();
		
		var img = new Bitmap(Assets.getBitmapData("img/mechwarriorFrameScaled.png"));
		
		//Bitmap unfortunately cannot receive events, so we wrap it in a sprite
		sprite = new Sprite();
		sprite.addChild(img);
		sprite.x = Lib.current.stage.stageWidth / 2 - sprite.width / 2;
		sprite.y = Lib.current.stage.stageHeight / 2 - sprite.height / 2;
		
		sprite.addEventListener(MouseEvent.CLICK, mechClicked);
		
		Lib.current.stage.addEventListener(TouchEvent.TOUCH_TAP, onTouch);
		Lib.current.stage.addEventListener(MouseEvent.CLICK, onClick);
		
		
		Lib.current.stage.addChild(sprite);
	}
	
	public function mechClicked(event: MouseEvent)
	{
			sprite.x = Lib.current.stage.stageWidth / 2 - sprite.width / 2;
			sprite.y = Lib.current.stage.stageHeight / 2 - sprite.height / 2;
			clickHandled = true;
	}
	public function onTouch(event: TouchEvent)
	{
		sprite.x = event.stageX - sprite.width / 2;
		sprite.y = event.stageY - sprite.height / 2;
	}
	
	public function onClick(event: MouseEvent)
	{
		if (clickHandled) {
			clickHandled = false;
			return;
		}
		sprite.x = event.stageX - sprite.width / 2;
		sprite.y = event.stageY - sprite.height / 2;
	}
}

Basically you just set a flag that the current click action has been handled.  Of course, this system falls on it's face once you start looking at multi-touch, as we will shortly

 

Keyboard Handling

So that's the basics of clicking and touching, now let's take a look at using keyboard control.  This will only work on devices with a physical keyboard.

package gfs;

import nme.Assets;
import nme.display.Bitmap;
import nme.display.Sprite;
import nme.events.Event;
import nme.events.KeyboardEvent;
import nme.Lib;
import nme.ui.Keyboard;


class Main extends Sprite
{
	private var sprite:Sprite;
	
	public function new()
	{
		super();
		
		var img = new Bitmap(Assets.getBitmapData("img/mechwarriorFrameScaled.png"));
		
		//Bitmap unfortunately cannot receive events, so we wrap it in a sprite
		sprite = new Sprite();
		sprite.addChild(img);
		
		sprite.x = Lib.current.stage.stageWidth / 2 - sprite.width / 2;
		sprite.y = Lib.current.stage.stageHeight / 2 - sprite.height / 2;

		Lib.current.stage.addEventListener(KeyboardEvent.KEY_UP, function(event) {
			if (event.keyCode == Keyboard.RIGHT)
			{
				if (event.altKey || event.ctrlKey || event.shiftKey)
					sprite.x += 10;
				else
					sprite.x += 1;
			}
			if (event.keyCode == Keyboard.LEFT)
			{
				if (event.altKey || event.ctrlKey || event.shiftKey)
					sprite.x -= 10;
				else
					sprite.x -= 1;
			}
		});

		Lib.current.stage.addChild(sprite);
	}
	
	public static function main()
	{
		Lib.current.addChild(new Main());
	}
} 

When you run this code, you can use the left and right arrows to move the sprite left or right.  Holding down the alt key, shift key or control key will cause the sprite to move by 10 pixels, otherwise it moves by 1 pixel in the given direction.

As you can see, handling keyboard events is almost exactly the same as handling mouse and touch events.  You simply wire up the stage to listen for the event KeyboardEvent, in this case KEY_UP.  You simple compare the keyCode of the event against the enum specified in Keyboard to see if your key has been pressed.  There are also a series of flags, altKey, ctrlKey and shiftKey that tell you the status of each of these keys.

One important thing to note, you do not use KeyboardEvent to handle text field input, instead use the methods of TextField.

 

Accelerometer/motion control handling 

Now let's take a look at how to handle the Accelerometer.  This code will compile on any platform, but will only work on mobile platforms for obvious reasons.

 

package gfs;

import nme.Assets;
import nme.display.Bitmap;
import nme.display.Sprite;
import nme.events.Event;
import nme.events.AccelerometerEvent;
import nme.Lib;
import nme.sensors.Accelerometer;
import nme.ui.Accelerometer;


class Main extends Sprite
{
	private var sprite:Sprite;
	
	public function new()
	{
		super();
		
		var img = new Bitmap(Assets.getBitmapData("img/mechwarriorFrameScaled.png"));
		
		//Bitmap unfortunately cannot receive events, so we wrap it in a sprite
		sprite = new Sprite();
		sprite.addChild(img);
		
		sprite.x = Lib.current.stage.stageWidth / 2 - sprite.width / 2;
		sprite.y = Lib.current.stage.stageHeight / 2 - sprite.height / 2;

		Lib.current.stage.addEventListener(Event.ENTER_FRAME, onFrameEnter);
		Lib.current.stage.addChild(sprite);
	}
	
	public function onFrameEnter(event:Event) {
		#if (android || ios)
		if (Accelerometer.isSupported) {
			var y = nme.ui.Accelerometer.get().y;
			Lib.trace(y);
			if (y < 1 && y > 0.6) sprite.y-=5; // straight up and down
			else if (y < 0.4 && y > 0.0) sprite.y+=5;  // turned 90 degrees from user
		}
		else
			Lib.trace("No Accelerometer support");
		#else
			Lib.trace("Not a mobile target");
		#end
	}
	
	public static function main()
	{
		Lib.current.addChild(new Main());
	}
}

When you run this code, our trusty mech sprite is drawn centred to the screen.  If the phone is straight up and down ( parallel to your head ) nothing will happen.  As you tilt the phone away from you, for the first 40 or so degrees the sprite will move up.  Then a dead zone, then for the next 40 os so degrees the sprite will move down, until you are holding the phone perpendicular to you, at which point the sprite will stop moving.

Now for a bit of a red herring… there are Accelerometer events… THEY DONT WORK!  Don't go that route.  The events are simply never fired.  Instead you need to poll the Acelerometer when you want data.  In this case we are going to use the ENTER_FRAME event, which is fired at the beginning of each frame, causing the onFrameEnter function being called.

In onFrameEnter, we have our first compiler conditional.  If you are used to pre-processor directives, this will be familiar with you.  Basically code starting with the # symbol tell the compiler what to id.  In this case, code within the #if (android || ios) will only be executed if those values are true.  If it is an iOS or Android device, we simply read the G value of the y axis.  A value of 1 indicates a motionless phone straight in from of you.  A value of 0 is a motionless phone perpendicular to you.  You can get the motion values of all 3 axis and use them to determine orientation as well as movement.

 

Handling multitouch

Finally let's take a look at multitouch support.  Once again you obviously need a multi-touch capable device to run this sample.

 

 

package gfs;


import nme.Assets;
import nme.display.Bitmap;
import nme.display.Sprite;
import nme.display.Stage;
import nme.display.StageScaleMode;
import nme.events.Event;
import nme.events.TouchEvent;
import nme.Lib;
import nme.ui.Multitouch;
import nme.ui.MultitouchInputMode;


class Main extends Sprite
{
	private var sprites: Array<Bitmap>;
	
	public function new()
	{
		super();
		
		sprites = new Array<Bitmap>();
		
		sprites.push(new Bitmap(Assets.getBitmapData("img/mechwarriorFrame.png")));
		sprites.push(new Bitmap(Assets.getBitmapData("img/mechwarriorFrame.png")));
		sprites.push(new Bitmap(Assets.getBitmapData("img/mechwarriorFrame.png")));
		sprites.push(new Bitmap(Assets.getBitmapData("img/mechwarriorFrame.png")));
		
		for (i in 0 ... sprites.length) {
			sprites[i].x = Std.random(800);
			sprites[i].y = Std.random(1280);
			Lib.current.stage.addChild(sprites[i]);
		}

		Lib.current.stage.addEventListener(TouchEvent.TOUCH_BEGIN, function(e) {
			sprites[e.touchPointID].x = e.localX;
			sprites[e.touchPointID].y = e.localY;
		});
	}

}

Here is the code running on my Galaxy Note.  Each mech sprite is positioned where you touched.  Up to four sprites/touches can be tracked in this example.

Export 04

So, each sprite represents the location I most recently touched with each finger. 

 

We simple create an array of images all holding the same image, one for each of our four fingers.  We then randomize it's starting location ( for no particular reason ) then add it to the stage.  Touch is handled exactly as it was before, the only real difference is the event can be fired a number of times.  You determine if it is multiple touch by checking the touchPointID value of event.  This ID value can be thought of as the index value of your finger… so if you are using a single finger, it will have a value of 0.  If you are using 3 fingers, and it is the third touchpoint, the value will be 2.  

 

One word of warning, there is mention in the docs of GESTURE modes… such as ZOOM, etc… these do not exist in NME.  If you want gesture support, you need to roll it yourself.

 

So that's IO in Haxe…  I have to say, other then a bug ( which was fixed almost instantly! ) and a red herring in the documentation about gesture support, that was a painless experience and has all the functionality I need.  Of course, I just used a portion of what's there… you can also mouse over, key up, mouse enter, etc...

 

I did run into a few annoyances with FlashDevelop though.  First, if you run an cpp target project, while one is already running on your device, not only will it fail, it will bork the debugger forcing you to reset FlashDevelop.  It sounds like a mild annoyance, but I did it probably 15 times!  Another annoyance is Flashdevelop loses intellisense when inside anonymous methods, or whatever Haxe/Flash calls them.


23. April 2013

As some of you may already know, I recently published my first book. Now teaming up with my publisher Packt Publishing we are organizing a giveaway!  Three lucky winners stand a chance to win an e-copy of PlayStation®Mobile Development Cookbook. Keep reading to find out how you can win a copy.

 EDIT: Winners have been announced

Overview of PlayStation®Mobile Development Cookbook:

Psm

• Learn how you can create your own fantastic PlayStation®Mobile (PSM) applications

• Develop 2D games quickly and easily, complete with graphics, audio, and input

• Discover how to construct your own 3D world, import models, and even create texture and vertex shaders

You can read more about this book and download free Sample Chapter: 

 

How to Enter?

In a nutshell, you just need to leave a comment in this thread.  Of course, I'd love to hear what you think of the contents of the book when you comment! The winner will be contacted via email, so be sure to use a valid email address or I won't be able to contact you.  The contest will run until Monday April 29th, at which point I will collect all the email addresses and select three at random as winners.


22. April 2013

In the previous post we looked at the basic structure of a program in Haxe/NME running on a number of different platforms.  Now I am going to check out the graphics portion of NME, specifically sprite/bitmap rendering.  I am going to need a graphic to use for this example and I chose to use these, specifically this one.  It's a sprite sheet from Mech Commander 2, a game Microsoft released for free, including all of the source code and assets.  Of course the images themselves are still copy protected, so do not use them in a commercial project!  Of course, you can use whatever image you want, just be aware that i've hard coded the dimensions in the following examples to match the dimensions of these images.

 

I've taken a single frame of animation and blown it up for the first sample.

MechwarriorFrameScaled

We are now going to look at the code required to display this guy on screen.  Since I am using FlashDevelop, I am following their folder structure.  So I've added my image to the folder assets/img as MechWarriorFrameScaled.png.  Your layout should look something like this:

Burp

 

Now let's take a look at the code:

 

 

package gfs;

import nme.Assets;
import nme.display.Bitmap;
import nme.display.Sprite;
import nme.events.Event;
import nme.Lib;
import nme.text.TextField;
import nme.text.TextFormat;

class Main extends Sprite
{
	public function new()
	{
		super();
		var mech:Bitmap = new Bitmap(Assets.getBitmapData("img/mechwarriorFrameScaled.png"));
		Lib.current.stage.addChild(mech);
	}
	
	public static function main()
	{
		// static entry point
		Lib.current.stage.align = nme.display.StageAlign.TOP_LEFT;
		Lib.current.stage.scaleMode = nme.display.StageScaleMode.NO_SCALE;
		Lib.current.addChild(new Main());
	}
}

 Pretty straight forward, in our constructor new() we create a new nme.Display.Bitmap by using the nme.Assets class's getBitmapData function, passing in our file name and directly ( below assets in the hierarchy, you don't need to specify the assets folder in the path ).  We then simply add the bitmap to the stage, which you can get with the static value Lib.current.stage.  We run this code and:

GFX1

 

Well, that was easy enough.  That code works just fine on every single platform I tested, which is most of them.

 

Often what you want to group a number of similar sprites, such as animation frames, together in a single sprite sheet, or in NME parlance, a tile sheet.  Let's take a look at drawing a single frame from a sprite sheet.  The following is an exploded view of our spritesheet with the single sprite we are interested bounded in blue.

GFX2

 

Let's take a look at the code required to load our sprites and display a single one.

 

 

 

package gfs;

import nme.Assets;
import nme.display.Bitmap;
import nme.display.Sprite;
import nme.display.Tilesheet;
import nme.events.Event;
import nme.geom.Rectangle;
import nme.Lib;
import nme.text.TextField;
import nme.text.TextFormat;

class Main extends Sprite
{
	public function new()
	{
		super();
		var sprites:Tilesheet = new Tilesheet(Assets.getBitmapData("img/mechwarrior.png"));
		sprites.addTileRect(new Rectangle(0, 0, 90, 80));
		
		var tileData = new Array<Float>();
		tileData = [0, 0, 0];
		sprites.drawTiles(graphics, tileData);
	}
	
	public static function main()
	{
		// static entry point
		Lib.current.stage.align = nme.display.StageAlign.TOP_LEFT;
		Lib.current.stage.scaleMode = nme.display.StageScaleMode.NO_SCALE;
		Lib.current.addChild(new Main());
	}
}

 

 

Instead of creating a bitmap, we instead create a nme.display.Tilesheet, once again using Assets to load the image file.  Next we the position of our sprite/tile in the sheet.  This is done by calling addTileRect and passing in a rectangle, which are the coordinates within the sheet that our individual sprite.  In this case, at 0,0 (top left), 90 wide and 80 pixels tall.

Next up we create a Float array called tileData.  This one will make a bit more sense in a second.  The array is basically composed of x then y position on screen where you want to draw the tile, followed by the index of the rect in the Tilesheet.  Again, this will make more sense in the future… for now just realize that we are saying to draw the 0th (first) tile in the tile sheet to the position 0,0 on screen.  That is exactly what we do when we call drawTiles.  This is optimized to make drawing tiles from the same sheet as fast as possible by minimizing the OpenGL overhead, something often referred to as sprite batching.

We run the application and we see:

GFX3

 

Our single tile from our tile sheet is rendered in the top left corner of the screen.  Again, this code runs on every platform I tested it.

 

Now drawing a single graphic from a tilesheet is all nice and good, but not really useful.  Often what you are going to want to do is draw an entire level composed of tiles.  Let's take a look how that works ( still using our mechwarrior tilesheet )

 

 

package gfs;

import nme.Assets;
import nme.display.Bitmap;
import nme.display.Sprite;
import nme.display.Tilesheet;
import nme.events.Event;
import nme.geom.Rectangle;
import nme.Lib;
import nme.text.TextField;
import nme.text.TextFormat;

class Main extends Sprite
{
	public function new()
	{
		super();
		var sprites:Tilesheet = new Tilesheet(Assets.getBitmapData("img/mechwarrior.png"));
		var tileData = new Array<Float>();
		
		for (i in 0...11)
		{
			sprites.addTileRect(new Rectangle(0, i * 80, 90, 80));
		}
		
		tileData = [
			0,   0,  0,
			90,  0,  1,
			180, 0,  2,
			270, 0,  3,
			360, 0,  4,
			450, 0,  5,
			540, 0,  6,
			0,   80, 0,
			90,  80, 1,
			180, 80, 2,
			270, 80, 3,
			360, 80, 4,
			450, 80, 5,
			540, 80, 6,
			
			];
						
		sprites.drawTiles(graphics, tileData);
	}
	
	public static function main()
	{
		// static entry point
		Lib.current.stage.align = nme.display.StageAlign.TOP_LEFT;
		Lib.current.stage.scaleMode = nme.display.StageScaleMode.NO_SCALE;
		Lib.current.addChild(new Main());
	}
}

 

 

The code is remarkably similar to before, except this time we are creating a number of tile rectangles within our tilesheet.  We are going to create a rectangle for all of the sprites in the first column of our sprite sheet, as shown:

GFX4

 

This time we are going to draw two rows worth of sprites on our screen, so we have a much more complicated tileData array, but it follows exactly the same logic as before:

0, 0, 0,
90, 0, 1,
180, 0, 2,
270, 0, 3, //snip

Considering the above snippet from the array for example, what we are saying is draw the tile at index 0 in the tilesheet to 0,0 on screen, then the tile at index 1 to to 90x,0y then the tile 3rd tile to 180,2, etc… if you were drawing a level, you would obviously have a much larger array.

We run this code and:

GFX5

 

Pretty cool, that's pretty much all you need to know to draw a simple tile based background.  Once again, this code works just fine on every platform I tested.  You may be wondering at this point what determines your screen resolution?  The answer is once again, the application.nmml file.  If you look in yours, you should see something similar to:

<window background="#000000" fps="60" />
<window width="800" height="480" unless="mobile" />
<window orientation="landscape" vsync="true" antialiasing="0" if="cpp" />

This is saying make our window resolution 800x480 unless mobile ( in which case it will be the device resolution ), we set some additional settings if the target is cpp ( iOS, Android, Mac and Windows are ).

 

Since we have a sprite sheet full of animations, let's take a look at doing a simple animation!

 

 

 

package gfs;

import nme.Assets;
import nme.display.Bitmap;
import nme.display.DisplayObject;
import nme.display.Sprite;
import nme.display.Tilesheet;
import nme.events.Event;
import nme.geom.Rectangle;
import nme.Lib;
import nme.text.TextField;
import nme.text.TextFormat;
import nme.events.TimerEvent;


class Main extends Sprite
{
	private var currentFrame: Int = 0;
	public var timer:nme.utils.Timer;
	private  var sprites: Tilesheet;
	
	public function new()
	{
		super();
		sprites = new Tilesheet(Assets.getBitmapData("img/mechwarrior.png"));
		var tileData = new Array<Float>();
		
		for (i in 0...11)
		{
			sprites.addTileRect(new Rectangle(0, i * 80, 90, 80));
		}
		
		sprites.drawTiles(graphics, [0,0,0,6], false, Tilesheet.TILE_SCALE);
		timer = new nme.utils.Timer(100);
		
		timer.addEventListener(TimerEvent.TIMER, onTimerTick);
		
		timer.start();
	}
	
	public function onTimerTick(e:TimerEvent): Void {
		graphics.clear();
		if (++currentFrame == 11) currentFrame = 0;
			sprites.drawTiles(graphics, [0, 0, currentFrame, 6], false, Tilesheet.TILE_SCALE);
	
	}
	public static function main()
	{
		// static entry point
		Lib.current.stage.align = nme.display.StageAlign.TOP_LEFT;
		Lib.current.stage.scaleMode = nme.display.StageScaleMode.NO_SCALE;
		Lib.current.addChild(new Main());
	}
}

 

At this point most of the code should be quite familiar.  We create tiles for each frame of animation from the left column of the tilesheet, which represents a mech walk animation. One thing that might look kinda odd is our tileData.  Once again we are only drawing a single tile at the location 0,0, but you may notice there is a 4th value [0,0,0,6].  If you notice in the drawTiles call we pass the flag Tilesheet.TILE_SCALE, that is telling  drawTiles that we will be passing a 4th value representing the amount to scale by ( 6x ).  There are a number of additional parameters you can pass drawTiles, here is an excerpt from the docs:

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 ...

So, for each flag you set, you have to pass an additional value in the float array you pass in to drawTiles.  Next up we create a timer that will fire ever 100 milliseconds.  We then create an event listener that will fire when the timer "ticks" calling the method onTimerTick.  Finally we start the timer.  In onTimerTick, each frame of animation we clear the screen and draw the next frame of animation  by incrementing the index within of the tile we want drawn by drawTiles.  If our animation exceeds the number of frames, we roll back to 0.

 

We run this code and:

Mechwalkcycle

 

Pretty cool!  This one however didn't work on every platform. :(  The timer event doesn't work on HTML.  Unfortunately the timer is only called once instead of every 100milliseconds.  This kind of stuff is rather troubling if you are trying to support multiple platforms at once, especially if you don't know Javascript well enough to troubleshoot the problem!  The key with Haxe development seems to be test early, often and on every platform you want to target.


EDIT: This bug has been fixed in the github version of NME as of April 24/th 2013.  So hopefully it will no longer effect you!

 

Fortunately there is an easy work around for this particular problem.  There is a timer class in the base Haxe library you can use.

 

 

 

public function new()
{
	super();
	sprites = new Tilesheet(Assets.getBitmapData("img/mechwarrior.png"));
	var tileData = new Array<Float>();
	
	for (i in 0...11)
	{
		sprites.addTileRect(new Rectangle(0, i * 80, 90, 80));
	}
	
	sprites.drawTiles(graphics, [0,0,0,6], false, Tilesheet.TILE_SCALE);
	
	timer = new Timer(100);
	
	timer.run = function(){
	graphics.clear();
	if (++currentFrame == 11) currentFrame = 0;
		sprites.drawTiles(graphics, [0, 0, currentFrame, 6], false, Tilesheet.TILE_SCALE);
	}
}

 

With the exception of one thing not working cross platform, I am pretty impressed by Haxe/NME's performance in the graphics category.  Rendering bitmaps, tile sheets and performing frame based animation is all rather simple. Moving on we will next look at handling input with Haxe and NME.

The next part on handling input is now online.

 


17. April 2013

 

With any project, you've got to start at the very beginning and that is exactly what I am going to do with Haxe.  First we are going to create the seminal Hello World and explore the cross platform nature of Haxe.

 

I originally worked in FlashDevelop to author these examples.  I ran into a couple gotchas as I went.  First, I discovered that package names need to start with a lower case character, but FlashDevelop will allow you to make this mistake.  On top, packages are more like Java, than C# or C++ namespaces.  Behind the scenes, they are mirrored on the file system.  So a package mike.is.great becomes /mike/is/great on the file system.  So, when renaming my package, I had to change at the file system level too… lesson learned.  Second, the debugger is incredibly fragile in FlashDevelop, if it goes on the fritz, restart FlashDevelop.  Finally, for some bizarre reason, you can't set a watch on a non local variable.  So if you want to watch a global value, make a local reference.  All told though, working in FlashDevelop is pretty pleasant.

 

Alright, back to HelloWorld.  I'll start with a straight Haxe version that runs in the Neko VM:

package gfs;

import neko.Lib;

class Main
{
	
	static function main()
	{
		Lib.println("Hello world");
	}
	
}

Not the most exciting code, but Hello World rarely is. Now we run our application in the Neko VM:

1

 

Nothing too exciting, but that's Hello World.  Now let's look at the Flash version of Hello World:

 

 

package gfs;

import flash.display.StageAlign;
import flash.display.StageScaleMode;
import flash.Lib;
import flash.text.TextField;

class Main
{
	
	static function main()
	{
		var stage = Lib.current.stage;
		
		// entry point
		var text = new TextField();
		text.text = "Hello world!";
		stage.addChild(text);
	}
	
}

 

And here it is running in Flash:

2

Once again, not the most exciting app, but it's Hello World.

 

That said, it's pretty annoying creating a version for each platform.  For this we will turn to NME.  NME provides a series of libraries that abstract out the different platform capabilities using a consistent set of libraries.  This HelloWorld is a little bit more advanced:

 

 

 

package gfs;

import nme.display.Sprite;
import nme.events.Event;
import nme.Lib;
import nme.text.TextField;
import nme.text.TextFieldAutoSize;
import nme.text.TextFormat;

class Main extends Sprite
{
	public function new()
	{
		super();
		var stage = Lib.current.stage;
		
		var textFormat = new TextFormat();
		textFormat.font = "Courier";
		textFormat.color = 0xff0000;
		textFormat.size = 42;
		
		var text = new TextField();
		text.text = "Hello World!";
		text.x = Lib.current.stage.stageWidth / 2 - 145;
		text.y = Lib.current.stage.stageHeight / 2 - 21;
		text.width = 290;
		
		text.setTextFormat(textFormat);
		Lib.current.stage.addChild(text);
	}
	
	public static function main()
	{
		// static entry point
		Lib.current.addChild(new Main());
	}
}

 

This code is pretty similar, once again, there is a main method.  You may find it a bit strange that Main inherits from Sprite… this is an ActionScript/Flash thing, the library NME models after.  Now the cool part is running it on various platforms, here are the results (in order) of running:

nme test ios -simulator

nme test Flash

nme test html5

 

iOS

3

 

Flash

Hello World Haxe FLash

 

HTML5

HaxeHTML5HelloWorld.png

 

One source, many platforms… have we reached nirvana?

 

Sadly no.  Here is the result of nme test mac:

Hello world haxe mac

 

Ugh, something obviously went wrong.  Coincidentally the same code runs on Windows, so this is a Mac specific problem.  The problem is, this code is implemented in C++, so then you have to jump in to the generated code.  So, what does this generated code look like?  Well, here's some now:

 

 

#include <hxcpp.h>

#ifndef INCLUDED_gfs_Main
#include <gfs/Main.h>
#endif
#ifndef INCLUDED_native_display_DisplayObject
#include <native/display/DisplayObject.h>
#endif
#ifndef INCLUDED_native_display_DisplayObjectContainer
#include <native/display/DisplayObjectContainer.h>
#endif
#ifndef INCLUDED_native_display_IBitmapDrawable
#include <native/display/IBitmapDrawable.h>
#endif
#ifndef INCLUDED_native_display_InteractiveObject
#include <native/display/InteractiveObject.h>
#endif
#ifndef INCLUDED_native_display_MovieClip
#include <native/display/MovieClip.h>
#endif
#ifndef INCLUDED_native_display_Sprite
#include <native/display/Sprite.h>
#endif
#ifndef INCLUDED_native_display_Stage
#include <native/display/Stage.h>
#endif
#ifndef INCLUDED_native_events_EventDispatcher
#include <native/events/EventDispatcher.h>
#endif
#ifndef INCLUDED_native_events_IEventDispatcher
#include <native/events/IEventDispatcher.h>
#endif
#ifndef INCLUDED_native_text_TextField
#include <native/text/TextField.h>
#endif
#ifndef INCLUDED_native_text_TextFormat
#include <native/text/TextFormat.h>
#endif
#ifndef INCLUDED_nme_Lib
#include <nme/Lib.h>
#endif
namespace gfs{

Void Main_obj::__construct()
{
HX_STACK_PUSH("Main::new","gfs/Main.hx",13);
{
	HX_STACK_LINE(14)
	super::__construct();
	HX_STACK_LINE(15)
	::native::display::Stage stage = ::nme::Lib_obj::get_current()->get_stage();
		HX_STACK_VAR(stage,"stage");
	HX_STACK_LINE(17)
	::native::text::TextFormat textFormat = ::native::text::TextFormat_obj::__new(null(),null(),
null(),null(),null(),null(),null(),null(),null(),null(),null(),null(),null());		
HX_STACK_VAR(textFormat,"textFormat");
	HX_STACK_LINE(18)
	textFormat->font = HX_CSTRING("Courier");
	HX_STACK_LINE(19)
	textFormat->color = (int)16711680;
	HX_STACK_LINE(20)
	textFormat->size = (int)42;
	HX_STACK_LINE(22)
	::native::text::TextField text = ::native::text::TextField_obj::__new();		
HX_STACK_VAR(text,"text");
	HX_STACK_LINE(23)
	text->set_text(HX_CSTRING("Hello World!"));
	HX_STACK_LINE(24)
	text->set_x(((Float(::nme::Lib_obj::get_current()->get_stage()
->get_stageWidth()) / Float((int)2)) - (int)150));
	HX_STACK_LINE(25)
	text->set_y(((Float(::nme::Lib_obj::get_current()->get_stage()
->get_stageHeight()) / Float((int)2)) - (int)21));
	HX_STACK_LINE(26)
	text->set_width((int)300);
	HX_STACK_LINE(28)
	text->setTextFormat(textFormat,null(),null());
	HX_STACK_LINE(29)
	::nme::Lib_obj::get_current()->get_stage()->addChild(text);
}
;
	return null();
}

Main_obj::~Main_obj() { }

Dynamic Main_obj::__CreateEmpty() { return  new Main_obj; }
hx::ObjectPtr< Main_obj > Main_obj::__new()
{  hx::ObjectPtr< Main_obj > result = new Main_obj();
	result->__construct();
	return result;}

Dynamic Main_obj::__Create(hx::DynamicArray inArgs)
{  hx::ObjectPtr< Main_obj > result = new Main_obj();
	result->__construct();
	return result;}

Void Main_obj::main( ){
{
		HX_STACK_PUSH("Main::main","gfs/Main.hx",33);
		HX_STACK_LINE(33)
		::nme::Lib_obj::get_current()->addChild(::gfs::Main_obj::__new());
	}
return null();
}


STATIC_HX_DEFINE_DYNAMIC_FUNC0(Main_obj,main,(void))


Main_obj::Main_obj()
{
}

void Main_obj::__Mark(HX_MARK_PARAMS)
{
	HX_MARK_BEGIN_CLASS(Main);
	super::__Mark(HX_MARK_ARG);
	HX_MARK_END_CLASS();
}

void Main_obj::__Visit(HX_VISIT_PARAMS)
{
	super::__Visit(HX_VISIT_ARG);
}

Dynamic Main_obj::__Field(const ::String &inName,bool inCallProp)
{
	switch(inName.length) {
	case 4:
		if (HX_FIELD_EQ(inName,"main") ) { return main_dyn(); }
	}
	return super::__Field(inName,inCallProp);
}

Dynamic Main_obj::__SetField(const ::String &inName,const Dynamic &inValue,bool inCallProp)
{
	return super::__SetField(inName,inValue,inCallProp);
}

void Main_obj::__GetFields(Array< ::String> &outFields)
{
	super::__GetFields(outFields);
};

static ::String sStaticFields[] = {
	HX_CSTRING("main"),
	String(null()) };

static ::String sMemberFields[] = {
	String(null()) };

static void sMarkStatics(HX_MARK_PARAMS) {
	HX_MARK_MEMBER_NAME(Main_obj::__mClass,"__mClass");
};

static void sVisitStatics(HX_VISIT_PARAMS) {
	HX_VISIT_MEMBER_NAME(Main_obj::__mClass,"__mClass");
};

Class Main_obj::__mClass;

void Main_obj::__register()
{
	Static(__mClass) = hx::RegisterClass(HX_CSTRING("gfs.Main"), hx::TCanCast< Main_obj> ,
sStaticFields,sMemberFields,
	&__CreateEmpty, &__Create,
	&super::__SGetClass(), 0, sMarkStatics, sVisitStatics);
}

void Main_obj::__boot()
{
}

} // end namespace gfs

 

You can read it, but barely. So when the cross platform part of NME breaks down, you are in for a world of hurt. That such a simple example breaks doesn't bode well for the future.

 

In the next post we look at graphics in Haxe and NME.


16. April 2013

 

While I was working on my PlayStation Mobile book I didn’t produce all that many PlayStation Mobile tutorials.  I guess it was a matter of PSM overload in a way, although you should expect to see a few more in the near future covering things that have been added since the book was released.  The good new is, there is another developer, Alex McGilvray, that has created some great PSM tutorials!  His series of tutorials are a bit lower leveled than the ones here on GameFromScratch and are a good compliment.

 

He has made the following tutorials:

Tutorial 1 -- Getting Started

Tutorial 2 Part 1 – Shader Programming

Tutorial 2 Part 2 – Vertices, Indices and Vertex Colors

Tutorial 2 Part 3 – Texture Coordinates

Tutorial 2 Part 4 – Vertex Buffers

Tutorial 2 Part 5 – The Render Loop

Tutorial 3 – Improving PSM Rendering Performance

 

Or you can just check out his blog.  Nice work Alex! 

Programming


AppGameKit Studio

See More Tutorials on DevGa.me!

Month List