1
0
mirror of https://github.com/originalnicodr/CinematicUnityExplorer.git synced 2025-07-19 01:57:56 +08:00

14 Commits

Author SHA1 Message Date
e3584654a6 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.
2025-06-20 18:33:11 -03:00
388c9fbc29 Added a new field on the freecam panel to add components to disable when enabling the freecam.
Some checks failed
Build CinematicUnityExplorer / build_dotnet (push) Has been cancelled
Build CinematicUnityExplorer / build_connector (push) Has been cancelled
2025-03-02 14:51:51 -03:00
2f35b9d2c7 Made the slow-down hotkey tilt the camera 90 degrees instead, to tilt vertically in games that don't allow vertical ARs.
Some checks are pending
Build CinematicUnityExplorer / build_dotnet (push) Waiting to run
Build CinematicUnityExplorer / build_connector (push) Waiting to run
2025-03-01 10:58:05 -03:00
b82cc62186 Destroy cinemachine in Cloned Freecam mode as we won't be enabling it back again since the cam will get destroyed anyway, given that in some games the cinemachine cam would still bother us even if it's disabled.
Some checks failed
Build CinematicUnityExplorer / build_dotnet (push) Has been cancelled
Build CinematicUnityExplorer / build_connector (push) Has been cancelled
2025-02-26 00:45:15 -03:00
9429b84cfa Made lights in the lights manager panel and effects in the postprocessing effects panel prioritize focusing on already opened inspector tabs when trying to inspect the corresponding object.
Some checks failed
Build CinematicUnityExplorer / build_dotnet (push) Has been cancelled
Build CinematicUnityExplorer / build_connector (push) Has been cancelled
2025-02-23 22:56:09 -03:00
2719c69753 Added new safeguards when adding and removing event handlers to the rendering pipeline. Also changed the "reset camera on new freecam sessions" option default value to true, as its more intuitive.
Some checks are pending
Build CinematicUnityExplorer / build_dotnet (push) Waiting to run
Build CinematicUnityExplorer / build_connector (push) Waiting to run
2025-02-23 20:27:50 -03:00
9144f89e32 Add Adjust ArrowSize (#101)
Some checks failed
Build CinematicUnityExplorer / build_dotnet (push) Has been cancelled
Build CinematicUnityExplorer / build_connector (push) Has been cancelled
Co-authored-by: originalnicodr <niconavall@gmail.com>
2025-02-07 19:39:21 -03:00
3bc1268444 Updated GitHub action upload-artifact from v3 to v4
Some checks failed
Build CinematicUnityExplorer / build_dotnet (push) Has been cancelled
Build CinematicUnityExplorer / build_connector (push) Has been cancelled
2025-02-02 18:48:04 -03:00
4c100daf83 Fixed bug when disabling orthographic property on freecam 2025-02-02 18:42:30 -03:00
e7d591176e Fixed poses not being saved correctly for some .NET versions.
Some checks failed
Build CinematicUnityExplorer / build_dotnet (push) Has been cancelled
Build CinematicUnityExplorer / build_connector (push) Has been cancelled
2025-01-20 13:21:09 -03:00
ca46607c49 Automatically update the default freecam every time a new freecam type is selected. 2025-01-20 11:51:50 -03:00
7fb67b4e28 Added steps on the freecam enabling to enable/disable the orthographic property, as I believe it doesn't make sense to freecam while that is enabled
(might still be worth making this a setting in the future tho)
2025-01-20 11:21:03 -03:00
c0f42ad983 Enhanced the camera selection logic on the world mouse inspector. Also fixed the camera coordinates not being displayed properly.
Based on the work of @p1xel8ted on https://github.com/yukieiji/UnityExplorer/pull/35.
2025-01-20 10:56:35 -03:00
6111c1743b Avoid processing freecam input movement when there is a focused input field. 2024-12-15 21:13:20 -03:00
12 changed files with 630 additions and 112 deletions

View File

@ -29,79 +29,79 @@ jobs:
# Upload artifacts
- name: Upload BepInEx.IL2CPP
uses: actions/upload-artifact@v3
uses: actions/upload-artifact@v4
with:
name: CinematicUnityExplorer.BepInEx.IL2CPP.zip
path: ./Release/CinematicUnityExplorer.BepInEx.IL2CPP/
- name: Upload BepInEx.IL2CPP.CoreCLR
uses: actions/upload-artifact@v3
uses: actions/upload-artifact@v4
with:
name: CinematicUnityExplorer.BepInEx.IL2CPP.CoreCLR.zip
path: ./Release/CinematicUnityExplorer.BepInEx.IL2CPP.CoreCLR/
- name: Upload BepInEx.Unity.IL2CPP.CoreCLR
uses: actions/upload-artifact@v3
uses: actions/upload-artifact@v4
with:
name: CinematicUnityExplorer.BepInEx.Unity.IL2CPP.CoreCLR.zip
path: ./Release/CinematicUnityExplorer.BepInEx.Unity.IL2CPP.CoreCLR/
- name: Upload BepInEx5.Mono
uses: actions/upload-artifact@v3
uses: actions/upload-artifact@v4
with:
name: CinematicUnityExplorer.BepInEx5.Mono.zip
path: ./Release/CinematicUnityExplorer.BepInEx5.Mono/
- name: Upload BepInEx6.Mono
uses: actions/upload-artifact@v3
uses: actions/upload-artifact@v4
with:
name: CinematicUnityExplorer.BepInEx6.Mono.zip
path: ./Release/CinematicUnityExplorer.BepInEx6.Mono/
- name: Upload BepInEx6.Unity.Mono
uses: actions/upload-artifact@v3
uses: actions/upload-artifact@v4
with:
name: CinematicUnityExplorer.BepInEx6.Unity.Mono.zip
path: ./Release/CinematicUnityExplorer.BepInEx6.Unity.Mono/
- name: Upload MelonLoader.IL2CPP
uses: actions/upload-artifact@v3
uses: actions/upload-artifact@v4
with:
name: CinematicUnityExplorer.MelonLoader.IL2CPP.zip
path: ./Release/CinematicUnityExplorer.MelonLoader.IL2CPP/
- name: Upload MelonLoader.IL2CPP.net6preview
uses: actions/upload-artifact@v3
uses: actions/upload-artifact@v4
with:
name: CinematicUnityExplorer.MelonLoader.IL2CPP.net6preview.zip
path: ./Release/CinematicUnityExplorer.MelonLoader.IL2CPP.net6preview/
- name: Upload MelonLoader.IL2CPP.CoreCLR
uses: actions/upload-artifact@v3
uses: actions/upload-artifact@v4
with:
name: CinematicUnityExplorer.MelonLoader.IL2CPP.CoreCLR.zip
path: ./Release/CinematicUnityExplorer.MelonLoader.IL2CPP.CoreCLR/
- name: Upload MelonLoader.Mono
uses: actions/upload-artifact@v3
uses: actions/upload-artifact@v4
with:
name: CinematicUnityExplorer.MelonLoader.Mono.zip
path: ./Release/CinematicUnityExplorer.MelonLoader.Mono/
- name: Upload Standalone.IL2CPP
uses: actions/upload-artifact@v3
uses: actions/upload-artifact@v4
with:
name: CinematicUnityExplorer.Standalone.IL2CPP.zip
path: ./Release/CinematicUnityExplorer.Standalone.IL2CPP/
- name: Upload Standalone.Mono
uses: actions/upload-artifact@v3
uses: actions/upload-artifact@v4
with:
name: CinematicUnityExplorer.Standalone.Mono.zip
path: ./Release/CinematicUnityExplorer.Standalone.Mono/
- name: Upload Editor
uses: actions/upload-artifact@v3
uses: actions/upload-artifact@v4
with:
name: CinematicUnityExplorer.Editor.zip
path: ./UnityEditorPackage/
@ -127,13 +127,13 @@ jobs:
run: ./build_connector.ps1
- name: Upload Unity IGCS Connector
uses: actions/upload-artifact@v3
uses: actions/upload-artifact@v4
with:
name: UnityIGCSConnector.dll
path: ./Release/UnityIGCSConnector.dll
- name: Upload Unity IGCS Connector 32bit
uses: actions/upload-artifact@v3
uses: actions/upload-artifact@v4
with:
name: UnityIGCSConnector.32.dll
path: ./Release/UnityIGCSConnector.32.dll

View File

@ -1,6 +1,7 @@
using HarmonyLib;
using System.Collections.Generic;
using UnityEngine;
using UnityExplorer.Config;
#if UNHOLLOWER
using IL2CPPUtils = UnhollowerBaseLib.UnhollowerUtils;
#endif
@ -12,8 +13,12 @@ namespace UnityExplorer
{
public class ArrowGenerator
{
public static GameObject CreateArrow(Vector3 arrowPosition, Quaternion arrowRotation, Color color){
public static GameObject CreateArrow(Vector3 arrowPosition, Quaternion arrowRotation, Color color)
{
try {
float arrowSizeValue = ConfigManager.Arrow_Size.Value;
Vector3 arrowSize = new Vector3(Math.Max(arrowSizeValue, 0.1f), Math.Max(arrowSizeValue, 0.1f), Math.Max(arrowSizeValue, 0.1f));
GameObject cylinder = GameObject.CreatePrimitive(PrimitiveType.Cylinder);
cylinder.GetComponent<Collider>().enabled = false;
cylinder.GetComponent<MeshFilter>().mesh = CreateCylinderMesh(0.01f, 20, 2);
@ -34,6 +39,7 @@ namespace UnityExplorer
GameObject arrow = new GameObject("CUE-Arrow");
cylinder.transform.SetParent(arrow.transform, true);
arrow.transform.localScale = arrowSize;
arrow.transform.position = arrowPosition;
arrow.transform.rotation = arrowRotation;
arrow.transform.position += 0.5f * arrow.transform.forward; // Move the arrow forward so the cylinder starts on the wanted position

View File

@ -1,4 +1,5 @@
using UnityEngine;
using UnityExplorer.Config;
using UniverseLib.UI;
using UniverseLib.UI.Models;
using UniverseLib.UI.Widgets.ScrollView;
@ -69,6 +70,9 @@ namespace UnityExplorer.UI.Panels
private void ToggleVisualizer(){
GameObject visualizer = light.transform.GetChild(0).gameObject;
float arrowSize = ConfigManager.Arrow_Size.Value;
Vector3 arrowSizeVec = new Vector3(Math.Max(arrowSize, 0.1f), Math.Max(arrowSize, 0.1f), Math.Max(arrowSize, 0.1f));
light.transform.GetChild(0).localScale = arrowSizeVec;
visualizer.SetActive(!visualizer.activeSelf);
}

View File

@ -75,9 +75,10 @@ namespace UnityExplorer.Serializers
this.scale = scale;
}
public readonly Vector3 position;
public readonly Vector3 angles;
public readonly Vector3 scale;
// They cant be read-only because it causes problems with XML serialization in older versions of .NET
public Vector3 position { get; set; }
public Vector3 angles { get; set; }
public Vector3 scale { get; set; }
public void CopyToTransform(Transform transform){
transform.localPosition = position;

View File

@ -32,8 +32,9 @@ namespace UnityExplorer.Config
public static ConfigElement<bool> Reflection_Hide_NativeInfoPtrs;
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<FreeCamPanel.FreeCameraType> Default_Freecam;
public static ConfigElement<KeyCode> Pause;
public static ConfigElement<KeyCode> Frameskip;
public static ConfigElement<KeyCode> Screenshot;
@ -61,6 +62,10 @@ namespace UnityExplorer.Config
public static ConfigElement<KeyCode> Reset_FOV;
public static ConfigElement<KeyCode> Toggle_Animations;
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; }
internal static readonly Dictionary<UIManager.Panels, ConfigElement<string>> PanelSaveData = new();
@ -189,11 +194,15 @@ namespace UnityExplorer.Config
Reset_Camera_Transform = new("Reset Camera transform on freecam disable",
"Reset the camera position and rotation between freecam sessions, so the freecam always starts from the gameplay position and rotation.",
false);
true);
Default_Freecam = new("Default Freecam mode",
"Default type of freecam selected on startup.",
FreeCamPanel.FreeCameraType.New);
Arrow_Size = new("Visualizers arrows size",
"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.",
@ -300,6 +309,19 @@ namespace UnityExplorer.Config
Toggle_Animations = new("Toggle NPC animations",
"Toggle NPC animations as selected in the Animator panel.",
KeyCode.Keypad0);
Default_Freecam = new("Default Freecam mode",
"Default type of freecam selected on startup (gets automatically updated with the last type of camera used).",
FreeCamPanel.FreeCameraType.New);
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

@ -100,6 +100,9 @@ namespace UnityExplorer
public static void InspectWithFilters(object target, string filterInputField, UnityExplorer.Inspectors.MemberFilter memberFilterFlags = UnityExplorer.Inspectors.MemberFilter.All, bool staticReflection = false, CacheObjectBase parent = null)
{
if (TryFocusActiveInspector(target))
return;
ReflectionInspector inspector = CreateInspector<ReflectionInspector>(target, staticReflection, parent);
inspector.filterInputField.Text = filterInputField;
//TODO: Update the member flags visually on the inspector.

View File

@ -46,9 +46,10 @@ namespace UnityExplorer.Inspectors
public override bool CanDragAndResize => false;
private Action<GameObject> inspectorAction = null;
internal Text objNameLabel;
internal Text objPathLabel;
internal Text mousePosLabel;
private Text inspectorLabelTitle;
private Text objNameLabel;
private Text objPathLabel;
private Text mousePosLabel;
public MouseInspector(UIBase owner) : base(owner)
{
@ -125,6 +126,43 @@ namespace UnityExplorer.Inspectors
return Inspecting;
}
/// <summary>
/// Updates the title text in the inspector UI, if the inspector title label is assigned.
/// </summary>
/// <param name="title">The new title text to display in the inspector.</param>
internal void UpdateInspectorTitle(string title)
{
// Unity null check - if inspectorLabelTitle is assigned, update its text.
if (inspectorLabelTitle)
{
inspectorLabelTitle.text = title;
}
}
/// <summary>
/// Updates the object name label in the inspector UI, if the label is assigned.
/// </summary>
/// <param name="name">The new object name to display.</param>
internal void UpdateObjectNameLabel(string name)
{
// Unity null check - if objNameLabel is assigned, update its text.
if (objNameLabel)
{
objNameLabel.text = name;
}
}
/// <summary>
/// Updates the object path label in the inspector UI, if the label is assigned.
/// </summary>
/// <param name="path">The new object path to display.</param>
internal void UpdateObjectPathLabel(string path)
{
// Unity null check - if objPathLabel is assigned, update its text.
if (objPathLabel)
{
objPathLabel.text = path;
}
}
public void UpdateInspect()
{
if (IInputManager.GetKeyDown(KeyCode.Escape))
@ -156,7 +194,7 @@ namespace UnityExplorer.Inspectors
lastMousePos = mousePos;
// use the raw mouse pos for the label
mousePosLabel.text = $"<color=grey>Mouse Position:</color> {mousePos.ToString()}";
mousePosLabel.text = $"<color=grey>Mouse Position:</color> {mousePos.x}, {mousePos.y}";
// constrain the mouse pos we use within certain bounds
if (mousePos.x < 350)
@ -196,11 +234,11 @@ namespace UnityExplorer.Inspectors
// Title text
Text title = UIFactory.CreateLabel(inspectContent,
inspectorLabelTitle = UIFactory.CreateLabel(inspectContent,
"InspectLabel",
"<b>Mouse Inspector</b> (press <b>ESC</b> to cancel)",
"",
TextAnchor.MiddleCenter);
UIFactory.SetLayoutElement(title.gameObject, flexibleWidth: 9999);
UIFactory.SetLayoutElement(inspectorLabelTitle.gameObject, flexibleWidth: 9999);
mousePosLabel = UIFactory.CreateLabel(inspectContent, "MousePosLabel", "Mouse Position:", TextAnchor.MiddleCenter);

View File

@ -17,10 +17,13 @@ namespace UnityExplorer.Inspectors.MouseInspectors
private static readonly List<CanvasGroup> wasDisabledCanvasGroups = new();
private static readonly List<GameObject> objectsAddedCastersTo = new();
private const string DEFAULT_INSPECTOR_TITLE = "<b>UI Inspector</b> (press <b>ESC</b> to cancel)";
public override void OnBeginMouseInspect()
{
SetupUIRaycast();
MouseInspector.Instance.objPathLabel.text = "";
MouseInspector.Instance.UpdateInspectorTitle(DEFAULT_INSPECTOR_TITLE);
MouseInspector.Instance.UpdateObjectPathLabel("");
}
public override void ClearHitData()
@ -71,9 +74,9 @@ namespace UnityExplorer.Inspectors.MouseInspectors
}
if (currentHitObjects.Any())
MouseInspector.Instance.objNameLabel.text = $"Click to view UI Objects under mouse: {currentHitObjects.Count}";
MouseInspector.Instance.UpdateObjectNameLabel($"Click to view UI Objects under mouse: {currentHitObjects.Count}");
else
MouseInspector.Instance.objNameLabel.text = $"No UI objects under mouse.";
MouseInspector.Instance.UpdateObjectNameLabel( $"No UI objects under mouse.");
}
private static void SetupUIRaycast()

View File

@ -1,4 +1,6 @@
namespace UnityExplorer.Inspectors.MouseInspectors
using UnityExplorer.UI.Panels;
namespace UnityExplorer.Inspectors.MouseInspectors
{
public class WorldInspector : MouseInspectorBase
{
@ -7,15 +9,26 @@
public override void OnBeginMouseInspect()
{
MainCamera = Camera.main;
if (!MainCamera)
if (!EnsureMainCamera())
{
ExplorerCore.LogWarning("No MainCamera found! Cannot inspect world!");
return;
}
}
/// <summary>
/// Assigns it as the MainCamera and updates the inspector title.
/// </summary>
/// <param name="cam">The camera to assign.</param>
private static void AssignCamAndUpdateTitle(Camera cam)
{
MainCamera = cam;
MouseInspector.Instance.UpdateInspectorTitle(
$"<b>World Inspector ({MainCamera.name})</b> (press <b>ESC</b> to cancel)"
);
}
public override void ClearHitData()
{
lastHitObject = null;
@ -26,11 +39,52 @@
inspectorAction(lastHitObject);
}
/// <summary>
/// Attempts to ensure that MainCamera is assigned. If not then attempts to find it.
/// If no cameras are available, logs a warning and returns null.
/// </summary>
private static Camera EnsureMainCamera()
{
if (MainCamera){
// We still call this in case the last title was from the UIInspector
AssignCamAndUpdateTitle(MainCamera);
return MainCamera;
}
if (Camera.main){
AssignCamAndUpdateTitle(Camera.main);
return MainCamera;
}
ExplorerCore.LogWarning("No Camera.main found, trying to find a camera named 'Main Camera' or 'MainCamera'...");
Camera namedCam = Camera.allCameras.FirstOrDefault(c => c.name is "Main Camera" or "MainCamera");
if (namedCam) {
AssignCamAndUpdateTitle(namedCam);
return MainCamera;
}
if (FreeCamPanel.inFreeCamMode && FreeCamPanel.GetFreecam()){
AssignCamAndUpdateTitle(FreeCamPanel.GetFreecam());
return MainCamera;
}
ExplorerCore.LogWarning("No camera named found, using the first camera created...");
var fallbackCam = Camera.allCameras.FirstOrDefault();
if (fallbackCam) {
AssignCamAndUpdateTitle(fallbackCam);
return MainCamera;
}
// If we reach here, no cameras were found at all.
ExplorerCore.LogWarning("No valid cameras found!");
return null;
}
public override void UpdateMouseInspect(Vector2 mousePos)
{
if (!MainCamera)
MainCamera = Camera.main;
if (!MainCamera)
// Attempt to ensure camera each time UpdateMouseInspect is called
// in case something changed or wasn't set initially.
if (!EnsureMainCamera())
{
ExplorerCore.LogWarning("No Main Camera was found, unable to inspect world!");
MouseInspector.Instance.StopInspect();
@ -51,8 +105,8 @@
if (obj != lastHitObject)
{
lastHitObject = obj;
MouseInspector.Instance.objNameLabel.text = $"<b>Click to Inspect:</b> <color=cyan>{obj.name}</color>";
MouseInspector.Instance.objPathLabel.text = $"Path: {obj.transform.GetTransformPath(true)}";
MouseInspector.Instance.UpdateObjectNameLabel($"<b>Click to Inspect:</b> <color=cyan>{obj.name}</color>");
MouseInspector.Instance.UpdateObjectPathLabel($"Path: {obj.transform.GetTransformPath(true)}");
}
}

View File

@ -30,6 +30,10 @@ namespace UnityExplorer.Loader.Standalone
public bool Auto_Scale_UI;
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;
@ -87,6 +91,11 @@ namespace UnityExplorer.Loader.Standalone
ConfigManager.Auto_Scale_UI.Value = this.Auto_Scale_UI;
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,15 +68,20 @@ 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;
static InputFieldRef positionInput;
static InputFieldRef moveSpeedInput;
static InputFieldRef componentsToDisableInput;
static Text followLookAtObjectLabel;
static ButtonRef inspectButton;
public static Toggle followRotationToggle;
static bool disabledCinemachine;
static bool disabledOrthographic;
static List<string> stringComponentsToDisable = new();
static List<Behaviour> componentsToDisable = new();
public static bool supportedInput => InputManager.CurrentType == InputType.Legacy;
@ -99,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();
@ -115,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;
@ -151,6 +226,8 @@ namespace UnityExplorer.UI.Panels
currentCameraType = FreeCameraType.Gameplay;
ourCamera = lastMainCamera;
MaybeToggleCinemachine(false);
MaybeToggleOrthographic(false);
ToggleCustomComponents(false);
// If the farClipPlaneValue is the default one try to use the one from the gameplay camera
if (farClipPlaneValue == 2000){
@ -169,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;
@ -185,7 +262,9 @@ namespace UnityExplorer.UI.Panels
ourCamera = GameObject.Instantiate(lastMainCamera);
lastMainCamera.enabled = false;
MaybeToggleCinemachine(false);
MaybeDeleteCinemachine();
MaybeToggleOrthographic(false);
ToggleCustomComponents(false);
// If the farClipPlaneValue is the default one try to use the one from the gameplay camera
if (farClipPlaneValue == 2000){
@ -208,8 +287,10 @@ namespace UnityExplorer.UI.Panels
// HDRP might introduce problems when moving the camera when replacing the worldToCameraMatrix,
// so we will try to move the real camera as well.
MaybeToggleCinemachine(false);
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;
@ -224,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;
@ -258,6 +339,8 @@ namespace UnityExplorer.UI.Panels
switch(currentCameraType) {
case FreeCameraType.Gameplay:
MaybeToggleCinemachine(true);
MaybeToggleOrthographic(true);
ToggleCustomComponents(true);
ourCamera = null;
if (lastMainCamera)
@ -277,6 +360,8 @@ namespace UnityExplorer.UI.Panels
break;
case FreeCameraType.ForcedMatrix:
MaybeToggleCinemachine(true);
MaybeToggleOrthographic(true);
ToggleCustomComponents(true);
MethodInfo resetCullingMatrixMethod = typeof(Camera).GetMethod("ResetCullingMatrix", new Type[] {});
resetCullingMatrixMethod.Invoke(ourCamera, null);
@ -315,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.
@ -338,6 +466,37 @@ namespace UnityExplorer.UI.Panels
}
}
static void MaybeDeleteCinemachine(){
if (ourCamera){
IEnumerable<Behaviour> comps = ourCamera.GetComponentsInChildren<Behaviour>();
foreach (Behaviour comp in comps)
{
string comp_type = comp.GetActualType().ToString();
if (comp_type == "Cinemachine.CinemachineBrain" || comp_type == "Il2CppCinemachine.CinemachineBrain"){
GameObject.Destroy(comp);
break;
}
}
}
}
static void MaybeToggleOrthographic(bool enable){
if (ourCamera) {
if (enable) {
// Only re-enable orthographic mode if we previously disabled it
if (disabledOrthographic) {
ourCamera.orthographic = true;
disabledOrthographic = false;
}
} else {
if (ourCamera.orthographic) {
disabledOrthographic = true;
ourCamera.orthographic = false;
}
}
}
}
static void SetCameraPositionInput(Vector3 pos)
{
if (!ourCamera || lastSetCameraPosition == pos)
@ -388,13 +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) => {
if (inFreeCamMode) {
EndFreecam();
BeginFreecam();
}
ConfigManager.Default_Freecam.Value = (FreeCameraType)idx;
MaybeResetFreecam();
});
foreach (FreeCameraType type in Enum.GetValues(typeof(FreeCameraType)).Cast<FreeCameraType>()) {
cameraTypeDropdown.options.Add(new Dropdown.OptionData(Enum.GetName(typeof(FreeCameraType), type)));
@ -402,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);
@ -417,6 +600,12 @@ namespace UnityExplorer.UI.Panels
AddSpacer(5);
AddInputField("ComponentsToDisable", "Components To Disable:", "CinemachineBrain", out componentsToDisableInput, ComponentsToDisableInput_OnEndEdit, 175);
componentsToDisableInput.Text = ConfigManager.Custom_Components_To_Disable.Value;
stringComponentsToDisable = ConfigManager.Custom_Components_To_Disable.Value.Split(',').Select(c => c.Trim()).Where(x => !string.IsNullOrEmpty(x)).ToList();
AddSpacer(5);
GameObject togglesRow = UIFactory.CreateHorizontalGroup(ContentRoot, "TogglesRow", false, false, true, true, 3, default, new(1, 1, 1, 0));
GameObject blockFreecamMovement = UIFactory.CreateToggle(togglesRow, "blockFreecamMovement", out blockFreecamMovementToggle, out Text blockFreecamMovementText);
@ -553,12 +742,12 @@ namespace UnityExplorer.UI.Panels
UIFactory.SetLayoutElement(obj, minHeight: height, flexibleHeight: 0);
}
GameObject AddInputField(string name, string labelText, string placeHolder, out InputFieldRef inputField, Action<string> onInputEndEdit)
GameObject AddInputField(string name, string labelText, string placeHolder, out InputFieldRef inputField, Action<string> onInputEndEdit, int minTextWidth = 100)
{
GameObject row = UIFactory.CreateHorizontalGroup(ContentRoot, $"{name}_Group", false, false, true, true, 3, default, new(1, 1, 1, 0));
Text posLabel = UIFactory.CreateLabel(row, $"{name}_Label", labelText);
UIFactory.SetLayoutElement(posLabel.gameObject, minWidth: 100, minHeight: 25);
UIFactory.SetLayoutElement(posLabel.gameObject, minWidth: minTextWidth, minHeight: 25);
inputField = UIFactory.CreateInputField(row, $"{name}_Input", placeHolder);
UIFactory.SetLayoutElement(inputField.GameObject, minWidth: 50, minHeight: 25, flexibleWidth: 9999);
@ -700,6 +889,121 @@ namespace UnityExplorer.UI.Panels
desiredMoveSpeed = parsed;
}
void ComponentsToDisableInput_OnEndEdit(string input)
{
EventSystemHelper.SetSelectedGameObject(null);
ConfigManager.Custom_Components_To_Disable.Value = input;
stringComponentsToDisable = input.Split(',').Select(c => c.Trim()).Where(x => !string.IsNullOrEmpty(x)).ToList();
}
static List<Behaviour> GetComponentsToDisable()
{
List<Behaviour> components = new();
if (stringComponentsToDisable == null || stringComponentsToDisable.Count == 0)
{
return components;
}
foreach (string stringComponent in stringComponentsToDisable)
{
List<string> pathToComponent = stringComponent.Split('/').Where(x => !string.IsNullOrEmpty(x)).ToList();
GameObject currentGameObject = ourCamera.gameObject;
for (var i = 0; i < pathToComponent.Count; i++)
{
string pathStep = pathToComponent[i];
if (i == 0 && pathStep == "~")
{
// Check if we can find the next steps game object in the path
i++;
pathStep = pathToComponent[i];
GameObject foundNextPathStep = null;
foreach (GameObject obj in SceneManager.GetActiveScene().GetRootGameObjects()) {
if (obj.name == pathStep)
{
foundNextPathStep = obj;
break;
}
}
if (!foundNextPathStep)
{
ExplorerCore.LogWarning($"Couldn't find {stringComponent} component to disable it.");
break;
}
currentGameObject = foundNextPathStep;
continue;
}
if (pathStep == "..") {
if (!currentGameObject.transform.parent)
{
ExplorerCore.LogWarning($"Couldn't find {stringComponent} component to disable it.");
break;
}
currentGameObject = currentGameObject.transform.parent.gameObject;
continue;
}
// Last member of the path, should be a component
if (i == pathToComponent.Count - 1) {
Behaviour comp = GetComponentByName(currentGameObject, pathStep);
if (!comp)
{
// Should we allow to disable entire GameObjects here if it can't find the right component?
ExplorerCore.LogWarning($"Couldn't find {stringComponent} component to disable it.");
break;
}
components.Add(comp);
}
else {
Transform nextGameObjectTransform = currentGameObject.transform.Find(pathStep);
if (!nextGameObjectTransform)
{
ExplorerCore.LogWarning($"Couldn't find {stringComponent} component to disable it.");
break;
}
currentGameObject = nextGameObjectTransform.gameObject;
}
}
}
return components;
}
static void ToggleCustomComponents(bool enable)
{
// If disable get the components again
if (!enable)
{
componentsToDisable = GetComponentsToDisable();
}
foreach(Behaviour comp in componentsToDisable)
{
// We could outright delete the components if on Cloned freecam mode
comp.enabled = enable;
}
}
static Behaviour GetComponentByName(GameObject obj, string componentsName)
{
if (obj)
{
IEnumerable<Behaviour> comps = obj.GetComponents<Behaviour>();
foreach (Behaviour comp in comps)
{
string comp_type = comp.GetActualType().ToString();
if (comp_type == componentsName){
return comp;
}
}
}
return null;
}
void NearClipInput_OnEndEdit(string input)
{
EventSystemHelper.SetSelectedGameObject(null);
@ -834,7 +1138,7 @@ namespace UnityExplorer.UI.Panels
}
Transform movingTransform = FreeCamPanel.GetFreecam().transform;
if (!FreeCamPanel.blockFreecamMovementToggle.isOn && !FreeCamPanel.cameraPathMover.playingPath && FreeCamPanel.connector?.IsActive != true) {
if (!FreeCamPanel.blockFreecamMovementToggle.isOn && !FreeCamPanel.cameraPathMover.playingPath && FreeCamPanel.connector?.IsActive != true && !IsInputFieldInFocus()) {
ProcessInput(movingTransform);
}
@ -868,6 +1172,20 @@ namespace UnityExplorer.UI.Panels
}
}
private bool IsInputFieldInFocus()
{
GameObject currentObject = EventSystemHelper.CurrentEventSystem.currentSelectedGameObject;
if (currentObject != null)
{
UnityEngine.UI.InputField selectedInputField = currentObject.GetComponent<UnityEngine.UI.InputField>();
if (selectedInputField != null)
{
return selectedInputField.isFocused;
}
}
return false;
}
private void OnPreCull()
{
UpdateRelativeMatrix();
@ -950,11 +1268,25 @@ namespace UnityExplorer.UI.Panels
if (IInputManager.GetKey(ConfigManager.Down.Value))
transform.position += transform.up * -1 * moveSpeed;
if (IInputManager.GetKey(ConfigManager.Tilt_Left.Value))
// 90 degrees tilt when pressing the speed down hotkey
if (IInputManager.GetKey(ConfigManager.Speed_Down_Movement.Value))
{
if (IInputManager.GetKeyDown(ConfigManager.Tilt_Left.Value)) {
transform.Rotate(0, 0, 90, Space.Self);
}
else if (IInputManager.GetKeyDown(ConfigManager.Tilt_Right.Value)) {
transform.Rotate(0, 0, - 90, Space.Self);
}
}
else
{
if (IInputManager.GetKey(ConfigManager.Tilt_Left.Value)) {
transform.Rotate(0, 0, moveSpeed * 10, Space.Self);
if (IInputManager.GetKey(ConfigManager.Tilt_Right.Value))
}
else if (IInputManager.GetKey(ConfigManager.Tilt_Right.Value)) {
transform.Rotate(0, 0, - moveSpeed * 10, Space.Self);
}
}
if (IInputManager.GetKey(ConfigManager.Tilt_Reset.Value)){
// Extract the forward direction of the original quaternion
@ -1030,42 +1362,67 @@ namespace UnityExplorer.UI.Panels
ExplorerCore.LogWarning($"Failed to listen to BeforeRender: {exception}");
}
#endif
// 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 { }
}
protected virtual void OnDisable()
{
@ -1079,37 +1436,58 @@ namespace UnityExplorer.UI.Panels
ExplorerCore.LogWarning($"Failed to unlisten from BeforeRender: {exception}");
}
#endif
// 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);
}
}
catch { }
}
EventInfo onBeforeRenderEvent = typeof(Application).GetEvent("onBeforeRender");
if (onBeforeRenderEvent != null) {