@dolami-inc/react-native-expo-unity 0.5.0 → 0.5.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -33,17 +33,13 @@ class ExpoUnityView(context: Context, appContext: AppContext) : ExpoView(context
33
33
  }
34
34
  }
35
35
 
36
- if (bridge.isReady) {
37
- // Unity already initializedjust reparent the view into this container
38
- bridge.addUnityViewToGroup(this)
39
- Log.i(TAG, "Unity already ready, reparented view")
36
+ if (bridge.isInitialized) {
37
+ // Unity already createdattach the view to this container
38
+ bridge.attachToContainer(this)
40
39
  } else {
41
- // Initialize Unity with a callback that reparents when ready
40
+ // Create Unity player, then attach the view once ready
42
41
  bridge.initialize(activity) {
43
- post {
44
- bridge.addUnityViewToGroup(this)
45
- Log.i(TAG, "Unity ready, view reparented into container")
46
- }
42
+ bridge.attachToContainer(this)
47
43
  }
48
44
  }
49
45
  }
@@ -51,7 +47,7 @@ class ExpoUnityView(context: Context, appContext: AppContext) : ExpoView(context
51
47
  override fun onWindowFocusChanged(hasWindowFocus: Boolean) {
52
48
  super.onWindowFocusChanged(hasWindowFocus)
53
49
  val bridge = UnityBridge.getInstance()
54
- if (!bridge.isReady) return
50
+ if (!bridge.isInitialized) return
55
51
  bridge.unityPlayer?.windowFocusChanged(hasWindowFocus)
56
52
  }
57
53
 
@@ -61,13 +57,12 @@ class ExpoUnityView(context: Context, appContext: AppContext) : ExpoView(context
61
57
 
62
58
  if (bridge.isInitialized) {
63
59
  if (autoUnloadOnUnmount) {
60
+ bridge.detachFromContainer()
64
61
  bridge.unload()
65
- Log.i(TAG, "Auto-unloaded (view detached)")
62
+ Log.i(TAG, "Detached and unloaded (view detached)")
66
63
  } else {
67
- // Park Unity in the background instead of unloading
68
- bridge.parkUnityViewInBackground()
69
64
  bridge.setPaused(true)
70
- Log.i(TAG, "Parked in background (autoUnloadOnUnmount=false)")
65
+ Log.i(TAG, "Paused (autoUnloadOnUnmount=false)")
71
66
  }
72
67
  }
73
68
 
@@ -18,10 +18,9 @@ import com.unity3d.player.UnityPlayerForActivityOrService
18
18
  * Singleton managing the UnityPlayer lifecycle.
19
19
  * Android equivalent of ios/UnityBridge.mm.
20
20
  *
21
- * Uses a "background parking" pattern: Unity is always attached to the
22
- * Activity's content view (at 1x1px, Z=-1) so it stays alive. When a
23
- * React Native view wants to show Unity, we reparent the FrameLayout
24
- * into that view. When it unmounts, we park it back in the background.
21
+ * Creates the Unity player and lets the ExpoUnityView add the
22
+ * FrameLayout directly no background parking, since Unity 6's
23
+ * window management times out when reparenting from a background view.
25
24
  */
26
25
  class UnityBridge private constructor() : IUnityPlayerLifecycleEvents, NativeCallProxy.MessageListener {
27
26
 
@@ -49,9 +48,6 @@ class UnityBridge private constructor() : IUnityPlayerLifecycleEvents, NativeCal
49
48
  /** Tracked here (not on the Module) so it survives module recreation. */
50
49
  var wasRunningBeforeBackground: Boolean = false
51
50
 
52
- var isReady: Boolean = false
53
- private set
54
-
55
51
  val isInitialized: Boolean
56
52
  get() = unityPlayer != null
57
53
 
@@ -62,6 +58,10 @@ class UnityBridge private constructor() : IUnityPlayerLifecycleEvents, NativeCal
62
58
  val unityPlayerView: FrameLayout?
63
59
  get() = unityPlayer?.frameLayout
64
60
 
61
+ /**
62
+ * Creates the UnityPlayer. The caller is responsible for adding
63
+ * [unityPlayerView] to the view hierarchy immediately after [onReady] fires.
64
+ */
65
65
  fun initialize(activity: Activity, onReady: (() -> Unit)? = null) {
66
66
  if (isInitialized) {
67
67
  onReady?.invoke()
@@ -83,26 +83,12 @@ class UnityBridge private constructor() : IUnityPlayerLifecycleEvents, NativeCal
83
83
  NativeCallProxy.registerListener(this)
84
84
  Log.i(TAG, "Unity player created")
85
85
 
86
- // Give Unity time to initialize its rendering pipeline
87
- Thread.sleep(1000)
88
-
89
- // Park the Unity view in the background (1x1px, behind everything)
90
- addUnityViewToBackground(activity)
91
-
92
- // Kick-start rendering
93
- player.windowFocusChanged(true)
94
- player.frameLayout?.requestFocus()
95
- player.resume()
96
-
97
86
  // Restore fullscreen state if Unity changed it
98
87
  if (!wasFullScreen) {
99
88
  activity.window.addFlags(WindowManager.LayoutParams.FLAG_FORCE_NOT_FULLSCREEN)
100
89
  activity.window.clearFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN)
101
90
  }
102
91
 
103
- isReady = true
104
- Log.i(TAG, "Unity initialized and ready")
105
-
106
92
  onReady?.invoke()
107
93
  } catch (e: Exception) {
108
94
  Log.e(TAG, "Failed to initialize Unity", e)
@@ -117,67 +103,42 @@ class UnityBridge private constructor() : IUnityPlayerLifecycleEvents, NativeCal
117
103
  }
118
104
 
119
105
  /**
120
- * Parks the Unity view in the Activity's content view at 1x1 pixels
121
- * behind all other views (Z=-1). This keeps Unity alive but invisible.
106
+ * Adds the Unity FrameLayout to the given container and starts rendering.
107
+ * Must be called after [initialize] completes.
122
108
  */
123
- private fun addUnityViewToBackground(activity: Activity) {
124
- val frame = unityPlayerView ?: return
125
-
126
- // Remove from current parent if any
127
- (frame.parent as? ViewGroup)?.let { parent ->
128
- parent.endViewTransition(frame)
129
- parent.removeView(frame)
109
+ fun attachToContainer(container: ViewGroup) {
110
+ val frame = unityPlayerView ?: run {
111
+ Log.w(TAG, "Unity player view not available")
112
+ return
130
113
  }
131
114
 
132
- frame.z = -1f
133
-
134
- val layoutParams = ViewGroup.LayoutParams(1, 1)
135
- activity.addContentView(frame, layoutParams)
136
- Log.i(TAG, "Unity view parked in background")
137
- }
138
-
139
- /**
140
- * Moves the Unity view from wherever it currently is into the
141
- * specified ViewGroup with MATCH_PARENT layout. Called when the
142
- * React Native component mounts.
143
- */
144
- fun addUnityViewToGroup(group: ViewGroup) {
145
- val frame = unityPlayerView ?: return
146
-
147
- // Remove from current parent
115
+ // Remove from current parent if any
148
116
  (frame.parent as? ViewGroup)?.removeView(frame)
149
117
 
150
- val layoutParams = ViewGroup.LayoutParams(
151
- ViewGroup.LayoutParams.MATCH_PARENT,
152
- ViewGroup.LayoutParams.MATCH_PARENT
118
+ val layoutParams = FrameLayout.LayoutParams(
119
+ FrameLayout.LayoutParams.MATCH_PARENT,
120
+ FrameLayout.LayoutParams.MATCH_PARENT
153
121
  )
154
- group.addView(frame, 0, layoutParams)
155
-
156
- unityPlayer?.windowFocusChanged(true)
157
- frame.requestFocus()
158
- unityPlayer?.resume()
159
-
160
- Log.i(TAG, "Unity view moved to visible container")
122
+ container.addView(frame, 0, layoutParams)
123
+ Log.i(TAG, "Unity view attached to container")
124
+
125
+ // Kick-start rendering after the view is in the hierarchy.
126
+ // Use post to let the layout pass complete first.
127
+ frame.post {
128
+ unityPlayer?.windowFocusChanged(true)
129
+ frame.requestFocus()
130
+ unityPlayer?.resume()
131
+ Log.i(TAG, "Rendering started")
132
+ }
161
133
  }
162
134
 
163
135
  /**
164
- * Parks the Unity view back to the background. Called when the
165
- * React Native component unmounts.
136
+ * Detaches the Unity view from its current parent without destroying it.
166
137
  */
167
- fun parkUnityViewInBackground() {
138
+ fun detachFromContainer() {
168
139
  val frame = unityPlayerView ?: return
169
- val activity = frame.context as? Activity ?: return
170
-
171
- (frame.parent as? ViewGroup)?.let { parent ->
172
- parent.endViewTransition(frame)
173
- parent.removeView(frame)
174
- }
175
-
176
- frame.z = -1f
177
-
178
- val layoutParams = ViewGroup.LayoutParams(1, 1)
179
- activity.addContentView(frame, layoutParams)
180
- Log.i(TAG, "Unity view parked back to background")
140
+ (frame.parent as? ViewGroup)?.removeView(frame)
141
+ Log.i(TAG, "Unity view detached from container")
181
142
  }
182
143
 
183
144
  fun sendMessage(gameObject: String, methodName: String, message: String) {
@@ -203,7 +164,6 @@ class UnityBridge private constructor() : IUnityPlayerLifecycleEvents, NativeCal
203
164
 
204
165
  fun unload() {
205
166
  if (!isInitialized) return
206
- isReady = false
207
167
  Log.i(TAG, "unload called")
208
168
  val action = Runnable {
209
169
  unityPlayer?.unload()
@@ -221,13 +181,11 @@ class UnityBridge private constructor() : IUnityPlayerLifecycleEvents, NativeCal
221
181
  override fun onUnityPlayerUnloaded() {
222
182
  Log.i(TAG, "onUnityPlayerUnloaded")
223
183
  unityPlayer = null
224
- isReady = false
225
184
  }
226
185
 
227
186
  override fun onUnityPlayerQuitted() {
228
187
  Log.i(TAG, "onUnityPlayerQuitted")
229
188
  unityPlayer = null
230
- isReady = false
231
189
  }
232
190
 
233
191
  // NativeCallProxy.MessageListener (Unity -> RN)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dolami-inc/react-native-expo-unity",
3
- "version": "0.5.0",
3
+ "version": "0.5.1",
4
4
  "description": "Unity as a Library (UaaL) bridge for React Native / Expo",
5
5
  "main": "src/index.ts",
6
6
  "types": "src/index.ts",