A few months ago, a client approached us with a project: to build a skill-based real money gaming app that includes multiple Unity games. The core feature of the application was to integrate several Unity games into a single Android and iOS app.
We started exploration and quickly found Unity as a Library (UaaL), which allows embedding Unity games inside native apps. However, integrating multiple games wasn’t straightforward, and we faced several technical challenges. Here’s how we tackled them.
Challenges We Faced (And How We Solved Them)
1. Running Multiple Unity Instances Wasn't Possible
With UaaL, we found that only one Unity instance could run at a time. This meant we couldn't integrate multiple separate Unity games into the app individually.
Solution: Merging All Games Into One Unity Project
To work around this, we had to merge all the Unity games into a single master Unity project and then integrate that with our Android and iOS app.
- We created a master Unity project.
- Each game was exported as a Unity package and then imported into the master project.
However, this led to new problems.
2. Conflicts in Layers, Sorting Layers, and Tags
Unity has a global list for layers, sorting layers, and tags, and merging multiple projects caused conflicts. Unity also limits layers to 32, so we had to ensure no overlap.
Solution: Standardizing Layers and Sorting Layers
To fix this:
- We created a fixed list of layers in the master project.
- Before exporting each game, we updated its layers to match the master project.
- For sorting layers, we manually updated sorting layer IDs by editing
TagManager.asset
,m_SortingLayerId
in a text editor like VS Code. - We combined all tags from the games and added them to the master project.
3. Duplicate Asset GUIDs Caused Issues
Each Unity asset has a unique GUID, but some games used copied assets, leading to duplicate GUIDs. This caused assets to get mapped incorrectly when merging the projects.
Solution: Regenerating GUIDs
We used the Unity GUID Regenerator to regenerate GUIDs and ensure each asset had a unique identifier. This fixed asset mismatching issues.
4. Quitting a Unity Game Quit the Whole Android App
By default, Unity games use Application.Quit()
to exit, but this closed the entire Android app, not just the game.
Solution: Running Unity in a Separate Process
We configured Unity to run in a separate process inside the Android app to prevent quitting the entire app:
<activity android:name=".UnityGameActivity" android:launchMode="singleTask" android:exported="true" android:theme="@style/Theme.AppCompat.Light.NoActionBar" android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode" android:process=":unityplayer" />
This ensured that quitting a game didn’t shut down the whole app.
5. Communication Between Unity and Flutter
Since our main app was built in Flutter, we needed a way for Unity and Flutter to communicate—for example, to start a game, send scores, and track analytics.
Solution: Creating a Communication System
We built a C# script in Unity to send data to Flutter via the Android/iOS native layers.
Example of how the game sends the score:
public void SubmitScore(int finalScore, int timeTaken)
{
var data = new Dictionary<string, string>
{
{ "timeTaken", timeTaken.ToString() },
{ "finalScore", finalScore.ToString() }
};
SendMessageToNative("SubmitScore", data);
}
private void SendMessageToNative(string methodName, Dictionary<string, string> data)
{
if (Application.platform == RuntimePlatform.Android)
{
using (AndroidJavaClass unityPlayer = new AndroidJavaClass("com.unity3d.player.UnityPlayer"))
using (AndroidJavaObject currentActivity = unityPlayer.GetStatic<AndroidJavaObject>("currentActivity"))
using (AndroidJavaObject hashMap = new AndroidJavaObject("java.util.HashMap"))
{
if (data != null)
{
foreach (var entry in data)
{
using (AndroidJavaObject key = new AndroidJavaObject("java.lang.String", entry.Key))
using (AndroidJavaObject value = new AndroidJavaObject("java.lang.String", entry.Value))
{
hashMap.Call<AndroidJavaObject>("put", key, value);
}
}
}
currentActivity.Call("SendMessageToAndroid", methodName, hashMap);
}
}
else if (Application.platform == RuntimePlatform.IPhonePlayer)
{
Dictionary<string, object> message = new Dictionary<string, object>
{
{ "methodName", methodName },
{ "data", data ?? new Dictionary<string, string>() }
};
Debug.Log($"Send Message to iOS - Method: {methodName}, Data: {message}");
NativeAPI.sendMessageToMobileApp(JsonConvert.SerializeObject(message));
}
else
{
Debug.LogWarning($"Platform not supported for sending data to Flutter. Method: {methodName}");
}
}
On the Native side, we forwarded this data to Flutter using method channels. For android, we implemented Inter-Process Communication (IPC) using Messenger for communication between UnityGameActivity and MainActivity
6. Slow Startup Time for Unity Games
We noticed a delay (a few seconds of black screen) whenever a Unity game started.
Solution: Preventing Unnecessary Compression
After a lot of extensive research, we found that Android compressed some unity resources, which took time to decompress at startup. To fix this, we told Android to skip compression for specific Unity files:
androidResources {
ignoreAssetsPattern = "!.svn:!.git:!.ds_store:!.scc:!CVS:!thumbs.db:!picasa.ini:!~"
noCompress = [
'.unity3d',
'.ress',
'.resource',
'.obb'
]
}
This significantly improved game startup time.
7. Dependency Issues in Android
When we integrated UaaL into the Android app, we ran into dependency conflicts due to multiple libraries requiring different versions of the same dependencies.
Solution: Using Unity’s External Dependency Manager
To resolve these issues, we used Unity’s External Dependency Manager (unity-jar-resolver).
This tool:
- Automatically resolved conflicts between dependencies.
- Ensured all required libraries were available at the correct versions.
- Helped avoid gradle build errors when integrating Unity into the Android app.
This saved us a lot of time debugging dependency issues.
8. App Size Became Too Large (Still Improving)
Since we bundled multiple games together, the app size became too big.
Solution: Using Addressables (Work in Progress)
We are implementing Unity Addressables, which allows us to load games dynamically from a server instead of bundling everything inside the app. This will reduce the APK size while keeping all games accessible.
App Launched Successfully! 🎉
After solving all these challenges, we launched the app on the Play Store. 🎮🚀
👉 Check it out here: MoneyRush
The app reached 1,000+ downloads in just a week and continues to grow! 🚀
Final Thoughts
Building this app was a challenge, but we learned a lot about integrating multiple Unity games into a single app. From handling dependency conflicts and GUID issues to setting up proper communication between Unity and Flutter, every issue taught us something valuable.
If you're looking for expert app development, feel free to reach out! We specialize in building complex custom software solutions that push boundaries. 🚀
About Boopesh Mahendran
Boopesh is one of the Co-Founders of CyberMind Works and the Head of Engineering. An alum of Madras Institute of Technology with a rich professional background, he has previously worked at Adobe and Amazon. His expertise drives the innovative solutions at CyberMind Works.
