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.