From e3584654a6320597c8a70b5ef1e12184b93a7b05 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nicol=C3=A1s=20Uriel=20Navall?= Date: Fri, 20 Jun 2025 18:33:11 -0300 Subject: [PATCH] Added advance setting to select target camera (#108) * Added a dropdown to select the camera you want to target. Also improved rendering tweaks error handling. * 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. * Refactored Preferred_Target_Camera into a path to the cam object to make it more robust. * Added missing config values type changes. * Refactor build guards by using reflection to cover all builds. * Replaced Array.IndexOf by a manual iteration because it wasn't working for some reason. Also polished some vertical spaces. --- src/Config/ConfigManager.cs | 11 + .../Editor/ExplorerEditorBehaviour.cs | 5 + src/UI/Panels/FreeCamPanel.cs | 240 +++++++++++++++--- 3 files changed, 220 insertions(+), 36 deletions(-) diff --git a/src/Config/ConfigManager.cs b/src/Config/ConfigManager.cs index bc8a1d6..edc3d45 100644 --- a/src/Config/ConfigManager.cs +++ b/src/Config/ConfigManager.cs @@ -33,6 +33,7 @@ namespace UnityExplorer.Config 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 @@ namespace UnityExplorer.Config 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 @@ namespace UnityExplorer.Config "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 @@ namespace UnityExplorer.Config 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.", + "\\"); } } } diff --git a/src/Loader/Standalone/Editor/ExplorerEditorBehaviour.cs b/src/Loader/Standalone/Editor/ExplorerEditorBehaviour.cs index ac5eaff..5bcf01f 100644 --- a/src/Loader/Standalone/Editor/ExplorerEditorBehaviour.cs +++ b/src/Loader/Standalone/Editor/ExplorerEditorBehaviour.cs @@ -31,7 +31,9 @@ namespace UnityExplorer.Loader.Standalone public bool Reset_Camera_Transform; public FreeCamPanel.FreeCameraType Default_Freecam; public string Custom_Components_To_Disable; + public string Preferred_Target_Camera; public float Arrow_Size = 1f; + public bool Advanced_Freecam_Selection; public KeyCode Pause; public KeyCode Frameskip; @@ -90,7 +92,10 @@ namespace UnityExplorer.Loader.Standalone 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 68ce98e..f32c10b 100644 --- a/src/UI/Panels/FreeCamPanel.cs +++ b/src/UI/Panels/FreeCamPanel.cs @@ -37,7 +37,7 @@ namespace UnityExplorer.UI.Panels 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 @@ namespace UnityExplorer.UI.Panels 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 @@ namespace UnityExplorer.UI.Panels 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,78 @@ namespace UnityExplorer.UI.Panels freecamCursorUnlocker.Enable(); } + 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.Where(c => c.name != "CUE Camera").ToArray(); + } + + private static Camera GetTargetCamera() + { + if (!ConfigManager.Advanced_Freecam_Selection.Value && !targetCameraDropdown) + { + return Camera.main; + } + + Camera[] cameras = GetAvailableCameras(); + + 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 + 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)); + + // The user selected a target camera at some point, default to that + if (ConfigManager.Preferred_Target_Camera.Value == GetGameObjectPath(cam.gameObject)) { + selectedCameraTargetIndex = i; + } + } + + // If couldn't find the user selected camera default to the main camera + if (selectedCameraTargetIndex == -1) + { + for (int i = 0; i < cameras.Length; i++) + { + if (cameras[i] == Camera.main) + { + selectedCameraTargetIndex = i; + break; + } + } + } + + SetTargetDropdownValueWithoutNotify(selectedCameraTargetIndex); + targetCameraDropdown.captionText.text = cameras[selectedCameraTargetIndex].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 = Camera.main; + Camera currentMain = GetTargetCamera(); + if (currentMain) { lastMainCamera = currentMain; @@ -175,7 +246,7 @@ namespace UnityExplorer.UI.Panels 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; @@ -219,7 +290,7 @@ namespace UnityExplorer.UI.Panels 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; @@ -234,7 +305,7 @@ namespace UnityExplorer.UI.Panels // 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; @@ -329,6 +400,49 @@ namespace UnityExplorer.UI.Panels freecamCursorUnlocker.Disable(); } + internal static void MaybeResetFreecam() + { + if (inFreeCamMode) { + EndFreecam(); + BeginFreecam(); + } + } + + internal static void UpdateTargetCameraAction(int newCameraIndex) + { + Camera[] cameras = GetAvailableCameras(); + Camera cam = cameras[newCameraIndex]; + ConfigManager.Preferred_Target_Camera.Value = GetGameObjectPath(cam.gameObject); + 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; + 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. @@ -433,14 +547,11 @@ namespace UnityExplorer.UI.Panels 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 +559,32 @@ namespace UnityExplorer.UI.Panels UIFactory.SetLayoutElement(cameraTypeDropdownObj, minHeight: 25, minWidth: 150); cameraTypeDropdown.value = (int)ConfigManager.Default_Freecam.Value; + 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); + targetCameraDropdown.onValueChanged.AddListener(UpdateTargetCameraAction); + + try { + Camera[] cameras = GetAvailableCameras(); + foreach (Camera cam in cameras) { + targetCameraDropdown.options.Add(new Dropdown.OptionData(cam.name)); + } + if (Camera.main) { + SetTargetDropdownValueWithoutNotify(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 +1363,65 @@ namespace UnityExplorer.UI.Panels } #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 +1437,61 @@ namespace UnityExplorer.UI.Panels } #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); } }