porting and maintaining your c++ game on android without losing your mind
DESCRIPTION
Presentation from David Wingrove & Katie Merrill from Golden Hammer Software http://www.goldenhammersoftware.com/ From the Barcelona Android User Group meetup: http://www.meetup.com/Barcelona-Android-User-Group/events/166734982/TRANSCRIPT
Porting and Maintaining Your C++ Game
on Android(without losing your mind)
Why C++ Cross-Platform support
(Some lesser platforms don’t have a JVM)
Don’t want to use Unity, etc Existing C++ Codebase
Overview NDK recap What goes where? C++ vs Java Streamlining packaged app data
Eliminating Data Duplication Compiling multiple architectures Other quirks we’ve run into Some downloads info about our apps
NDK Application.mk
APP_PROJECT_PATH := $(call my-dir)APP_BUILD_SCRIPT := $(call my-dir)/Android.mkAPP_MODULES := GHEngineAPP_OPTIM := releaseAPP_STL := stlport_staticAPP_PLATFORM := android-8
NDK Android.mk (static lib)LOCAL_PATH := $(call my-dir)
LOCAL_CFLAGS := -Wno-psabi
LOCAL_CFLAGS += -D ANDROID
LOCAL_MODULE := GHEngine
LOCAL_MODULE_FILENAME := libGHEngine
LOCAL_C_INCLUDES += $(LOCAL_PATH)/../../../../Base
LOCAL_SRC_FILES += ../../../../Base/GHAppRunner.cpp
include $(BUILD_STATIC_LIBRARY)
NDK Android.mk (shared lib – loaded by java)
LOCAL_MODULE := libGHEngine
LOCAL_SRC_FILES := ../../GHEngine/obj/local/armeabi/libGHEngine.a
include $(PREBUILT_STATIC_LIBRARY)
LOCAL_STATIC_LIBRARIES += libGHEngine
include $(BUILD_SHARED_LIBRARY)
$(shell cp libs/armeabi/libGHBowling.so ../../../GHBowlingBase/libs/armeabi)
NDK JNILoading the C++ Library
public class GHBowlingBaseActivity
extends Activity {
static {
System.loadLibrary("GHBowling");
}
}
Loads the file named libGHBowling.so
NDK JNIJava (calling C++)
public class GHEngineInterface {public native void runNativeFrame();public native void launchNativeApp(int windowWidth,
int windowHeight,String externalStoragePath,
int isTablet,int iapIsOn);
//resizeWindow, handleTouchStart, handleTouchEnd, handleTouchPos//handleAcceleration, handleJavaPause, handleJavaResume,//handleJavaShutdown,handleBackPressed, calculatePublicKey,//onInterstitialRewardGranted,onRewardInterstitialAvailabilityChange,//onInterstitialDismiss, loadFile,handleTextureLoadConfirmed,//onAdActivation, onAdDeactivation, onIAPPurchase}
NDK JNIC++ (called by Java)
static jobject globalEngineInterface = 0;extern "C“ __attribute__((visibility("default"))) voidJava_goldenhammer_ghbowlingbase_GHEngineInterface_launchNativeApp (JNIEnv* env,
jobject engineInterface, jint windowWidth, jint windowHeight, jstring jExternalStoragePath, jint isTablet, jint useIAP)
{globalEngineInterface = env->NewGlobalRef(engineInterface);//Engine/Game Initialization goes here.
}
When you shut down:
env->DeleteGlobalRef(globalEngineInterface);
NDK JNIJava (called by C++)public class GHEngineInterface
{
public void showInterstitialAd() {
if (mInterstitialHandler != null) {
mInterstitialHandler.showInterstitial();
} else {
onInterstitialDismiss();
}
}
};
NDK JNIC++ (calling Java)
GHAndroidInterstitialAd class declaration:
JNIEnv& mJNIEnv;
jobject mEngineInterface;
jmethodID mShowAdMethod;
GHAndroidInterstitialAd ctor:
jclass cls = mJNIEnv.GetObjectClass(mEngineInterface);
mShowAdMethod = mJNIEnv.GetMethodID(cls, "showInterstitialAd", "()V");
GHAndroidInterstitialAd::activate:
mJNIEnv.CallVoidMethod(mJavaObj, mShowAdMethod);
What goes Where?C++ or Java C++
All your platform-independent/pre-existing code Bare minimum wrapper for Android implementation of
platform services Java
Typical Android platform code Middleware integration
Can swap middleware vendors of the same service without touching C++
Exceptions: OpenGL (initialization in Java, most code in C++ or GLSL) File I/O (mix of Java, C++)
What Goes WhereJava OpenGL initialization Sound through SoundPool File handle loading through AssetManager Image loading through BitmapFactory Google Play, In-App Billing, etc Ads and other middleware integration
AdMob, AdColony, Chartboost, PlayHaven, Facebook, etc
Input Handling (touches, accelerometer, buttons/gamepad)
What Goes WhereC++ All your game code
Ideally 90% of your app OpenGL rendering code
Need to handle reinit after lost device fopen using file handle from Java Thin wrapper over JNI calls for
everything else
What Goes WhereOur code distribution.
2800 lines Java 1600 in base project 1200 in master project
Includes middleware integration
50k-150k lines C++ Varies depending on game 6400 Android-specific C++
Eliminating Data DuplicationProblem Eclipse wants all data underneath
project We want to reuse data (between
projects) in our own directory structure
We hate maintaining multiple copies of the same data files.
Eliminating Data DuplicationSolution
Batch file/shell script to copy data On Maccp ../../../../../data/GHBowling/ballrollloop.wav
../../../GHBowling/assets/ballrollloop.wav
cp ../../../../../data/Bowling/GHBowlingAndroid/backwall.jpg ../../../GHBowling/assets/backwall.jpg
On Windowscopy ..\..\..\..\..\data\GHBowling\ballrollloop.wav ..\..\..\
GHBowling\assets\ballrollloop.wav
copy ..\..\..\..\..\data\Bowling\GHBowlingAndroid\backwall.jpg ..\..\..\GHBowling\assets\backwall.jpg
Eliminating Data DuplicationBatch File Generation Tool for initial generation
Looks through set of directories with a specified order of preference
Some files are different per-platform We may have Android-specific files We may not, but we prefer iOS-specific to generic
Downside: some unnecessary files get copied
Maintenance usually done by hand
Packaged App DataProblem Data is packaged through Android
build process All files except those with certain
excluded file extensions (pre-compressed file types) are automatically compressed.
Platform-agnostic file reading code doesn’t know to uncompress: sees garbage
Excluded file extensions Source: Android Asset Packaging
Tool/* these formats are already compressed, or don't
compress well */
static const char* kNoCompressExt[] = { ".jpg", ".jpeg", ".png", ".gif", ".wav", ".mp2", ".mp3", ".ogg", ".aac", ".mpg", ".mpeg", ".mid", ".midi", ".smf", ".jet", ".rtttl", ".imy", ".xmf", ".mp4", ".m4a", ".m4v", ".3gp", ".3gpp", ".3g2", ".3gpp2", ".amr", ".awb", ".wma", ".wmv" };
App Data CompressionSolution
One option: forgo Eclipse and pass –0 to the AAPT via command line (universally or for certain extensions)
What we do
cp ../../../../../data/GHBowlingiOS/arrowpixel.glsl ../../../GHBowling/assets/arrowpixel.glsl.mp3
Compiling for x86 (or other architectures) In Application.mk:
APP_ABI := x86 armeabi
Supported values: armeabi armeabi-v7a x86 mips all
Compiling for x86Problem Shared library Android.mk needs to
include the correct static library for each architecture For arm: /armeabi/libGHEngine.a For x86: /x86/libGHEngine.a
Compiling for x86Solution
include $(CLEAR_VARS)
LOCAL_MODULE := libGHEngine
LOCAL_SRC_FILES :=
../../GHEngine/obj/local/$(TARGET_ARCH_ABI)/libGHEngine.a
include $(PREBUILT_STATIC_LIBRARY)
Building on WindowsProblem We really like verbose filenames
GHBowlingYellowBallThrowWith190DegreeSpinTransitionXMLLoaderTransition.cpp
Our GHEngine project has lots of files Linker includes all of those filenames
in one shell command Exceeds maximum line length on
Windows cmd (8191 characters)
Building on WindowsProblem We really like verbose filenames
Ok, more like GHGUIPopTransitionXMLLoader.cpp
Our GHEngine project has lots of files
Linker includes all of those filenames in one shell command
Exceeds maximum line length on Windows cmd (8191 characters)
Building on WindowsSolution In Android.mk:LOCAL_SHORT_COMMANDS := true
Build system generates intermediate list file and then invokes it with a much shorter command line
Downside: slower compiles Can use only in projects that need it.