For one game I need ability to change screen orientation (portrait or landscape) at runtime. For Andriod and iOS targets.
I searched for ready-to-use extension, but found nothing. I’m not going to say that such extension does not exists, but I spent more than a an hour, and still found nothing.
Finally I decide to write my own.
Requirements
- It must work on Android and iOS;
- It must work for lime legacy (we still use legacy, because it is more stable);
- Should work without legacy (potentially).
At first let’s gogole how to change screen orientation.
Changing screen orientation on Android
Really super simple, in one line of code:
mainActivity.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_XXX);
Changing screen orientation on iOS #1
That’s harder. On iOS 5 and earlier you can use:
[[UIDevice currentDevice] setOrientation:UIInterfaceOrientationXXX]
But in iOS 6 setOrientation
is deprecated, you should use following code inside view controller:
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation
{
return UIInterfaceOrientationXXX(interfaceOrientation);
}
Moreover, shouldAutorotateToInterfaceOrientation
is deprecated in iOS 7, and you should use:
- (NSUInteger)supportedInterfaceOrientations {
return UIInterfaceOrientationMaskXXX;
}
The one good thing in all this – we don’t need that at all 🙂
Changing screen orientation on iOS #2
After grepping through openfl and lime sources, I found:
- Legacy mode already have such function –
Stage.setFixedOrientation(Stage.OrientationXXX)
- Non-legacy use SDL2, so we can use
SDL_SetHint(SDL_HINT_ORIENTATIONS, "XXX")
to set orientation
Making an Extension
To create base files for extension just run:
lime create extension NameOfYourExtension
This command will create following:
dependencies/
– folder for dependencies;android/
– folder for android; sub-projectsrc/
– .java files;org/
haxe/
extension/
NameOfYourExtension.java
– code for android extension;
AndroidManifest.xml
– simple android manifest;build.xml
– build file for android part;project.properties
ndll/
– .ndll files will be here;project/
– folder for native extension part;common/
– .cpp files (you can change that later in Build.xml);ExternalInterface.cpp
– bridge between haxe and native code;NameOfYourExtension.cpp
– super simple extension example;include/
– .h files (you can change that later in Build.xml);Utils.h
– function prototypes for NameOfYourExtension.cpp (in my point of view it should have name NameOfYourExtension.h instead of Utils.h, we can change that later);Build.xml
– build file for hxcpp;
NameOfYourExtension.hx
– haxe code;haxelib.json
– simple config for haxelib;include.xml
– how extension will be included.
In my case I used EsOrientation
as extension name.
Step 1
At first I moved haxe code from root package to extension.eightsines
, I like when every class have package.
If you also want to do this, create following folders structure:
extension
eightsines
EsOrientation.hx
And modify haxe code:
package extension.eightsines; // cool package
#if cpp
import cpp.Lib; // to use native functions
#end
// I removed loading libs for neko, because this extension is only for Android and iOS
#if android
import openfl.utils.JNI; // to use java functions
#end
#if legacy
import openfl.display.Stage; // to change orientation in legacy
#end
class EsOrientation {
// Avail orientations
public static inline var ORIENTATION_UNSPECIFIED : Int = 0;
public static inline var ORIENTATION_PORTRAIT : Int = 1;
public static inline var ORIENTATION_LANDSCAPE : Int = 2;
// ... more code will be here ...
public static function setScreenOrientation(screenOrientation : Int) : Void {
// ... more code will be here ...
}
}
Step 2
Let’s make part for Android.
package org.haxe.extension.esorientation; // I moved file from org.haxe.extension package to it own package
import android.content.pm.ActivityInfo;
import org.haxe.extension.Extension;
public class EsOrientation extends Extension {
// Same values as in Haxe code.
public static final int ORIENTATION_UNSPECIFIED = 0;
public static final int ORIENTATION_PORTRAIT = 1;
public static final int ORIENTATION_LANDSCAPE = 2;
public static void setRequestedOrientation(int haxeScreenOrientation) {
int requestedOrientation;
// Map haxe values to android values
switch (haxeScreenOrientation) {
case ORIENTATION_PORTRAIT:
requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_PORTRAIT;
break;
case ORIENTATION_LANDSCAPE:
requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE;
break;
default:
requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED;
}
// Super simple one-liner.
// Extension.mainActivity is reference to game activity
Extension.mainActivity.setRequestedOrientation(requestedOrientation);
}
}
Also I moved file from original package. If you also want to move file to different package, you should change folders structure and modify package
attribute inside AndroidManifest.xml
:
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="org.haxe.extension.esorientation">
</manifest>
Also modify include.xml
in extension folder:
<?xml version="1.0" encoding="utf-8"?>
<project>
<!-- ... code for iOS will be added later ... -->
<dependency name="es-orientation" path="dependencies/android" if="android" />
<android extension="org.haxe.extension.esorientation.EsOrientation" />
</project>
Step 3
Let’s link android code with haxe code.
// below last "public static inline var ..."
#if android
private static var setRequestedOrientationNative = JNI.createStaticMethod(
"org.haxe.extension.esorientation.EsOrientation", // fully qualified class name
"setRequestedOrientation", // method name
"(I)V" // JNI method signature
);
#end
...
// inside setScreenOrientation()
#if android
setRequestedOrientationNative(screenOrientation);
#end
That’s all 🙂 Android part should work now. I really like how quick and simple you can integrate android code with haxe code.
P. S. More info about JNI method signatures
Step 4
As legacy mode doesn’t require any code (spoiler: that’s lie), just write Haxe part:
public static function setScreenOrientation(screenOrientation : Int) : Void {
#if android
setRequestedOrientationNative(screenOrientation);
#elseif legacy
switch (screenOrientation) {
case ORIENTATION_PORTRAIT:
Stage.setFixedOrientation(Stage.OrientationPortraitAny);
case ORIENTATION_LANDSCAPE:
Stage.setFixedOrientation(Stage.OrientationLandscapeAny);
default:
Stage.setFixedOrientation(Stage.OrientationAny);
}
#end
}
Step 5
Android part should work well for both legacy and non-legacy modes, but iOS part require different approach.
At first, open .h file and change original “int SampleMethod(int inputValue)” to something we need:
#ifndef ES_ORIENTATION_H
#define ES_ORIENTATION_H
namespace EsOrientation {
#if defined(IPHONE) // I added guard #if for iphone
void setRequestedOrientation(int screenOrientation);
#endif
}
#endif
Than write real function in .cpp file:
#include "SDL.h"
#include "EsOrientation.h"
// Same values as in Haxe code
#define ORIENTATION_UNSPECIFIED 0
#define ORIENTATION_PORTRAIT 1
#define ORIENTATION_LANDSCAPE 2
namespace EsOrientation {
void setRequestedOrientation(int screenOrientation) {
if (screenOrientation == ORIENTATION_PORTRAIT) {
SDL_SetHint(SDL_HINT_ORIENTATIONS, "Portrait");
} else if (screenOrientation == ORIENTATION_LANDSCAPE) {
SDL_SetHint(SDL_HINT_ORIENTATIONS, "LandscapeLeft LandscapeRight");
} else {
SDL_SetHint(SDL_HINT_ORIENTATIONS, "Portrait LandscapeLeft LandscapeRight");
}
}
}
And finally mofify ExtrenalInterface.cpp
:
// ... includes and defines, see full code at github ...
static value es_orientation_set_requested_orientation(value screenOrientation) {
#if defined(IPHONE)
setRequestedOrientation(val_int(screenOrientation));
#endif
return alloc_null();
}
extern "C" void es_orientation_main() {
val_int(0); // Fix Neko init
}
extern "C" int es_orientation_register_prims() {
return 0;
}
DEFINE_PRIM(es_orientation_set_requested_orientation, 1);
DEFINE_ENTRY_POINT(es_orientation_main);
Our main function is es_orientation_set_requested_orientation
. This function will be used in haxe, so it must return value
. Even if you don’t want to return anything, you must return alloc_null()
. Also every parameter has value
type. To get real value, you must use special functions. For example to get value of type int
you should use val_int()
.
Next you must write two magic functions – es_orientation_main
and es_orientation_register_prims
🙂 I don’t really know what they do. And actually you don’t even need to write them – they already generated by lime create extension
.
To be able to use C-function in Haxe, you must use DEFINE_PRIM
macro. First parameter is function name, second parameter – parameters count. Out function has one parameter – so second parameter must be = 1.
Step 6
To be able to build it, you should modify Build.xml
inside project
folder:
<?xml version="1.0" encoding="utf-8"?>
<xml>
<include name="${HXCPP}/build-tool/BuildCommon.xml" />
<set name="DEBUGEXTRA" value="-debug" if="fulldebug" />
<files id="common">
<compilerflag value="-Iinclude" />
<file name="src/ExternalInterface.cpp" />
</files>
<files id="ios">
<compilerflag value="-Iinclude" />
<compilerflag value="-Iinclude/SDL2" />
<compilerflag value="-Iinclude/SDL2/configs/default" />
<file name="src/ios/EsOrientation.mm" />
</files>
<target
id="ndll"
output="${LIBPREFIX}es_orientation${DEBUGEXTRA}${LIBEXTRA}"
tool="linker"
toolid="${STD_MODULE_LINK}"
>
<outdir name="../ndll/${BINDIR}" />
<files id="common" />
<files id="ios" if="ios" />
</target>
<target id="default">
<target id="ndll" if="ios" unless="legacy" />
</target>
</xml>
You shoudn’t write it from scratch, just modify Build.xml
created by lime create extension
.
If you don’t plan to support desktop platforms, you can safely delete <set name="SLIBEXT" value="..." if="..." />
lines and other lines related to windows / mac / linux or neko.
Also I split files to two groups – common for every platform and ios only for ios. It can be useful when support for new platrorm will be added (in far far future 🙂 ).
Also SDL.h is included to be able to use SDL functions and constants. I have SDL2 installed (via homebrew), but don’t want to put mac only include paths, so I just copy SDL headers from lime heades to my extension. I feel that this is wrong way, but don’t want to spend much time on that.
At last modify include.xml
in extension root:
<project>
<ndll name="es_orientation" if="ios" unless="legacy" />
<dependency name="es-orientation" path="dependencies/android" if="android" />
<android extension="org.haxe.extension.esorientation.EsOrientation" />
</project>
As usual, you shouldn’t write this from scratch, lime create extension
will generate basic rules.
Step 7
Let’s link native code with haxe code:
// after JNI.createStaticMethod for android
#if (ios && !legacy)
private static var recheckScreenOrientationNative = Lib.load(
"es_orientation_legacy", // library name
"es_orientation_legacy_recheck_screen_orientation", // function name
1 // parameters count
);
#end
...
public static function setScreenOrientation(screenOrientation : Int) : Void {
#if android
setRequestedOrientationNative(screenOrientation);
#elseif legacy
switch (screenOrientation) {
case ORIENTATION_PORTRAIT:
Stage.setFixedOrientation(Stage.OrientationPortraitAny);
case ORIENTATION_LANDSCAPE:
Stage.setFixedOrientation(Stage.OrientationLandscapeAny);
default:
Stage.setFixedOrientation(Stage.OrientationAny);
}
#elseif ios
setRequestedOrientationNative(screenOrientation);
#end
}
It harder than android, but still understandable.
Step 8
To build extension, register your extension with haxelib (Just use “haxelib dev …”, don’t upload maybe non-working extension to haxelib site), and than use lime rebuild name-of-extension-in-haxelib
.
Let’s try!
- Works well on Android;
- Semi-works on iOS.
On Android screen orientation is changed immediately, but on iOS it will change screen orientation if you physically rotate the device.
I haxe zero expirience in iOS development, but I guess it happens because shouldAutorotateToInterfaceOrientation
(insnde lime legacy) and supportedInterfaceOrientations
(inside SDL2) was called by iOS only when user physically rotates device.
Fortunatelly, I found one extension for cordova with solution (line 65 and below). It opens and immediately closes empty view controller, and this did the trick.
Step 9
Both legacy and non-legacy modes need that trick. It is simple to copy + paste code info non-legacy code, but for legacy we need separate binary.
To do this, you should add legacy path to Build.xml
:
<files id="common-legacy">
<compilerflag value="-I_legacy/include" />
<file name="_legacy/src/ExternalInterface.cpp" />
</files>
<files id="ios-legacy">
<compilerflag value="-I_legacy/include" />
<file name="_legacy/src/ios/EsOrientation.mm" />
</files>
<target
id="ndll-legacy"
output="${LIBPREFIX}es_orientation_legacy${DEBUGEXTRA}${LIBEXTRA}"
tool="linker"
toolid="${STD_MODULE_LINK}"
>
<outdir name="../ndll/${BINDIR}" />
<files id="common-legacy" />
<files id="ios-legacy" if="ios"/>
</target>
Modify default target:
<target id="default">
<target id="ndll" if="ios" unless="legacy" />
<target id="ndll-legacy" if="ios legacy" />
</target>
And also modify include.xml
:
<?xml version="1.0" encoding="utf-8"?>
<project>
<ndll name="es_orientation" if="ios" unless="legacy" />
<ndll name="es_orientation_legacy" if="ios legacy" />
<dependency name="es-orientation" path="dependencies/android" if="android" />
<android extension="org.haxe.extension.esorientation.EsOrientation" />
</project>
Step 10
It should work now 🙂
Link to repo with full code: haxe-es-orientation
Precompiled libraries not included, so if you want to use this extension not only for educational purposes, either compile it by yourself, or fill issue on github.
2 thoughts on “OpenFL extension in 10 steps”