Using Cordova to package an OpenFL HTML5 application for Android – Part 2: The actual OpenFL project

In this article we’re going to create a nifty little game – we then package as a HTML5 application using Cordova though that’s what the next article in this series is about.

Prerequisites:

  • A PC running Windows 7 or above – for this blog post I’ve used a virtual machine running Windows 7 32bit
  • OpenFL 7.1.2 – at the time of writing 8.8.0 is the latest, you can surely use too but I’ve my reasons to stick with the older version
  • Lime 6.2.0
  • Haxe 3.2.1
  • FlashDevelop
  • FireFox or another recent HTML5 capable browser
  • Have read the first article in this series for a better understanding what this is all about

Because we’re going to develop a mobile game and myself being a nostalgist I decided for something most of you might remember: Snake! While Nokia didn’t invent the game concept themselves it became pretty popular since it shipped with their 1997 monochrome phone 6110 and numerous later models. It can be said that 1997 was the birth of mobile gaming on a phone.

The following image shows the Snake game in action. Please note it was taken out of a photo from a Nokia 3210.

cordovaNokiaSnake
Like many games at that time, as well as almost two decades before and still today they take advantage of a well-known technology called tiles. Let’s dive a bit back in time to the glory era of e.g. the Nintendo Entertainment System – NES in short. Back then, even high-end workstations and of course video game consoles neither had the processing power nor the memory nowaday’s average cellphones have. Let me give you some numbers. The NES has 2KB of system RAM while a typical cellphone has around 2GB. That’s a million times the RAM! Of course that’s not the only factor to consider. The video RAM and the game cartridges themselves were pretty limited too. As you can imagine game developers in the 90s had to find some clever ways to save memory while making graphic intense games at the same time. So instead of using huge full screen bitmaps, the game world is composed out of reusable small tiles.

I’m afraid all of the above sounds a little abstract, so let me give you an example. Here’s a screen taken from Super Mario 3. Can you make out individual tiles? No worries if you can’t – simply click on the image to overlay a grid.

As you should see now, there’s a lot of repetition. The ground Mario’s standing on is 15 times the same tile, the little pyramid to his left is made out of 6 instances of the same tile and you can surely see the re-using of tiles in the cloud or the green pipes.

I can almost hear the question: ‘Why do we take the tile bassed approach at all? We aren’t low on system resources nowadays!’. You’re right of course! The answer to this question is simple: it has some advantages like e.g. easy collision detection of game objects and it’s easy to understand and implement! It’s a solid concept available for decades.

Now that you know a bit more about tiles let’s start with our project!

Fire up FlashDevelop and create a new OpenFL project, name it SnakeMobile.

cordovaFlashDevelop

Snake’s gameplay is rather simple. You have to move the snake around the screen and collect dots that appear randomly while avoiding to hit the solid borders of the playfield. As soon as you collect a dot, the snake will grow longer thus making the game harder to play because you have to be careful not to hit the snake itself.
If you take a look back at the original Snake game at the top of this article, you instantly notice we need some graphics that represent the snake, the dot and the borders. If you want to be creative, you can draw your own graphics! Here’s the graphics I made for this article.
snakeTiles

This image contains all of the tiles we use in our game bundled as a single file. Each tile is 64×64 pixels in size and simply placed next to each other. This is called a texture-atlas. With a little bit of imagination you can see that e.g. the star to the right will serve as our dot and the thin-lines are actually part of our on-screen maze.
On a Nokia cell phone at this time, the game was controlled using the phone’s keypad buttons. Nowadays cellphones typically haven’t got any real physical keys instead of power and volume buttons. We use it’s touchscreen! So additionally we need visual representations of directional keys. Take these:

keyLeft
keyDown
keyUp
keyRight

We could have put those inside the texture-atlas too but I wanted to keep things simple. This time the graphics are individual files.
Simply right-click those five graphics and store them inside your projects assets/img folder.
In FlashDevelop’s Project Manager you can right-click that folder and select Explore to open Windows Explorer and find the folder’s location on your harddrive.

cordovaFlashDevelopPM

Let’s start coding! Head back to the Project Manager and double-click src/Main.hx

cordovaFDMain

to bring up the Main class – the place for all of our code.

First we need to import some more of OpenFL’s APis we take use of inside our game.

Next we need to declare the class variables – those will be accessible from any function inside our Main class. The following code goes between class Main extends Sprite { and public function new()

As soon as the Main class is instantiated (which usually happens after OpenFL’s built-in HTML5 preloader finished loading), it’s constructor is called. So move down to super(); inside public function new()
Here we’re going to to do a lot of things!
– Figure out the screen’s dimensions
– Initialize our graphics and scale them according to the screen’s dimensions
– Add the virtual keypad
– Add touch listeners to the keypad
– Set up the playfield
– Put the graphics on screen
– Start the game 😉

So let’s start by figuring out the screen’s dimensions!

As you’ve probably guessed Lib.application.window.width returns the width of the screen in pixels – in this case it’s the size of the browser window our game runs in. But what’s stage.window.scale good for? Isn’t window.width enough? No! Depending on the hardware, some browsers internally use a bigger resolution than the device’s physical resolution. This has to do with something called devicePixelRatio and is surely a topic for an own article. Just take my worth, we need to calculate our screen’s dimension using this formula to get sharp looking graphics.

tileHeight is the height of an individial tile. We determine it’s size by dividing the screen’s height by the number of tiles we want to have in a single row. Because our tile is quadratic, we can make tileWidth the same as tileHeight afterwards.

This bit of code instantiates our texture-atlas as a BitmapData object out of which we copy the individual tile graphics and store them in the textures array for later usage.

The last visual thing on screen will be our virtual keypad. Add the following lines:

Here we’re re-using the masterTexture BitmapData object we created earlier, replace it by the appropriate bitmap out of our assets folder and scale it according to the screen dimensions.
Out of this newly created texture we create individual buttons for up, down, left and right, give them an unique name, assign an eventListener to each one and finally add them to the displaylist – so we can see them on-screen. The eventListener will capture if you put your finger on the touchscreen and as soon as we created it’s callback function move the snake.

One important thing is still missing: the game area. Since it’s a tile-based game it follows a regular grid. Please take a look back at the screen shot of Super Mario 3 and overlay the grid. As you can see, there are 15 tiles horizontally and 11 vertically – each tile having the same size in pixels.
If we want to rebuild the pyramid to the left of Mario we could do something like this. As we can see it’s composed out of six tiles. Let’s find out the position – in tiles – of the top-most part by simply counting the blocks, starting at 0. If you count correctly, it’s 7 tiles from the top and 0 from the left.
In pseudo-code:
pyramidPart1.positionX=0
pyramidPart1.positionY=7

The next part of the pyramid is directly below:
pyramidPart2.positionX=0
pyramidPart2.positionY=8

To shorten this a bit, we could construct the whole pyramid by the following lines of code:
pyramidPart1.positionX=0
pyramidPart1.positionY=7
pyramidPart2.positionX=0
pyramidPart2.positionY=8
pyramidPart3.positionX=1
pyramidPart3.positionY=8
pyramidPart4.positionX=0
pyramidPart4.positionY=9
pyramidPart5.positionX=1
pyramidPart5.positionY=9
pyramidPart6.positionX=2
pyramidPart6.positionY=9

I guess I don’t need to mention that this is a whole lot of work and very confusing. There’s a way more elegant way. Instead of storing each tile’s position in individal variables, we’re using a two-dimension array.
Getting back to the pyramid in Super Mario 3, we could do something like this (in pseudo-code):

levelData=
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0

0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
1,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,0,0,0,0
1,1,1,0,0,0,0,0,0,0,0,0,0,0,0
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0

If you count the numbers horizontaly & vertically, you’ll realize it resembles the number of tiles from Super Mario – 15 & 11. If you take an even closer look – beside a lot of 0, there’s 5 times the 1! With a little bit of imagination again, you’ll even realize that it resembles the shape of the pyramid! So just imagine this: if we loop over that two-dimensional array and as soon as we discover a 1 we put a pyramid tile on the screen. But where to put it? We can simply figure this out too! Let’s say a single tile from Super Mario is 16 x 16 pixels in size. Now look at the array and find the top-most 1. You’ll find out that it’s at position 7 vertically and 0 horizontally. Hmhm, sounds familiar! If we then go and multiply this number by the size of a tile (16), we have our screen position!
That’s bascially what we’re going to do now. We’re creating a two-dimensional array of numbers, which are actually references to the textures array we created in the beginning.
Add this lines of code:

As outlined above, we create a two-dimensional array called gameGrid and store references to our textures – which ultimately designs our playfield.
If you still don’t get the picture, no worries! I’m here to help you!
I’ve overlayed a screenshot of our future playfield onto the two-dimensional array from the code snippet above.
cordovaArrayOverlayAnd for an even better understanding, here is our texture-atlas with the corresponding texture numbers used in the array.
cordovaTextureAtlasNumbered
Next step is looping through the array and instantiate a new Bitmap for each number we find, use the appropriate texture out of the textures array and put this Bitmap inside the container Sprite. Finally we center the container Sprite on screen.

We’re almost ready with our constructor function. We just need to add another eventListener, which will update the game, a call to a function which resets the game and lastly remove some garbage we don’t need anymore.

That’s it for the constructor. You might think now would be a good time to check out what we did so far. If you’re going to compile the game, the debugger will moan! Why? Yes, we didn’t add the callBack functions yet.

Simply add this three empty functions for now. We’ll work on them later.

 

Voilá! NOW is the time to test what we did! First we need to make sure that we’re targeting HTML5 though. Take a look at FlashDevelop’s tool bar and set the two drop-down boxes to Debug and html5 respectively.
cordovaFlashDevelopToolBar
Afterwards hit CTRL + Return.
If you followed carefully – and everything went well – you should be presented with a screen like this:
cordovaFireFoxSnake

Not too bad, isn’t it? I’d recommend getting yourself a cup of coffee now. You deserve a break!
Let’s work on the empty functions we created earlier. Every game needs some sort of start point. That’s the purpose of resetGame(). It’ll reset the class variables to their default values upon start of the game and in case you hit something solid during play and the game should restart.

These are the most basic variables our game uses.

startX startY This is the position in tiles, our snake will start to move from
currentXPosition currentYPosition These keep track of the snake’s position during the game
oldXposition oldYPosition A backup of the previous two
snakeLength The length of the snake in tiles. This number increses if you collect dots
direction This variable stores the direction the snake is moving to. It’s an integer from 0-3, where 0 means up, 1 means right, 2 means down and 3 means left
oldDirection Again a backup of the snake’s direction
tail An array that keeps track of the tail pieces following the snake.

The resetGame() function isn’t complete though.

Here we’re creating a duplicate of our gameGrid array – why this? During the game, we need to change the array. For example if we spawn a dot the player should pickup, we need to insert a 14 – which actually is a reference to the dot’s texture inside the textures array. If we were to restart the game later because the player lost the game, there would be no way to restore the gameGrid to it’s original state because it’s partly overwritten. That’s why during the game we’re working with it’s duplicate theMap.
A few lines are missing. Add these:

As I said we’re modifying the array. Here we set the value of the snake’s current position inside the array to 13. This is the texture number of the snake’s body pieces inside the textures array.
gimmeADot() and updateMap() are functions we’ll work on next. oldTime is used for calculating the speed of the snake.

If you remember, the goal of the game is to pick up dots – which are actually stars in our case. Upon start of the game and as soon as you collect one the game needs to spawn a new dot.

That’s what we’re doing here. We loop over the theMap array at random positions and check if it’s free – 0. If it is we set it’s value to 14 – again a reference to the star’s texture inside textures.
randomRange is a helper function which returns a random integer between the two numbers you feed it with.
randomRange(0, 7); – will return a whole number between 0 and 7 – 3 for example.

As the game progresses, the graphics on-screen must change! If you move the snake to the left, it’s visible position changes. That’s what the updateMap() function is good for.

Now there are many ways to do this. The most straight-forward approach would be creating an empty canvas and draw the tiles onto each time the graphics need to be updated. I’d be a bit like taking the sledgehammer to crack a nut in our case.
If you remember, in the constructor we created Bitmap instances for all of our tiles. Did you know that we can simply change it’s associated BitmapData – the texture inside our textures array – by a call to bitmap.bitmapData=anotherBitmapData?
That’s what we’re doing! We loop over the theMap array, retrieve each texture number and assign it’s BitmapData to it’s associated Bitmap.

Let’s move on to the empty handleKey() function we created earlier and populate it with this:

Not too much to say about this. This function registers pushes onto the virtual keys on-screen.
You might just wonder why we compare it to oldDirection before setting direction to the desired direction. This makes sure we don’t move into the snake by simply trying to go backwards – e.g. if the snake moves up and you’re pressing down.

Previously we added another empty function: updateGame().
Fill it with this:

This is the callBack function registered with the ENTER_FRAME event. This is a special event that get’s fired at the speed of the projects framerate. OpenFL defaults to 60 frames per second – which means updateGame() would be called roughly every 16.6 ms – so way too fast!

Well, as a side note: that’s only half the story. In OpenFL a framerate of 60 in conjunction with the HTML5 target means something different! It tries to use the refresh rate of your display! So while most mobile devices should have a refresh rate of 60hz, there a 144hz desktop monitors. You’re right – this means the game would try to run at 144fps!

Anyway, we slow it down by waiting delay number of milliseconds. It’s value is set to 350. So whenever 350 milliseconds have passed, we move the snake by a call to advance(direction), the last function we still need to implement.
If the player has hit a wall, the gameOver variable will become true. If it’s the case, we delay for 2 seconds – 2000ms – before restarting the game by a call to resetGame().

Wow we’re almost ready! Let’s work on the last function advance().

It will ultimately move our snake! Additionally it checks if the snake hits something solid or collects a star and act accordingly. Start with this:

This functions non-optional parameter whichDirection tells which direction (wow – who would’ve guessed that?) we want the snake to move to. If you remember, 0 means going up for example. To make sure we stay in the bounds of our gameGrid array – though we’re working with it’s replicate theMap – we need to check if a step in the desired direction would bring us out of bounds. futureX and futureY hold the position the snake would be if we move in the desired direction.

Now that we know it’s safe to move in that direction we need to check if we either collect a star, hit a wall or even the snake’s body itself at the future position.

This is done by retrieving the texture number inside the theMap array at the future position and compare it’s value with two numbers: 0 and 14.
0 is pretty self-explaining – it means it’s an empty area. 14 refers to star. Anything in-between is considered a solid tile – a wall or a part of the snake.
– If we hit a 14, enlarge the snake by 3 tiles and spawn a new star.
– If we hit anything else than 14 or 0 it’s game over.
Yeah, it’s as simple as this. 😉

We’re almost there! The snake is just missing it’s tail! As you might have noticed the only thing we actually move is the snake’s first tile – the head. To turn this head into a snake, we need to trail it by tail pieces!

As soon as the snake moved to it’s future position, it’s previous position stored in oldXPositon and oldYPosition respectively becomes a tail piece. The tail array keeps track of those pieces.
After we added a tailPiece, we check if the number of tails in the tail array exceeds the number of segments the snake should have. This is determined by the variable snakeLength. If it does, we remove the first element from the array, get the corresponding tile inside theMap at it’s position and reset it to 0 – empty gamearea.

We did it! Our game is ready to run! Hit CTRL + Return and have a good play! 🙂

cordovaSnakeFinal

I bet it’s not that much fun because we have to use our mouse to push the virtual keys.
Well a desktop PC wasn’t our target platform – we want to package it for Android using Cordova!

To do this we need to make some slight modifications. Every OpenFL HTML5 project get’s a preloader by default. It’s pupose is preloading assets – the images in our case. This makes complete sense because normally the game would run from a webserver and served to your PC over the internet first. The preloader makes sure that those assets are available if you need them in code. If we’re going to package this game as an Android application it’s purpose might seem useless because those assets are bundled yet. Nevertheless – due to OpenFL’s internal workings we still need to go through the preloading stage because otherwise it won’t know about the assets existence anyway.
So OpenFL’s default preloader shows a thin horizontal bar which scales according to the total bytes it needs to preload and the bytes which have been preloaded. We don’t want to see that bar but keep the ‘preloading’ functionality.
Fortunately OpenFL offers the ability to use a custom preloader. Since we do not want to re-invent the wheel we simply customize the default preloader – mainly stripping out the bar.
The default preloader is part of your OpenFL installation and usually located at:
C:\HaxeToolkit\openfl\openfl\7,1,2\src\openfl\display\Preloader.hx

This might be different on your system, depending on where you’ve installed Haxe and what OpenFL version you’re using. It ain’t that important though we don’t need it. 😉
Go to FlashDevelop’s Project Manager panel, right-click src and select Add, New class…

cordovaFlashDevelopAddClass

In the following dialog give the class the name Preloader and click OK.
Now overwrite it with this code:

That’s it! Just an empty preloader which fires an COMPLETE event as soon as it finished loading.
We still need find a way to tell OpenFL that it should use this instead of it’s default preloader though.
This can be done inside the project.xml file, which contains fundamental settings for your project.
Double-click this file in the Project Manager to open it.
At the moment it should look a little something like this:

Find the line:
<app main=“Main” file=“SnakeMobile” path=“bin” />
and replace it by:
<app main=”Main” file=”SnakeMobile” preloader=”Preloader” path=”bin” />

This forces OpenFL to use our custom preloader.
Now would be a good time to alter other settings inside project.xml.
As you can see, <window width=”800″ height=”480″ unless=”mobile” /> sets the size of our window to a fixed 800 x 480 pixels. That’s cool if you want to target flash for example. But what happens if we target HTML5? It means that we’re working with an actual resolution of 800 x 480 but the browser stretches it to fit the browser window. This results in a loss of quality – things get a little blurry. What we do want instead is take advantage of the full browser window unstretched!
Put these two lines below:
<window width=”0″ height=”0″ resizable=”true” antialiasing=”0″ if=”html5″ />
<window allow-high-dpi=”true” />

width=0 and height=0 is an exception for HTML5. Instead of setting it to a fixed value, it uses the size of the current browser window in pixels.

One more file needs modifications. To display our game inside the browser, OpenFl generates an index.html file, which is based on a template located at:
C:\HaxeToolkit\openfl\openfl\7,1,2\assets\templates\html5\template

There’s two things we need to do with it:
– embedd cordova.js, a Javacript layer used by Cordova
– listen for a deviceReady event which Cordova fires and start our application accordingly

Go to FlashDevelop’s Project Manager once more, right-click your project’s name – SnakeMobile (Haxe) select Add -> New Folder… -> name it templates
Afterwards right-click the freshly created folder select Add -> New HTML File… -> name it index.html and open it by a double-click.
Simply overwrite it’s contents with:

Again we need to tell OpenFl to use this instead of it’s own template, so open project.xml once more and add this line somewhere:
<template path=”templates/index.html” rename=”index.html” if=”html5″ />


Awesome! Now our application is ready to be packaged using Cordova! This will be topic in the next article in this series.

Leave a Reply

Your email address will not be published. Required fields are marked *