Advertisement

Write Once, Publish Everywhere With HaxePunk: Cross-Platform Tips

by

This Cyber Monday Tuts+ courses will be reduced to just $3 (usually $15). Don't miss out.

Welcome to the second part of this tutorial series on making cross-platform games with HaxePunk. When we left off, we had finished creating a simple drag-racing game that can be compiled for many different platforms. In this second part, I'll give some tips to make your games work well on multiple types of devices. We'll talk about screen sizes and resolutions, input types, interface layouts, and tips for app store submissions.

Screen Sizes and Resolutions

The screen is the window into your game, and should not be left as an afterthought. Think about the devices you plan to release your game on. A Windows/Mac/Linux release can usually depend on the user having a screen large enough to fit the game in windowed mode, and can letterbox any resolution differences in fullscreen mode. 

Mobile devices are quite different. There are many screens of various resolutions and sizes. You can't guarantee that the player will play on a device with the same resolution as your game. Scaling is going to happen.

In the first article in this series, I walked through the development of a small example game. You can download the full source code project using the button to the right of this article. Last time, you might have noticed statements like this:

y = -image.scaledHeight;

There are width and height properties for images, and there are scaledWidth and scaledHeight properties as well. The meanings of the width and height of an image is obvious. The scaled properties are a little more complex. The scaledWidth property is the image's width multiplied by the scale of the image multiplied by the scale of the game, and scaledHeight is similar, but for height.

However, this gets a little confusing when the game is scaled automatically, as might happen on an Android device with a lower screen resolution than the game was built for. In a situation like this, the scale property that HaxePunk reads to set the scale of images, and thus their scaledWidth/Height, will likely not be set correctly. 

In fact, there is often no scaling at all, just a shrinking of the screen. To fix this, we can calculate the amount of scaling we would like, based on the resolution of the game and the resolution of the screen that the game is running on. In Main.hx we can add this code before we set the active scene:

var ratio:Float = Math.min(HXP.stage.stageWidth / screenWidth, HXP.stage.stageHeight / screenHeight);
HXP.screen.scaleX = ratio;
HXP.screen.scaleY = ratio;
HXP.resize(HXP.stage.stageWidth, HXP.stage.stageHeight);

In the above code, screenWidth and screenHeight are variables I've created and have set to the width and height I used for the game. You could also simply use constants such as 640 and 480, but I prefer to use variables.

The code uses the Math.min() function to set the ratio variable to the lowest difference in screen dimensions to prevent graphics from being stretched if the differences in width and height are not equal. You may want to allow stretching, in which case you will need to set HXP.screen.scaleX and scaleY to different values.

After the ratio is calculated, HXP.resize() is called. This function is what actually resizes the screen.  You might also want to save the ratio(s) for use elsewhere, but I have rarely found it necessary.

With the screen resized, we are still able to do things like this:

//place entity in lower-right corner of screen, whatever the size
entity.x = HXP.screen.width - entity.scaledWidth;
entity.y = HXP.screen.height - entity.scaledHeight;

This lets us have a consistent user interface for a game across many devices.

Game Orientation

In the previous tutorial, I talked about the project.xml file, which lets us easily configure many aspects of our game. Among other things, we can set the orientation of the game, which is useful for mobile devices. For example, if we wanted our game to run in portrait mode on mobile devices but in landscape mode on desktop:


<window orientation="portrait" if="mobile" />
<window orientation="landscape" unless="mobile" />

Input and Conditional Compilation

Input differs quite a bit between types of devices. It's unreasonable to expect the player to attach a bluetooth keyboard and mouse to play a game on their phone, and it is unlikely even today that a desktop will be equipped with a touchscreen.

In the example game from the previous tutorial, I used HaxePunk's Key class to check for keypresses. On touchscreen devices, however, it would make make sense to not import the Key class, keeping the size of the game lower, because we will use touch controls.

Haxe makes this easy for us by letting us use conditional compilation. It works like this:

#if condition
var x = 0;
someFunction();
#elseif another_condition
var y = 1;
#else
var z = 2;
anotherFunction();
#end

The conditions are evaluated at compile time, which means that we can include or exclude code depending on the platform we are targeting! To exclude the Key class when targeting mobile devices, simply do this:

import com.haxepunk.utils.Input; //you'll probably still want this, because it handles input for all types of devices
#if !mobile
import com.haxepunk.utils.Key;
#end

//We also want to remove any keyboard defines we might have, like so
#if !mobile
Input.define("left", [Key.A, Key.LEFT]);
Input.define("right", [Key.D, Key.RIGHT]);
#end

Notice the ! (not) logical operator used above. We can also use && (and) as well as || (or) in conditional compilation. If you wanted to include code for mobile devices but not for iOS, you could say

#if (mobile && !ios)
//code here
#end

As you can see, conditional compilation is quite powerful! Let's go back to input handling for a minute. Excluding the Key class when compiling for targets with a touchscreen is nice, but it doesn't automatically check for touch input. 

Using the racing game from the last tutorial as an example, we could change the input checking from this:

if(Input.pressed("left"))
{
	move("left");
}
if(Input.pressed("right"))
{
	move("right");
}

To this:

#if mobile
if(Input.mousePressed)
{
	if(Input.mouseX < HXP.screen.width * .5)
	{
		move("left");
	}
	else
	{
		move("right");
	}
}
#else
if(Input.pressed("left"))
{
	move("left");
}
if(Input.pressed("right"))
{
	move("right");
}
#end<br>

Now the keyboard will be used to control movement on desktop platforms, and touching the left or right side of the screen will control movement on mobile platforms!

If you'd like, you can also specify your own keywords to use with conditional compilation, both in the source code of the game and in the .xml project file.

#if myOwnKeyword
aCoolSecretForWhateverReason();
#end

To include the code above, you can simply pass an option when compiling:

lime test <platform> -DmyOwnKeyword

This could be used to mark review copies or beta releases as such inside the game itself, to discourage leaks or beta test different parts of the game with different people. It could also be used to create a demo version that limits areas of the game, or even used to make a personal version as a surprise gift.

Submitting To Multiple App Stores

After you've made a great cross-platform game, the next step is to release it on various app stores and marketplaces. Each marketplace will have different requirements, but there are things you can do that will apply to all (or at least the vast majority of) marketplaces. Of course, the best advice I can offer in this area is to carefully read the submission instructions to find out what each marketplace wants of you.

An obvious requirement is providing screenshots. The number and required resolution will vary, but each marketplace should tell you what they want. I'd recommend providing absolutely no less than two screenshots, but preferably four or more.

Just as the requirements for screenshots will vary, so will the requirements for icons. Some stores will want a lower-resolution icon as well as a higher-resolution icon, so it's best to start with a large icon that can be scaled down to different sizes.

Some stores will also allow you to upload one or more videos. I'd suggest creating one video that demonstrates the basics of your game when submitting to mobile app stores, and at least one video for other marketplaces (Steam, Desura, etc). Remember: if a picture is worth a thousand words, and a video can be thought of as many pictures played in sequence, a video is quite valuable!

There are also several pieces of information that should be required on any store you submit a game to: title, description, icon, and category. It's helpful to players (potential or otherwise) if this information is the same across all platforms.

Now You Can Publish Everywhere

After building a game with OpenFL, the binary should be ready to submit to any marketplace for the platform you built for with no changes needed. Just remember to build and submit a release version, not a debug version!

Now that you've seen ways to make your games work well on a variety of devices, I hope you'll put this knowledge to good use and make some cool games! One last word of advice: finishing a game is great, and something to be proud of, but releasing a game can take just as much work!

Advertisement