snendev / admob-example

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Bevy Admob Example

This is an example repo showing how to integrate Google Admob with a Bevy project.

It is ultimately built off (a fork of) NiklasEi's Bevy Game Template.

This README is WIP and should eventually point to a writeup on how this works + how to extend it.

Instructions

To get this to work:

  1. Install NiklasEi's xbuild fork:
cargo install --git https://github.com/niklasei/xbuild
  1. Configure your android environment. (Use x doctor to see if there is anything critical missing.) See the android environment setup section for more detail.

  2. Ensure that you have a device visible on x devices. I prefer to plug an android device into my computer; alternatively, you can run an emulator. Either way, find the associated host value, e.g. adb:XXXXXXXXXXXXXX.

  3. Run x run -p snendev_ad_example_mobile --device <host-code>; once compiled, the app should run on your device.

  4. A button should show on screen to call a test ad.

  5. Once the ad is exited, it will return to the Bevy app. If the ad was redeemed, the text should change to say "Ad redeemed"; if the ad was cancelled, it will show the original "Load ad" text.

How it works

The key Android class to be aware of is the Activity class. Activity is the base class for any application screens/routes/what-have-you that an app may want to run for the user. Bevy uses winit under the hood for window management, and when targeting the Android environment, winit does this by establishing a rendering context inside a NativeActivity using the android-activity crate.

NativeActivity is a special subclass of Activity that is designed for running native code, such as Rust code. We can think of the runtime established by android-activity like this:

_______________________________
|         JVM                 |
| ___________________________ |
| |   NativeActivity        | |
| | ______________________  | |
| | |  Bevy (Rust)       |  | |
| | |____________________|  | |
| |_________________________| |
|_____________________________|

Ultimately, the way we want to render admob ads is to switch to a different Activity which can render ads. (Alternatively, this could instead render admob NativeAds inside the Rust code, but this would imply the additional work of figuring out how to render these ads in native code, probably by integrating the appropriate video formats and more.) This requires understanding how to call Java code from the Rust side, which is the job of the Java Native Interface (jni). There is a jni crate which does exactly this!

bevy_winit exports a constant with a method that returns a pointer to the activity. (Originally, I was also able to access a pointer to the JVM via ndk-context, which is used by various crates already in our dependencies, but this constant provides much more direct access to what we need. Shoutout to this discord comment which helped a lot.)

From here we need to call Activity.startActivity, which requires an Android Intent that points to the target Activity class. Importantly, the JNI does not know how to find non-system classes from inside our Bevy app, due to how the thread model works. Thus we cannot call startActivity directly from our JNI code, since we cannot get a handle to the class object.

So, to get around this, this solution defines a custom NativeActivity class which extends the typical NativeActivity and adds some useful methods that we can access from the JNI. See mobile/kotlin/NativeActivity.kt for more details. These methods can define the appropriate logic for starting our NativeActivity and receiving results from the AdActivity that loads and renders the ad. Each of the public methods defined in NativeActivity.kt, namely startAdActivity, didEarnReward, and didCancel, are called from a Bevy system, meaning that Bevy ultimately has access to ad redemption information.

This example also issues log statements that are helpful for understanding the app lifecycle. Importantly, the Bevy app is "paused" while the AdActivity is active, so we don't need to do anything special to stop systems from running during that time (for the most part).

There are several TODOs and caveats here, including pre-loading ads and configuring some useful loading screens in case the ad still doesn't render immediately. Additionally, the Android docs suggest using separate methods from Activity classes defined in the AndroidX package. These classes support a more thorough implementations of the startActivityForResult method (which can be buggy in a few ways on its own, see the docs). Since NativeActivity does not provide these methods, a proper solution should ensure that startActivityForResult is used in a way that mimics these advanced AndroidX APIs.

Notes on xbuild

xbuild is the crate which bundles the application for us; getting this to work required a good amount of configuration. All the important stuff can be found in mobile/manifest.yaml.

Of particular note are the following configuration details:

  • We need gradle: true so that we can configure additional dependencies, etc.
  • We can add the admob dependency in the dependencies section, since xbuild packages this into the gradle build files that it creates.
  • manifest.application.has_code must be set to True since it is False by default! If false, Android will not import any of the kotlin files we have written, even though they will be copied into the built project.
  • manifest.application.activities must be included to specify our NativeActivity and our AdActivity. Order is important here! The first activity in this list, our custom NativeActivity, will be treated as the main activity (and the metadata xml fields which include our native code are always added to this first activity.)

Android Environment Setup

  • Install Android Studio or sdkmanager

    • These tools will be used to install a bunch of useful packages.

      • Using Android Studio, this can be accomplished with the SDK Manager GUI.

      • Using sdkmanager, use the --list flag to find the appropriate packages and install them with the --install flag.

      • The packages needed include:

        • build-tools
        • cmdline-tools
        • cmake
        • some platform (e.g. "Android 33")
        • some ndk (e.g. "NDK 26.1.10909125")
    • It is now useful to add some environment variables to your system:

      • ANDROID_HOME is the root folder of your Android installation.
      • ANDROID_SDK_ROOT is the same as ANDROID_HOME; it is supposedly deprecated, but excluding it has caused issues for me at times. TODO: test if I can remove this yet.
      • ANDROID_NDK_ROOT is $ANDROID_HOME/ndk/<your-chosen-ndk-version>. Be sure to update this if you want to change which NDK you build with.
    • A bunch of these binaries must now be made available in your PATH, so that the build system can detect all the required binaries.

      • platform tools: $ANDROID_HOME\platform-tools
      • command-line tools: $ANDROID_HOME\cmdline-tools\latest\bin (or a version string instead of latest if relevant)
      • an llvm toolchain: $ANDROID_NDK_ROOT\toolchains\llvm\prebuilt\<your-os>\bin (note the platform string on your own environment, e.g. linux-x86_64)
  • Download Gradle, it doesn't matter where you put it

    • Add a GRADLE_HOME environment variable to this location
    • Add $GRADLE_HOME\bin to your PATH

NiklasEi's game template docs is another useful resource.

About


Languages

Language:Rust 34.2%Language:Kotlin 28.1%Language:Shell 13.2%Language:JavaScript 10.8%Language:Makefile 5.2%Language:CSS 4.4%Language:HTML 3.6%Language:Objective-C 0.3%Language:C 0.1%