1
0
mirror of https://github.com/originalnicodr/CinematicUnityExplorer.git synced 2025-07-18 09:27:57 +08:00

Added advance setting to select target camera (#108)
Some checks failed
Build CinematicUnityExplorer / build_dotnet (push) Has been cancelled
Build CinematicUnityExplorer / build_connector (push) Has been cancelled

* 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.
This commit is contained in:
Nicolás Uriel Navall
2025-06-20 18:33:11 -03:00
committed by GitHub
parent 388c9fbc29
commit e3584654a6
3 changed files with 220 additions and 36 deletions

View File

@ -33,6 +33,7 @@ namespace UnityExplorer.Config
public static ConfigElement<bool> Auto_Scale_UI;
public static ConfigElement<bool> Reset_Camera_Transform;
public static ConfigElement<float> Arrow_Size;
public static ConfigElement<bool> Advanced_Freecam_Selection;
public static ConfigElement<KeyCode> Pause;
public static ConfigElement<KeyCode> Frameskip;
@ -63,6 +64,7 @@ namespace UnityExplorer.Config
public static ConfigElement<FreeCamPanel.FreeCameraType> Default_Freecam;
public static ConfigElement<string> Custom_Components_To_Disable;
public static ConfigElement<string> 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.",
"\\");
}
}
}

View File

@ -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;

View File

@ -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<Camera>();
}
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<Camera>();
ourCamera = new GameObject("CUE Camera").AddComponent<Camera>();
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<Camera>();
cameraMatrixOverrider = new GameObject("CUE Camera").AddComponent<Camera>();
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<Camera>();
ourCamera = new GameObject("CUE Camera").AddComponent<Camera>();
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<FreeCameraType>()) {
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);
}
}