13 - Multiple Platforms
One of the big advantages of using HaxeFlixel is the ability to build your games for multiple platforms. You can build a working HTML5, Windows, Linux, Mac, Android and iOS game - all from the same code! So far, we've been working with HTML5, and, for the most part, you don't have to do too much to get your game working on other platforms - if you test it under Windows right now, it should mostly work just fine (although, without music, which we'll discuss later). However, you might run into some issues if you try to build it for a mobile device - at the very least, you won't be able to play it without a keyboard. We'll focus on Windows and Android in this tutorial and add a few things to make them work better on those platforms.
HaxeFlixel comes equipped with a powerful feature called conditionals. By adding some conditionals to your code, you can make it so that only certain pieces of code will be compiled depending on the conditionals you use. For example, if you are building for Android, which would probably not have a mouse, you could setup a conditional to skip over all mouse-related code. Alternately, you might also add some code to deal with touches, which would only work on mobile platforms, so you would make sure to wrap that logic in conditionals so that it doesn't try to build on Windows or HTML5.
It's not all that complicated. We'll start with something simple: adding the ability to toggle fullscreen mode on Windows.
Let's add a button to the OptionsState
:
-
When we initialize and interact with our button, we only want to do it on
desktop
platforms. So, at the top of the class, add:#if desktop var fullscreenButton:FlxButton; #end
HAXE -
In
create()
, somewhere after we add ourvolumeBar
object, add:#if desktop fullscreenButton = new FlxButton(0, volumeBar.y + volumeBar.height + 8, FlxG.fullscreen ? "FULLSCREEN" : "WINDOWED", clickFullscreen); fullscreenButton.screenCenter(X); add(fullscreenButton); #end
HAXE -
Then we need to add our callback function for that button:
#if desktop function clickFullscreen() { FlxG.fullscreen = !FlxG.fullscreen; fullscreenButton.text = FlxG.fullscreen ? "FULLSCREEN" : "WINDOWED"; save.data.fullscreen = FlxG.fullscreen; } #end
HAXEYes, we wrap the entire function in our conditional - if we're not building for
desktop
, this function will not exist. You'll also notice that we are saving the value ofFlxG.fullscreen
- this will be used to 'remember' the game's screen state when they next launch the game. -
Next, let's change around our
Main.hx
so that when the game starts it will launch into whatever state the game was last in - fullscreen or not. We need to move things around a little, since we need to find out if we're going into fullscreen or not BEFORE we callnew FlxGame()
, but we need to set the volume AFTER, sonew()
should look like this:var startFullscreen:Bool = false; var save:FlxSave = new FlxSave(); save.bind("TurnBasedRPG"); #if desktop if (save.data.fullscreen != null) { startFullscreen = save.data.fullscreen; } #end super(); addChild(new FlxGame(320, 240, MenuState, 1, 60, 60, false, startFullscreen)); if (save.data.volume != null) { FlxG.sound.volume = save.data.volume; } save.close();
HAXE -
Next, we need to give players a way to exit the game if they're in fullscreen mode - since they won't easily have access to the close-button anymore.
We do that by adding a new button to theMenuState
:#if desktop var exitButton:FlxButton; #end
HAXEAnd in
create()
:#if desktop exitButton = new FlxButton(FlxG.width - 28, 8, "X", clickExit); exitButton.loadGraphic(AssetPaths.button__png, true, 20, 20); add(exitButton); #end
HAXEThen our callback function just looks like:
#if desktop function clickExit() { Sys.exit(0); } #end
HAXE
There you go! Now try it out on both HTML5 and Windows targets and notice the differences - you can't go fullscreen in HTML5, and you can in Windows. You also get the button to exit the game on the MenuState while on Windows.
To make our game work with Android, we have to make a few more changes. First, we need to make sure that our Project.xml
is setup correctly so that our settings are right for Android.
-
In your app tag, at the very top of the file, add this attribute:
package="com.haxeflixel.turnBasedRPG"
XMLThis will give your app a package name to be used on the Android device - it's best to be as unique as possible.
-
You might be wondering how we're going to allow the user to move around in our game on a device without the keyboard or mouse. Well, HaxeFlixel comes with a
FlxVirtualPad
class which we can use to accept touch input from the user and translate it into movement. Head over toPlayState.hx
:Define:
#if mobile public static var virtualPad:FlxVirtualPad; #end
HAXECreate and add (in create):
#if mobile virtualPad = new FlxVirtualPad(FULL, NONE); add(virtualPad); #end
HAXE -
Since the player can't move while they're in combat, we're going to remove the distraction of our virtual pad while combat is happening. In the
startCombat()
function, add:#if mobile virtualPad.visible = false; #end
HAXEAnd, to make it reappear, in
update()
, somewhere around where we setinCombat
tofalse
, add:#if mobile virtualPad.visible = true; #end
HAXE -
The only place where we actually check for button presses is in our
Player
class, in theupdateMovement()
function. We currently have a section of code that is setting different gs to true based on keyboard input - but since the keyboard might be disabled on some platforms, we need to change that to look like this:#if FLX_KEYBOARD up = FlxG.keys.anyPressed([UP, W]); down = FlxG.keys.anyPressed([DOWN, S]); left = FlxG.keys.anyPressed([LEFT, A]); right = FlxG.keys.anyPressed([RIGHT, D]); #end
HAXE -
Right after that logic, we'll add our logic that checks each of the buttons of our virtual pad to see which ones are pressed:
#if mobile var virtualPad = PlayState.virtualPad; up = up || virtualPad.buttonUp.pressed; down = down || virtualPad.buttonDown.pressed; left = left || virtualPad.buttonLeft.pressed; right = right || virtualPad.buttonRight.pressed; #end
HAXEBecause of the way we've structured our movement logic, we don't need to make any other changes! Getting
up
set to true via keyboard or our virtual pad will do the same thing. -
Things are a little different for our combat screen. We could setup the virtual pad to work with our combat menu, but that would feel a little clunky, given that the user has the power to tap on whatever they want on the screen. Instead, we're going to utilize touches to figure out what the player wants to do.
This means, in our update function, we need to wrap everything that's currently inside
updateKeyboardInput()
with:#if FLX_KEYBOARD ... #end
HAXE -
Inside of our
if (!wait)
statement, add:updateTouchInput();
HAXEAnd
function updateTouchInput() { #if FLX_TOUCH for (touch in FlxG.touches.justReleased()) { for (choice in choices.keys()) { var text = choices[choice]; if (touch.overlaps(text)) { selectSound.play(); selected = choice; movePointer(); makeChoice(); return; } } } #end }
HAXEWhat this does is gets a list of all the touches that were just released this update and checks them one at a time to see if any were on top of one of our choices texts. If one of them is, we play our select sound, and act on the selection - ignoring any other touches that might have happened this update.
And that's everything! You should be able to run your game on HTML5, Windows (Neko, too!), and Android and have it work on each with slight variations. And you only have one project and one codebase!
Next we'll polish up our game and add a little juice to make it POP!