@dolami-inc/react-native-expo-unity 0.4.5 → 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.
@@ -2,8 +2,6 @@ package expo.modules.unity
2
2
 
3
3
  import android.content.Context
4
4
  import android.util.Log
5
- import android.view.ViewGroup
6
- import android.widget.FrameLayout
7
5
  import expo.modules.kotlin.AppContext
8
6
  import expo.modules.kotlin.viewevent.EventDispatcher
9
7
  import expo.modules.kotlin.views.ExpoView
@@ -29,47 +27,28 @@ class ExpoUnityView(context: Context, appContext: AppContext) : ExpoView(context
29
27
 
30
28
  val bridge = UnityBridge.getInstance()
31
29
 
32
- if (!bridge.isInitialized) {
33
- bridge.initialize(activity)
34
- }
35
-
36
30
  bridge.onMessage = { message ->
37
31
  post {
38
32
  onUnityMessage(mapOf("message" to message))
39
33
  }
40
34
  }
41
35
 
42
- mountUnityView()
43
- }
44
-
45
- private fun mountUnityView() {
46
- val playerView = UnityBridge.getInstance().unityPlayerView ?: run {
47
- Log.w(TAG, "Unity player view not available yet")
48
- return
36
+ if (bridge.isInitialized) {
37
+ // Unity already created — attach the view to this container
38
+ bridge.attachToContainer(this)
39
+ } else {
40
+ // Create Unity player, then attach the view once ready
41
+ bridge.initialize(activity) {
42
+ bridge.attachToContainer(this)
43
+ }
49
44
  }
50
-
51
- // Remove from previous parent if needed
52
- (playerView.parent as? ViewGroup)?.removeView(playerView)
53
-
54
- addView(
55
- playerView,
56
- FrameLayout.LayoutParams(
57
- FrameLayout.LayoutParams.MATCH_PARENT,
58
- FrameLayout.LayoutParams.MATCH_PARENT
59
- )
60
- )
61
-
62
- Log.i(TAG, "Unity view mounted")
63
45
  }
64
46
 
65
- override fun onAttachedToWindow() {
66
- super.onAttachedToWindow()
67
- // Start rendering after the view is in the window hierarchy,
68
- // so Unity's surface is properly connected to the display.
47
+ override fun onWindowFocusChanged(hasWindowFocus: Boolean) {
48
+ super.onWindowFocusChanged(hasWindowFocus)
69
49
  val bridge = UnityBridge.getInstance()
70
- if (bridge.isInitialized) {
71
- bridge.startRendering()
72
- }
50
+ if (!bridge.isInitialized) return
51
+ bridge.unityPlayer?.windowFocusChanged(hasWindowFocus)
73
52
  }
74
53
 
75
54
  override fun onDetachedFromWindow() {
@@ -78,11 +57,12 @@ class ExpoUnityView(context: Context, appContext: AppContext) : ExpoView(context
78
57
 
79
58
  if (bridge.isInitialized) {
80
59
  if (autoUnloadOnUnmount) {
60
+ bridge.detachFromContainer()
81
61
  bridge.unload()
82
- Log.i(TAG, "Auto-unloaded (view detached)")
62
+ Log.i(TAG, "Detached and unloaded (view detached)")
83
63
  } else {
84
64
  bridge.setPaused(true)
85
- Log.i(TAG, "Auto-paused on unmount (autoUnloadOnUnmount=false)")
65
+ Log.i(TAG, "Paused (autoUnloadOnUnmount=false)")
86
66
  }
87
67
  }
88
68
 
@@ -1,10 +1,14 @@
1
1
  package expo.modules.unity
2
2
 
3
3
  import android.app.Activity
4
+ import android.graphics.PixelFormat
4
5
  import android.os.Handler
5
6
  import android.os.Looper
6
7
  import android.util.Log
7
8
  import android.view.View
9
+ import android.view.ViewGroup
10
+ import android.view.WindowManager
11
+ import android.widget.FrameLayout
8
12
  import com.expounity.bridge.NativeCallProxy
9
13
  import com.unity3d.player.IUnityPlayerLifecycleEvents
10
14
  import com.unity3d.player.UnityPlayer
@@ -14,8 +18,9 @@ import com.unity3d.player.UnityPlayerForActivityOrService
14
18
  * Singleton managing the UnityPlayer lifecycle.
15
19
  * Android equivalent of ios/UnityBridge.mm.
16
20
  *
17
- * Uses UnityPlayerForActivityOrService which manages its own rendering
18
- * surface internallysuitable for UaaL (Unity as a Library) embedding.
21
+ * Creates the Unity player and lets the ExpoUnityView add the
22
+ * FrameLayout directlyno background parking, since Unity 6's
23
+ * window management times out when reparenting from a background view.
19
24
  */
20
25
  class UnityBridge private constructor() : IUnityPlayerLifecycleEvents, NativeCallProxy.MessageListener {
21
26
 
@@ -50,20 +55,41 @@ class UnityBridge private constructor() : IUnityPlayerLifecycleEvents, NativeCal
50
55
  * Returns the UnityPlayer's FrameLayout for embedding.
51
56
  * UnityPlayerForActivityOrService creates its own rendering surface internally.
52
57
  */
53
- val unityPlayerView: View?
58
+ val unityPlayerView: FrameLayout?
54
59
  get() = unityPlayer?.frameLayout
55
60
 
56
- fun initialize(activity: Activity) {
57
- if (isInitialized) return
61
+ /**
62
+ * Creates the UnityPlayer. The caller is responsible for adding
63
+ * [unityPlayerView] to the view hierarchy immediately after [onReady] fires.
64
+ */
65
+ fun initialize(activity: Activity, onReady: (() -> Unit)? = null) {
66
+ if (isInitialized) {
67
+ onReady?.invoke()
68
+ return
69
+ }
58
70
 
59
71
  val runInit = Runnable {
60
72
  try {
73
+ // Set RGBA_8888 format for proper rendering
74
+ activity.window.setFormat(PixelFormat.RGBA_8888)
75
+
76
+ // Save fullscreen state before Unity potentially changes it
77
+ val flags = activity.window.attributes.flags
78
+ val wasFullScreen = (flags and WindowManager.LayoutParams.FLAG_FULLSCREEN) != 0
79
+
61
80
  val player = UnityPlayerForActivityOrService(activity, this)
62
81
  unityPlayer = player
63
82
 
64
83
  NativeCallProxy.registerListener(this)
84
+ Log.i(TAG, "Unity player created")
85
+
86
+ // Restore fullscreen state if Unity changed it
87
+ if (!wasFullScreen) {
88
+ activity.window.addFlags(WindowManager.LayoutParams.FLAG_FORCE_NOT_FULLSCREEN)
89
+ activity.window.clearFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN)
90
+ }
65
91
 
66
- Log.i(TAG, "Unity initialized")
92
+ onReady?.invoke()
67
93
  } catch (e: Exception) {
68
94
  Log.e(TAG, "Failed to initialize Unity", e)
69
95
  }
@@ -77,14 +103,42 @@ class UnityBridge private constructor() : IUnityPlayerLifecycleEvents, NativeCal
77
103
  }
78
104
 
79
105
  /**
80
- * Must be called after the Unity view is attached to the window hierarchy.
81
- * Triggers Unity's rendering pipeline.
106
+ * Adds the Unity FrameLayout to the given container and starts rendering.
107
+ * Must be called after [initialize] completes.
108
+ */
109
+ fun attachToContainer(container: ViewGroup) {
110
+ val frame = unityPlayerView ?: run {
111
+ Log.w(TAG, "Unity player view not available")
112
+ return
113
+ }
114
+
115
+ // Remove from current parent if any
116
+ (frame.parent as? ViewGroup)?.removeView(frame)
117
+
118
+ val layoutParams = FrameLayout.LayoutParams(
119
+ FrameLayout.LayoutParams.MATCH_PARENT,
120
+ FrameLayout.LayoutParams.MATCH_PARENT
121
+ )
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
+ }
133
+ }
134
+
135
+ /**
136
+ * Detaches the Unity view from its current parent without destroying it.
82
137
  */
83
- fun startRendering() {
84
- val player = unityPlayer ?: return
85
- player.resume()
86
- player.windowFocusChanged(true)
87
- Log.i(TAG, "Rendering started")
138
+ fun detachFromContainer() {
139
+ val frame = unityPlayerView ?: return
140
+ (frame.parent as? ViewGroup)?.removeView(frame)
141
+ Log.i(TAG, "Unity view detached from container")
88
142
  }
89
143
 
90
144
  fun sendMessage(gameObject: String, methodName: String, message: String) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dolami-inc/react-native-expo-unity",
3
- "version": "0.4.5",
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",