From dea23fa4f0e1463e47fb6a896a4b9e59feae6bcd Mon Sep 17 00:00:00 2001 From: Morten Daniel Fornes Date: Mon, 1 Jun 2026 16:30:33 +0200 Subject: [PATCH 1/4] unload and destroy --- android/src/main/java/no/fuse/rnunity/RNUnityManager.java | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/android/src/main/java/no/fuse/rnunity/RNUnityManager.java b/android/src/main/java/no/fuse/rnunity/RNUnityManager.java index 5afafa1..a79e920 100644 --- a/android/src/main/java/no/fuse/rnunity/RNUnityManager.java +++ b/android/src/main/java/no/fuse/rnunity/RNUnityManager.java @@ -182,6 +182,14 @@ public void onUnityPlayerQuitted() { Log.d("RNUnityManager", "onUnityPlayerQuitted"); } + public void unload() { + player.unload(); + } + + public void destroy() { + player.destroy(); + } + static void resetPlayerParent() { var view = player.getFrameLayout(); if (view.getParent() == null) From 0ddb00601c79918fcf52d5e5dc08fbd2dd23cec8 Mon Sep 17 00:00:00 2001 From: Semen Lunin Date: Tue, 1 Jul 2025 15:09:50 +0300 Subject: [PATCH 2/4] android changes --- .../java/no/fuse/rnunity/RNUnityManager.java | 244 +++++++----------- .../java/no/fuse/rnunity/RNUnityModule.java | 150 +++++++++++ .../java/no/fuse/rnunity/RNUnityView.java | 70 +++++ .../main/java/no/fuse/rnunity/UPlayer.java | 121 +++++++++ 4 files changed, 433 insertions(+), 152 deletions(-) create mode 100644 android/src/main/java/no/fuse/rnunity/RNUnityView.java create mode 100644 android/src/main/java/no/fuse/rnunity/UPlayer.java diff --git a/android/src/main/java/no/fuse/rnunity/RNUnityManager.java b/android/src/main/java/no/fuse/rnunity/RNUnityManager.java index a79e920..0c262a9 100644 --- a/android/src/main/java/no/fuse/rnunity/RNUnityManager.java +++ b/android/src/main/java/no/fuse/rnunity/RNUnityManager.java @@ -1,33 +1,37 @@ package no.fuse.rnunity; -import android.app.Activity; +import static no.fuse.rnunity.RNUnityModule.*; +import no.fuse.rnunity.RNUnityModule.UnityPlayerCallback; + import android.os.Handler; -import android.os.Looper; -import android.util.Log; -import android.widget.FrameLayout; import android.view.View; -import android.view.ViewGroup; -import android.view.ViewParent; -import android.view.Window; -import android.view.WindowManager; +import android.util.Log; +import com.facebook.infer.annotation.Assertions; +import com.facebook.react.bridge.Arguments; import com.facebook.react.bridge.LifecycleEventListener; import com.facebook.react.bridge.ReactApplicationContext; import com.facebook.react.uimanager.SimpleViewManager; +import com.facebook.react.bridge.ReactContext; +import com.facebook.react.bridge.ReadableArray; +import com.facebook.react.bridge.WritableMap; +import com.facebook.react.common.MapBuilder; +import com.facebook.react.module.annotations.ReactModule; import com.facebook.react.uimanager.ThemedReactContext; -import com.unity3d.player.IUnityPlayerLifecycleEvents; -import com.unity3d.player.UnityPlayerForActivityOrService; -import com.unity3d.player.UnityPlayer; +import com.facebook.react.uimanager.annotations.ReactProp; +import com.facebook.react.uimanager.events.RCTEventEmitter; import java.lang.reflect.InvocationTargetException; -import java.lang.reflect.Method; +import java.util.Map; import javax.annotation.Nonnull; +import javax.annotation.Nullable; -public class RNUnityManager extends SimpleViewManager implements LifecycleEventListener, View.OnAttachStateChangeListener, IUnityPlayerLifecycleEvents { +@ReactModule(name = RNUnityManager.REACT_CLASS) +public class RNUnityManager extends SimpleViewManager implements LifecycleEventListener, View.OnAttachStateChangeListener { public static final String REACT_CLASS = "UnityView"; - public static UnityPlayerForActivityOrService player; + public static RNUnityView view; public RNUnityManager(ReactApplicationContext reactContext) { super(); @@ -42,184 +46,120 @@ public String getName() { @Nonnull @Override - protected FrameLayout createViewInstance(@Nonnull ThemedReactContext reactContext) { + protected RNUnityView createViewInstance(@Nonnull ThemedReactContext reactContext) { Log.d("RNUnityManager", "createViewInstance"); final RNUnityModule module = RNUnityModule.getInstance(); + view = new RNUnityView(reactContext); + view.addOnAttachStateChangeListener(this); - final Activity activity = reactContext.getCurrentActivity(); - final Handler handler = new Handler(Looper.getMainLooper()); - - final Window window = activity.getWindow(); - final int windowFlags = window.getAttributes().flags; - - if (player == null) { - player = new UnityPlayerForActivityOrService(activity, this); + if (getPlayer() != null) { + try { + view.setUnityPlayer(getPlayer()); + } catch (InvocationTargetException | NoSuchMethodException | IllegalAccessException e) {} } else { - // Force-remove parent view to avoid exceptions thrown - resetPlayerParent(); - - // Restart Unity after delay to workaround a glitch - // where Unity sometimes seems to stop rendering - handler.postDelayed(new Runnable() { + try { + createPlayer(reactContext.getCurrentActivity(), new UnityPlayerCallback() { @Override - public void run() { - activity.runOnUiThread(new Runnable() { - @Override - public void run() { - Log.d("RNUnityManager", "Restarting Unity player"); - player.pause(); - player.resume(); - } - }); + public void onReady() throws InvocationTargetException, NoSuchMethodException, IllegalAccessException { + view.setUnityPlayer(getPlayer()); + module.emitEvent("ready", ""); } - }, 199); - } - - // Restore original window flags after Unity has modified them - activity.runOnUiThread(new Runnable() { - @Override - public void run() { - int flags = windowFlags; - boolean keepAwake = module.getKeepAwake(); - - // Race condition: Keep awake might be enabled or disabled after - // the original flags were saved - if (keepAwake) { - flags |= WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON; - } else { - flags &= ~WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON; + + @Override + public void onUnload() { + module.emitEvent("onPlayerUnload", ""); } - - Log.d("RNUnityManager", "Resetting window flags; keepAwake=" + keepAwake); - window.setFlags(flags, -1); - } - }); - - // Race condition: Keep awake is sometimes turned off after a few - // seconds. Check this every second for a couple of seconds and turn it - // on again if necessary. - for (int i = 2; i < 9; i++) { - final int delay = i * 1000; - handler.postDelayed(new Runnable() { + @Override - public void run() { - activity.runOnUiThread(new Runnable() { - @Override - public void run() { - final Window window = activity.getWindow(); - final int windowFlags = window.getAttributes().flags; - final boolean existing = (windowFlags & WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON) == WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON; - final boolean keepAwake = module.getKeepAwake(); - - if (existing != keepAwake) { - Log.d("RNUnityManager", "Keep awake flag out of sync; delay=" + delay); - module.setKeepAwake(keepAwake); - } - } - }); + public void onQuit() { + module.emitEvent("onPlayerQuit", ""); } - }, delay); - } - - player.getFrameLayout().addOnAttachStateChangeListener(this); - player.windowFocusChanged(true); - player.getFrameLayout().requestFocus(); - player.resume(); - - // Notify the app that Unity is ready to receive messages - module.emitEvent("ready", ""); - - return player.getFrameLayout(); + }); + } catch (InvocationTargetException | NoSuchMethodException | IllegalAccessException e) {} + } + + return view; } @Override - public void onDropViewInstance(FrameLayout view) { - Log.d("RNUnityManager", "onDropViewInstance: " + view); - + public void onDropViewInstance(RNUnityView view) { view.removeOnAttachStateChangeListener(this); - player.pause(); + super.onDropViewInstance(view); } @Override public void onHostResume() { - Log.d("RNUnityManager", "onHostResume"); - - if (player != null) - player.resume(); + if (isUnityReady()) { + assert getPlayer() != null; + getPlayer().resume(); + restoreUnityUserState(); + } } @Override public void onHostPause() { - Log.d("RNUnityManager", "onHostPause"); - - if (player != null) - player.pause(); + if (isUnityReady()) { + assert getPlayer() != null; + getPlayer().pause(); + } } @Override public void onHostDestroy() { - Log.d("RNUnityManager", "onHostDestroy"); + if (isUnityReady()) { + assert getPlayer() != null; + getPlayer().destroy(); + } } @Override - public void onViewAttachedToWindow(View view) { - Log.d("RNUnityManager", "onViewAttachedToWindow: " + view); + public void onViewAttachedToWindow(View v) { + restoreUnityUserState(); } @Override - public void onViewDetachedFromWindow(View view) { - Log.d("RNUnityManager", "onViewDetachedFromWindow: " + view); - } + public void onViewDetachedFromWindow(View v) {} - @Override - public void onUnityPlayerUnloaded() { - Log.d("RNUnityManager", "onUnityPlayerUnloaded"); + public void unloadUnity(RNUnityView view) { + if (isUnityReady()) { + getPlayer().unload(); + } } - @Override - public void onUnityPlayerQuitted() { - Log.d("RNUnityManager", "onUnityPlayerQuitted"); + public void pauseUnity(RNUnityView view, boolean pause) { + if (isUnityReady()) { + assert getPlayer() != null; + getPlayer().pause(); + } } - public void unload() { - player.unload(); + public void resumeUnity(RNUnityView view) { + if (isUnityReady()) { + assert getPlayer() != null; + getPlayer().resume(); + } } - public void destroy() { - player.destroy(); + public void windowFocusChanged(RNUnityView view, boolean hasFocus) { + if (isUnityReady()) { + assert getPlayer() != null; + getPlayer().windowFocusChanged(hasFocus); + } } - - static void resetPlayerParent() { - var view = player.getFrameLayout(); - if (view.getParent() == null) - return; - - ((ViewGroup) view.getParent()).removeView(view); - - if (view.getParent() == null) - return; - - Log.d("RNUnityManager", "Using reflection to reset parent!"); - - try { - Method method = View.class.getDeclaredMethod("assignParent", new Class[]{ ViewParent.class }); - method.setAccessible(true); - method.invoke(player, new Object[]{ null }); - method.setAccessible(false); - } catch (NoSuchMethodException e) { - e.printStackTrace(); - } catch (IllegalArgumentException e) { - e.printStackTrace(); - } catch (IllegalAccessException e) { - e.printStackTrace(); - } catch (InvocationTargetException e) { - e.printStackTrace(); + + private void restoreUnityUserState() { + // restore the unity player state + if (isUnityPaused()) { + Handler handler = new Handler(); + handler.postDelayed(new Runnable() { + @Override + public void run() { + if (getPlayer() != null) { + getPlayer().pause(); + } + } + }, 300); } - - if (view.getParent() == null) - return; - - Log.e("RNUnityManager", "Unable to reset parent of player " + player); } } diff --git a/android/src/main/java/no/fuse/rnunity/RNUnityModule.java b/android/src/main/java/no/fuse/rnunity/RNUnityModule.java index 1c26c3a..bd11cf9 100644 --- a/android/src/main/java/no/fuse/rnunity/RNUnityModule.java +++ b/android/src/main/java/no/fuse/rnunity/RNUnityModule.java @@ -2,7 +2,10 @@ package no.fuse.rnunity; import android.app.Activity; +import android.graphics.PixelFormat; import android.util.Log; +import android.os.Build; +import android.view.ViewGroup; import android.view.WindowManager; import com.facebook.react.bridge.ReactApplicationContext; @@ -13,14 +16,36 @@ import com.unity3d.player.UnityPlayer; +import static android.view.ViewGroup.LayoutParams.MATCH_PARENT; +import java.lang.reflect.InvocationTargetException; + public class RNUnityModule extends ReactContextBaseJavaModule { static RNUnityModule instance; + private static UPlayer unityPlayer; + public static boolean _isUnityReady; + public static boolean _isUnityPaused; + public static boolean _fullScreen; // Called by C# public static RNUnityModule getInstance() { return instance; } + public static UPlayer getPlayer() { + if (!_isUnityReady) { + return null; + } + return unityPlayer; + } + + public static boolean isUnityReady() { + return _isUnityReady; + } + + public static boolean isUnityPaused() { + return _isUnityPaused; + } + boolean keepAwake; public RNUnityModule(ReactApplicationContext reactContext) { @@ -79,4 +104,129 @@ public void run() { }); } } + + public static void createPlayer(final Activity activity, final UnityPlayerCallback callback) throws InvocationTargetException, NoSuchMethodException, IllegalAccessException { + if (unityPlayer != null) { + callback.onReady(); + + return; + } + + if (activity != null) { + activity.runOnUiThread(new Runnable() { + @Override + public void run() { + activity.getWindow().setFormat(PixelFormat.RGBA_8888); + int flag = activity.getWindow().getAttributes().flags; + boolean fullScreen = false; + if ((flag & WindowManager.LayoutParams.FLAG_FULLSCREEN) == WindowManager.LayoutParams.FLAG_FULLSCREEN) { + fullScreen = true; + } + + try { + unityPlayer = new UPlayer(activity, callback); + } catch (ClassNotFoundException | InstantiationException | IllegalAccessException | InvocationTargetException e) {} + + try { + // wait a moment. fix unity cannot start when startup. + Thread.sleep(1000); + } catch (Exception e) {} + + // start unity + try { + addUnityViewToBackground(); + } catch (InvocationTargetException | IllegalAccessException | NoSuchMethodException e) {} + + unityPlayer.windowFocusChanged(true); + + try { + unityPlayer.requestFocusPlayer(); + } catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException e) {} + + unityPlayer.resume(); + + if (!fullScreen) { + activity.getWindow().addFlags(WindowManager.LayoutParams.FLAG_FORCE_NOT_FULLSCREEN); + activity.getWindow().clearFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN); + } + + _isUnityReady = true; + + try { + callback.onReady(); + } catch (InvocationTargetException | IllegalAccessException | NoSuchMethodException e) {} + } + }); + } + } + + public static void pause() { + if (unityPlayer != null) { + unityPlayer.pause(); + _isUnityPaused = true; + } + } + + public static void resume() { + if (unityPlayer != null) { + unityPlayer.resume(); + _isUnityPaused = false; + } + } + + public static void unload() { + if (unityPlayer != null) { + unityPlayer.unload(); + _isUnityPaused = false; + } + } + + public static void addUnityViewToBackground() throws InvocationTargetException, NoSuchMethodException, IllegalAccessException { + if (unityPlayer == null) { + return; + } + + if (unityPlayer.getParentPlayer() != null) { + // NOTE: If we're being detached as part of the transition, make sure + // to explicitly finish the transition first, as it might still keep + // the view's parent around despite calling `removeView()` here. This + // prevents a crash on an `addContentView()` later on. + // Otherwise, if there's no transition, it's a no-op. + // See https://stackoverflow.com/a/58247331 + ((ViewGroup) unityPlayer.getParentPlayer()).endViewTransition(unityPlayer.requestFrame()); + ((ViewGroup) unityPlayer.getParentPlayer()).removeView(unityPlayer.requestFrame()); + } + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { + unityPlayer.setZ(-1f); + } + + final Activity activity = ((Activity) unityPlayer.getContextPlayer()); + ViewGroup.LayoutParams layoutParams = new ViewGroup.LayoutParams(1, 1); + activity.addContentView(unityPlayer.requestFrame(), layoutParams); + } + + public static void addUnityViewToGroup(ViewGroup group) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException { + if (unityPlayer == null) { + return; + } + + if (unityPlayer.getParentPlayer() != null) { + ((ViewGroup) unityPlayer.getParentPlayer()).removeView(unityPlayer.requestFrame()); + } + + ViewGroup.LayoutParams layoutParams = new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT); + group.addView(unityPlayer.requestFrame(), 0, layoutParams); + unityPlayer.windowFocusChanged(true); + unityPlayer.requestFocusPlayer(); + unityPlayer.resume(); + } + + public interface UnityPlayerCallback { + void onReady() throws InvocationTargetException, NoSuchMethodException, IllegalAccessException; + + void onUnload(); + + void onQuit(); + } } diff --git a/android/src/main/java/no/fuse/rnunity/RNUnityView.java b/android/src/main/java/no/fuse/rnunity/RNUnityView.java new file mode 100644 index 0000000..eede7aa --- /dev/null +++ b/android/src/main/java/no/fuse/rnunity/RNUnityView.java @@ -0,0 +1,70 @@ +package no.fuse.rnunity; + +import static no.fuse.rnunity.RNUnityModule.*; +import android.content.Context; + +import android.annotation.SuppressLint; +import android.content.res.Configuration; +import android.widget.FrameLayout; + +import java.lang.reflect.InvocationTargetException; + +@SuppressLint("ViewConstructor") +public class RNUnityView extends FrameLayout { + private UPlayer view; + public boolean keepPlayerMounted = false; + + public RNUnityView(Context context) { + super(context); + } + + public void setUnityPlayer(UPlayer player) throws InvocationTargetException, NoSuchMethodException, IllegalAccessException { + this.view = player; + addUnityViewToGroup(this); + } + + @Override + public void onWindowFocusChanged(boolean hasWindowFocus) { + super.onWindowFocusChanged(hasWindowFocus); + + if (view == null) { + return; + } + + view.windowFocusChanged(hasWindowFocus); + + if (!keepPlayerMounted || !_isUnityReady) { + return; + } + + // pause Unity on blur, resume on focus + if (hasWindowFocus && _isUnityPaused) { + // view.requestFocus(); + view.resume(); + } else if (!hasWindowFocus && !_isUnityPaused) { + view.pause(); + } + } + + @Override + protected void onConfigurationChanged(Configuration newConfig) { + super.onConfigurationChanged(newConfig); + + if (view != null) { + view.configurationChanged(newConfig); + } + } + + @Override + protected void onDetachedFromWindow() { + if (!this.keepPlayerMounted) { + try { + addUnityViewToBackground(); + } catch (InvocationTargetException | NoSuchMethodException | IllegalAccessException e) { + throw new RuntimeException(e); + } + } + + super.onDetachedFromWindow(); + } +} diff --git a/android/src/main/java/no/fuse/rnunity/UPlayer.java b/android/src/main/java/no/fuse/rnunity/UPlayer.java new file mode 100644 index 0000000..d93a22f --- /dev/null +++ b/android/src/main/java/no/fuse/rnunity/UPlayer.java @@ -0,0 +1,121 @@ +package no.fuse.rnunity; + +import android.app.Activity; +import android.content.res.Configuration; +import android.widget.FrameLayout; + +import com.unity3d.player.*; + +import java.lang.reflect.Constructor; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; + +public class UPlayer { + private static UnityPlayer unityPlayer; + + public UPlayer(final Activity activity, final RNUnityModule.UnityPlayerCallback callback) throws ClassNotFoundException, InvocationTargetException, IllegalAccessException, InstantiationException { + super(); + Class _player = null; + + try { + _player = Class.forName("com.unity3d.player.UnityPlayerForActivityOrService"); + } catch (ClassNotFoundException e) { + _player = Class.forName("com.unity3d.player.UnityPlayer"); + } + + Constructor constructor = _player.getConstructors()[1]; + unityPlayer = (UnityPlayer) constructor.newInstance(activity, new IUnityPlayerLifecycleEvents() { + @Override + public void onUnityPlayerUnloaded() { + callback.onUnload(); + } + + @Override + public void onUnityPlayerQuitted() { + callback.onQuit(); + } + }); + } + + public static void UnitySendMessage(String gameObject, String methodName, String message) { + UnityPlayer.UnitySendMessage(gameObject, methodName, message); + } + + public void pause() { + unityPlayer.pause(); + } + + public void windowFocusChanged(boolean b) { + unityPlayer.windowFocusChanged(b); + } + + public void resume() { + unityPlayer.resume(); + } + + public void unload() { + unityPlayer.unload(); + } + + public Object getParentPlayer() throws NoSuchMethodException, InvocationTargetException, IllegalAccessException { + try { + Method getFrameLayout = unityPlayer.getClass().getMethod("getFrameLayout"); + FrameLayout frame = (FrameLayout) this.requestFrame(); + + return frame.getParent(); + } catch (NoSuchMethodException e) { + Method getParent = unityPlayer.getClass().getMethod("getParent"); + + return getParent.invoke(unityPlayer); + } + } + + public void configurationChanged(Configuration newConfig) { + unityPlayer.configurationChanged(newConfig); + } + + public void destroy() { + unityPlayer.destroy(); + } + + public void requestFocusPlayer() throws NoSuchMethodException, InvocationTargetException, IllegalAccessException { + try { + Method getFrameLayout = unityPlayer.getClass().getMethod("getFrameLayout"); + + FrameLayout frame = (FrameLayout) this.requestFrame(); + frame.requestFocus(); + } catch (NoSuchMethodException e) { + Method requestFocus = unityPlayer.getClass().getMethod("requestFocus"); + + requestFocus.invoke(unityPlayer); + } + } + + public FrameLayout requestFrame() throws NoSuchMethodException { + try { + Method getFrameLayout = unityPlayer.getClass().getMethod("getFrameLayout"); + + return (FrameLayout) getFrameLayout.invoke(unityPlayer); + } catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException e) { + // If it is old UnityPlayer, use isInstance() and cast() to bypass incompatible type checks when compiling using newer versions of UnityPlayer + if (FrameLayout.class.isInstance(unityPlayer)) { + return FrameLayout.class.cast(unityPlayer); + } else { + return null; + } + } + } + + public void setZ(float v) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException { + try { + Method setZ = unityPlayer.getClass().getMethod("setZ"); + + setZ.invoke(unityPlayer, v); + } catch (NoSuchMethodException e) {} + } + + public Object getContextPlayer() { + return unityPlayer.getContext(); + } + +} From 127249457f3003bd26d04727ac70f980f4c2b984 Mon Sep 17 00:00:00 2001 From: Semen Lunin Date: Thu, 10 Jul 2025 17:04:12 +0300 Subject: [PATCH 3/4] add unloadUnity method, changes for android/ios --- .../java/no/fuse/rnunity/RNUnityModule.java | 14 +++++++ ios/RNUnity/RNUnity.m | 42 ++++++++++++++----- ios/RNUnity/UnityResponderView.m | 1 + src/UnityModule.ts | 4 ++ src/UnityView.tsx | 1 + 5 files changed, 52 insertions(+), 10 deletions(-) diff --git a/android/src/main/java/no/fuse/rnunity/RNUnityModule.java b/android/src/main/java/no/fuse/rnunity/RNUnityModule.java index bd11cf9..cbfa1d7 100644 --- a/android/src/main/java/no/fuse/rnunity/RNUnityModule.java +++ b/android/src/main/java/no/fuse/rnunity/RNUnityModule.java @@ -105,6 +105,20 @@ public void run() { } } + @ReactMethod + public void unloadUnity() { + final Activity activity = getCurrentActivity(); + if (activity != null) { + activity.runOnUiThread(new Runnable() { + @Override + public void run() { + Log.d("RNUnityModule", "Unload Unity"); + unload(); + } + }); + } + } + public static void createPlayer(final Activity activity, final UnityPlayerCallback callback) throws InvocationTargetException, NoSuchMethodException, IllegalAccessException { if (unityPlayer != null) { callback.onReady(); diff --git a/ios/RNUnity/RNUnity.m b/ios/RNUnity/RNUnity.m index e4de413..c1b53a4 100644 --- a/ios/RNUnity/RNUnity.m +++ b/ios/RNUnity/RNUnity.m @@ -51,24 +51,38 @@ + (void)initFromSwift { _RNUnity_argv = *_NSGetArgv(); } ++ (bool)unityIsInitialized { + return _RNUnity_sharedInstance && [[self ufw] appController]; +} + + (void)applicationWillResignActive:(UIApplication *)application { - [[[self ufw] appController] applicationWillResignActive: application]; + if ([RNUnity unityIsInitialized]) { + [[[self ufw] appController] applicationWillResignActive: application]; + } } + (void)applicationDidEnterBackground:(UIApplication *)application { - [[[self ufw] appController] applicationDidEnterBackground: application]; + if ([RNUnity unityIsInitialized]) { + [[[self ufw] appController] applicationDidEnterBackground: application]; + } } + (void)applicationWillEnterForeground:(UIApplication *)application { - [[[self ufw] appController] applicationWillEnterForeground: application]; + if ([RNUnity unityIsInitialized]) { + [[[self ufw] appController] applicationWillEnterForeground: application]; + } } + (void)applicationDidBecomeActive:(UIApplication *)application { - [[[self ufw] appController] applicationDidBecomeActive: application]; + if ([RNUnity unityIsInitialized]) { + [[[self ufw] appController] applicationDidBecomeActive: application]; + } } + (void)applicationWillTerminate:(UIApplication *)application { - [[[self ufw] appController] applicationWillTerminate: application]; + if ([RNUnity unityIsInitialized]) { + [[[self ufw] appController] applicationWillTerminate: application]; + } } + (id)launchWithOptions:(NSDictionary *)applaunchOptions { @@ -84,10 +98,11 @@ + (void)applicationWillTerminate:(UIApplication *)application { // Unity is not initialized [framework setExecuteHeader: &__dso_handle]; } - [framework setRNUnityProxy: (id)self]; + [framework setDataBundleId: [bundle.bundleIdentifier cStringUsingEncoding:NSUTF8StringEncoding]]; - [framework runEmbeddedWithArgc: self.argc argv: self.argv appLaunchOpts: applaunchOptions]; + [framework setRNUnityProxy: (id)self]; + [framework runEmbeddedWithArgc: self.argc argv: self.argv appLaunchOpts: applaunchOptions]; [self setUfw:framework]; // Notify the app that Unity is ready to receive messages @@ -122,13 +137,13 @@ - (dispatch_queue_t)methodQueue { } RCT_EXPORT_METHOD(pause) { - if (_RNUnity_sharedInstance) { + if ([RNUnity unityIsInitialized]) { [[RNUnity ufw] pause:true]; } } RCT_EXPORT_METHOD(resume) { - if (_RNUnity_sharedInstance) { + if ([RNUnity unityIsInitialized]) { [[RNUnity ufw] pause:false]; } } @@ -136,7 +151,7 @@ - (dispatch_queue_t)methodQueue { RCT_EXPORT_METHOD(sendMessage:(NSString *)gameObject functionName:(NSString *)functionName message:(NSString *)message) { - if (_RNUnity_sharedInstance) { + if ([RNUnity unityIsInitialized]) { [[RNUnity ufw] sendMessageToGOWithName:[gameObject UTF8String] functionName:[functionName UTF8String] message:[message UTF8String]]; } } @@ -157,4 +172,11 @@ - (void)emitEvent:(NSString *)name data:(NSString *)data { }); } +RCT_EXPORT_METHOD(unloadUnity) { + if ([RNUnity unityIsInitialized]) { + [[RNUnity ufw] unloadApplication]; + [RNUnity setUfw:nil]; + } +} + @end diff --git a/ios/RNUnity/UnityResponderView.m b/ios/RNUnity/UnityResponderView.m index 3ab30c0..f76376e 100644 --- a/ios/RNUnity/UnityResponderView.m +++ b/ios/RNUnity/UnityResponderView.m @@ -20,6 +20,7 @@ - (void)layoutSubviews { [_unityView removeFromSuperview]; _unityView.frame = self.bounds; [self insertSubview:_unityView atIndex:0]; + _unityView = nil; UIWindow *main = [[[UIApplication sharedApplication] delegate] window]; diff --git a/src/UnityModule.ts b/src/UnityModule.ts index 6b2b186..ddcecd5 100644 --- a/src/UnityModule.ts +++ b/src/UnityModule.ts @@ -103,6 +103,10 @@ class UnityModuleImpl implements UnityModule { setKeepAwake(enabled: boolean) { RNUnity.setKeepAwake(enabled) } + + unloadUnity() { + RNUnity.unloadUnity() + } } export const UnityModule: UnityModule = new UnityModuleImpl() diff --git a/src/UnityView.tsx b/src/UnityView.tsx index eaae5fe..4d1d62e 100644 --- a/src/UnityView.tsx +++ b/src/UnityView.tsx @@ -36,6 +36,7 @@ export class UnityView extends React.Component { componentWillUnmount() { if (this.props.keepAwake) { UnityModule.setKeepAwake(false) + UnityModule.unloadUnity() } this.listener?.remove() From f5fc5b23f516ab8d69d7c86d81b52342d72e2079 Mon Sep 17 00:00:00 2001 From: Morten Daniel Fornes Date: Tue, 2 Jun 2026 13:08:33 +0200 Subject: [PATCH 4/4] Stop calling removed unloadUnity --- src/UnityView.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/UnityView.tsx b/src/UnityView.tsx index 4d1d62e..a4afa2e 100644 --- a/src/UnityView.tsx +++ b/src/UnityView.tsx @@ -36,7 +36,7 @@ export class UnityView extends React.Component { componentWillUnmount() { if (this.props.keepAwake) { UnityModule.setKeepAwake(false) - UnityModule.unloadUnity() + // UnityModule.unloadUnity() } this.listener?.remove()