Skip to content

Blue5alamander/planet-android

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

11 Commits
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Planet Android

Documentation GitHub License Discord

Android support for the Planet engine

What it provides

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 a share/ prefix — a request for fonts/main.ttf is loaded from share/fonts/main.ttf in the APK.
  • A log sink which redirects planet::log output to logcat under the planet tag, 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).

Adding it to your project

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 Java side

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 blue5alamander

Call 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.

Assets

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.

Writable storage

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.

About

Android support for the planet engine

Resources

Stars

Watchers

Forks

Releases

Packages

Contributors

Languages