A minimal, working raylib project for Android (and desktop) with a clean game architecture.
raylib_android/
βββ assets/
β βββ textures/ # PNG/JPG images (bundled into APK)
β βββ audio/ # WAV/OGG sound effects + music
β βββ fonts/ # TTF custom fonts
βββ src/
β βββ main.cpp # Entry point: creates Application, sets Splash, runs
β βββ CMakeLists.txt # raylib via FetchContent, glbs src/**/*.cpp
β βββ Core/ # Engine core
β β βββ Application # Owns loop + systems + screen, runs update/render
β β βββ GameLoop # Frame timing, delta time, FPS counter
β β βββ Platform # Platform init (crash handler), asset path
β β βββ Screen # Base screen interface (update/render/onEnter/onExit)
β βββ Systems/ # Subsystems
β β βββ Rendering # Wraps BeginDrawing/EndDrawing, common draw helpers
β β βββ Input # Keyboard, mouse, touch input
β β βββ Audio # Init/close audio device, load/play sounds/music
β β βββ UI # Button struct, hit-testing, rendering
β βββ Screens/ # Game screens (state machine)
β β βββ Splash # 3s splash -> auto-transition to MainMenu
β β βββ MainMenu # Play / Settings / Exit buttons
β β βββ Game # Placeholder gameplay screen
β βββ Game/ # Game-specific logic
β β βββ Entities # Entity manager (create/destroy/query)
β β βββ Components # Transform, Render, Physics structs
β β βββ Logic # Game simulation, collision detection
β βββ AndroidEngine/ # Android-specific helpers
β β βββ crash_handler/h/cpp # SIGSEGV/SIGABRT/SIGFPE/SIGILL handler
β βββ app/ # Android Gradle project
β βββ build.gradle
β βββ src/main/AndroidManifest.xml
βββ scripts/
βββ build_apk.sh # Build, sign, and verify APK
βββ clean.sh # Remove build artifacts
βββ install_prereqs.sh # Check/install build prerequisites
βββ adb_debug.sh # ADB WiFi pairing + install
βββ logcat.sh # View/analyze Android logs
βββ serve_apk.py # HTTP server for APK + crash uploads
βββ find_tools.sh # Common dependency detection
main.cpp
ββ Core::Application
ββ Core::GameLoop (frame timing)
ββ Systems::Input (input polling)
ββ Systems::Audio (sound/music)
ββ Core::Screen* (current screen)
ββ Screens::Splash -> screens::MainMenu
ββ Screens::MainMenu -> Screens::Game | exit
ββ Screens::Game -> Screens::MainMenu
Systems::UI / Rendering / Audio / Input are stateless utility structs.
Game::EntityManager + components provide optional ECS-like gameplay layer.
- Android Studio (SDK + NDK) or Android SDK command-line tools
- JDK 17+ (OpenJDK 17 recommended)
- CMake 3.22+ (auto-installed by Gradle if missing)
Install missing tools:
./scripts/install_prereqs.sh./scripts/build_apk.shThe script:
- Checks prerequisites (JDK, SDK, NDK, CMake)
- Auto-installs NDK if missing via
sdkmanager - Prunes stale CMake caches to avoid config conflicts
- Runs Gradle to compile native code and package APK
- Signs the APK with the debug keystore (auto-created if missing)
- Shows the final APK path and install instructions
Output: artifacts/outputs/apk/debug/raylib_android-debug.apk
Gradle + Ninja handle incremental builds automatically. Only changed .cpp files
are recompiled. No manual cleanup needed between builds.
./scripts/clean.sh # Remove all build artifacts
./scripts/clean.sh cxx # Remove only CMake caches (fixes config issues)
./scripts/clean.sh apk # Remove only APK filescmake -B artifacts/linux -S src && cmake --build artifacts/linux -j$(nproc)
./artifacts/linux/raylib_android./scripts/adb_debug.sh installOr directly:
adb install artifacts/outputs/apk/debug/raylib_android-debug.apkCommon install failures:
| Error | Fix |
|---|---|
INSTALL_FAILED_UPDATE_INCOMPATIBLE |
adb uninstall com.raylib.android then retry |
INSTALL_FAILED_NO_MATCHING_ABIS |
Build includes arm64-v8a, armeabi-v7a, x86_64 |
App not installed (no ADB) |
Enable "Install unknown apps" for your browser/download manager |
- Build the APK:
./scripts/build_apk.sh - Start the server:
./scripts/serve_apk.sh - Open the URL shown (e.g.
http://10.0.0.4:8080/) on your phone - Tap the APK download link and install
./scripts/logcat.sh # Watch live logs (raylib_android + stderr)
./scripts/logcat.sh install # Install APK then watch logs
./scripts/logcat.sh dump 100 # Show last 100 log lines./scripts/logcat.sh crash # Show crash dumpAfter a crash, logs are also saved to /data/data/com.raylib.android/files/crash_log.txt
on the device (if the crash handler fires). Retrieve via ADB:
adb shell cat /data/data/com.raylib.android/files/crash_log.txt./scripts/logcat.sh verify # Show APK signature + contentsThe build script automatically signs debug APKs with the Android debug keystore
at ~/.android/debug.keystore (created automatically if missing).
If you need to re-sign manually:
apksigner sign --ks ~/.android/debug.keystore --ks-pass pass:android \
--ks-key-alias androiddebugkey --key-pass pass:android \
--v1-signing-enabled true --v2-signing-enabled true \
artifacts/outputs/apk/debug/raylib_android-debug.apkDebug keystore credentials:
- Store password:
android - Key alias:
androiddebugkey - Key password:
android
ANativeActivity_onCreate: raylib provides this internally insrc/platforms/raylib_android.c. Linked with-Wl,--whole-archiveso the linker doesn't strip it (CMakeLists.txt handles this).- Asset loading: raylib loads assets directly from the APK via the native asset manager. No extraction step needed.
- Touch input: raylib maps touch events to mouse events automatically on
Android.
Systems::Input::touchPos()usesGetTouchPosition(0)(falling back toGetMousePosition()on desktop). - OpenGL ES: raylib uses OpenGL ES 2.0+ on Android.
- Entry point: Define
int main(void)as usual. raylib'sANativeActivity_onCreatehandles the activity lifecycle and callsmain()after initializing the display. - Native-only APK: No Java/Kotlin code. The manifest has
hasCode="false". Debug signing is applied manually bybuild_apk.shafter Gradle packaging. (AGP 8.x does not sign native-only APKs.) - Inline functions in rlgl.h: raylib 5.5 defines most
rlglfunctions asinline(withoutstatic). This can cause "duplicate symbol" linker errors if raylib is linked more than once. The template links raylib only once viatarget_link_libraries.