Skip to content

Commit 5fbfe59

Browse files
committed
refactor: Update Unity integration and enhance message handling
This commit modifies the Unity integration by changing the gameframework dependency to a local path for development. It improves the UnityEngineController by adding initialization checks and retry logic for Unity view attachment. The UnityPlayerUtils class is updated to support command-line argument initialization, and new methods for sending binary messages and batching messages are introduced in the UnityController. Additionally, the FlutterBridge is enhanced to handle binary messages and batch processing, improving communication efficiency between Flutter and Unity. Various build settings in XCodePostBuild.cs are also updated to ensure proper framework configuration.
1 parent edd7081 commit 5fbfe59

31 files changed

Lines changed: 9031 additions & 79 deletions

engines/unity/dart/ios/Classes/UnityEngineController.swift

Lines changed: 73 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -38,49 +38,90 @@ public class UnityEngineController: GameEngineController {
3838
DispatchQueue.main.async { [weak self] in
3939
guard let self = self else { return }
4040

41-
do {
42-
// Load Unity framework
43-
self.unityFramework = self.loadUnityFramework()
44-
45-
guard let unity = self.unityFramework else {
46-
self.sendEvent(name: "onError", data: ["message": "Failed to load Unity framework"])
41+
let utils = UnityPlayerUtils.shared
42+
43+
// Check if Unity integration was initialized in AppDelegate
44+
if !utils.isIntegrationInitialized() {
45+
NSLog("UnityEngineController: Warning - Unity integration not initialized in AppDelegate")
46+
NSLog("UnityEngineController: Call UnityPlayerUtils.shared.InitUnityIntegrationWithOptions() in AppDelegate")
47+
}
48+
49+
// Initialize Unity if not already done (calls runEmbedded)
50+
if !utils.isUnityReady() {
51+
guard utils.initializeUnity() else {
52+
self.sendEvent(name: "onError", data: ["message": "Failed to initialize Unity. Make sure Unity integration is initialized in AppDelegate."])
4753
return
4854
}
49-
50-
// Set up Unity framework
51-
unity.setDataBundleId("com.unity3d.framework")
52-
unity.register(self)
53-
54-
// Show Unity view
55-
unity.showUnityWindow()
56-
57-
// Get Unity view
58-
if let unityView = unity.appController()?.rootViewController?.view {
59-
self.unityView = unityView
60-
self.attachEngine()
61-
}
62-
63-
// Mark as ready
64-
self._isReady = true
65-
self.unityReady = true
66-
67-
self.sendEvent(name: "onCreated", data: nil)
68-
self.sendEvent(name: "onLoaded", data: nil)
69-
70-
} catch {
71-
self.sendEvent(name: "onError", data: ["message": error.localizedDescription])
7255
}
56+
57+
// Get Unity framework from utils
58+
guard let unity = utils.getUnityFramework() else {
59+
self.sendEvent(name: "onError", data: ["message": "Failed to get Unity framework"])
60+
return
61+
}
62+
63+
self.unityFramework = unity
64+
65+
// Register as listener for Unity events
66+
unity.register(self)
67+
68+
// Show Unity window
69+
unity.showUnityWindow()
70+
71+
// Get Unity view with retry logic
72+
// Unity's view hierarchy may not be immediately ready after runEmbedded()
73+
// The waitForUnityView method handles retrying and sends events when ready
74+
self.waitForUnityView(utils: utils, retries: 10)
7375
}
7476
}
7577

7678
public override func attachEngine() {
7779
DispatchQueue.main.async { [weak self] in
7880
guard let self = self, let unityView = self.unityView else { return }
7981

80-
if unityView.superview == nil {
81-
self.addEngineView(unityView)
82-
self.sendEvent(name: "onAttached", data: nil)
82+
// Remove Unity view from its current superview if it has one
83+
// This is critical - Unity may have added the view to its own window hierarchy
84+
if let superview = unityView.superview {
85+
unityView.removeFromSuperview()
86+
superview.layoutIfNeeded()
87+
}
88+
89+
// Add Unity view to our container
90+
self.addEngineView(unityView)
91+
// Force layout update to ensure Unity view is properly sized
92+
unityView.layoutIfNeeded()
93+
94+
self.sendEvent(name: "onAttached", data: nil)
95+
}
96+
}
97+
98+
/// Wait for Unity view to become available with retry logic
99+
/// Unity's view hierarchy may not be immediately ready after runEmbedded()
100+
private func waitForUnityView(utils: UnityPlayerUtils, retries: Int, attempt: Int = 0) {
101+
if let unityView = utils.getUnityView() {
102+
// Unity view is ready - attach it
103+
self.unityView = unityView
104+
self.attachEngine()
105+
NSLog("UnityEngineController: Unity view attached successfully")
106+
107+
// Mark as ready after view is attached
108+
self._isReady = true
109+
self.unityReady = true
110+
111+
self.sendEvent(name: "onCreated", data: nil)
112+
self.sendEvent(name: "onLoaded", data: nil)
113+
} else if attempt < retries {
114+
// Unity view not ready yet, retry after a short delay
115+
let delayMs = 100 * (attempt + 1) // 100ms, 200ms, 300ms, 400ms, 500ms
116+
NSLog("UnityEngineController: Unity view not ready, retrying in \(delayMs)ms (attempt \(attempt + 1)/\(retries))")
117+
118+
DispatchQueue.main.asyncAfter(deadline: .now() + .milliseconds(delayMs)) { [weak self] in
119+
self?.waitForUnityView(utils: utils, retries: retries, attempt: attempt + 1)
83120
}
121+
} else {
122+
// All retries exhausted
123+
NSLog("UnityEngineController: ERROR - Unity view not available after \(retries) attempts")
124+
self.sendEvent(name: "onError", data: ["message": "Unity view not available after initialization"])
84125
}
85126
}
86127

engines/unity/dart/ios/Classes/UnityEnginePlugin.swift

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,16 @@ public class UnityEnginePlugin: NSObject, FlutterPlugin {
1313
private static let engineType = "unity"
1414

1515
public static func register(with registrar: FlutterPluginRegistrar) {
16+
// Auto-initialize Unity integration with command-line arguments
17+
// This eliminates the need for manual AppDelegate modification
18+
// Note: launchOptions is not available here, but Unity works without it for most use cases
19+
UnityPlayerUtils.shared.InitUnityIntegrationWithOptions(
20+
argc: CommandLine.argc,
21+
argv: CommandLine.unsafeArgv,
22+
nil
23+
)
24+
NSLog("UnityEnginePlugin: Auto-initialized Unity integration")
25+
1626
// Register Unity factory with the game framework
1727
let factory = UnityEngineFactory()
1828
GameEngineRegistry.shared.registerFactory(

engines/unity/dart/ios/Classes/UnityPlayerUtils.swift

Lines changed: 80 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,17 @@ import Foundation
22
import UIKit
33
import UnityFramework
44

5+
// MARK: - Global Variables for Unity Initialization
6+
7+
/// Command-line argument count for Unity initialization
8+
private var gArgc: Int32 = 0
9+
10+
/// Command-line argument values for Unity initialization
11+
private var gArgv: UnsafeMutablePointer<UnsafeMutablePointer<Int8>?>? = nil
12+
13+
/// Application launch options for Unity initialization
14+
private var appLaunchOpts: [UIApplication.LaunchOptionsKey: Any]? = nil
15+
516
/// Unity Player utility class for managing Unity framework lifecycle
617
///
718
/// Provides utility methods for Unity player management in Flutter integration.
@@ -30,6 +41,65 @@ import UnityFramework
3041
super.init()
3142
}
3243

44+
// MARK: - Unity Integration Setup
45+
46+
/// Initialize Unity integration with command-line arguments
47+
/// Call this from AppDelegate.application(_:didFinishLaunchingWithOptions:)
48+
///
49+
/// - Parameters:
50+
/// - argc: Command-line argument count (CommandLine.argc)
51+
/// - argv: Command-line argument values (CommandLine.unsafeArgv)
52+
/// - launchingOptions: Application launch options from didFinishLaunchingWithOptions
53+
@objc public func InitUnityIntegrationWithOptions(
54+
argc: Int32,
55+
argv: UnsafeMutablePointer<UnsafeMutablePointer<Int8>?>?,
56+
_ launchingOptions: [UIApplication.LaunchOptionsKey: Any]?
57+
) {
58+
gArgc = argc
59+
gArgv = argv
60+
appLaunchOpts = launchingOptions
61+
NSLog("UnityPlayerUtils: Unity integration options initialized")
62+
}
63+
64+
/// Initialize and run Unity embedded in the app
65+
/// This must be called after InitUnityIntegrationWithOptions
66+
///
67+
/// - Returns: true if Unity was initialized successfully, false otherwise
68+
@objc public func initializeUnity() -> Bool {
69+
// Check if already initialized
70+
if isUnityReady() {
71+
NSLog("UnityPlayerUtils: Unity already initialized")
72+
return true
73+
}
74+
75+
// Load Unity framework
76+
guard let framework = loadUnityFramework() else {
77+
NSLog("UnityPlayerUtils: Failed to load Unity framework")
78+
return false
79+
}
80+
81+
// Set Unity data bundle ID
82+
framework.setDataBundleId("com.unity3d.framework")
83+
84+
// Run Unity embedded with command-line arguments
85+
// This is the critical call that actually starts Unity!
86+
framework.runEmbedded(withArgc: gArgc, argv: gArgv, appLaunchOpts: appLaunchOpts)
87+
88+
// Set Unity's window level below Flutter's window
89+
// This ensures Flutter UI renders on top of Unity, not the other way around
90+
if let window = framework.appController()?.window {
91+
window.windowLevel = UIWindow.Level(UIWindow.Level.normal.rawValue - 1)
92+
}
93+
94+
NSLog("UnityPlayerUtils: Unity initialized and running embedded")
95+
return true
96+
}
97+
98+
/// Check if Unity integration options have been set
99+
@objc public func isIntegrationInitialized() -> Bool {
100+
return gArgv != nil
101+
}
102+
33103
// MARK: - Unity Framework Management
34104

35105
/// Load Unity framework
@@ -201,9 +271,14 @@ import UnityFramework
201271

202272
// MARK: - Utility Methods
203273

204-
/// Get Unity view
274+
/// Get Unity view (uses rootView directly, which is more reliable than rootViewController.view)
205275
@objc public func getUnityView() -> UIView? {
206-
return unityFramework?.appController()?.rootViewController?.view
276+
return unityFramework?.appController()?.rootView
277+
}
278+
279+
/// Check if Unity view is available and ready to be attached
280+
@objc public func isUnityViewReady() -> Bool {
281+
return unityFramework?.appController()?.rootView != nil
207282
}
208283

209284
/// Check if Unity is ready
@@ -219,12 +294,6 @@ import UnityFramework
219294
}
220295
}
221296

222-
// MARK: - UnityFramework Extension
223-
224-
/// Extension to get Unity framework instance
225-
extension UnityFramework {
226-
@objc public static func getInstance() -> UnityFramework? {
227-
return UnityPlayerUtils.shared.getUnityFramework()
228-
}
229-
}
230-
297+
// Note: DO NOT add an extension that overrides UnityFramework.getInstance()
298+
// The getInstance() method is provided by Unity's UnityFramework class itself
299+
// and must not be overridden as it would create a circular dependency.

engines/unity/dart/ios/gameframework_unity.podspec

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,10 @@ Provides Unity 2022.3.x support for embedding Unity games in Flutter application
2020

2121
# Unity Framework will be added by Unity export
2222
s.ios.vendored_frameworks = 'UnityFramework.framework'
23+
24+
# Preserve Unity Data folder structure (critical for IL2CPP to work)
25+
# This ensures the Data folder is copied with the framework to the app bundle
26+
s.preserve_paths = 'UnityFramework.framework/Data'
2327

2428
# Flutter.framework does not contain a i386 slice.
2529
s.pod_target_xcconfig = { 'DEFINES_MODULE' => 'YES', 'EXCLUDED_ARCHS[sdk=iphonesimulator*]' => 'i386' }

engines/unity/dart/lib/gameframework_unity.dart

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,3 +28,4 @@ library gameframework_unity;
2828

2929
export 'src/unity_controller.dart';
3030
export 'src/unity_engine_plugin.dart';
31+
export 'src/unity_message_batcher.dart';

0 commit comments

Comments
 (0)