Android support for the Planet engine
Two pieces of platform glue, both of which install themselves from static initialisers so there is no API for you to call from your game code:
- An asset loader which reads from the APK's asset tree. It registers itself with
planet::asset_manager, and looks assets up below ashare/prefix — a request forfonts/main.ttfis loaded fromshare/fonts/main.ttfin the APK. - A log sink which redirects
planet::logoutput to logcat under theplanettag, mapping planet's log levels onto the Android priorities.
Because both are installed by static initialisers in translation units that nothing else references, the linker will discard them unless you link the library with --whole-archive (see below).
The library expects to be built as part of your app's native build, alongside Planet SDL. Add it to your CMakeLists.txt and link it into the shared object that Gradle builds:
add_subdirectory(path/to/planet-android)
add_library(mygame SHARED native-lib.cpp)
target_link_libraries(mygame
mygamelib
-Wl,--whole-archive
SDL3-static
planet-android
# ...the rest of your planet libraries
-Wl,--no-whole-archive
)planet-android links android, log and planet-sdl publicly, so you don't need to name those yourself.
If you're using Planet Vk then note that the NDK ships libvulkan.so and the Vulkan headers in its sysroot, but no CMake package config, so find_package(Vulkan) fails. Declare the imported target yourself before adding planet-vk, which will then skip its own find_package:
if(NOT TARGET Vulkan::Vulkan)
add_library(Vulkan::Vulkan INTERFACE IMPORTED)
set_target_properties(Vulkan::Vulkan PROPERTIES
INTERFACE_LINK_LIBRARIES "vulkan")
endif()Vulkan is only available from API 24, so your minSdk can be no lower than that.
The asset loader needs a reference to the Android AssetManager before any asset is loaded. jvm/com/blue5alamander/planet/android/Asset.java provides the Asset class that holds it, and the simplest way to get it compiled into your app is to symlink it into your Gradle source tree:
cd app/src/main/java/com
ln -s ../../cpp/path/to/planet-android/jvm/com/blue5alamander blue5alamanderCall Asset.useManager from your activity's onCreate before super.onCreate, which is when SDL starts the native SDL_main thread:
import com.blue5alamander.planet.android.Asset
import org.libsdl.app.SDLActivity
class MainActivity : SDLActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
Asset.useManager(getAssets())
super.onCreate(savedInstanceState)
}
override fun getMainSharedObject(): String {
return "libmygame.so"
}
override fun getLibraries(): Array<String> {
return arrayOf<String>()
}
/// This is needed so the SDL3 native functions are available
companion object {
init {
System.loadLibrary("mygame")
}
}
}If useManager hasn't been called by the time an asset is requested then the loader throws, rather than silently failing to find anything.
The loader prefers the native AAssetManager and falls back to calling back into Asset.loader on the JVM side, so assets are still readable if the native manager can't be obtained.
Your game's runtime data has to end up in src/main/assets/share/ for the loader to find it. If your CMake build already has an install component for the data then you can stage it there with a custom target and have Gradle build it:
add_custom_target(mygame-assets
COMMAND ${CMAKE_COMMAND} --install ${CMAKE_BINARY_DIR}
--component share
--prefix ${CMAKE_CURRENT_SOURCE_DIR}/../assets
VERBATIM)
add_dependencies(mygame-assets mygame)Name the target in your externalNativeBuild block so it gets run:
externalNativeBuild {
cmake {
targets 'mygame', 'mygame-assets'
}
}Gradle doesn't order the native build before mergeAssets, so on clean or parallel builds the merge can snapshot a partially staged share/ tree and quietly leave files out of the APK. Make each variant's asset merge wait for its native build:
androidComponents {
onVariants(selector().all()) { variant ->
def cap = variant.name.substring(0, 1).toUpperCase() + variant.name.substring(1)
afterEvaluate {
tasks.named("merge${cap}Assets").configure {
dependsOn("externalNativeBuild${cap}")
}
}
}
}The staged src/main/assets/share/ is generated, so add it to your .gitignore.
The APK's assets are read only. For save games and configuration, pass the activity's filesDir down to native code and hand it to planet's SDL configuration:
useDirectory(filesDir.path)extern "C" JNIEXPORT void
Java_com_example_mygame_MainActivity_useDirectory(
JNIEnv *env, jobject, jstring jdir) {
std::shared_ptr<char const> dir(
env->GetStringUTFChars(jdir, NULL),
[&](char const *ptr) { env->ReleaseStringUTFChars(jdir, ptr); });
sdl.config.set_game_folder(dir.get());
}Because onCreate runs before SDL_main, the planet::sdl::init you want to configure won't exist yet at the point the JNI call arrives. Save the path and apply it once SDL has been initialised.