In some scenario developers using native platform technologies (like Android/Java & iOS/Objective C) want to include in their apps/games features powered by Unity (usually for 3D/2D Real Time Rendering), like AR experience, interaction with 3D models, 2D mini games and more.
Starting with Unity 2019.3.a2, we are introducing a new feature to use Unity as a library in native apps by integrating the Unity runtime components and your content in a native platform project. The Unity Runtime Library exposes controls to manage when and how to load/activate/unload within the native application.
Please note that using Unity as a library requires that you deeply understand the architecture of Android/Java & iOS/Objective C applications.
Limitations
While we tested many scenarios for Unity as a library hosted by a native app, Unity does not control anymore the lifecycle of the runtime, so we cannot guarantee it’ll work in all possible use cases.
For example:
- Unity as a Library supports rendering only full screen, rendering on a part of the screen isn’t supported.
- Loading more than one instance of the Unity runtime isn’t supported.
- You may need to adapt 3rd party Plug-ins (native or managed) to work properly.
How it works
The build process overall is still the same, Unity creates the iOS Xcode and Android Gradle projects, but to enable this feature we changed the generated iOS Xcode and Android Gradle projects, they now have the following structure:
- A library part (iOS framework and Android Archive (AAR) file) that includes all source & plugins
- A thin launcher part that includes app representation data and runs library
Step by step explanations on how to include the iOS / Android library part into your native application when needed.
The rest of this post describes the Xcode / Android Gradle project changes.
iOS
Main functionality revolves around Xcode project to have additional target named UnityFramework. This target includes source/plugins and dependent frameworks and produce UnityFramework.framework file. Launch Screen, Xib, icons, Data, etc. stays in Unity-iPhone target. Unity-iPhone target will have single dependency on UnityFramework target.
UnityFramework provides simple API to manage player from native side to load/unload, pause /unpause when needed, send a message to game object and set bundle where player Data folder is located this way you can have framework file with everything Unity player needs to run in one place.
Change
Unity as a library feature brings some change to API and Xcode project structure. For the most use cases everything will work as before but if you developing or using plugins, custom BuildPostProcessor, PBXProject, CI there is risk these changes affecting your project even if you do not use Unity as a Library feature.
1. PBXProject
PBXProject.GetUnityTargetName and pbxProject->TargetGuidByName(“Unity-iPhone”) are obsolete. Instead use either pbxProject->GetUnityFrameworkTargetGuid() (for source, plugins, dependent frameworks, source build options) or pbxProject->GetUnityMainTargetGuid().
Any use of these obsolete functions will throw an exception.
// Obsolete
string targetGuid = proj.TargetGuidByName("Unity-iPhone");
string targetGuid = proj.TargetGuidByName(PBXProject.GetUnityTargetName());
// Instead call one of these
string targetGuid = proj.GetUnityFrameworkTargetGuid();
string targetGuid = proj.GetUnityMainTargetGuid();
How to support both old and new code paths in your package or custom build postropressor ?
- Rely on plugin importer capabilities whenever possible. Egz. to specify additional frameworks
- Use reflection:
string mainTargetGuid;
string unityFrameworkTargetGuid;
var unityMainTargetGuidMethod = proj.GetType().GetMethod("GetUnityMainTargetGuid");
var unityFrameworkTargetGuidMethod = proj.GetType().GetMethod("GetUnityFrameworkTargetGuid");
if (unityMainTargetGuidMethod != null && unityFrameworkTargetGuidMethod != null)
{
mainTargetGuid = (string)unityMainTargetGuidMethod.Invoke(proj, null);
unityFrameworkTargetGuid = (string)unityFrameworkTargetGuidMethod.Invoke(proj, null);
}
else
{
mainTargetGuid = proj.TargetGuidByName ("Unity-iPhone");
unityFrameworkTargetGuid = mainTargetGuid;
}
2. ProjectCapabilityManager
ProjectCapabilityManager is now accepting guid of a target.
3. xcodebuild
Some build settings have suffixes to specify exact target, they are: *_APP for application target and *_FRAMEWORK for framework target. When building with xcodebuild use suffixed version for:
- PRODUCT_NAME → PRODUCT_NAME_APP
- PROVISIONING_PROFILE → PROVISIONING_PROFILE_APP
- PROVISIONING_PROFILE_SPECIFIER → PROVISIONING_PROFILE_SPECIFIER_APP
- OTHER_LDFLAGS → OTHER_LDFLAGS_FRAMEWORK
Example
Example on how to integrate Unity as a Library into native iOS application.
Android
With Unity as a Library, the structure of the Android Gradle project has changed. If you are using a custom mainTemplate.gradle or AndroidManifest.xml, these changes might affect your project or unity plugin. Previously, Unity used a single gradle module to build an Android app. Now, Unity creates a gradle project with two modules - a unityLibrary module and a launcher module.
Change
The unityLibrary module contains the Unity runtime and project data. This module is a library that can be easily integrated into any other gradle project. It allows embedding Unity into existing native Android applications. The launcher module contains all the icons and the application name. It is a simple Android application that launches Unity.
1. Gradle Project Structure
Unity as a Library have new gradle project structure that better match best practices of android studio project structure: a root folder that includes some sub-projects that can be built and used individually. This makes importing a unity android studio project much easier.
2. Gradle template workflows
Gradle templates describe and configure how to build the android app with gradle. Each gradle template represents a gradle project. Gradle projects can include and depend on other gradle projects.
Unity as a Library gradle templates look like this:
- baseProjectTemplate.gradle - contains configuration that is shared between all other templates/gradle projects (repositories and the dependency on the android gradle plugin);
- launcherTemplate.gradle - contains instructions how to build the android application (bundling, signing, apk splitting). Depends on the unityLibrary project. Outputs either an APK or an app bundle;
- mainTemplate.gradle - contains instructions how to build unity as a library. Outputs a .aar. Users can provide a custom template to override the Unity template (editor setting);
- libTemplate.gradle - works the same way as previously.
3. Manifest workflows
Unity as a Library changes the way unity android application manifests work. Previously, there was a single AndroidManifest.xml that was used to provide the android app with icons, activities, permissions and other settings.
With the new system, we have two manifest files instead of one:
- LauncherManifest.xml - responsible for icons, app name, starting activity (and its intents), install location, supported screen sizes, setting ‘isGame’;
- LibraryManifest.xml - responsible for declaring the unity activity, permissions, theme used by the unity activity, vr modes, vr performance, making the activity non-resizable (for VR), setting max aspect ratio, reacting to configuration changes, orientations, launch modes, android UI hardware acceleration, used features (like gamepad or graphics API), notch support. This manifest can be overridden by providing a custom AndroidManifest.xml inside the Plugins/Android folder.
4. IUnityPlayerLifecycleEvents
Since 2019.3.b4+ IUnityPlayerLifecycleEvents provides a way to react to two important lifecycle events of Unity Player:
- Unload - IUnityPlayerLifecycleEvents.onUnityPlayerUnloaded will be called when unity player will be unloaded though Application.Unload or UnityPlayer.unload(). (UnityPlayer is in unloaded/pause state atm.)
- Quit - IUnityPlayerLifecycleEvents.onUnityPlayerQuitted is called when Unity player did quit. Please note process where unity was running will be killed after this call.
You can pass instance of IUnityPlayerLifecycleEvents to UnityPlayer constructor or override methods in subclasses of UnityPlayer or UnityPlayerActivity
Example
Example on how to integrate Unity as a Library into native Android application.