The Secret to Great Terrain on Mobile

We’ve got two games (one with tens of thousands of downloads) with an OBB expansion file and it works just fine – never had a reported problem with it.

  1. In Unity, on Player settings, check Split Application Binary under Publishing Settings.

  2. Build the game, then check/rename the two files (not 100% sure this is necessary, but works for us):
    main.1.com.myname.gamename.apk
    main.1.com.myname.gamename.obb

  3. In the Google developer console, on the APK page, switch to Advanced mode, then drag the apk file into the upload panel. After it’s finished uploading, you’ll have an option to add an expansion file. Select the OBB and upload it.

  4. Voila! It works!

1 Like

@gecko If you didn’t add code to check to make sure the OBB file successfully downloaded, then you are risking the possibility that a user only downloaded the main apk and not the OBB and your scene will have exceptions, etc.

@mbowen89 : From what I’ve read, that’s only for older devices/versions of Android…is that your understanding? We require 4.2+ and haven’t heard of any problems.

1 Like

Thank you guys.I really appreciate the clear directions and multi analysis.
It seems the deal is upload at your own risk.
It’s unlikely downloads will fail but if you don’t cater to it you’re breaking the terms of service.
I have some thinking to do. a LOT of people are asking for BrightRidge on Android.

Meanwhile, here’s some end results of my terrain on mobile:

https://www.instagram.com/protopopgames/

I also remember reading that on older devices, it would more likely have issues. Here’s what Google says:

That’s the one thing, a user might actually accidentally delete an obb when cleaning up their device, and not remove the APK. I’m just saying that it’s really not that much code or difficult when you sit down to do it (adding in OBB code to verify) so if you make any money at all from your apps, it’s worth it.

1 Like

@mbowen89 – where do I find that OBB code for verification?

using UnityEngine;
using System.Collections;
using System.IO;
using System;

public class GooglePlayDownloader
{
    #if UNITY_ANDROID
    private static AndroidJavaClass detectAndroidJNI;

    public static bool RunningOnAndroid()
    {
        #if UNITY_ANDROID
        if (detectAndroidJNI == null)
        {
            detectAndroidJNI = new AndroidJavaClass("android.os.Build");
        }
        return detectAndroidJNI.GetRawClass() != IntPtr.Zero;
        #else
        return false;
        #endif
          

    }
  
    private static AndroidJavaClass Environment;
    private const string Environment_MEDIA_MOUNTED = "mounted";

    static GooglePlayDownloader()
    {
        if (!RunningOnAndroid())
            return;

        Environment = new AndroidJavaClass("android.os.Environment");
      
        using (AndroidJavaClass dl_service = new AndroidJavaClass("com.unity3d.plugin.downloader.UnityDownloaderService"))
        {
        // stuff for LVL -- MODIFY FOR YOUR APPLICATION!
            dl_service.SetStatic("BASE64_PUBLIC_KEY", "KEY");
        // used by the preference obfuscater
            dl_service.SetStatic("SALT", new byte[]{1, 43, 256-12, 256-1, 54, 98, 256-100, 256-12, 43, 2, 256-8, 256-4, 9, 5, 256-106, 256-108, 256-33, 45, 256-1, 84});
        }
    }
  
    public static string GetExpansionFilePath()
    {
        populateOBBData();

        if (Environment.CallStatic<string>("getExternalStorageState") != Environment_MEDIA_MOUNTED)
            return null;
          
        const string obbPath = "Android/obb";
          
        using (AndroidJavaObject externalStorageDirectory = Environment.CallStatic<AndroidJavaObject>("getExternalStorageDirectory"))
        {
            string root = externalStorageDirectory.Call<string>("getPath");
            return String.Format("{0}/{1}/{2}", root, obbPath, obb_package);
        }
    }
    public static string GetMainOBBPath(string expansionFilePath)
    {
        populateOBBData();

        if (expansionFilePath == null)
            return null;
        string main = String.Format("{0}/main.{1}.{2}.obb", expansionFilePath, obb_version, obb_package);
        if (!File.Exists(main)) {
            return null;
        } else {
            //new obb exists, delete old ones
            DeleteOldOBBs();
        }
        return main;
    }
    public static string GetPatchOBBPath(string expansionFilePath)
    {
        populateOBBData();

        if (expansionFilePath == null)
            return null;
        string main = String.Format("{0}/patch.{1}.{2}.obb", expansionFilePath, obb_version, obb_package);
        if (!File.Exists(main)) {
            return null;
        } else {
            //new obb exists, delete old ones
            DeleteOldOBBs();
        }
        return main;
    }
    public static void FetchOBB()
    {
        using (AndroidJavaClass unity_player = new AndroidJavaClass("com.unity3d.player.UnityPlayer"))
        {
            AndroidJavaObject current_activity = unity_player.GetStatic<AndroidJavaObject>("currentActivity");
  
            AndroidJavaObject intent = new AndroidJavaObject("android.content.Intent",
                                                            current_activity,
                                                            new AndroidJavaClass("com.unity3d.plugin.downloader.UnityDownloaderActivity"));
  
            int Intent_FLAG_ACTIVITY_NO_ANIMATION = 0x10000;
            intent.Call<AndroidJavaObject>("addFlags", Intent_FLAG_ACTIVITY_NO_ANIMATION);
            intent.Call<AndroidJavaObject>("putExtra", "unityplayer.Activity",
                                                        current_activity.Call<AndroidJavaObject>("getClass").Call<string>("getName"));
            current_activity.Call("startActivity", intent);
  
            if (AndroidJNI.ExceptionOccurred() != System.IntPtr.Zero)
            {
                Debug.LogError("Exception occurred while attempting to start DownloaderActivity - is the AndroidManifest.xml incorrect?");
                AndroidJNI.ExceptionDescribe();
                AndroidJNI.ExceptionClear();
            }
        }
    }

    public static void DeleteOldOBBs() {
        populateOBBData();
        string expansionFilePath = GetExpansionFilePath();
        if (expansionFilePath == null) {
            return;
        }          
        for (int i = obb_version - 1; i > 0; i--) {
            string old_obb = String.Format("{0}/main.{1}.{2}.obb", expansionFilePath, i, obb_package);
            if (File.Exists(old_obb)) {
                File.Delete(old_obb);
            }
        }
    }
  
    // This code will reuse the package version from the .apk when looking for the .obb
    // Modify as appropriate
    private static string obb_package;
    private static int obb_version = 0;
    private static void populateOBBData()
    {
        if (obb_version != 0)
            return;
        using (AndroidJavaClass unity_player = new AndroidJavaClass("com.unity3d.player.UnityPlayer"))
        {
            AndroidJavaObject current_activity = unity_player.GetStatic<AndroidJavaObject>("currentActivity");
            obb_package = current_activity.Call<string>("getPackageName");
            AndroidJavaObject package_info = current_activity.Call<AndroidJavaObject>("getPackageManager").Call<AndroidJavaObject>("getPackageInfo", obb_package, 0);
            obb_version = package_info.Get<int>("versionCode");
        }
    }
    #endif
}

And then in my main scene script I have some code that does this basically:

#if UNITY_ANDROID
    public void CheckOBBDownloadUpdate() {
        mainPath = GooglePlayDownloader.GetMainOBBPath(expPath);
        if (mainPath != null) {
            //OBB finished downloading?!
            obb_download_failed = false;
        }
    }

    public void RetryDownload(){
        GooglePlayDownloader.FetchOBB();
        CheckOBBDownloadUpdate();
    }
    #endif

This is from June 2015, I don’t think even Unity 5, so take with a grain of salt, but it should work the same.

Oh so Google will still host the “backup” obb? we dont have to host it ourselves, just include this fallback? Thats enough encouragement for me to give it another try. a LOT of people have been asking for BrightRidge on Android. Thanks for including the code:)

1 Like

When you upload your apk, you upload the OBB file. Google then will do it’s best to make sure the user downloads it when installing. What your code does, is checks to make sure the OBB file is indeed on the device, and if not, contact Google servers and download it.

1 Like

Im going to try this again this weekend - thank you:)

Hey Protopop, game looks amazing, i will download it once it releases.

Anyways, what are you using for terrain shaders?? I have had issues, and alot of games made in Unity I see the same issue… This is not from my game but same issue… wondering what you may have used ? Thanks!

PS: Have you used Terrain Composer 2 yet?? I been a tester and been using, really loving it, and I used TC1 for years.

1 Like

Thanks recon0303:) Your game looks like fun. I’m a big fan of bright colors and easy to read landscapes. My games can look kind of messy comparatively:)

Im just using the terrain standard shader. Ive tried using asset store ones but i always seem to end up with these really low res terrain textures, so i just stuck with the basics.

My game is available for IOS:) Its at version 6 - try it out if you like - i appreciate the support:
https://itunes.apple.com/us/app/nimian-legends-brightridge/id1021026309?ls=1&mt=8

thats not my game, at all, that is just a image I found that looked like the same issue. But thanks anyways. would love to try, but I don’t have any thing apple related, I will wait for your Android version as I develop and have 6 devices for Android .

1 Like

I’ve been following your forum for quite some time now! It’s amazing to see how far you’ve come and your tips have definitely helped me out quite a bit.

Have you thought about releasing an android version or have you already?

1 Like

Thank you:) Im working on the android version, and ive been just figuring out how to get a beta on the Play store - i almost have it figured out.

ps theres a pic of an early android beta above - its the old version but the new one mostly works too - just some filters to fix.

@protopop I see it now :slight_smile:
I can’t wait to try it.
Good luck uploading! I know its a b***h getting it to work.

1 Like

Love & Tin. A new BrightRidge Adventure coming in update 6

3 Likes

Love what you are doing here!! :slight_smile:

3 Likes

Thanks Adam. I’m a big fan of your work, your professional approach with customers, and your heavy contributions to discussions. You’ve done great work:)

I think world design is so much fun. Its connection to math makes it interesting on a technical level, and a philosophical one too when you think about the similarities between math based terrain and real nature, and what that might mean for the basis of nature in real life.

1 Like