From f0f5d989222c57dd6d0f8f1be88e7d153455295d Mon Sep 17 00:00:00 2001 From: originalnicodr Date: Mon, 16 Jun 2025 12:48:34 -0300 Subject: [PATCH 1/6] Added a dropdown to select the camera you want to target. Also improved rendering tweaks error handling. --- src/UI/Panels/FreeCamPanel.cs | 186 +++++++++++++++++++++++++++------- 1 file changed, 152 insertions(+), 34 deletions(-) diff --git a/src/UI/Panels/FreeCamPanel.cs b/src/UI/Panels/FreeCamPanel.cs index 68ce98e4..1f62ff48 100644 --- a/src/UI/Panels/FreeCamPanel.cs +++ b/src/UI/Panels/FreeCamPanel.cs @@ -37,7 +37,7 @@ public FreeCamPanel(UIBase owner) : base(owner) public override string Name => "Freecam"; public override UIManager.Panels PanelType => UIManager.Panels.Freecam; - public override int MinWidth => 450; + public override int MinWidth => 500; public override int MinHeight => 750; public override Vector2 DefaultAnchorMin => new(0.4f, 0.4f); public override Vector2 DefaultAnchorMax => new(0.6f, 0.6f); @@ -68,6 +68,7 @@ public FreeCamPanel(UIBase owner) : base(owner) static ButtonRef startStopButton; public static Dropdown cameraTypeDropdown; + internal static Dropdown targetCameraDropdown; internal static FreeCameraType currentCameraType; public static Toggle blockFreecamMovementToggle; public static Toggle blockGamesInputOnFreecamToggle; @@ -103,14 +104,15 @@ public FreeCamPanel(UIBase owner) : base(owner) internal static void BeginFreecam() { - inFreeCamMode = true; connector?.UpdateFreecamStatus(true); previousMousePosition = IInputManager.MousePosition; - CacheMainCamera(); SetupFreeCamera(); + // Need to be done after CacheMainCamera to not trigger targetCameraDropdown onValueChanged + inFreeCamMode = true; + inspectButton.GameObject.SetActive(true); UpdateClippingPlanes(); @@ -119,9 +121,58 @@ internal static void BeginFreecam() freecamCursorUnlocker.Enable(); } - static void CacheMainCamera() + private static Camera[] GetAvailableCameras() + { + Camera[] cameras = {}; + try + { + cameras = Camera.allCameras; + } + // Some ILCPP games might not have Camera.allCameras available + catch { + cameras = RuntimeHelper.FindObjectsOfTypeAll(); + } + + return cameras; + } + + private static Camera GetTargetCamera() { Camera currentMain = Camera.main; + Camera[] cameras = GetAvailableCameras(); + + // If the list of camera was updated since the last time we checked, update the dropdown and select the current main camera if available + // Could filter out CUE camera here, but we shouldnt reach a point where the CUE camera is alive at this point + if (!cameras.Select(c => c.name).SequenceEqual(targetCameraDropdown.options.ToArray().Select(c => c.text))) + { + targetCameraDropdown.options.Clear(); + for (int i = 0; i < cameras.Length; i++) + { + Camera cam = cameras[i]; + targetCameraDropdown.options.Add(new Dropdown.OptionData(cam.name)); + + if (currentMain && cam == currentMain) + { + targetCameraDropdown.value = i; + targetCameraDropdown.captionText.text = cam.name; + } + } + } + + // Fallback to the first camera + if (targetCameraDropdown.value >= cameras.Length) + { + ExplorerCore.LogWarning($"Selected camera index {targetCameraDropdown.value} is out of bounds, resetting to 0."); + targetCameraDropdown.value = 0; + } + + return cameras[targetCameraDropdown.value]; + } + + static void CacheMainCamera() + { + Camera currentMain = GetTargetCamera(); + if (currentMain) { lastMainCamera = currentMain; @@ -262,6 +313,12 @@ static void SetupFreeCamera() internal static void EndFreecam() { + if (ourCamera == null) + { + ExplorerCore.LogWarning("EndFreecam called but ourCamera is null, returning."); + return; + } + inFreeCamMode = false; connector?.UpdateFreecamStatus(false); @@ -292,8 +349,8 @@ internal static void EndFreecam() MaybeToggleOrthographic(true); ToggleCustomComponents(true); MethodInfo resetCullingMatrixMethod = typeof(Camera).GetMethod("ResetCullingMatrix", new Type[] {}); - resetCullingMatrixMethod.Invoke(ourCamera, null); + resetCullingMatrixMethod.Invoke(ourCamera, null); ourCamera.ResetWorldToCameraMatrix(); ourCamera.ResetProjectionMatrix(); ourCamera = null; @@ -329,6 +386,14 @@ internal static void EndFreecam() freecamCursorUnlocker.Disable(); } + internal static void MaybeResetFreecam() + { + if (inFreeCamMode) { + EndFreecam(); + BeginFreecam(); + } + } + // Experimental feature to automatically disable cinemachine when turning on the gameplay freecam. // If it causes problems in some games we should consider removing it or making it a toggle. // Also, if there are more generic Unity components that control the camera we should include them here. @@ -433,14 +498,11 @@ protected override void ConstructPanelContent() GameObject CameraModeRow = UIFactory.CreateHorizontalGroup(ContentRoot, "CameraModeRow", false, false, true, true, 3, default, new(1, 1, 1, 0)); Text CameraMode = UIFactory.CreateLabel(CameraModeRow, "Camera Mode", "Camera Mode:"); - UIFactory.SetLayoutElement(CameraMode.gameObject, minWidth: 100, minHeight: 25); + UIFactory.SetLayoutElement(CameraMode.gameObject, minWidth: 75, minHeight: 25); GameObject cameraTypeDropdownObj = UIFactory.CreateDropdown(CameraModeRow, "CameraType_Dropdown", out cameraTypeDropdown, null, 14, (idx) => { ConfigManager.Default_Freecam.Value = (FreeCameraType)idx; - if (inFreeCamMode) { - EndFreecam(); - BeginFreecam(); - } + MaybeResetFreecam(); }); foreach (FreeCameraType type in Enum.GetValues(typeof(FreeCameraType)).Cast()) { cameraTypeDropdown.options.Add(new Dropdown.OptionData(Enum.GetName(typeof(FreeCameraType), type))); @@ -448,6 +510,31 @@ protected override void ConstructPanelContent() UIFactory.SetLayoutElement(cameraTypeDropdownObj, minHeight: 25, minWidth: 150); cameraTypeDropdown.value = (int)ConfigManager.Default_Freecam.Value; + Text TargetCamLabel = UIFactory.CreateLabel(CameraModeRow, "Target_cam_label", " Target cam:"); + UIFactory.SetLayoutElement(TargetCamLabel.gameObject, minWidth: 75, minHeight: 25); + + GameObject targetCameraDropdownObj = UIFactory.CreateDropdown(CameraModeRow, "TargetCamera_Dropdown", out targetCameraDropdown, null, 14, null); + + targetCameraDropdown.onValueChanged.AddListener(delegate { + MaybeResetFreecam(); + }); + + try { + Camera[] cameras = GetAvailableCameras(); + foreach (Camera cam in cameras) { + targetCameraDropdown.options.Add(new Dropdown.OptionData(cam.name)); + } + if (Camera.main) { + targetCameraDropdown.value = Array.IndexOf(cameras, Camera.main); + targetCameraDropdown.captionText.text = Camera.main.name; + } + } + catch (Exception ex) { + ExplorerCore.LogWarning(ex); + } + + UIFactory.SetLayoutElement(targetCameraDropdownObj, minHeight: 25, minWidth: 150); + AddSpacer(5); GameObject posRow = AddInputField("Position", "Freecam Pos:", "eg. 0 0 0", out positionInput, PositionInput_OnEndEdit); @@ -1226,48 +1313,65 @@ protected virtual void OnEnable() } #endif - try - { - // These doesn't exist for Unity <2017 nor when using HDRP - Type renderPipelineManagerType = ReflectionUtility.GetTypeByName("RenderPipelineManager"); - if (renderPipelineManagerType != null){ + // These doesn't exist for Unity <2017 nor when using HDRP + Type renderPipelineManagerType = ReflectionUtility.GetTypeByName("RenderPipelineManager"); + if (renderPipelineManagerType != null){ + try { EventInfo beginFrameRenderingEvent = renderPipelineManagerType.GetEvent("beginFrameRendering"); if (beginFrameRenderingEvent != null) { beginFrameRenderingEvent.AddEventHandler(null, OnBeforeEvent); } + } + catch { } + + try { EventInfo endFrameRenderingEvent = renderPipelineManagerType.GetEvent("endFrameRendering"); if (endFrameRenderingEvent != null) { endFrameRenderingEvent.AddEventHandler(null, OnAfterEvent); } + } + catch { } + try { EventInfo beginCameraRenderingEvent = renderPipelineManagerType.GetEvent("beginCameraRendering"); if (beginCameraRenderingEvent != null) { beginCameraRenderingEvent.AddEventHandler(null, OnBeforeEvent); } + } + catch { } + + try { EventInfo endCameraRenderingEvent = renderPipelineManagerType.GetEvent("endCameraRendering"); if (endCameraRenderingEvent != null) { endCameraRenderingEvent.AddEventHandler(null, OnAfterEvent); } + } + catch { } + try { EventInfo beginContextRenderingEvent = renderPipelineManagerType.GetEvent("beginContextRendering"); if (beginContextRenderingEvent != null) { beginContextRenderingEvent.AddEventHandler(null, OnBeforeEvent); } + } + catch { } + + try { EventInfo endContextRenderingEvent = renderPipelineManagerType.GetEvent("endContextRendering"); if (endContextRenderingEvent != null) { endContextRenderingEvent.AddEventHandler(null, OnAfterEvent); } } + catch { } + } + try { EventInfo onBeforeRenderEvent = typeof(Application).GetEvent("onBeforeRender"); if (onBeforeRenderEvent != null) { onBeforeRenderEvent.AddEventHandler(null, onBeforeRenderAction); } } - catch (Exception exception) - { - ExplorerCore.LogWarning($"Failed to add event handler to rendering pipeline: {exception}"); - } + catch { } } protected virtual void OnDisable() @@ -1283,47 +1387,61 @@ protected virtual void OnDisable() } #endif - try - { - // These doesn't exist for Unity <2017 nor when using HDRP - Type renderPipelineManagerType = ReflectionUtility.GetTypeByName("RenderPipelineManager"); - if (renderPipelineManagerType != null){ + // These doesn't exist for Unity <2017 nor when using HDRP + Type renderPipelineManagerType = ReflectionUtility.GetTypeByName("RenderPipelineManager"); + if (renderPipelineManagerType != null){ + try { EventInfo beginFrameRenderingEvent = renderPipelineManagerType.GetEvent("beginFrameRendering"); if (beginFrameRenderingEvent != null) { beginFrameRenderingEvent.RemoveEventHandler(null, OnBeforeEvent); } + } + catch { } + + try { EventInfo endFrameRenderingEvent = renderPipelineManagerType.GetEvent("endFrameRendering"); if (endFrameRenderingEvent != null) { endFrameRenderingEvent.RemoveEventHandler(null, OnAfterEvent); } - + } + catch { } + + try { EventInfo beginCameraRenderingEvent = renderPipelineManagerType.GetEvent("beginCameraRendering"); if (beginCameraRenderingEvent != null) { beginCameraRenderingEvent.RemoveEventHandler(null, OnBeforeEvent); } + } + catch { } + + try { EventInfo endCameraRenderingEvent = renderPipelineManagerType.GetEvent("endCameraRendering"); if (endCameraRenderingEvent != null) { endCameraRenderingEvent.RemoveEventHandler(null, OnAfterEvent); } - + } + catch { } + + try { EventInfo beginContextRenderingEvent = renderPipelineManagerType.GetEvent("beginContextRendering"); if (beginContextRenderingEvent != null) { beginContextRenderingEvent.RemoveEventHandler(null, OnBeforeEvent); } + } + catch { } + + try { EventInfo endContextRenderingEvent = renderPipelineManagerType.GetEvent("endContextRendering"); if (endContextRenderingEvent != null) { endContextRenderingEvent.RemoveEventHandler(null, OnAfterEvent); } } - - EventInfo onBeforeRenderEvent = typeof(Application).GetEvent("onBeforeRender"); - if (onBeforeRenderEvent != null) { - onBeforeRenderEvent.RemoveEventHandler(null, onBeforeRenderAction); - } + catch { } } - catch (Exception exception) - { - ExplorerCore.LogWarning($"Failed to remove event handler from rendering pipeline: {exception}"); + + EventInfo onBeforeRenderEvent = typeof(Application).GetEvent("onBeforeRender"); + if (onBeforeRenderEvent != null) { + onBeforeRenderEvent.RemoveEventHandler(null, onBeforeRenderAction); } } From 98fb915be15c79ef6adc78f68ee8ca382daa33ba Mon Sep 17 00:00:00 2001 From: originalnicodr Date: Mon, 16 Jun 2025 16:42:29 -0300 Subject: [PATCH 2/6] Added settings to optionally render the target camera dropdown and to remember the selected target camera across sessions. Also renamed the CUE camera objects for standardization (and filtering) sake. --- src/Config/ConfigManager.cs | 11 +++ .../Editor/ExplorerEditorBehaviour.cs | 5 + src/UI/Panels/FreeCamPanel.cs | 94 +++++++++++++------ 3 files changed, 83 insertions(+), 27 deletions(-) diff --git a/src/Config/ConfigManager.cs b/src/Config/ConfigManager.cs index bc8a1d61..6e68ea5b 100644 --- a/src/Config/ConfigManager.cs +++ b/src/Config/ConfigManager.cs @@ -33,6 +33,7 @@ public static class ConfigManager public static ConfigElement Auto_Scale_UI; public static ConfigElement Reset_Camera_Transform; public static ConfigElement Arrow_Size; + public static ConfigElement Advanced_Freecam_Selection; public static ConfigElement Pause; public static ConfigElement Frameskip; @@ -63,6 +64,7 @@ public static class ConfigManager public static ConfigElement Default_Freecam; public static ConfigElement Custom_Components_To_Disable; + public static ConfigElement Preferred_Target_Camera; // internal configs internal static InternalConfigHandler InternalHandler { get; private set; } @@ -198,6 +200,10 @@ private static void CreateConfigElements() "Cam Paths nodes and Lights Manager lights visualizers' arrow size (must be positive) (needs visualizer toggled to reflect changes).", 1f); + Advanced_Freecam_Selection = new("Advanced Freecam Selection", + "Enables certain advanced settings on the Freecam panel, in case the user can't get the freecam to work properly (requires game reset).", + false); + Pause = new("Pause", "Toggle the pause of the game.", KeyCode.PageUp); @@ -311,6 +317,11 @@ private static void CreateConfigElements() Custom_Components_To_Disable = new("Custom components to disable", "List of custom components to disable when enabling the freecam (gets automatically updated when editing it from the freecam panel).", ""); + + Preferred_Target_Camera = new("Preferred Target Camera", + "The camera that will be targeted by the freecam methods.\n" + + "Only used when Advanced Freecam Selection is enabled.", + -1); } } } diff --git a/src/Loader/Standalone/Editor/ExplorerEditorBehaviour.cs b/src/Loader/Standalone/Editor/ExplorerEditorBehaviour.cs index ac5eaffd..9cc4a0b4 100644 --- a/src/Loader/Standalone/Editor/ExplorerEditorBehaviour.cs +++ b/src/Loader/Standalone/Editor/ExplorerEditorBehaviour.cs @@ -31,7 +31,9 @@ public class ExplorerEditorBehaviour : MonoBehaviour public bool Reset_Camera_Transform; public FreeCamPanel.FreeCameraType Default_Freecam; public string Custom_Components_To_Disable; + public int Preferred_Target_Camera; public float Arrow_Size = 1f; + public bool Advanced_Freecam_Selection; public KeyCode Pause; public KeyCode Frameskip; @@ -90,7 +92,10 @@ internal void LoadConfigs() ConfigManager.Reset_Camera_Transform.Value = this.Reset_Camera_Transform; ConfigManager.Default_Freecam.Value = this.Default_Freecam; ConfigManager.Custom_Components_To_Disable.Value = this.Custom_Components_To_Disable; + ConfigManager.Preferred_Target_Camera.Value = this.Preferred_Target_Camera; + ConfigManager.Arrow_Size.Value = this.Arrow_Size; + ConfigManager.Advanced_Freecam_Selection.Value = this.Advanced_Freecam_Selection; ConfigManager.Pause.Value = this.Pause; ConfigManager.Frameskip.Value = this.Frameskip; diff --git a/src/UI/Panels/FreeCamPanel.cs b/src/UI/Panels/FreeCamPanel.cs index 1f62ff48..112da341 100644 --- a/src/UI/Panels/FreeCamPanel.cs +++ b/src/UI/Panels/FreeCamPanel.cs @@ -139,10 +139,16 @@ private static Camera[] GetAvailableCameras() private static Camera GetTargetCamera() { Camera currentMain = Camera.main; - Camera[] cameras = GetAvailableCameras(); + if (!ConfigManager.Advanced_Freecam_Selection.Value && !targetCameraDropdown) + { + return currentMain; + } + + Camera[] cameras = GetAvailableCameras().Where(c => c.name != "CUE Camera").ToArray(); + + int selectedCameraTargetIndex = -1; // If the list of camera was updated since the last time we checked, update the dropdown and select the current main camera if available - // Could filter out CUE camera here, but we shouldnt reach a point where the CUE camera is alive at this point if (!cameras.Select(c => c.name).SequenceEqual(targetCameraDropdown.options.ToArray().Select(c => c.text))) { targetCameraDropdown.options.Clear(); @@ -151,12 +157,29 @@ private static Camera GetTargetCamera() Camera cam = cameras[i]; targetCameraDropdown.options.Add(new Dropdown.OptionData(cam.name)); - if (currentMain && cam == currentMain) - { - targetCameraDropdown.value = i; - targetCameraDropdown.captionText.text = cam.name; + // The user selected a target camera at some point, default to that + if (i == ConfigManager.Preferred_Target_Camera.Value) { + selectedCameraTargetIndex = i; } } + + // If couldn't find the user selected camera default to the main camera + if (selectedCameraTargetIndex == -1) + { + selectedCameraTargetIndex = Array.IndexOf(cameras, Camera.main); + } + +#if STANDALONE + // Standalone doesn't have a reference to Dropdown.SetValueWithoutNotify + // Should still check this implementation + targetCameraDropdown.onValueChanged.RemoveListener(UpdateTargetCameraAction); + targetCameraDropdown.value = selectedCameraTargetIndex; + targetCameraDropdown.onValueChanged.AddListener(UpdateTargetCameraAction); +#else + targetCameraDropdown.SetValueWithoutNotify(selectedCameraTargetIndex); +#endif + + targetCameraDropdown.captionText.text = cameras[selectedCameraTargetIndex].name; } // Fallback to the first camera @@ -226,7 +249,7 @@ static void SetupFreeCamera() lastMainCamera.enabled = false; } - ourCamera = new GameObject("UE_Freecam").AddComponent(); + ourCamera = new GameObject("CUE Camera").AddComponent(); ourCamera.gameObject.tag = "MainCamera"; GameObject.DontDestroyOnLoad(ourCamera.gameObject); ourCamera.gameObject.hideFlags = HideFlags.HideAndDontSave; @@ -270,7 +293,7 @@ static void SetupFreeCamera() MaybeToggleOrthographic(false); ToggleCustomComponents(false); - cameraMatrixOverrider = new GameObject("[CUE] Camera Matrix Overrider").AddComponent(); + cameraMatrixOverrider = new GameObject("CUE Camera").AddComponent(); cameraMatrixOverrider.enabled = false; cameraMatrixOverrider.transform.position = lastMainCamera.transform.position; cameraMatrixOverrider.transform.rotation = lastMainCamera.transform.rotation; @@ -285,7 +308,7 @@ static void SetupFreeCamera() // Fallback in case we couldn't find the main camera for some reason if (!ourCamera) { - ourCamera = new GameObject("UE_Freecam").AddComponent(); + ourCamera = new GameObject("CUE Camera").AddComponent(); ourCamera.gameObject.tag = "MainCamera"; GameObject.DontDestroyOnLoad(ourCamera.gameObject); ourCamera.gameObject.hideFlags = HideFlags.HideAndDontSave; @@ -394,6 +417,12 @@ internal static void MaybeResetFreecam() } } + internal static void UpdateTargetCameraAction(int newCameraIndex) + { + ConfigManager.Preferred_Target_Camera.Value = newCameraIndex; + MaybeResetFreecam(); + } + // Experimental feature to automatically disable cinemachine when turning on the gameplay freecam. // If it causes problems in some games we should consider removing it or making it a toggle. // Also, if there are more generic Unity components that control the camera we should include them here. @@ -510,30 +539,41 @@ protected override void ConstructPanelContent() UIFactory.SetLayoutElement(cameraTypeDropdownObj, minHeight: 25, minWidth: 150); cameraTypeDropdown.value = (int)ConfigManager.Default_Freecam.Value; - Text TargetCamLabel = UIFactory.CreateLabel(CameraModeRow, "Target_cam_label", " Target cam:"); - UIFactory.SetLayoutElement(TargetCamLabel.gameObject, minWidth: 75, minHeight: 25); + if (ConfigManager.Advanced_Freecam_Selection.Value) + { + Text TargetCamLabel = UIFactory.CreateLabel(CameraModeRow, "Target_cam_label", " Target cam:"); + UIFactory.SetLayoutElement(TargetCamLabel.gameObject, minWidth: 75, minHeight: 25); - GameObject targetCameraDropdownObj = UIFactory.CreateDropdown(CameraModeRow, "TargetCamera_Dropdown", out targetCameraDropdown, null, 14, null); + GameObject targetCameraDropdownObj = UIFactory.CreateDropdown(CameraModeRow, "TargetCamera_Dropdown", out targetCameraDropdown, null, 14, null); - targetCameraDropdown.onValueChanged.AddListener(delegate { - MaybeResetFreecam(); - }); + targetCameraDropdown.onValueChanged.AddListener(UpdateTargetCameraAction); - try { - Camera[] cameras = GetAvailableCameras(); - foreach (Camera cam in cameras) { - targetCameraDropdown.options.Add(new Dropdown.OptionData(cam.name)); + try { + Camera[] cameras = GetAvailableCameras(); + foreach (Camera cam in cameras) { + targetCameraDropdown.options.Add(new Dropdown.OptionData(cam.name)); + } + if (Camera.main) { +#if STANDALONE + // Standalone doesn't have a reference to Dropdown.SetValueWithoutNotify + // Should still check this implementation + targetCameraDropdown.onValueChanged.RemoveListener(UpdateTargetCameraAction); + targetCameraDropdown.value = Array.IndexOf(cameras, Camera.main); + targetCameraDropdown.onValueChanged.AddListener(UpdateTargetCameraAction); +#else + targetCameraDropdown.SetValueWithoutNotify(Array.IndexOf(cameras, Camera.main)); +#endif + + targetCameraDropdown.captionText.text = Camera.main.name; + } } - if (Camera.main) { - targetCameraDropdown.value = Array.IndexOf(cameras, Camera.main); - targetCameraDropdown.captionText.text = Camera.main.name; + catch (Exception ex) { + ExplorerCore.LogWarning(ex); } - } - catch (Exception ex) { - ExplorerCore.LogWarning(ex); - } - UIFactory.SetLayoutElement(targetCameraDropdownObj, minHeight: 25, minWidth: 150); + UIFactory.SetLayoutElement(targetCameraDropdownObj, minHeight: 25, minWidth: 150); + } + AddSpacer(5); From a9f89a0341197ffb4929175d170f33982b4937bd Mon Sep 17 00:00:00 2001 From: originalnicodr Date: Wed, 18 Jun 2025 00:59:44 -0300 Subject: [PATCH 3/6] Refactored Preferred_Target_Camera into a path to the cam object to make it more robust. --- src/UI/Panels/FreeCamPanel.cs | 21 +++++++++++++++++---- 1 file changed, 17 insertions(+), 4 deletions(-) diff --git a/src/UI/Panels/FreeCamPanel.cs b/src/UI/Panels/FreeCamPanel.cs index 112da341..03f426c5 100644 --- a/src/UI/Panels/FreeCamPanel.cs +++ b/src/UI/Panels/FreeCamPanel.cs @@ -133,7 +133,7 @@ private static Camera[] GetAvailableCameras() cameras = RuntimeHelper.FindObjectsOfTypeAll(); } - return cameras; + return cameras.Where(c => c.name != "CUE Camera").ToArray(); } private static Camera GetTargetCamera() @@ -144,7 +144,7 @@ private static Camera GetTargetCamera() return currentMain; } - Camera[] cameras = GetAvailableCameras().Where(c => c.name != "CUE Camera").ToArray(); + Camera[] cameras = GetAvailableCameras(); int selectedCameraTargetIndex = -1; @@ -158,7 +158,7 @@ private static Camera GetTargetCamera() targetCameraDropdown.options.Add(new Dropdown.OptionData(cam.name)); // The user selected a target camera at some point, default to that - if (i == ConfigManager.Preferred_Target_Camera.Value) { + if (ConfigManager.Preferred_Target_Camera.Value == GetGameObjectPath(cam.gameObject)) { selectedCameraTargetIndex = i; } } @@ -419,10 +419,23 @@ internal static void MaybeResetFreecam() internal static void UpdateTargetCameraAction(int newCameraIndex) { - ConfigManager.Preferred_Target_Camera.Value = newCameraIndex; + Camera[] cameras = GetAvailableCameras(); + Camera cam = cameras[newCameraIndex]; + ConfigManager.Preferred_Target_Camera.Value = GetGameObjectPath(cam.gameObject); MaybeResetFreecam(); } + public static string GetGameObjectPath(GameObject obj) + { + string path = "/" + obj.name; + while (obj.transform.parent != null) + { + obj = obj.transform.parent.gameObject; + path = "/" + obj.name + path; + } + return path; + } + // Experimental feature to automatically disable cinemachine when turning on the gameplay freecam. // If it causes problems in some games we should consider removing it or making it a toggle. // Also, if there are more generic Unity components that control the camera we should include them here. From 7785f9c51ad59f920f88594152f61516514f5e35 Mon Sep 17 00:00:00 2001 From: originalnicodr Date: Fri, 20 Jun 2025 15:41:57 -0300 Subject: [PATCH 4/6] Added missing config values type changes. --- src/Config/ConfigManager.cs | 4 ++-- src/Loader/Standalone/Editor/ExplorerEditorBehaviour.cs | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Config/ConfigManager.cs b/src/Config/ConfigManager.cs index 6e68ea5b..edc3d454 100644 --- a/src/Config/ConfigManager.cs +++ b/src/Config/ConfigManager.cs @@ -64,7 +64,7 @@ public static class ConfigManager public static ConfigElement Default_Freecam; public static ConfigElement Custom_Components_To_Disable; - public static ConfigElement Preferred_Target_Camera; + public static ConfigElement Preferred_Target_Camera; // internal configs internal static InternalConfigHandler InternalHandler { get; private set; } @@ -321,7 +321,7 @@ private static void CreateConfigElements() Preferred_Target_Camera = new("Preferred Target Camera", "The camera that will be targeted by the freecam methods.\n" + "Only used when Advanced Freecam Selection is enabled.", - -1); + "\\"); } } } diff --git a/src/Loader/Standalone/Editor/ExplorerEditorBehaviour.cs b/src/Loader/Standalone/Editor/ExplorerEditorBehaviour.cs index 9cc4a0b4..5bcf01ff 100644 --- a/src/Loader/Standalone/Editor/ExplorerEditorBehaviour.cs +++ b/src/Loader/Standalone/Editor/ExplorerEditorBehaviour.cs @@ -31,7 +31,7 @@ public class ExplorerEditorBehaviour : MonoBehaviour public bool Reset_Camera_Transform; public FreeCamPanel.FreeCameraType Default_Freecam; public string Custom_Components_To_Disable; - public int Preferred_Target_Camera; + public string Preferred_Target_Camera; public float Arrow_Size = 1f; public bool Advanced_Freecam_Selection; From 676f412a60a3e6ac2c3ac5ed28de97a0d118c1a7 Mon Sep 17 00:00:00 2001 From: originalnicodr Date: Fri, 20 Jun 2025 17:21:13 -0300 Subject: [PATCH 5/6] Refactor build guards by using reflection to cover all builds. --- src/UI/Panels/FreeCamPanel.cs | 38 +++++++++++++++++------------------ 1 file changed, 18 insertions(+), 20 deletions(-) diff --git a/src/UI/Panels/FreeCamPanel.cs b/src/UI/Panels/FreeCamPanel.cs index 03f426c5..f0aaf1cf 100644 --- a/src/UI/Panels/FreeCamPanel.cs +++ b/src/UI/Panels/FreeCamPanel.cs @@ -169,16 +169,7 @@ private static Camera GetTargetCamera() selectedCameraTargetIndex = Array.IndexOf(cameras, Camera.main); } -#if STANDALONE - // Standalone doesn't have a reference to Dropdown.SetValueWithoutNotify - // Should still check this implementation - targetCameraDropdown.onValueChanged.RemoveListener(UpdateTargetCameraAction); - targetCameraDropdown.value = selectedCameraTargetIndex; - targetCameraDropdown.onValueChanged.AddListener(UpdateTargetCameraAction); -#else - targetCameraDropdown.SetValueWithoutNotify(selectedCameraTargetIndex); -#endif - + SetTargetDropdownValueWithoutNotify(selectedCameraTargetIndex); targetCameraDropdown.captionText.text = cameras[selectedCameraTargetIndex].name; } @@ -425,6 +416,22 @@ internal static void UpdateTargetCameraAction(int newCameraIndex) MaybeResetFreecam(); } + internal static void SetTargetDropdownValueWithoutNotify(int selectedCameraTargetIndex) + { + // Some build types don't have a reference to Dropdown.SetValueWithoutNotify + MethodInfo SetValueWithoutNotifyMethod = targetCameraDropdown.GetType().GetMethod("SetValueWithoutNotify", new[] { typeof(int) }); + if (SetValueWithoutNotifyMethod != null) + { + SetValueWithoutNotifyMethod.Invoke(targetCameraDropdown, new object[] { selectedCameraTargetIndex }); + } + else + { + targetCameraDropdown.onValueChanged.RemoveListener(UpdateTargetCameraAction); + targetCameraDropdown.value = selectedCameraTargetIndex; + targetCameraDropdown.onValueChanged.AddListener(UpdateTargetCameraAction); + } + } + public static string GetGameObjectPath(GameObject obj) { string path = "/" + obj.name; @@ -567,16 +574,7 @@ protected override void ConstructPanelContent() targetCameraDropdown.options.Add(new Dropdown.OptionData(cam.name)); } if (Camera.main) { -#if STANDALONE - // Standalone doesn't have a reference to Dropdown.SetValueWithoutNotify - // Should still check this implementation - targetCameraDropdown.onValueChanged.RemoveListener(UpdateTargetCameraAction); - targetCameraDropdown.value = Array.IndexOf(cameras, Camera.main); - targetCameraDropdown.onValueChanged.AddListener(UpdateTargetCameraAction); -#else - targetCameraDropdown.SetValueWithoutNotify(Array.IndexOf(cameras, Camera.main)); -#endif - + SetTargetDropdownValueWithoutNotify(Array.IndexOf(cameras, Camera.main)); targetCameraDropdown.captionText.text = Camera.main.name; } } From 8c2c06aff410eb75c6c5ab4cbd1137254aa9aa3d Mon Sep 17 00:00:00 2001 From: originalnicodr Date: Fri, 20 Jun 2025 18:08:15 -0300 Subject: [PATCH 6/6] Replaced Array.IndexOf by a manual iteration because it wasn't working for some reason. Also polished some vertical spaces. --- src/UI/Panels/FreeCamPanel.cs | 21 ++++++++++----------- 1 file changed, 10 insertions(+), 11 deletions(-) diff --git a/src/UI/Panels/FreeCamPanel.cs b/src/UI/Panels/FreeCamPanel.cs index f0aaf1cf..f32c10b6 100644 --- a/src/UI/Panels/FreeCamPanel.cs +++ b/src/UI/Panels/FreeCamPanel.cs @@ -138,10 +138,9 @@ private static Camera[] GetAvailableCameras() private static Camera GetTargetCamera() { - Camera currentMain = Camera.main; if (!ConfigManager.Advanced_Freecam_Selection.Value && !targetCameraDropdown) { - return currentMain; + return Camera.main; } Camera[] cameras = GetAvailableCameras(); @@ -166,7 +165,14 @@ private static Camera GetTargetCamera() // If couldn't find the user selected camera default to the main camera if (selectedCameraTargetIndex == -1) { - selectedCameraTargetIndex = Array.IndexOf(cameras, Camera.main); + for (int i = 0; i < cameras.Length; i++) + { + if (cameras[i] == Camera.main) + { + selectedCameraTargetIndex = i; + break; + } + } } SetTargetDropdownValueWithoutNotify(selectedCameraTargetIndex); @@ -327,12 +333,6 @@ static void SetupFreeCamera() internal static void EndFreecam() { - if (ourCamera == null) - { - ExplorerCore.LogWarning("EndFreecam called but ourCamera is null, returning."); - return; - } - inFreeCamMode = false; connector?.UpdateFreecamStatus(false); @@ -363,8 +363,8 @@ internal static void EndFreecam() MaybeToggleOrthographic(true); ToggleCustomComponents(true); MethodInfo resetCullingMatrixMethod = typeof(Camera).GetMethod("ResetCullingMatrix", new Type[] {}); - resetCullingMatrixMethod.Invoke(ourCamera, null); + ourCamera.ResetWorldToCameraMatrix(); ourCamera.ResetProjectionMatrix(); ourCamera = null; @@ -565,7 +565,6 @@ protected override void ConstructPanelContent() UIFactory.SetLayoutElement(TargetCamLabel.gameObject, minWidth: 75, minHeight: 25); GameObject targetCameraDropdownObj = UIFactory.CreateDropdown(CameraModeRow, "TargetCamera_Dropdown", out targetCameraDropdown, null, 14, null); - targetCameraDropdown.onValueChanged.AddListener(UpdateTargetCameraAction); try {