August 3 update notes

A couple days ago, I released a new version of Run 3, stretching the definition of “balance update” in the process. Here are the things I “balanced”:

+ The Student
+ The Gentleman
+ The Child
+ The Angel
+ The Runner
+ The Student’s unlock cost
+ The Gentleman’s animations
+ The “free respawn” upgrade
+ Cloud saves
+ Infinite mode
+ Infinite mode’s end-of-run statistics
+ The level editor

− The Student
− The Bunny
− The Lizard’s, Bunny’s, and Child’s unlock costs
− The Lizard’s sleepiness
− Part 24 of the Low-power Tunnel
− The length of a meter in Infinite mode (for compatibility, the leaderboards are unchanged)
− The box-pushing challenges

In this post, I’ll be going into detail about what the changes were and why I made them. Well, except for the Student, who I’ll address in a separate post.

The Gentleman

is now a bit better at maneuvering and has a higher maximum speed. When playing as him, batteries spawn about 1% closer together.

That last part may not seem like much, but it adds up faster than you’d expect. I need to be very careful about these sorts of changes, because with enough batteries, the Gentleman could go forever without touching the ground.

The first change gives him more control over which way he goes after collecting a battery, meaning you’re less likely to have to jump off the battery. The second and third changes help boost his batteries collected per minute.

I made these changes because I designed the Gentleman as the most efficient way to collect batteries, but most players felt it was too hard to keep the batteries. I did intend for him to be hard to use (because if he was easy and efficient, he’d be the only choice), but not so hard that everyone avoided him.

We’ll see how much these changes help.

The Child

no longer has to “bounce” multiple times to reach his full jump height. (A “bounce” is when he jumps instantly upon hitting the ground, like the Bunny does.) In the previous update, he bounced a bit higher each time: 5% on the first bounce, 8% on the second, and 10% after that.

I decided that was unnecessarily complicated, that people would almost never do three bounces, and that the 5% difference was too subtle. Now, instead of building up height, he skips straight to the third bounce.

In this version, when the Child bounces, he always pushes 10% harder than normal. Thanks to physics, this means he jumps ~18% higher, and the extra height lets him float ~41% farther. Not bad!

The Angel

now gets a little more speed when he dashes. Also, he doesn’t slow down as much at the end of a dash, so by dashing repeatedly, he can break his usual speed limit.

This was only a small tweak, meant to emphasize the Angel’s strengths. The difference probably isn’t obvious, but it’s there.

The Runner

performs better while on the ground. She runs faster, runs sideways faster, and is better at changing direction. As a result, she can now earn The Lazy Way.

The tough part about designing the Runner is that she has to seem bad at first. When you unlock the Skater at level 10, I want him to feel like a big improvement, but as you get better at the game, I want you to realize that the Runner is a solid choice too.

I’ve decided to approach this by making the Runner good in subtle ways. She can’t make big jumps, but she’s the best at small jumps, and sometimes, that makes a big difference. Maneuverability isn’t the most important trait, but it matters when you suddenly realize you need to change course, and the Runner is good at it.

This latest change follows the same reasoning: you don’t usually stay on the ground very long, but when you do, the Runner will benefit more than most other characters. And it just makes sense: of course the Runner should be extra-good at running.

The Student’s unlock cost

is now 10000 rather than 4000, since the Student is one of the better characters in the game. I still want all the characters to be viable, but the Student is – and will continue to be – above average.

She went to the trouble of understanding the Tunnels, and used this knowledge to make a device to make her life easier. Others, like the Duplicator and Pastafarian, just take what they’re given without really questioning it (oh, and the Skater knowingly sabotages himself). In my opinion, the Student ought to have at least some advantage.

The Gentleman’s animations

are not actually new. I made them over a year ago, and they’ve actually been in the game for most of that time. You can see him do a backflip in one cutscene (click the battery on the map to see it). The new part was adding those animations to his gameplay.

Have you seen him do a full flip yet? It’s possible if you set it up right!

The “free respawn” upgrade

now lowers all respawn costs by 10, instead of just the first.

Cloud saves

are now more reliable. In the old version, they wouldn’t necessarily trigger, even if there was new data to save. Now they do. Yay!

Infinite mode

gives you a greater variety of levels early on. This also means it’ll probably start out harder, but I think it’s worth it.

Plus, I added more levels to the pool, which adds even more variety. (For the record, I do this every update, even if I don’t mention it in the update notes.)

Infinite mode’s end-of-run statistics

are better in every way. Let’s be honest: just seeing the batteries/minute statistic wasn’t enough, and it’s annoying that it replaced the gameplay tips and story tidbits. The new UI shows you a bunch more information, and lets you see per-character information while you’re at it.

Let me know if there’s any other statistics you think the game should track.

The level editor

now shows you the route you took the last time you playtested. Also, when you stop playtesting, the camera scrolls to where you were when you quit, letting you tweak that part.

The Bunny

slows down a tiny bit faster, and fewer batteries spawn in Infinite mode when playing it.

While testing the new statistics, I noticed that I was getting batteries faster with the Bunny than the Gentleman, which should never happen. I want the Bunny to be the best at many things (speed, jump distance, jump height, changing direction in midair), but it shouldn’t get lots of batteries on top of that.

I think what happened is that since the Bunny was so hard to use, I was fine giving it lots of batteries. Then I kept buffing it for balance, and I forgot to decrease the batteries. Oops!

The Lizard’s, Bunny’s, and Child’s unlock costs

are lower, except not quite. It seems I forgot to update the Bunny’s cost, so it’ll have to wait until the next release. (I’m going to set it to 2000, like the Child.)

Since there’s another way to get all three of these characters, they don’t need to be as expensive as they were. The Lizard was 2000, the Bunny was (is) 4000, and the Child was 6000, but most players would unlock them in Explore mode before earning that many batteries. Plus, if they did buy the Child, they might regret it once they finished the Low-power Tunnel. I certainly don’t want people feeling like they wasted 6000 batteries…

The Lizard’s sleepiness

has gone down by 67%. That is, it used to sleep for a half hour, and now it only sleeps for ten minutes.

Part 24 of the Low-power Tunnel

got a few more tiles at the end. Since you only get a brief flash of light, these tiles make it easier to spot the final platform. Plus, they give you a little more leeway to keep you from running off the side of that platform.

The rest of the level is still just as hard, but that’s ok. It isn’t so frustrating when you fall at the beginning or in the middle, because at least you don’t have to replay the entire thing. If you do fall at the end of a level as long as this, it needs to feel like you got a fair chance.

The length of a meter in Infinite mode

is now half of what it used to be. In other words, the distance counter ticks up twice as fast. In other other words, the average tile is now 2.5 meters long, not 1.25.

This change has been a long time coming, and the reason I didn’t do it earlier is because I was worried about messing up highscores. Then I realized I didn’t have to mess up highscores; I could just divide scores by 2 before submitting them. Now Kongregate’s leaderboards show scores in units of “double-meters,” but at least they’re compatible.

I’ve been planning this change ever since I started thinking about the characters’ physiology. I realized that the game’s physics would be more believable (or at least less unbelievable) if the characters were closer to a meter tall, so I changed it. I’d picked the old length without really thinking about it, so there was no reason not to change it. Well, no reason besides the fact that it would confuse people.

In case you’re wondering, here’s how tall each character is:

The cast of Run 3
(Click to zoom in.)

The box-pushing challenges

are easier thanks to the Student’s changes. Also, the Runner’s parts are now 100% optional.

I made these changes so that more people will be able to see what comes next. Before any elitists complain, I’m not trying to make the game so easy that everyone in the world can beat it. I just don’t think a minigame should be the game’s ultimate challenge.

That’s all for now, but I’ll put a link here once I write the post on the Student. Hopefully you enjoyed this peek behind the scenes!

Scaling Strategy 2: Stage Dimensions

Instead of the current screen’s resolution, you could look at the stage dimensions.

I know that the “screen resolution” method means that one inch on your screen is one inch on theirs. That can be nice to have, especially when dealing with tablets. On the other hand, it’s not so great for small screens. A one-inch-wide button on a low-end device could fill a quarter of the screen!

Plus, you can’t even get at the screen resolution in Flash, and HTML5 may or may not be able to access that information.

The “stage dimensions” method, on the other hand, works on all platforms, handles smaller screens gracefully, and is much easier to test.

Using the stage’s dimensions

Let’s say I’ve convinced you to give this method a shot. How does it work?

The first step is to take the stage dimensions and convert them into scaleX and scaleY values. Then scale all your objects using these values.

Flash defines four stage scale modes, each representing a different way to generate scaleX and scaleY. Let’s take a look.


Click and drag in this demo to get a sense of the NO_SCALE option. (Requires JavaScript.)

Somehow I doubt this is the one you’re looking for.

Implementing NO_SCALE

You already implemented it. Moving on!


Quick: what’s the simplest way you can think of to handle scaling?

Remember, you have access to stageWidth and stageHeight, and you can set scaleX and scaleY.

If you guessed “scale x and y independently,” you’re right!

Hey, I never said it was a good way to handle scaling. I just said that it’s simple.

Implementing EXACT_FIT

var scaleX:Float = stage.stageWidth / 800;
var scaleY:Float = stage.stageHeight / 600;

Lib.current.scaleX = scaleX;
Lib.current.scaleY = scaleY;

That’s it!

Note: you can’t set stage.scaleX and stage.scaleY. In most cases, Lib.current works equally well, which is why I used that. (Just avoid adding children directly to the stage.)


Obviously, you don’t want to stretch your content like that. Somehow, you have to make sure that each individual object keeps the same proportions.

There are two main ways to do this, and NO_BORDER is one of them.

Implementing NO_BORDER

NO_BORDER considers the same two values as EXACT_FIT, but it picks only one. Specifically, it picks the larger one.

var scaleX:Float = stage.stageWidth / 800;
var scaleY:Float = stage.stageHeight / 600;

var scale:Float = Math.max(scaleX, scaleY);

Lib.current.scaleX = scale;
Lib.current.scaleY = scale;

Imagine your stage is made wider, so that scaleX comes out to 1.55, and scaleY is still 1.

NO_BORDER will choose the larger of the two values (1.55), making the stage the right horizontal size, but too large vertically.


If NO_BORDER tends to make objects too large, SHOW_ALL errs on the side of keeping them small.

Implementing SHOW_ALL

Like NO_BORDER, except with Math.min().

var scaleX:Float = stage.stageWidth / 800;
var scaleY:Float = stage.stageHeight / 600;

var scale:Float = Math.min(scaleX, scaleY);

Lib.current.scaleX = scale;
Lib.current.scaleY = scale;

Given the choice between 1.55 and 1, SHOW_ALL will choose the smaller value. This ensures that everything will fit, though there will also be extra space.

In most apps, extra space is better than objects being cut off, making SHOW_ALL the clear winner.

Scaling Strategy 1: Pixel Density

In my previous post, I explained why the Layout library isn’t suited for high-resolution screens. The short version is, that library is designed for a constant pixel density. Any layout that looked good on a high-resolution screen would look terrible on a low-resolution one, and vice versa.

The SimpleSWFLayout sample project, as it appears on a high-resolution device.
The SimpleSWFLayout sample project, as it appears on a high-resolution device.

Here, the header is not scaled vertically, which is why it appears so short. Ideally, we’d want a way to increase its height based on the device’s resolution.

Fortunately, OpenFL provides a way to access the pixel density of the current screen: Capabilities.screenDPI. Now all we need is a layout library that supports pixel density scaling. Conveniently, I happen to have made one!

To convert SimpleSWFLayout to my library, follow these instructions.

Enabling pixel-density-based scaling


Add that somewhere, and you’re done!

Also make sure not to compile for Flash or HTML5. Capabilities.screenDPI just doesn’t work on those platforms. :/

SimpleSWFLayout with DPI scaling

Same device as before, but now I can actually see the details!

Ok, so there aren’t any details to see. But if there were details, I’d totally be able to see them!

Not-so-simple SWF Layout

OpenFL provides a sample project named SimpleSWFLayout, demonstrating how you can set up a layout that scales along with the window.

How the SimpleSWFLayout looks

Three items are being resized here:

  • The background scales to fit the screen.
  • The header scales horizontally to take up almost all of the window (except for a small margin on both sides). It doesn’t scale vertically.
  • The column scales vertically to take up most of the space below the header, and it doesn’t scale horizontally. It has a larger margin below it than above it.

All this is automatic, courtesy of the Layout library!

How it works

The Layout library uses some clever tricks to make this easier on you. But if you don’t know that these tricks exist, you can’t really take advantage of them, can you? Let’s fix that.

The project is called SimpleSWFLayout because it loads its three assets from a SWF file (layout.swf, found in the asset folder). For convenience, it also includes the layout.fla file used to generate the swf.

If you have Flash Professional, you can open up layout.fla and see that it has all three items already arranged correctly. The header already has a 12 pixel margin above it, and a 16 pixel margin on each side. The column is already placed 20 pixels below the header, 16 pixels from the left, and 54 pixels above the bottom.

So all you need to do is tell the library that you want to keep these margins constant. That’s what’s going on when the example specifies TOP, STRETCH for the header and STRETCH, LEFT for the column. (Traditionally, x comes before y, but for some reason the opposite is true here. I don’t know why.)

The base stage dimensions

If you’re paying really close attention, you may notice that the background in layout.fla is 800×600, while the SimpleSWFLayout project starts at 800×500.

Don’t worry! It doesn’t need to match.

The background is what defines the “base” stage size. The margins will be calculated based on the size of the background, and then scaled to fit the actual stage size. It’s just like if the window started at 800×600 and was promptly scaled down to 800×500.

Whenever you add a new object, it will use the base stage size to figure out what margins it needs. Even if you’ve already scaled the window, it’ll refer to the background’s original width and height.

Other alignments

The example demonstrates how to align something to the left and top. But that’s not much of a demonstration, is it? To keep an object 16 pixels away from the left edge, all the Layout library has to do is not change its x coordinate.

But you can align objects to the right, bottom, or center of the screen too. If you align to the right, the Layout library will update the x coordinate so that the object moves with the right edge of the screen. However far it started from the right, that’s how far it will be after resizing. Same with aligning to the bottom.

(Well, mostly. There’s some inconsistent behavior for small windows, but I won’t get into that.)

If you choose to align an object to the center of the screen, the Layout library will remember how far it was from the center, and keep it that far. I like to think of this as “pinning” the object to the center, because it’s like you stick a pin in the object wherever it crosses the center line. Whenever the center line moves, that point on the object follows that point on the center line. (The object doesn’t actually have to touch the center line to be “pinned,” but I still like the metaphor.)

One of my first criticisms of this library was that it didn’t allow you to place objects relative to one another, and keep them that way. But that just isn’t true.

Let’s say your main menu has three buttons right next to one another. You put all three in a line just below the center, and pin them (both horizontally and vertically). When you resize the stage, all three buttons will move by the same amount in the same direction.

In a small window. In a large window.

They’re staying in the same position relative to one another, and they’re staying just below the center. Looks good!

The problem

There’s something wrong with this implementation. Have you figured it out yet?

Hint: there are only two options for scaling. Either you keep the size constant, or you fill the stage, minus a constant value.

Take a moment to think about it.

(Or just scroll down if you aren’t in the mood for games.)

The SimpleSWFLayout sample project, rendered in an enormous window.
The SimpleSWFLayout sample project, as it appears in an extra-large window. This screenshot was taken on a Nexus 6.

That right there is a lot of empty space.

The problem is those constant values I mentioned. A constant 16-pixel margin looks good enough on screens around 800×600, but when the screen is over 2000 pixels wide, it just gets lost. And a constant width and height for the column and header respectively means they take up less and less of the screen as it gets bigger.

In short, the Layout library is not, in itself, enough for your mobile layout needs. Fortunately, I’ve created a more advanced alternative. Documentation coming soon!

HXP Projects && Template Directories

If you look closely at the OpenFL “docs” page, you’ll notice it lists two options under “project files.” The first of the two is the XML format you know and love, or at least know, but what’s up with the second?

Before I answer that question, I have some questions for you to answer.

Is your project file getting out of hand? Do you wish the XML format supported string manipulation? Do you find yourself typing out multiple <set> tags to simulate the “or” operator? Have you ever tried and failed to copy a directory with the <template> tag?

If you answered “yes” to any of these questions, HXP files may be right for you. Consult your physician today!

So anyway, what are HXP files? The short answer is, they’re HX files with an extra P.

The P stands for “Project,” so the long answer is, they’re project files which let you use Haxe instead of XML. You can write functions, manipulate strings, declare variables, and even access Haxe’s standard library.

Let’s try it out!

Ok, find a sample project that uses an HXP file, and… oh. There aren’t any, are there?

Fine, we’ll use DisplayingABitmap. I’ve created an HXP version of the project file, available here. Save it as project.hxp, delete project.xml, and you’re ready to give it a try.

Lime recognizes the file format, so just run lime test flash and it’ll find the new project file. It should show the OpenFL logo in the middle of the screen.

Congratulations! Through no fault of your own, you have gotten an HXP project file to work!

Converting your XML file to HXP

Running a sample project is all well and good, but what about that much larger project file you want to convert? Or perhaps that large file that you don’t want to convert because it will be so much work, but you have to convert anyway? Either way works.

To start, let’s reduce the amount of typing you need to do. Haxe has a wonderful feature known as “functions” (you may have heard of them), and I’ve written a set of functions that match some of the tags used in XML project files. Paste them at the end of project.hxp so that you don’t have to scroll past them each time.

Here’s a conversion guide:

<set name="godMode" />
    defines.set("godMode", 1);
    //Or if you only need it for conditionals:
    var godMode:Bool = true;

<setenv name="DARK_AND_STORMY_NIGHT" />
    setenv("DARK_AND_STORMY_NIGHT", "1");
    //That second value must be a string and cannot be skipped.

<setenv name="TRUE" value="FALSE" />
    setenv("TRUE", "FALSE");
    //Please do not do this.

<haxedef name="sburb" />
    haxedefs["sburb"] = 1;
    //Please do not do this either.

<haxedef name="dump=pretty" />
    haxedefs["dump"] = "pretty";

<haxeflag name="-dce" value="std" />
    haxeflags.push("-dce std");

<section if="flash"> </section>
    if(target == Platform.FLASH) { }

<section if="godMode" unless="mobile"> </section>
    if(defines.exists("godMode") && platformType != PlatformType.MOBILE) { }

<ndll name="regexp" />

<assets path="assets/text" rename="text" include="*.txt|*xml" exclude="*.rtf" />
    includeAssets("assets/text", "text", ["*.txt", "*.xml"], ["*.rtf"]);

<certificate path="correcthorse.keystore" password="batterystaple" alias="AzureDiamond" alias-password="hunter2" />
    certificate = new Keystore("correcthorse.keystore", "batterystaple", "AzureDiamond", "hunter2");

<dependency name="GooglePlayServices" path="pathtoGooglePlayServices" />
    dependency("GooglePlayServices", "path\to\GooglePlayServices");

<dependency name="GameKit.framework" />

<dependency path="/Library/Frameworks/LameKit.framework" />
    //This is where Apple recommends you place your custom frameworks.

<include path="industrial/application.xml" />
    merge(new ProjectXMLParser("industrial/application.xml", defines));

<template path="templates/" rename="src/com/jir/research/" />
    templateFile("templates/", "src/com/jir/research/");

<template path="templates/" />
    //Currently this is not working correctly, and even if it was,
    //it wouldn't do exactly what you'd expect. So...

<template path="spam/eggs/" rename="res/camelot/" />
    templateDirectory("spam/eggs/", "res/camelot/");
    //Note: The XML version is purely hypothetical. You have to
    //use HXP to copy a directory directly.

If that isn’t enough, or if I got something wrong, refer to the source. (Also be sure to let me know.)

One step at a time

If this is too much work to do all at once, then keep your old project file around, and import it into the new one:

merge(new ProjectXMLParser("old_application.xml", defines));

With that done, you’re free to move items over one at a time. Just be warned that some features, such as the <template> tag for folders, may quit working correctly. If this happens, focus on converting them first.

OpenFL Template Basics: Overriding AndroidManifest.xml

What do you do if you need to change AndroidManifest.xml, and OpenFL’s customization options aren’t enough? Sure you can add permissions using <config:android permission="android.permission.WHATEVER" />, and change the app title, and so on…

But what if you want to support multiple screen sizes? You need to include a <supports-screens> tag, or Google may hide your app from tablet users! This is yet another of the billions of scenarios that OpenFL fails to account for. (Sheesh, OpenFL dev team, get a move on.)

What you need is full control of the AndroidManifest.xml, but if you change it in the Lime project, it’ll just get reverted when you update to a new version. And making an extension for this is just plain overkill.

Creating Your Own Manifest File

Before you can worry about overriding AndroidManifest.xml, you’re going to need something to replace it with. Ideally something as similar as possible, to avoid breaking anything. Start by getting a copy of Lime’s template file. (For the record, if you want to replace a different template file, search this folder.)

Save your copy of AndroidManifest.xml somewhere in your project. I suggest making a “templates” folder for files like this, but you can put it wherever. Open up your copy of the file and add the following right before </manifest>:

        android:xlargeScreens="true" />

Using Your Manifest File

Finally! Enough preparation, it’s time to circumvent the natural order and impose our will upon OpenFL! No longer shall we be bound by the constraints imposed upon us! Together we shall rise up, take control, and-

What’s that? “Get on with it”? Oh fine…

Type this in your project.xml file:

<template path="templates/AndroidManifest.xml" rename="app/src/main/AndroidManifest.xml" />

Which Files Can be Overridden?

To answer this question, first compile your project for Android, and open Export/android/bin (or bin/android/bin). Take a good look – everything the light touches is your kingdom. By which I mean you can override the files in that folder.

However, you need to make sure put your file in the same folder as the file you’re overriding. is located in app/src/main/java/org/haxe/lime, so you’d put this in project.xml:

<template path="templates/" rename="app/src/main/java/org/haxe/lime/" />

OpenFL Extensions

For the most part, OpenFL does an excellent job of providing you with the features you need in a platform-independent manner. It tells you the screen size, loads your assets, and even maps Android’s back button to the escape key.

Unfortunately, the OpenFL dev team cannot think of everything. Maybe you want to adjust the screen brightness on Android. Or the music volume. Maybe you want access to the camera on iOS. Or maybe you need to integrate a custom library used internally at your company, which the OpenFL dev team could not possibly have integrated for you.

For simplicity, I’ll be using screen brightness as an example. Setting this can be done in only 1-3 lines of code on both iOS and Android. The catch is, neither of those examples are in Haxe, and there’s no way to convert them to Haxe. If only you’d written the app “normally” rather than using OpenFL, you could just copy-paste those few lines of code, and you’d be done! But no, you wanted luxuries like cross-platform compilation, and now you have to somehow use Haxe code to invoke functions in Objective-C and Java.

Fun Fact

Did you know, when you compile for Android, OpenFL creates a complete, self-contained Android project, and then tells the Android SDK to compile that? And when compiling for iOS, it creates an Xcode project, and then has Xcode do the remaining work?

You can see for yourself by checking your output folder (probably either bin or Export; I’ll assume the former). Dig into bin/android/bin, and you’ll find all the files and folders you’d expect to find in a normal Android project. You could even, if you felt bold enough, modify the project, and compile it directly using the Android SDK. (Warning: Don’t actually do this! OpenFL will almost certainly overwrite your changes.)

The same applies to iOS – after compiling, you can check out bin/ios/bin to see the project that OpenFL created. You could try modifying this too, but again, OpenFL is going to revert your changes. There has to be a better way.

Creating an Extension

The OpenFL team is well aware of this problem, and in their infinite wisdom they created the “extension” feature. Also in their infinite wisdom, they wrote no documentation whatsoever before moving on to the next feature.

Extensions are basically mini-projects consisting of native (or Java) code, as well as Haxe bindings. You include them in your project, and then you can call the native (or Java) code by calling the Haxe bindings. Let’s look at an example.

Start by running the following:

$ lime create extension SetBrightness

I’m calling it “SetBrightness” because that’s all we’ll be doing here. You can call it something else.

Open up the folder, and you’ll find the following structure:

  • haxelib.json – Allows you to submit to Haxelib.
  • include.xml – Like project.xml in your main project, this tells OpenFL what to do with all the other files here.
  • SetBrightness.hx – Contains Haxe bindings for your Java and C++ functions. At first, everything in here will be sample code.
  • dependencies/
    • android/ – An Android library to be included in your Android builds. This is what lets you include Java code. All files in this folder are processed as templates, so you can use that syntax.
      • build.gradle – A project file for this Android library. Leave this alone unless you’re familiar with Gradle.
      • src/ – Despite the name, you can’t just put source files in here. They actually go in a child folder.
        • main/
          • AndroidManifest.xml – The manifest file for your Android library. If your extension requires permissions, this is the place to put them.
          • java/ – Java source files go here.
            • org/haxe/extension/
              • – The recommended place for your Java code. It comes with useful callbacks for monitoring the activity lifecycle, or you can ignore all that and write static functions.
  • ndll/ – When you compile your C++ code, the result will go in here.
    • Linux/
    • Linux64/
    • Mac/
    • Mac64/
    • Windows/
  • project/ – The root folder for your C++ project.
    • Build.xml – Build file for your C++ project. Only files named here (or included from those named here) will be compiled.
    • common/ – C++ source files (but not header files) go here.
      • ExternalInterface.cpp – Registers your C++ functions, allowing SetBrightness.hx to access them.
      • SetBrightness.cpp – Put C++ code here, based on the sample code that starts here.
    • include/ – C++ header files go here.
      • Utils.h – Header file for SetBrightness.cpp. Functions must be declared here in order for ExternalInterface.cpp to access them.

Writing Code for Android

Click through all the folders under dependencies/ until you reach Add the following code:

public static void setBrightness(float brightness) {
    WindowManager.LayoutParams layout = Extension.mainActivity.getWindow().getAttributes();
    layout.screenBrightness = brightness;

That’s all well and good, but how do you call this function? The answer… is JNI. *Dramatic thunder crashes* Actually, it’s not that bad if you’re only dealing with one function. Climb your way back to the root SetBrightness/ folder, and add this to SetBrightness.hx:

#if android
public static function setBrightness(brightness:Float):Void {
private static var setbrightness_set_brightness_jni = JNI.createStaticMethod("org.haxe.extension.SetBrightness", "setBrightness", "(F)V");

That’s still a little much. Fortunately, shoe[box] came up with an easier way. Start by including the “inthebox-macros” library in your project, change the package in SetBrightness.hx to org.haxe.extension, and add @:build(ShortCuts.mirrors()) just before the class declaration. Now the code above can be replaced with this:

#if android
@JNI public static function setBrightness(brightness:Float):Void;

All that’s left is to include the extension in your project (see below), and you can call SetBrightness.setBrightness(0.8); from Haxe.


When you try to use this extension on Android, you’ll run into a few errors. First, a compile error:

Error: Source path "[path]/SetBrightness/ndll/Android/" does not exist

This happens because you aren’t compiling an ndll for Android, but by default Lime expects you to. To fix the error, go into include.xml and replace <ndll name="SetBrightness" /> with <ndll name="SetBrightness" unless="android" />.

Next, you’ll get a runtime error:

android.view.ViewRootImpl$CalledFromWrongThreadException: Only the original thread that created a view hierarchy can touch its views.

(The only thing that matters about this error message is that it contains the word “thread.” When developing an OpenFL extension, all thread-related errors have the same solution.)

On Android, some tasks have to be done on the main thread. When you use JNI, it runs on a thread other than the main one. Fortunately, lets you get back to the main thread.

This function takes a Runnable object, so you’ll have to create one of those. Take all the code in your function, and put it inside the run() function:

public static void setBrightness(float brightness) { Runnable() {
        @Override public void run() {
            WindowManager.LayoutParams layout = Extension.mainActivity.getWindow().getAttributes();
            layout.screenBrightness = brightness;

(Remember, you only need to do this if you get a thread-related error. Usually, it isn’t worth the trouble.)

And that’s it for Android. On to iOS!

Writing Code for iOS

You’ll notice that the extension is set up for C++ code, but to access system properties like brightness, you need to use Objective-C code. Fortunately, this part’s easy: just change the .cpp file extensions to .mm. You’ll also need to update their names in Build.xml. And because Objective-C is specific to iOS, I suggest disabling them for everything else.

<compilerflag value="-Iinclude" if="iphone"/>

<file name="common/" if="iphone"/>
<file name="common/" if="iphone"/>

If you need C++ code on other platforms, just disable the C++ files on iOS:

<file name="common/ExternalInterface.cpp" unless="iphone"/>
<file name="common/SetBrightness.cpp" unless="iphone"/>

Now to write some actual Objective-C code! Put this in

void setBrightness(float brightness) {
    [[UIScreen mainScreen] setBrightness:brightness];

Now update Utils.h:

void setBrightness(float brightness);

Don’t forget to update

static void setbrightness_setBrightness (value brightness) {
DEFINE_PRIM (setbrightness_set_brightness, 1);

Last but not least, create the Haxe bindings in SetBrightness.hx:

#if ios
public static function setBrightness(brightness:Float):Void {
private static var setbrightness_set_brightness = Lib.load ("setbrightness", "setbrightness_set_brightness", 1);

Phew! That was a lot of updating. [Edit: inthebox-macros can generate the Haxe code, and I wrote a utility to generate and Build.xml.]

Time to compile!

$ lime rebuild . ios

Including the Extension

Almost done! All that’s left is to include it in your project.

This can actually be done in two ways. The easy way works if you’re only using one machine, or if the relative path is the same on all machines (for example, if the extension project is contained within your main project). All you have to do is add this to your project.xml or application.xml file:

<include path="path/to/SetBrightness" />

The slightly harder way is to register it with Haxelib by running the following from the root of the extension:

$ haxelib dev SetBrightness .

Then include it:

<haxelib name="SetBrightness" />