1
0
mirror of https://github.com/originalnicodr/CinematicUnityExplorer.git synced 2026-10-10 13:03:15 +08:00

25 Commits

Author SHA1 Message Date
Sebastián A. 361f10704e Add support for multiple IGCS addons (#132)
Build CinematicUnityExplorer / build_dotnet (push) Has been cancelled
Build CinematicUnityExplorer / build_connector (push) Has been cancelled
* feat: fill camera data to igcs connector.

* Add camera locked data

* feat: support all possible dof implementations

* Remove misterious constants
2026-03-25 09:05:02 -03:00
Nicolás Uriel Navall e6bcbcd0cc Add Discord support link and badge to README
Build CinematicUnityExplorer / build_dotnet (push) Has been cancelled
Build CinematicUnityExplorer / build_connector (push) Has been cancelled
2026-02-23 21:04:22 -03:00
Nicolás Uriel Navall 1b93dd67f8 Separated Reset button behaviour from the button check so we can properly call ResetCameraTransform on SetupFreeCamera.
Build CinematicUnityExplorer / build_dotnet (push) Has been cancelled
Build CinematicUnityExplorer / build_connector (push) Has been cancelled
2025-12-07 20:44:13 -03:00
Nicolás Uriel Navall 733887ea50 Refactored HDRP camera handling on ForcedMatrix freecam mode. 2025-12-07 20:00:20 -03:00
Nicolás Uriel Navall d042771896 Fixed aspect ratio breaking when hotsampling in ForcedMatrix freecam mode.
Build CinematicUnityExplorer / build_dotnet (push) Has been cancelled
Build CinematicUnityExplorer / build_connector (push) Has been cancelled
2025-12-04 20:55:20 -03:00
Nicolás Uriel Navall df5a1542ba Added extra checks regarding HDRP rendering on ForcedMatrix freecam method.
Build CinematicUnityExplorer / build_dotnet (push) Has been cancelled
Build CinematicUnityExplorer / build_connector (push) Has been cancelled
2025-12-03 19:57:22 -03:00
Nicolás Uriel Navall 27851a88ea Added Unity Input System support (#116)
Build CinematicUnityExplorer / build_dotnet (push) Has been cancelled
Build CinematicUnityExplorer / build_connector (push) Has been cancelled
* Added support for new input system. First implementation.

* Deleted unnecessary prefix calls, typed most "var" variables, added support for wasReleasedThisFrame, used GetButtonWasPressed on IInputManager::GetKeyUp, refactored GetMouseButton to use the cached buttons, and split the postfix functions into multiple ones.

* Fixed builds for other .net versions.

* Added a class to handle gamepad input to use with the freecam.

* Fixed some logging stuff, and added a dropdown on the freecam panel to select the controller that you want to use to control the freecam. Also refactored the freecam panel a bit UI wise.

* Made the gamepad interceptor class compatible with the INewInputSystem by reusing its patch functions for the gamepad buttons. Also did some cleaning regarding key normalization.

* Fixed runtime warnings and added gamepad controls to the readme. Also made changing the FOV faster.

* Fixed dpad on some versions of the engine/library. Also refactored the gamepad input code on FreeCamBehaviour::ProcessInput(), inverted dpad up and down functionalities, modified the fov change speed, added a minimum value for the fov, and added a reset tilt button to the gamepad.

* Update README.md

* Fixed freecam default mode on forced, and hid the controller dropdown on the freecam panel when the game uses the legacy input system.

* Added more controller hotkeys,

* Added slower camera rotation based on fov.

* Added an ExplorerCore log call warning when trying to play a path with fewer than 3 nodes.
2025-11-29 20:08:28 -03:00
Nicolás Uriel Navall 5c556d99c5 Upgraded CUE version.
Build CinematicUnityExplorer / build_dotnet (push) Has been cancelled
Build CinematicUnityExplorer / build_connector (push) Has been cancelled
2025-10-18 17:32:15 -03:00
Nicolás Uriel Navall 91e5bcb38c Added gameobject target support for automatic disabling on freecam, and made the lookup gameobject more robust. Also improved error messages around it. 2025-10-18 17:31:28 -03:00
Nicolás Uriel Navall a08a299a8f Reordered FreeCameraType, made ForcedMatrix the default one, and renamed Advanced_Freecam_Selection setting to Freecam_Camera_Target_Selection. 2025-10-18 15:28:51 -03:00
Nicolás Uriel Navall 32bf1a6246 Update UniverseLib
Build CinematicUnityExplorer / build_dotnet (push) Has been cancelled
Build CinematicUnityExplorer / build_connector (push) Has been cancelled
2025-10-04 11:40:21 -03:00
Nicolás Uriel Navall e3584654a6 Added advance setting to select target camera (#108)
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
originalnicodr 388c9fbc29 Added a new field on the freecam panel to add components to disable when enabling the freecam.
Build CinematicUnityExplorer / build_dotnet (push) Has been cancelled
Build CinematicUnityExplorer / build_connector (push) Has been cancelled
2025-03-02 14:51:51 -03:00
originalnicodr 2f35b9d2c7 Made the slow-down hotkey tilt the camera 90 degrees instead, to tilt vertically in games that don't allow vertical ARs.
Build CinematicUnityExplorer / build_dotnet (push) Waiting to run
Build CinematicUnityExplorer / build_connector (push) Waiting to run
2025-03-01 10:58:05 -03:00
originalnicodr 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.
Build CinematicUnityExplorer / build_dotnet (push) Has been cancelled
Build CinematicUnityExplorer / build_connector (push) Has been cancelled
2025-02-26 00:45:15 -03:00
originalnicodr 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.
Build CinematicUnityExplorer / build_dotnet (push) Has been cancelled
Build CinematicUnityExplorer / build_connector (push) Has been cancelled
2025-02-23 22:56:09 -03:00
originalnicodr 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.
Build CinematicUnityExplorer / build_dotnet (push) Waiting to run
Build CinematicUnityExplorer / build_connector (push) Waiting to run
2025-02-23 20:27:50 -03:00
Yuki.kaco 9144f89e32 Add Adjust ArrowSize (#101)
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
originalnicodr 3bc1268444 Updated GitHub action upload-artifact from v3 to v4
Build CinematicUnityExplorer / build_dotnet (push) Has been cancelled
Build CinematicUnityExplorer / build_connector (push) Has been cancelled
2025-02-02 18:48:04 -03:00
originalnicodr 4c100daf83 Fixed bug when disabling orthographic property on freecam 2025-02-02 18:42:30 -03:00
originalnicodr e7d591176e Fixed poses not being saved correctly for some .NET versions.
Build CinematicUnityExplorer / build_dotnet (push) Has been cancelled
Build CinematicUnityExplorer / build_connector (push) Has been cancelled
2025-01-20 13:21:09 -03:00
originalnicodr ca46607c49 Automatically update the default freecam every time a new freecam type is selected. 2025-01-20 11:51:50 -03:00
originalnicodr 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
originalnicodr 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
originalnicodr 6111c1743b Avoid processing freecam input movement when there is a focused input field. 2024-12-15 21:13:20 -03:00
26 changed files with 2563 additions and 251 deletions
+15 -15
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
+38 -24
View File
@@ -16,10 +16,13 @@
<p align="center">
✨ Powered by <a href="https://github.com/yukieiji/UniverseLib">UniverseLib</a>
</p>
<p align="center">
🛠️ Need support? Join our <a href="discord.gg/FqTpKutNah">Discord server</a>!
</p>
# Releases [![](https://img.shields.io/github/downloads/originalnicodr/CinematicUnityExplorer/total.svg)](../../releases)
[![](https://img.shields.io/github/release/originalnicodr/CinematicUnityExplorer.svg?label=version)](../../releases/latest) [![](https://img.shields.io/github/actions/workflow/status/originalnicodr/CinematicUnityExplorer/dotnet.yml)](https://github.com/originalnicodr/CinematicUnityExplorer/actions) [![](https://img.shields.io/github/downloads/originalnicodr/CinematicUnityExplorer/latest/total.svg)](../../releases/latest)
[![](https://img.shields.io/github/release/originalnicodr/CinematicUnityExplorer.svg?label=version)](../../releases/latest) [![](https://img.shields.io/github/actions/workflow/status/originalnicodr/CinematicUnityExplorer/dotnet.yml)](https://github.com/originalnicodr/CinematicUnityExplorer/actions) [![](https://img.shields.io/github/downloads/originalnicodr/CinematicUnityExplorer/latest/total.svg)](../../releases/latest) [![](https://dcbadge.limes.pink/api/server/https://discord.gg/FqTpKutNah?style=flat)](https://discord.gg/FqTpKutNah)
<p align="center">
<a href="https://www.youtube.com/watch?v=--8DOKWzGJ4" target="_blank">
@@ -100,27 +103,38 @@ I wrote a [guide](https://framedsc.com/GeneralGuides/cinematic-unity-explorer.ht
The table below describes all of the relevant default hotkeys. You can edit them to your liking by going to the Options panel, changing the hotkeys you want, and saving the settings.
Feature | Key
-|-
Freecam | `Insert`
Move the camera forward/left/right/backward | `W`/`A`/`S`/`D` - `UpArrow`/`LeftArrow`/`RightArrow`/`DownArrow`
Move the camera upwards | `Space`
Move the camera downwards | `LeftControl`
Speed up movement | `LeftShift`
Speed down movement | `LeftAlt`
Change freecam orientation | Right mouse click
Tilt left | `Numpad 1`
Tilt right | `Numpad 3`
Reset tilt | `Numpad 2`
Increase FOV | `Numpad +`
Decrease FOV | `Numpad -`
Reset FOV | `Numpad *`
Pause | `PageUp`
Block Freecam movement and rotation | `Home`
Block games input | `Numpad .`
Frameskip | `PageDown`
HUD Toggle | `Delete`
Freeze NPC animations | `Numpad 0`
Feature | Keyboard | Gamepad
-|-|-
Freecam toggle | `Insert` | `R3 + X`
Move the camera forward/left/backward/right | `W`/`A`/`S`/`D` - `UpArrow`/`LeftArrow`/`DownArrow`/`RightArrow`/ | Left stick
Move the camera upwards | `Space` | `LT`
Move the camera downwards | `LeftControl` | `RT`
Speed up movement | `LeftShift` | `Y`
Speed down movement | `LeftAlt` | `X`
Change freecam orientation | Right mouse click | Right stick
Tilt left | `Numpad 1` | Dpad left
Tilt right | `Numpad 3` | Dpad right
Reset tilt | `Numpad 2` | `A`
Decrease FOV | `Numpad -` | Dpad up
Increase FOV | `Numpad +` | Dpad down
Reset FOV | `Numpad *` | `B`
Pause | `PageUp` |
Block Freecam movement and rotation | `Home` |
Block games input | `Numpad .` |
Frameskip | `PageDown` |
HUD Toggle | `Delete` |
Freeze NPC animations | `Numpad 0` |
Time scale override toggle | | `L3 + X`
Decrease time scale | | `X + L1`
Increase time scale | | `X + R1`
Camera path: Add node | | `A`
Camera path: Navigate to previous node | | `L1`
Camera path: Navigate to next node | | `R1`
Camera path: Start/stop playback | | `START`
Camera path: Pause/unpause playback | | `SELECT`
> [!NOTE]
> Gamepad is only compatible if the game is using [Unity Input System](https://docs.unity3d.com/Packages/com.unity.inputsystem@1.0/manual/index.html).
# Features
@@ -152,8 +166,8 @@ By default the camera only follows the object's position, but you can also make
As a side note, you can also "look at" a specific object in the world to force the camera to look directly toward it no matter the camera movement.
### Game input block for Unity's legacy system
Added game input block for Unity's legacy system. You can now block (or unblock) the game's input when using the freecam, as long as the game is using the Unity Legacy Input system. If the game uses a custom solution or the latest Unity system then this won't work. Implementing this for Unity's new system is in the backlog, so if you find a game using it (should say "Initialized new InputSystem support." on the logs) then please let me know so I can implement it using that game!
### Game input block for Unity's Legacy and Input System package
Added game input block for Unity's Legacy system and Unity Input System package. You can now block (or unblock) the game's input when using the freecam, as long as the game is using either of the mentioned systems. If the game uses a custom solution (or the actions engine system, as far as I understand), then this won't work.
## Lights manager
It allows you to spawn spotlights and pointlights, as well as toggle the game's original lights to allow you to relight the scene however you want (a task that is pretty common for screenshots and lighting artists). Please note that this only turns off scene lights and won't turn off ambient light or lights created by emissive materials, effects, or shaders.
Binary file not shown.
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "com.originalnicodr.cinematicunityexplorer",
"version": "1.3.0",
"version": "1.4.0",
"displayName": "CinematicUnityExplorer",
"description": "UnityExplorer fork focused on providing tools for creating marketing material for Unity games.",
"unity": "2017.1",
+59 -29
View File
@@ -2,13 +2,30 @@
#include <Windows.h>
#include <stdint.h>
#include <stdio.h>
#include <Psapi.h>
#define EXPOSE __declspec(dllexport)
// Since we're using C, we're a bit limited on how we define enums,
// so for the screenshot session, we have to do this.
#ifdef _M_IX86
typedef uint8_t SessionStatus;
#else
typedef int SessionStatus;
#endif
#define SESSION_STATUS_OK ((SessionStatus)0)
#define SESSION_STATUS_CAMERA_NOT_ENABLED ((SessionStatus)1)
#define SESSION_STATUS_CAMERA_PATH_PLAYING ((SessionStatus)2)
#define SESSION_STATUS_ALREADY_SESSION_ACTIVE ((SessionStatus)3)
#define SESSION_STATUS_CAMERA_FEAT_NOT_AVAILABLE ((SessionStatus)4)
#define SESSION_STATUS_UNKNOWN_ERROR ((SessionStatus)5)
// The function that will be the one called from the C# side, that will update the corresponding values.
typedef void (*MoveCameraCallback)(float, float, float, int);
typedef void (*SessionCallback)(void);
typedef int (*SessionCallback)(void);
typedef uint8_t* (*GetCameraDataFunc)(void);
@@ -16,15 +33,19 @@ MoveCameraCallback GlobalCallback = NULL;
SessionCallback GlobalStartSession = NULL;
SessionCallback GlobalEndSession = NULL;
EXPOSE uint8_t* U_IGCS_CameraData[10];
// There are things that only needs to be run once.
static int first_initialization = 1;
EXPOSE int __cdecl IGCS_StartScreenshotSession(uint8_t _ignore) {
EXPOSE SessionStatus __cdecl IGCS_StartScreenshotSession(uint8_t _ignore) {
if (GlobalStartSession) {
GlobalStartSession();
printf("Called StartSession\n");
return (SessionStatus)GlobalStartSession();
}
return 0;
return SESSION_STATUS_CAMERA_FEAT_NOT_AVAILABLE;
}
EXPOSE void __cdecl IGCS_EndScreenshotSession() {
@@ -32,6 +53,34 @@ EXPOSE void __cdecl IGCS_EndScreenshotSession() {
printf("Called EndSession\n");
}
int connect_all_tools() {
HMODULE mods[1024];
DWORD cbNeeded;
HANDLE proc = GetCurrentProcess();
int cameraSize = 0;
if (!EnumProcessModules(proc, mods, sizeof(mods), &cbNeeded)) {
return -1;
}
for (int i = 0; i < (cbNeeded / sizeof(HMODULE)); i++) {
FARPROC connect_camera_tools = GetProcAddress(mods[i], "connectFromCameraTools");
GetCameraDataFunc getCameraData = (GetCameraDataFunc)GetProcAddress(mods[i], "getDataFromCameraToolsBuffer");
if (connect_camera_tools == NULL) continue;
char module_name[512];
GetModuleFileNameA(mods[i], module_name, sizeof(module_name));
printf("Connected to %s tool\n", module_name);
connect_camera_tools();
U_IGCS_CameraData[cameraSize] = getCameraData();
cameraSize++;
}
return 0;
}
EXPOSE uint8_t* __cdecl U_IGCS_Initialize(MoveCameraCallback cb, SessionCallback start_cb, SessionCallback end_cb) {
AllocConsole();
printf("Initializing callback\n");
@@ -39,37 +88,18 @@ EXPOSE uint8_t* __cdecl U_IGCS_Initialize(MoveCameraCallback cb, SessionCallback
GlobalStartSession = start_cb;
GlobalEndSession = end_cb;
// Load IGCS
#ifdef _M_IX86
HMODULE igcs = LoadLibraryA("IgcsConnector.addon32");
#else
HMODULE igcs = LoadLibraryA("IgcsConnector.addon64");
#endif
if (!igcs) {
MessageBoxA(
NULL,
"IgcsConnector.addon64 was not found, make sure it is in the same directory as the executable.",
"Unable to find IgcsConnector",
MB_OK | MB_ICONERROR);
return NULL;
}
FARPROC cameraToolsFunction = GetProcAddress(igcs, "connectFromCameraTools");
GetCameraDataFunc getCameraData = (GetCameraDataFunc)GetProcAddress(igcs, "getDataFromCameraToolsBuffer");
if (first_initialization) {
cameraToolsFunction();
if (connect_all_tools()) {
printf("Error connecting cameras\n");
return 0;
}
first_initialization = 0;
}
// TODO: move this where it belongs. Maybe at some point we should actually fill in the data.
uint8_t* cameraData = getCameraData();
cameraData[0] = 0;
printf("Camera connected!\n");
return cameraData;
return &U_IGCS_CameraData[0];
}
+7 -1
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
+2
View File
@@ -87,7 +87,9 @@ namespace UnityExplorer.UI.Panels
ButtonRef destroyButton = UIFactory.CreateButton(UIRoot, "Delete", "Delete");
UIFactory.SetLayoutElement(destroyButton.GameObject, minWidth: 30, minHeight: 25, flexibleWidth: 9999);
destroyButton.OnClick += () => {
int deletedIndex = GetCamPathsPanel().controlPoints.IndexOf(point);
GetCamPathsPanel().controlPoints.Remove(point);
GetCamPathsPanel().OnNodeDeleted(deletedIndex);
GetCamPathsPanel().nodesScrollPool.Refresh(true, false);
GetCamPathsPanel().MaybeRedrawPath();
};
+4
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);
}
+970
View File
@@ -0,0 +1,970 @@
// Uncomment to enable verbose logging in gamepad interceptor
//#define IGAMEPAD_DEBUG
using System;
using System.Collections.Generic;
using System.Reflection;
using HarmonyLib;
using UnityEngine;
using UnityExplorer;
using UnityExplorer.UI.Panels;
#if UNHOLLOWER
using IL2CPPUtils = UnhollowerBaseLib.UnhollowerUtils;
#endif
#if INTEROP
using IL2CPPUtils = Il2CppInterop.Common.Il2CppInteropUtils;
#endif
namespace UniverseLib.Input
{
/// <summary>
/// Reflection wrapper to interact with gamepad inputs while blocking their actual state from the game.
/// </summary>
public static class IGamepadInputInterceptor
{
// --- Configuration ---
private static bool _isEnabled = false;
private static int _targetGamepadIndex = 0;
// --- Reflected types ---
private static Type _gamepadType;
private static Type _buttonControlType;
private static Type _axisControlType;
private static Type _vector2ControlType;
private static Type _inputControlType;
private static PropertyInfo _basePathProp;
// --- Reflected properties and methods for triggering state capture ---
private static PropertyInfo _buttonIsPressedProp;
private static PropertyInfo _buttonWasPressedProp;
private static PropertyInfo _buttonWasReleasedProp;
private static MethodInfo _axisReadValueMethod;
private static MethodInfo _vector2ReadValueMethod;
// --- Cached reflection for device checking ---
private static PropertyInfo _deviceProp;
// --- Cached reflection for Vector2 property access ---
private static PropertyInfo _vector2XProp;
private static PropertyInfo _vector2YProp;
// --- Cached reflection for gamepad control access ---
private static PropertyInfo _gamepadAllProp;
private static PropertyInfo _gamepadCurrentProp;
private static PropertyInfo _gamepadLeftStickProp;
private static PropertyInfo _gamepadRightStickProp;
// --- Vendor name normalization ---
private static readonly Dictionary<string, string> _vendorPrefixMap = new()
{
{ "/xinputcontrollerwindows", "/gamepad" },
{ "/xinputcontroller", "/gamepad" },
{ "/dualshockgamepad", "/gamepad" },
{ "/dualsensegamepad", "/gamepad" },
{ "/dualshock4gamepadhid", "/gamepad" },
{ "/dualshock3gamepadhid", "/gamepad" },
{ "/androidgamepad", "/gamepad" },
{ "/switchprocontrollerhid", "/gamepad" },
};
// --- Control collections ---
// Maps normalized paths to control objects
private static readonly Dictionary<string, object> _axisControls = new();
private static readonly Dictionary<string, object> _vector2Controls = new();
// --- State dictionaries (populated by postfix patches) ---
private static readonly Dictionary<string, float> _axisValues = new();
private static readonly Dictionary<string, Vector2> _vector2Values = new();
// --- Initialization ---
/// <summary>
/// Initializes the gamepad input interceptor. Must be called during game startup.
/// </summary>
public static void Init()
{
try
{
#if IGAMEPAD_DEBUG
ExplorerCore.LogWarning("[IGamepadInputInterceptor] === Initializing ===");
#endif
LoadReflectedTypes();
if (_gamepadType == null || _axisControlType == null)
{
#if IGAMEPAD_DEBUG
ExplorerCore.LogWarning("[IGamepadInputInterceptor] Failed to load required types");
#endif
return;
}
// Only patch analog controls (buttons are handled by INewInputSystem)
PatchAnalogControls();
//INewInputSystem.Init() should have been run beforehand
DiscoverGamepadControls();
_isEnabled = true;
#if IGAMEPAD_DEBUG
ExplorerCore.LogWarning("[IGamepadInputInterceptor] === Initialization complete ===");
#endif
}
catch (Exception ex)
{
ExplorerCore.LogWarning($"[IGamepadInputInterceptor] Init failed: {ex.Message}\n{ex.StackTrace}");
}
}
/// <summary>
/// Enables input interception. When enabled, captured inputs will be replaced with default values.
/// </summary>
public static void Enable()
{
_isEnabled = true;
#if IGAMEPAD_DEBUG
ExplorerCore.LogWarning("[IGamepadInputInterceptor] Input interception enabled");
#endif
}
/// <summary>
/// Disables input interception. Game will receive normal gamepad input.
/// </summary>
public static void Disable()
{
_isEnabled = false;
#if IGAMEPAD_DEBUG
ExplorerCore.LogWarning("[IGamepadInputInterceptor] Input interception disabled");
#endif
}
/// <summary>
/// Sets which gamepad to target when multiple gamepads are connected.
/// </summary>
public static void SetTargetGamepad(int index)
{
_targetGamepadIndex = index;
#if IGAMEPAD_DEBUG
ExplorerCore.LogWarning($"[IGamepadInputInterceptor] Target gamepad set to index {index}");
#endif
// Re-discover controls for the new gamepad
ClearControlCaches();
DiscoverGamepadControls();
}
// --- Type Loading ---
private static void LoadReflectedTypes()
{
_gamepadType = ReflectionUtility.GetTypeByName("UnityEngine.InputSystem.Gamepad, Unity.InputSystem");
_buttonControlType = ReflectionUtility.GetTypeByName("UnityEngine.InputSystem.Controls.ButtonControl, Unity.InputSystem");
_axisControlType = ReflectionUtility.GetTypeByName("UnityEngine.InputSystem.Controls.AxisControl, Unity.InputSystem");
_vector2ControlType = ReflectionUtility.GetTypeByName("UnityEngine.InputSystem.Controls.Vector2Control, Unity.InputSystem");
_inputControlType = ReflectionUtility.GetTypeByName("UnityEngine.InputSystem.InputControl, Unity.InputSystem");
_basePathProp = _inputControlType?.GetProperty("path", BindingFlags.Public | BindingFlags.Instance);
_buttonIsPressedProp = _buttonControlType?.GetProperty("isPressed", BindingFlags.Public | BindingFlags.Instance);
_buttonWasPressedProp = _buttonControlType?.GetProperty("wasPressedThisFrame", BindingFlags.Public | BindingFlags.Instance);
_buttonWasReleasedProp = _buttonControlType?.GetProperty("wasReleasedThisFrame", BindingFlags.Public | BindingFlags.Instance);
_axisReadValueMethod = _axisControlType?.GetMethod("ReadValue", Type.EmptyTypes);
_vector2ReadValueMethod = _vector2ControlType?.GetMethod("ReadValue", Type.EmptyTypes);
_deviceProp = _inputControlType?.GetProperty("device", BindingFlags.Public | BindingFlags.Instance);
_vector2XProp = _vector2ControlType?.GetProperty("x", BindingFlags.Public | BindingFlags.Instance);
_vector2YProp = _vector2ControlType?.GetProperty("y", BindingFlags.Public | BindingFlags.Instance);
_gamepadAllProp = _gamepadType?.GetProperty("all", BindingFlags.Static | BindingFlags.Public);
_gamepadCurrentProp = _gamepadType?.GetProperty("current", BindingFlags.Static | BindingFlags.Public);
_gamepadLeftStickProp = _gamepadType?.GetProperty("leftStick", BindingFlags.Public | BindingFlags.Instance);
_gamepadRightStickProp = _gamepadType?.GetProperty("rightStick", BindingFlags.Public | BindingFlags.Instance);
#if IGAMEPAD_DEBUG
LogLoadedType("Gamepad", _gamepadType);
LogLoadedType("ButtonControl", _buttonControlType);
LogLoadedType("AxisControl", _axisControlType);
LogLoadedType("Vector2Control", _vector2ControlType);
#endif
}
private static void LogLoadedType(string name, Type type)
{
if (type != null)
ExplorerCore.LogWarning($"[IGamepadInputInterceptor] Loaded type: {name}");
else
ExplorerCore.LogWarning($"[IGamepadInputInterceptor] Failed to load type: {name}");
}
// --- Patching ---
private static void PatchAnalogControls()
{
try
{
if (_vector2ControlType != null)
{
MethodInfo vector2ReadValueMethod = _vector2ControlType.GetMethod("ReadValue",
BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly,
null, Type.EmptyTypes, null);
// If not found with DeclaredOnly, try getting from base type
if (vector2ReadValueMethod == null)
{
Type baseType = _vector2ControlType.BaseType;
if (baseType != null && baseType.IsGenericType)
{
vector2ReadValueMethod = baseType.GetMethod("ReadValue",
BindingFlags.Public | BindingFlags.Instance,
null, Type.EmptyTypes, null);
}
}
if (vector2ReadValueMethod != null)
{
PatchMethod(vector2ReadValueMethod, null, nameof(Postfix_Vector2ReadValue));
#if IGAMEPAD_DEBUG
ExplorerCore.LogWarning("[IGamepadInputInterceptor] Patched Vector2Control.ReadValue()");
#endif
}
}
if (_axisControlType != null)
{
MethodInfo readValueMethod = _axisControlType.GetMethod("ReadValue",
BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly,
null, Type.EmptyTypes, null);
// If not found with DeclaredOnly, try getting from base type
if (readValueMethod == null)
{
Type baseType = _axisControlType.BaseType;
if (baseType != null && baseType.IsGenericType)
{
readValueMethod = baseType.GetMethod("ReadValue",
BindingFlags.Public | BindingFlags.Instance,
null, Type.EmptyTypes, null);
}
}
if (readValueMethod != null)
{
PatchMethod(readValueMethod, null, nameof(Postfix_AxisReadValue));
#if IGAMEPAD_DEBUG
ExplorerCore.LogWarning("[IGamepadInputInterceptor] Patched AxisControl.ReadValue()");
#endif
}
}
}
catch (Exception ex)
{
ExplorerCore.LogWarning($"[IGamepadInputInterceptor] Error during patching: {ex.Message}");
}
}
private static void PatchMethod(MethodInfo method, string prefixName, string postfixName)
{
try
{
if (method == null)
return;
#if CPP
if (IL2CPPUtils.GetIl2CppMethodInfoPointerFieldForGeneratedMethod(method) == null)
return;
#endif
MethodInfo prefixMethod = prefixName != null ? AccessTools.Method(typeof(IGamepadInputInterceptor), prefixName) : null;
MethodInfo postfixMethod = postfixName != null ? AccessTools.Method(typeof(IGamepadInputInterceptor), postfixName) : null;
ExplorerCore.Harmony.Patch(method,
prefix: prefixMethod != null ? new HarmonyMethod(prefixMethod) : null,
postfix: postfixMethod != null ? new HarmonyMethod(postfixMethod) : null);
}
catch (Exception ex)
{
ExplorerCore.LogWarning($"[IGamepadInputInterceptor] Failed to patch method: {ex.Message}");
}
}
// --- Control Discovery ---
private static void DiscoverGamepadControls()
{
try
{
#if IGAMEPAD_DEBUG
ExplorerCore.LogWarning("[IGamepadInputInterceptor] Discovering gamepad controls...");
#endif
object gamepadInstance = GetGamepadInstance(_targetGamepadIndex);
if (gamepadInstance == null)
{
#if IGAMEPAD_DEBUG
ExplorerCore.LogWarning("[IGamepadInputInterceptor] No gamepad found at specified index");
#endif
return;
}
// Instead of iterating allControls and trying to cast them,
// directly access the typed control properties on the Gamepad class.
// This gives us the controls with their correct types immediately.
// List of known public control properties on Gamepad
string[] buttonProperties = new[]
{
"buttonSouth", "buttonNorth", "buttonWest", "buttonEast",
"leftStickButton", "rightStickButton", "leftShoulder", "rightShoulder",
"startButton", "selectButton", "leftTrigger", "rightTrigger"
};
string[] vector2Properties = new[] { "leftStick", "rightStick" };
// Clear any existing gamepad buttons from INewInputSystem before registering new ones
// This prevents duplicates when switching gamepads or re-initializing
//var keysToRemove = INewInputSystem.buttonControls.Keys.Where(k => k.StartsWith("/gamepad") || k.Contains("gamepad")).ToList();
//foreach (var key in keysToRemove)
//{
// INewInputSystem.buttonControls.Remove(key);
//}
// Register all button controls
foreach (string propName in buttonProperties)
{
try
{
PropertyInfo prop = _gamepadType.GetProperty(propName, BindingFlags.Public | BindingFlags.Instance);
if (prop != null)
{
object control = prop.GetValue(gamepadInstance, BindingFlags.Default, null, null, null);
if (control != null)
{
string normalizedPath = ExtractAndNormalizePath(control);
if (!string.IsNullOrEmpty(normalizedPath))
{
INewInputSystem.gamepadButtonControls[normalizedPath] = control;
#if IGAMEPAD_DEBUG
ExplorerCore.LogWarning($"[IGamepadInputInterceptor] Registered button: {normalizedPath} ({propName})");
#endif
}
}
}
}
catch (Exception ex)
{
ExplorerCore.LogWarning($"[IGamepadInputInterceptor] Failed to access button {propName}: {ex.Message}");
}
}
// Register D-pad individual direction buttons
try
{
PropertyInfo dpadProp = _gamepadType.GetProperty("dpad", BindingFlags.Public | BindingFlags.Instance);
if (dpadProp != null)
{
object dpadControl = dpadProp.GetValue(gamepadInstance, BindingFlags.Default, null, null, null);
if (dpadControl != null)
{
string[] dpadDirections = new[] { "up", "down", "left", "right" };
foreach (string direction in dpadDirections)
{
try
{
PropertyInfo dpadDirProp = dpadControl.GetType().GetProperty(direction, BindingFlags.Public | BindingFlags.Instance);
if (dpadDirProp != null)
{
object buttonControl = dpadDirProp.GetValue(dpadControl, BindingFlags.Default, null, null, null);
if (buttonControl != null)
{
string buttonPath = ExtractAndNormalizePath(buttonControl);
if (!string.IsNullOrEmpty(buttonPath))
{
INewInputSystem.gamepadButtonControls[buttonPath] = buttonControl;
#if IGAMEPAD_DEBUG
ExplorerCore.LogWarning($"[IGamepadInputInterceptor] Registered dpad button: {buttonPath} (dpad.{direction})");
#endif
}
}
}
}
catch (Exception ex)
{
ExplorerCore.LogWarning($"[IGamepadInputInterceptor] Failed to access dpad.{direction}: {ex.Message}");
}
}
}
}
}
catch (Exception ex)
{
ExplorerCore.LogWarning($"[IGamepadInputInterceptor] Failed to access dpad: {ex.Message}");
}
// Register all vector2 controls (sticks)
foreach (string propName in vector2Properties)
{
try
{
PropertyInfo prop = _gamepadType.GetProperty(propName, BindingFlags.Public | BindingFlags.Instance);
if (prop != null)
{
object control = prop.GetValue(gamepadInstance, BindingFlags.Default, null, null, null);
if (control != null)
{
string normalizedPath = ExtractAndNormalizePath(control);
if (!string.IsNullOrEmpty(normalizedPath))
{
_vector2Controls[normalizedPath] = control;
#if IGAMEPAD_DEBUG
ExplorerCore.LogWarning($"[IGamepadInputInterceptor] Registered vector2: {normalizedPath} ({propName})");
#endif
}
// Also register individual stick axes as axis controls
// Use the stick name to differentiate between left and right sticks
string[] axisNames = new[] { "x", "y" };
foreach (string axisName in axisNames)
{
try
{
PropertyInfo axisProp = control.GetType().GetProperty(axisName, BindingFlags.Public | BindingFlags.Instance);
if (axisProp != null)
{
object axisControl = axisProp.GetValue(control, BindingFlags.Default, null, null, null);
if (axisControl != null)
{
string rawAxisPath = ExtractAndNormalizePath(axisControl);
if (!string.IsNullOrEmpty(rawAxisPath))
{
// Inject the stick name into the path: /gamepad/x -> /gamepad/leftstick/x
string stickName = propName.ToLowerInvariant(); // "leftstick" or "rightstick"
string axisPath = $"/gamepad/{stickName}/{axisName}";
_axisControls[axisPath] = axisControl;
#if IGAMEPAD_DEBUG
ExplorerCore.LogWarning($"[IGamepadInputInterceptor] Registered stick axis: {axisPath} ({propName}.{axisName})");
#endif
}
}
}
}
catch (Exception ex)
{
ExplorerCore.LogWarning($"[IGamepadInputInterceptor] Failed to access {propName}.{axisName}: {ex.Message}");
}
}
}
}
}
catch (Exception ex)
{
ExplorerCore.LogWarning($"[IGamepadInputInterceptor] Failed to access vector2 {propName}: {ex.Message}");
}
}
// Register triggers as AXIS controls so analog values can be captured
// Triggers are ButtonControl but also support analog values
string[] triggerProperties = new[] { "leftTrigger", "rightTrigger" };
foreach (string propName in triggerProperties)
{
try
{
PropertyInfo prop = _gamepadType.GetProperty(propName, BindingFlags.Public | BindingFlags.Instance);
if (prop != null)
{
object control = prop.GetValue(gamepadInstance, BindingFlags.Default, null, null, null);
if (control != null)
{
string normalizedPath = ExtractAndNormalizePath(control);
if (!string.IsNullOrEmpty(normalizedPath))
{
// Add trigger axis path (e.g., /gamepad/lefttrigger for analog value)
_axisControls[normalizedPath] = control;
#if IGAMEPAD_DEBUG
ExplorerCore.LogWarning($"[IGamepadInputInterceptor] Registered trigger axis: {normalizedPath} ({propName})");
#endif
}
}
}
}
catch (Exception ex)
{
ExplorerCore.LogWarning($"[IGamepadInputInterceptor] Failed to access {propName}: {ex.Message}");
}
}
int totalControls = INewInputSystem.gamepadButtonControls.Count + _axisControls.Count + _vector2Controls.Count;
#if IGAMEPAD_DEBUG
ExplorerCore.LogWarning($"[IGamepadInputInterceptor] Registered {totalControls} controls " +
$"({INewInputSystem.gamepadButtonControls.Count} buttons, {_axisControls.Count} axes, {_vector2Controls.Count} vectors)");
#endif
}
catch (Exception ex)
{
ExplorerCore.LogWarning($"[IGamepadInputInterceptor] Error discovering controls: {ex.Message}\n{ex.StackTrace}");
}
}
private static object GetGamepadInstance(int index)
{
try
{
// Try to manually select the controller by index from the all collection
if (_gamepadAllProp != null)
{
object allGamepads = _gamepadAllProp.GetValue(null, BindingFlags.Default, null, null, null);
if (allGamepads != null)
{
Type collectionType = allGamepads.GetType();
PropertyInfo countProp = collectionType.GetProperty("Count", BindingFlags.Public | BindingFlags.Instance);
PropertyInfo itemProp = collectionType.GetProperty("Item", BindingFlags.Public | BindingFlags.Instance);
if (countProp != null && itemProp != null)
{
int count = (int)countProp.GetValue(allGamepads, BindingFlags.Default, null, null, null);
if (index >= 0 && index < count)
{
return itemProp.GetValue(allGamepads, new object[] { index });
}
}
}
}
// Fallback to Gamepad.current
if (_gamepadCurrentProp != null)
{
return _gamepadCurrentProp.GetValue(null, BindingFlags.Default, null, null, null);
}
return null;
}
catch (Exception ex)
{
ExplorerCore.LogWarning($"[IGamepadInputInterceptor] Error getting gamepad instance: {ex.Message}");
return null;
}
}
// Some builds have problems when trying to retrieve property names, so we have to get some fallbacks into place while also trying to get a unified format.
private static string ExtractAndNormalizePath(object control)
{
if (control == null)
return null;
string path = null;
// Method 1: Try ToString() - safest on IL2CPP
try
{
string toString = control.ToString();
if (!string.IsNullOrEmpty(toString))
{
// Parse path from ToString (e.g., "ButtonControl{/xinputcontrollerwindows/buttonSouth}")
int slashIndex = toString.IndexOf('/');
if (slashIndex >= 0)
{
int closeIndex = toString.IndexOf('}', slashIndex);
if (closeIndex >= 0)
{
path = toString.Substring(slashIndex, closeIndex - slashIndex);
}
}
}
}
catch { }
// Method 2: Try reflection on path property (managed builds)
#if CPP
if (string.IsNullOrEmpty(path))
{
try
{
if (_basePathProp != null)
{
object pathObj = _basePathProp.GetValue(control, BindingFlags.Default, null, null, null);
path = pathObj as string;
}
}
catch { }
}
#endif
// Method 3: Try via name property (fallback)
if (string.IsNullOrEmpty(path))
{
try
{
PropertyInfo nameProp = control.GetType().GetProperty("name", BindingFlags.Public | BindingFlags.Instance);
if (nameProp != null)
{
string controlName = nameProp.GetValue(control, BindingFlags.Default, null, null, null) as string;
if (!string.IsNullOrEmpty(controlName))
{
path = $"/gamepad/{controlName}";
}
}
}
catch { }
}
if (string.IsNullOrEmpty(path))
return null;
return NormalizeControlPath(path);
}
/// <summary>
/// Normalizes gamepad control paths to a common format across different controller types.
/// </summary>
public static string NormalizeControlPath(string rawPath)
{
if (string.IsNullOrEmpty(rawPath))
return rawPath;
string path = rawPath.ToLowerInvariant().Trim();
foreach (var kvp in _vendorPrefixMap)
{
if (path.StartsWith(kvp.Key))
{
path = kvp.Value + path.Substring(kvp.Key.Length);
break;
}
}
// Remove numeric gamepad suffix (/gamepad1 → /gamepad)
const string prefix = "/gamepad";
int prefixLen = prefix.Length;
if (path.StartsWith(prefix) &&
path.Length > prefixLen &&
char.IsDigit(path[prefixLen]))
{
path = prefix + path.Substring(prefixLen + 1);
}
// Normalize D-pad paths: /gamepad/up -> /gamepad/dpad/up
// Some versions of the engine/library return these names
if (path == "/gamepad/up" || path == "/gamepad/down" ||
path == "/gamepad/left" || path == "/gamepad/right")
{
string direction = path.Substring("/gamepad/".Length);
path = $"/gamepad/dpad/{direction}";
}
return path;
}
private static void ClearControlCaches()
{
INewInputSystem.gamepadButtonControls.Clear();
_axisControls.Clear();
_vector2Controls.Clear();
_axisValues.Clear();
_vector2Values.Clear();
}
// --- Public API ---
/// <summary>
/// Gets whether a button is currently pressed.
/// </summary>
/// <param name="buttonPath">Normalized path like "/gamepad/buttonsouth" or vendor-specific path</param>
public static bool IsButtonPressed(string buttonPath)
{
if (!_isEnabled)
return false;
string key = buttonPath.ToLower();
#if IGAMEPAD_DEBUG
ExplorerCore.LogWarning($"[IGamepadInputInterceptor] IsButtonPressed querying: {key}");
#endif
// Trigger the property getter to populate state via INewInputSystem
if (INewInputSystem.gamepadButtonControls.TryGetValue(key, out object button) && _buttonIsPressedProp != null)
{
#if IGAMEPAD_DEBUG
ExplorerCore.LogWarning($"[IGamepadInputInterceptor] IsButtonPressed found button, invoking getter");
#endif
_buttonIsPressedProp.GetValue(button, BindingFlags.Default, null, null, null);
}
#if IGAMEPAD_DEBUG
else
{
ExplorerCore.LogWarning($"[IGamepadInputInterceptor] IsButtonPressed NOT found in gamepadButtonControls. Available: {string.Join(", ", INewInputSystem.gamepadButtonControls.Keys)}");
}
#endif
bool result = INewInputSystem.buttonPressedStates.TryGetValue(key, out bool pressed) && pressed;
#if IGAMEPAD_DEBUG
//ExplorerCore.LogWarning($"[IGamepadInputInterceptor] IsButtonPressed returning: {result}");
#endif
return result;
}
/// <summary>
/// Gets whether a button was pressed this frame.
/// </summary>
public static bool WasButtonPressedThisFrame(string buttonPath)
{
if (!_isEnabled)
return false;
string key = buttonPath.ToLower();
// Trigger the property getter to populate state via INewInputSystem
if (INewInputSystem.gamepadButtonControls.TryGetValue(key, out object button) && _buttonWasPressedProp != null)
{
_buttonWasPressedProp.GetValue(button, BindingFlags.Default, null, null, null);
}
return INewInputSystem.buttonWasPressedStates.TryGetValue(key, out bool pressed) && pressed;
}
/// <summary>
/// Gets whether a button was released this frame.
/// </summary>
public static bool WasButtonReleasedThisFrame(string buttonPath)
{
if (!_isEnabled)
return false;
string key = buttonPath.ToLower();
// Trigger the property getter to populate state via INewInputSystem
if (INewInputSystem.gamepadButtonControls.TryGetValue(key, out object button) && _buttonWasReleasedProp != null)
{
_buttonWasReleasedProp.GetValue(button, BindingFlags.Default, null, null, null);
}
return INewInputSystem.buttonWasReleasedStates.TryGetValue(key, out bool released) && released;
}
/// <summary>
/// Gets the current value of an analog axis (trigger, stick axis, etc).
/// </summary>
/// <param name="axisPath">Path like "/gamepad/lefttrigger" or "/gamepad/leftstick/x"</param>
public static float GetAxisValue(string axisPath)
{
if (!_isEnabled)
return 0f;
string normalized = NormalizeControlPath(axisPath ?? "");
#if IGAMEPAD_DEBUG
ExplorerCore.LogWarning($"[IGamepadInputInterceptor] GetAxisValue querying: {normalized}");
#endif
// Trigger the ReadValue() method to populate state
if (_axisControls.TryGetValue(normalized, out object axis) && _axisReadValueMethod != null)
{
#if IGAMEPAD_DEBUG
ExplorerCore.LogWarning($"[IGamepadInputInterceptor] GetAxisValue found axis, invoking ReadValue()");
#endif
_axisReadValueMethod.Invoke(axis, null);
}
#if IGAMEPAD_DEBUG
else
{
ExplorerCore.LogWarning($"[IGamepadInputInterceptor] GetAxisValue NOT found for: {normalized}. Available: {string.Join(", ", _axisControls.Keys)}");
}
#endif
return _axisValues.TryGetValue(normalized, out float value) ? value : 0f;
}
/// <summary>
/// Gets the current value of a 2D input (stick, dpad).
/// </summary>
/// <param name="vectorPath">Path like "/gamepad/leftstick" or "/gamepad/dpad"</param>
public static Vector2 GetVector2Value(string vectorPath)
{
if (!_isEnabled)
return new Vector2(0f, 0f);
string normalized = NormalizeControlPath(vectorPath ?? "");
// Trigger the ReadValue() method to populate state
if (_vector2Controls.TryGetValue(normalized, out object vector2) && _vector2ReadValueMethod != null)
{
_vector2ReadValueMethod.Invoke(vector2, null);
}
return _vector2Values.TryGetValue(normalized, out var value) ? value : new Vector2(0f, 0f);
}
/// <summary>
/// Gets all currently registered control paths.
/// </summary>
public static IEnumerable<string> GetRegisteredButtonPaths() => INewInputSystem.gamepadButtonControls.Keys;
/// <summary>
/// Gets all currently registered axis paths.
/// </summary>
public static IEnumerable<string> GetRegisteredAxisPaths() => _axisControls.Keys;
/// <summary>
/// Gets all currently registered vector2 paths.
/// </summary>
public static IEnumerable<string> GetRegisteredVector2Paths() => _vector2Controls.Keys;
// --- Postfix Methods (capture state and optionally block) ---
private static void Postfix_Vector2ReadValue(object __instance, ref object __result)
{
#if IGAMEPAD_DEBUG
ExplorerCore.LogWarning($"[IGamepadInputInterceptor] Postfix_Vector2ReadValue called, instance={(__instance != null ? "not null" : "null")}, result={(__result != null ? "not null" : "null")}");
#endif
if (__instance == null || __result == null)
return;
// Verify the vector2 control is properly attached to a device before proceeding
try
{
if (_deviceProp != null)
{
object device = _deviceProp.GetValue(__instance, BindingFlags.Default, null, null, null);
if (device == null)
return; // Control not attached to a device, skip
}
}
catch
{
return; // If we can't check device, skip to be safe
}
string path = ExtractAndNormalizePath(__instance);
if (!string.IsNullOrEmpty(path))
{
try
{
if (_vector2XProp != null && _vector2YProp != null)
{
float x = (float)_vector2XProp.GetValue(__result, BindingFlags.Default, null, null, null);
float y = (float)_vector2YProp.GetValue(__result, BindingFlags.Default, null, null, null);
#if IGAMEPAD_DEBUG
ExplorerCore.LogWarning($"[IGamepadInputInterceptor] Postfix_Vector2ReadValue: {path} = ({x}, {y})");
#endif
_vector2Values[path] = new Vector2(x, y);
if (_isEnabled && FreeCamPanel.ShouldOverrideInput() && _vector2XProp.CanWrite && _vector2YProp.CanWrite)
{
_vector2XProp.SetValue(__result, 0f, BindingFlags.Default, null, null, null);
_vector2YProp.SetValue(__result, 0f, BindingFlags.Default, null, null, null);
}
}
}
#if IGAMEPAD_DEBUG
catch (Exception ex)
{
ExplorerCore.LogWarning($"[IGamepadInputInterceptor] Error in Postfix_Vector2ReadValue: {ex.Message}");
}
#else
catch
{
}
#endif
}
}
private static void Postfix_AxisReadValue(object __instance, ref float __result)
{
if (__instance == null)
return;
// Verify the axis control is properly attached to a device before proceeding
try
{
if (_deviceProp != null)
{
object device = _deviceProp.GetValue(__instance, BindingFlags.Default, null, null, null);
if (device == null)
return; // Control not attached to a device, skip
}
}
catch
{
return; // If we can't check device, skip to be safe
}
string path = ExtractAndNormalizePath(__instance);
#if IGAMEPAD_DEBUG
ExplorerCore.LogWarning($"[IGamepadInputInterceptor] Postfix_AxisReadValue extracted path: {path}, value: {__result}");
#endif
// Try to identify if this axis belongs to a stick and which one by comparing the parent stick's axes
if (!string.IsNullOrEmpty(path))
{
string adjustedPath = path;
try
{
// Get the gamepad instance to access stick controls
object gamepadInstance = GetGamepadInstance(_targetGamepadIndex);
if (gamepadInstance != null)
{
// Check if this axis is part of leftStick
if (_gamepadLeftStickProp != null)
{
object leftStick = _gamepadLeftStickProp.GetValue(gamepadInstance, BindingFlags.Default, null, null, null);
if (leftStick != null)
{
PropertyInfo leftStickXProp = leftStick.GetType().GetProperty("x", BindingFlags.Public | BindingFlags.Instance);
PropertyInfo leftStickYProp = leftStick.GetType().GetProperty("y", BindingFlags.Public | BindingFlags.Instance);
object leftStickX = leftStickXProp?.GetValue(leftStick, BindingFlags.Default, null, null, null);
object leftStickY = leftStickYProp?.GetValue(leftStick, BindingFlags.Default, null, null, null);
// Compare by reference to see if __instance is the x or y of leftStick
if (__instance == leftStickX || __instance == leftStickY)
{
adjustedPath = path.Replace("/gamepad/x", "/gamepad/leftstick/x").Replace("/gamepad/y", "/gamepad/leftstick/y");
#if IGAMEPAD_DEBUG
ExplorerCore.LogWarning($"[IGamepadInputInterceptor] Identified as LEFT STICK axis, adjusted path: {adjustedPath}");
#endif
}
}
}
// If not left stick, check if it's part of rightStick
if (adjustedPath == path && _gamepadRightStickProp != null)
{
object rightStick = _gamepadRightStickProp.GetValue(gamepadInstance, BindingFlags.Default, null, null, null);
if (rightStick != null)
{
PropertyInfo rightStickXProp = rightStick.GetType().GetProperty("x", BindingFlags.Public | BindingFlags.Instance);
PropertyInfo rightStickYProp = rightStick.GetType().GetProperty("y", BindingFlags.Public | BindingFlags.Instance);
object rightStickX = rightStickXProp?.GetValue(rightStick, BindingFlags.Default, null, null, null);
object rightStickY = rightStickYProp?.GetValue(rightStick, BindingFlags.Default, null, null, null);
// Compare by reference to see if __instance is the x or y of rightStick
if (__instance == rightStickX || __instance == rightStickY)
{
adjustedPath = path.Replace("/gamepad/x", "/gamepad/rightstick/x").Replace("/gamepad/y", "/gamepad/rightstick/y");
#if IGAMEPAD_DEBUG
ExplorerCore.LogWarning($"[IGamepadInputInterceptor] Identified as RIGHT STICK axis, adjusted path: {adjustedPath}");
#endif
}
}
}
}
}
catch (Exception ex)
{
ExplorerCore.LogWarning($"[IGamepadInputInterceptor] Error identifying stick axis: {ex.Message}");
}
#if IGAMEPAD_DEBUG
ExplorerCore.LogWarning($"[IGamepadInputInterceptor] Postfix_AxisReadValue storing: {adjustedPath} = {__result}");
#endif
_axisValues[adjustedPath] = __result;
// Block input if interception is enabled
if (_isEnabled && FreeCamPanel.ShouldOverrideInput())
{
#if IGAMEPAD_DEBUG
ExplorerCore.LogWarning($"[IGamepadInputInterceptor] BLOCKING Postfix_AxisReadValue: {adjustedPath} (was {__result}, now 0f)");
#endif
__result = 0f;
}
}
}
}
}
+295
View File
@@ -28,6 +28,8 @@ namespace UniverseLib.Input
ILegacyInput.Init();
break;
case InputType.InputSystem:
INewInputSystem.Init();
break;
case InputType.None:
break;
}
@@ -38,6 +40,7 @@ namespace UniverseLib.Input
case InputType.Legacy:
return ILegacyInput.GetKey(key);
case InputType.InputSystem:
return INewInputSystem.GetKey(key);
case InputType.None:
default:
return InputManager.GetKey(key);
@@ -49,6 +52,8 @@ namespace UniverseLib.Input
case InputType.Legacy:
return ILegacyInput.GetKeyDown(key);
case InputType.InputSystem:
string buttonName = $"/keyboard/{key.ToString()}".ToLower();
return INewInputSystem.GetButtonWasPressed(buttonName);
case InputType.None:
default:
return InputManager.GetKeyDown(key);
@@ -60,6 +65,9 @@ namespace UniverseLib.Input
case InputType.Legacy:
return ILegacyInput.GetKeyUp(key);
case InputType.InputSystem:
// Closest equivalent in the new input system is "wasPressedThisFrame"?
string buttonName = $"/keyboard/{key.ToString()}".ToLower();
return INewInputSystem.GetButtonWasReleased(buttonName);
case InputType.None:
default:
return InputManager.GetKeyUp(key);
@@ -71,6 +79,7 @@ namespace UniverseLib.Input
case InputType.Legacy:
return ILegacyInput.GetMouseButton(button);
case InputType.InputSystem:
return INewInputSystem.GetMouseButton(button);
case InputType.None:
default:
return InputManager.GetMouseButton(button);
@@ -82,6 +91,7 @@ namespace UniverseLib.Input
case InputType.Legacy:
return ILegacyInput.GetMouseButtonDown(button);
case InputType.InputSystem:
return INewInputSystem.GetMouseButtonDown(button);
case InputType.None:
default:
return InputManager.GetMouseButtonDown(button);
@@ -325,4 +335,289 @@ namespace UniverseLib.Input
}
}
}
public static class INewInputSystem
{
// --- Reflected members ---
private static PropertyInfo isPressedProp;
private static PropertyInfo wasPressedProp;
private static PropertyInfo wasReleasedProp;
private static MethodInfo isValueConsideredPressed;
private static MethodInfo isPressedMethod;
private static MethodInfo isInProgressMethod;
private static MethodInfo wasPressedMethod;
private static MethodInfo wasPerformedMethod;
// --- State dictionaries ---
internal static readonly Dictionary<string, object> buttonControls = new();
internal static readonly Dictionary<string, object> gamepadButtonControls = new();
internal static readonly Dictionary<string, bool> buttonPressedStates = new();
internal static readonly Dictionary<string, bool> buttonWasPressedStates = new();
internal static readonly Dictionary<string, bool> buttonWasReleasedStates = new();
private static readonly Dictionary<string, bool> buttonIsValueConsideredPressedStates = new();
private static readonly Dictionary<string, bool> actionInProgressStates = new();
private static readonly Dictionary<string, bool> actionWasPerformedStates = new();
// --- Initialization ---
public static void Init()
{
Type buttonControlType = ReflectionUtility.GetTypeByName("UnityEngine.InputSystem.Controls.ButtonControl, Unity.InputSystem");
Type inputActionType = ReflectionUtility.GetTypeByName("UnityEngine.InputSystem.InputAction, Unity.InputSystem");
if (buttonControlType == null || inputActionType == null)
{
ExplorerCore.LogWarning("Unity.InputSystem types not found; InputSystem integration disabled.");
return;
}
// === Patch all relevant ButtonControl properties ===
PatchButtonControl(buttonControlType);
// === Patch InputAction methods ===
PatchInputAction(inputActionType);
// === Cache controls ===
GetButtonControls();
IGamepadInputInterceptor.Init();
}
private static void PatchButtonControl(Type buttonControlType)
{
isPressedProp = buttonControlType.GetProperty("isPressed");
wasPressedProp = buttonControlType.GetProperty("wasPressedThisFrame");
wasReleasedProp = buttonControlType.GetProperty("wasReleasedThisFrame");
isValueConsideredPressed = buttonControlType.GetMethod("IsValueConsideredPressed");
PatchPropertyGetter(isPressedProp, nameof(Postfix_IsPressed));
PatchPropertyGetter(wasPressedProp, nameof(Postfix_WasPressedThisFrame));
PatchPropertyGetter(wasReleasedProp, nameof(Postfix_WasReleasedThisFrame));
PatchPropertyGetter(wasReleasedProp, nameof(Postfix_IsValueConsideredPressed));
}
private static void PatchInputAction(Type inputActionType)
{
isPressedMethod = inputActionType.GetMethod("IsPressed");
isInProgressMethod = inputActionType.GetMethod("IsInProgress");
wasPressedMethod = inputActionType.GetMethod("WasPressedThisFrame");
wasPerformedMethod = inputActionType.GetMethod("WasPerformedThisFrame");
PatchMethod(isPressedMethod, nameof(Postfix_IsPressed));
PatchMethod(isInProgressMethod, nameof(Postfix_IsInProgress));
PatchMethod(wasPressedMethod, nameof(Postfix_WasPressedThisFrame));
PatchMethod(wasPerformedMethod, nameof(Postfix_WasPerformedThisFrame));
}
private static void PatchPropertyGetter(PropertyInfo prop, string postfixName)
{
if (prop?.GetGetMethod() == null) return;
#if CPP
if (IL2CPPUtils.GetIl2CppMethodInfoPointerFieldForGeneratedMethod(prop.GetGetMethod()) == null)
throw new Exception();
#endif
ExplorerCore.Harmony.Patch(prop.GetGetMethod(),
postfix: new HarmonyMethod(AccessTools.Method(typeof(INewInputSystem), postfixName)));
}
private static void PatchMethod(MethodInfo method, string postfixName)
{
if (method == null) return;
#if CPP
if (IL2CPPUtils.GetIl2CppMethodInfoPointerFieldForGeneratedMethod(method) == null)
throw new Exception();
#endif
ExplorerCore.Harmony.Patch(method,
postfix: new HarmonyMethod(AccessTools.Method(typeof(INewInputSystem), postfixName)));
}
// --- Cache button controls ---
public static void GetButtonControls()
{
Type buttonControlType = ReflectionUtility.GetTypeByName("UnityEngine.InputSystem.Controls.ButtonControl, Unity.InputSystem");
PropertyInfo pathProp = buttonControlType?.GetProperty("path", BindingFlags.Public | BindingFlags.Instance);
RegisterDeviceControls("UnityEngine.InputSystem.Keyboard", "UnityEngine.InputSystem.Controls.KeyControl", pathProp);
RegisterDeviceControls("UnityEngine.InputSystem.Mouse", "UnityEngine.InputSystem.Controls.ButtonControl", pathProp);
// Note: Gamepad buttons are registered by IGamepadInputInterceptor with proper path normalization
}
internal static void RegisterDeviceControls(string deviceTypeName, string controlTypeName, PropertyInfo pathProp)
{
Type deviceType = ReflectionUtility.GetTypeByName($"{deviceTypeName}, Unity.InputSystem");
if (deviceType == null) return;
PropertyInfo currentProp = deviceType.GetProperty("current", BindingFlags.Public | BindingFlags.Static);
object deviceInstance = currentProp?.GetValue(null, null);
if (deviceInstance == null) return;
foreach (PropertyInfo prop in deviceType.GetProperties(BindingFlags.Public | BindingFlags.Instance))
{
if (prop.GetIndexParameters().Length > 0)
continue;
if (prop.PropertyType.FullName == controlTypeName)
{
object control = prop.GetValue(deviceInstance, null);
if (control == null)
continue;
string path = pathProp?.GetValue(control, null) as string;
if (!string.IsNullOrEmpty(path))
buttonControls[path.ToLower()] = control;
}
}
}
// --- Access methods ---
public static bool GetKey(KeyCode key)
{
string buttonName = PropToKeycode($"/keyboard/{key.ToString()}".ToLower());
return GetButtonPressed(buttonName);
}
public static bool GetButtonPressed(string buttonName)
{
string key = buttonName.ToLower();
if (buttonControls.TryGetValue(key, out object button))
{
isPressedProp?.GetValue(button, null);
return buttonPressedStates.TryGetValue(key, out bool value) && value;
}
return false;
}
public static bool GetButtonWasPressed(string buttonName)
{
string key = buttonName.ToLower();
if (buttonControls.TryGetValue(key, out object button))
{
wasPressedProp?.GetValue(button, null);
return buttonWasPressedStates.TryGetValue(key, out bool value) && value;
}
return false;
}
public static bool GetButtonWasReleased(string buttonName)
{
string key = buttonName.ToLower();
if (buttonControls.TryGetValue(key, out object button))
{
wasReleasedProp?.GetValue(button, null);
return buttonWasReleasedStates.TryGetValue(key, out bool value) && value;
}
return false;
}
public static bool GetMouseButton(int button)
{
string buttonPath = button switch
{
0 => "/mouse/leftbutton",
1 => "/mouse/rightbutton",
2 => "/mouse/middlebutton",
3 => "/mouse/forwardbutton",
4 => "/mouse/backbutton",
_ => $"/mouse/button{button}"
};
return GetButtonPressed(buttonPath);
}
public static bool GetMouseButtonDown(int button){
string buttonPath = button switch
{
0 => "/mouse/leftbutton",
1 => "/mouse/rightbutton",
2 => "/mouse/middlebutton",
3 => "/mouse/forwardbutton",
4 => "/mouse/backbutton",
_ => $"/mouse/button{button}"
};
return GetButtonWasPressed(buttonPath);
}
// --- Postfixes ---
private static void Postfix_IsPressed(object __instance, ref bool __result)
=> StoreState(__instance, ref __result, buttonPressedStates);
private static void Postfix_WasPressedThisFrame(object __instance, ref bool __result)
=> StoreState(__instance, ref __result, buttonWasPressedStates);
private static void Postfix_WasReleasedThisFrame(object __instance, ref bool __result)
=> StoreState(__instance, ref __result, buttonWasReleasedStates);
private static void Postfix_IsValueConsideredPressed(object __instance, ref bool __result)
=> StoreState(__instance, ref __result, buttonIsValueConsideredPressedStates);
private static void Postfix_IsInProgress(object __instance, ref bool __result)
=> StoreState(__instance, ref __result, actionInProgressStates);
private static void Postfix_WasPerformedThisFrame(object __instance, ref bool __result)
=> StoreState(__instance, ref __result, actionWasPerformedStates);
private static void StoreState(object __instance, ref bool __result, Dictionary<string, bool> dict)
{
try
{
Type type = __instance.GetType();
PropertyInfo pathProp = type.GetProperty("path") ?? type.GetProperty("name");
string key = (pathProp?.GetValue(__instance, null)?.ToString() ?? string.Empty).ToLower();
// Normalize gamepad paths so they match what IGamepadInputInterceptor registered
key = IGamepadInputInterceptor.NormalizeControlPath(key);
dict[key] = __result;
if (FreeCamPanel.ShouldOverrideInput())
{
__result = false;
}
}
catch (Exception ex)
{
ExplorerCore.LogWarning($"Failed to store state: {ex.Message}");
}
}
// Input system property name doesnt match the KeyCode enum name, so we need to map some
public static string PropToKeycode(string propName)
{
return propName switch
{
"/keyboard/leftcontrol" => "/keyboard/leftctrl",
"/keyboard/rightcontrol" => "/keyboard/rightctrl",
"/keyboard/keypad0" => "/keyboard/numpad0",
"/keyboard/keypad1" => "/keyboard/numpad1",
"/keyboard/keypad2" => "/keyboard/numpad2",
"/keyboard/keypad3" => "/keyboard/numpad3",
"/keyboard/keypad4" => "/keyboard/numpad4",
"/keyboard/keypad5" => "/keyboard/numpad5",
"/keyboard/keypad6" => "/keyboard/numpad6",
"/keyboard/keypad7" => "/keyboard/numpad7",
"/keyboard/keypad8" => "/keyboard/numpad8",
"/keyboard/keypad9" => "/keyboard/numpad9",
"/keyboard/keypaddivide" => "/keyboard/numpaddivide",
"/keyboard/keypadmultiply" => "/keyboard/numpadmultiply",
"/keyboard/keypadminus" => "/keyboard/numpadminus",
"/keyboard/keypadplus" => "/keyboard/numpadplus",
"/keyboard/keypadperiod" => "/keyboard/numpadperiod",
"/keyboard/keypadequals" => "/keyboard/numpadequals",
"/keyboard/keypadenter" => "/keyboard/numpadenter",
"/keyboard/alpha0" => "/keyboard/0",
"/keyboard/alpha1" => "/keyboard/1",
"/keyboard/alpha2" => "/keyboard/2",
"/keyboard/alpha3" => "/keyboard/3",
"/keyboard/alpha4" => "/keyboard/4",
"/keyboard/alpha5" => "/keyboard/5",
"/keyboard/alpha6" => "/keyboard/6",
"/keyboard/alpha7" => "/keyboard/7",
"/keyboard/alpha8" => "/keyboard/8",
"/keyboard/alpha9" => "/keyboard/9",
"/keyboard/print" => "/keyboard/printscreen",
_ => propName
};
}
}
}
+4 -3
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;
+106 -9
View File
@@ -11,6 +11,52 @@ namespace CinematicUnityExplorer.Cinematic
// StepCommand is basically the offset of step_left and step_up, what IGCS sends to move the camera.
using StepCommand = Mono.CSharp.Tuple<float, float>;
[StructLayout(LayoutKind.Sequential)]
struct Vec3
{
public float x, y, z;
public void FromVector3(Vector3 v)
{
this.x = v.x; this.y = v.y; this.z = v.z;
}
}
[StructLayout(LayoutKind.Sequential)]
struct Vec4
{
public float x, y, z, w;
public void FromQuaternion(Quaternion q) { this.x = q.x; this.y = q.y ; this.z = q.z; this.w = q.w; }
}
[StructLayout(LayoutKind.Sequential)]
struct CameraToolsData
{
public byte cameraEnabled;
public byte cameraMovementLocked;
public byte reserved1;
public byte reserved2;
public float fov;
public Vec3 coordinates;
public Vec4 lookQuaternion;
public Vec3 rotationMatrixUpVector;
public Vec3 rotationMatrixRightVector;
public Vec3 rotationMatrixForwardVector;
public float pitch;
public float yaw;
public float roll;
}
enum SessionStatus : int
{
Ok = 0,
Error_CameraNotEnabled = 1,
Error_CameraPathPlaying = 2,
Error_AlreadySessionActive = 3,
Error_CameraFeatureNotAvailable = 4,
Error_UnknownError = 5
}
internal static class NativeMethods
{
[DllImport("kernel32.dll")]
@@ -26,7 +72,7 @@ namespace CinematicUnityExplorer.Cinematic
private delegate void MoveCameraCallback(float step_left, float step_up, float fov, int from_start);
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
private delegate void SessionCallback();
private delegate int SessionCallback();
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
private delegate IntPtr U_IGCS_Initialize(MoveCameraCallback callback, SessionCallback start_cb, SessionCallback end_cb);
@@ -49,13 +95,60 @@ namespace CinematicUnityExplorer.Cinematic
// This object *must* be used with a Lock.
private readonly Queue<StepCommand> commands = new();
private IntPtr CameraStatus = IntPtr.Zero;
private IntPtr AllCamerasData = IntPtr.Zero;
private IEnumerable<IntPtr> GetValidCameras()
{
for (int i = 0; i < 10; i++)
{
IntPtr camPtr = Marshal.ReadIntPtr(AllCamerasData, i * IntPtr.Size);
if (camPtr == IntPtr.Zero)
yield break;
yield return camPtr;
}
}
public void UpdateFreecamStatus(bool enabled)
{
if (CameraStatus == IntPtr.Zero) return;
Marshal.WriteByte(CameraStatus, enabled ? (byte)0x1 : (byte)0x0);
if (AllCamerasData == IntPtr.Zero) return;
unsafe
{
foreach (IntPtr pCamera in this.GetValidCameras())
{
CameraToolsData* data = (CameraToolsData*)pCamera;
data->cameraEnabled = enabled ? (byte)1 : (byte) 0;
}
}
}
internal void UpdateCameraData(Camera cam)
{
if (AllCamerasData == IntPtr.Zero) return;
Transform transform = cam.transform;
unsafe
{
foreach (IntPtr pCamera in this.GetValidCameras())
{
CameraToolsData* data = (CameraToolsData*)pCamera;
data->fov = cam.fieldOfView;
data->cameraMovementLocked = _isActive ? (byte)1 : (byte)0;
data->coordinates.FromVector3(transform.position);
data->lookQuaternion.FromQuaternion(transform.rotation);
data->rotationMatrixRightVector.FromVector3(transform.right);
data->rotationMatrixUpVector.FromVector3(transform.up);
data->rotationMatrixForwardVector.FromVector3(transform.forward);
Vector3 rot = transform.eulerAngles;
data->pitch = rot.x * Mathf.Deg2Rad;
data->yaw = rot.y * Mathf.Deg2Rad;
data->roll = rot.z * Mathf.Deg2Rad;
}
}
}
public void ExecuteCameraCommand(Camera cam)
@@ -92,9 +185,11 @@ namespace CinematicUnityExplorer.Cinematic
commands.Enqueue(new StepCommand(stepLeft, stepUp));
}
}
private void StartSession()
private int StartSession()
{
if (_isActive) return (int)SessionStatus.Error_AlreadySessionActive;
_isActive = true;
return (int)SessionStatus.Ok;
}
// At the EndSession, since we have a queue system, we have to have a special check when the session ends and
@@ -111,7 +206,7 @@ namespace CinematicUnityExplorer.Cinematic
endSessionPosition = null;
}
private void EndSession()
private int EndSession()
{
endSessionPosition = position;
position = null;
@@ -119,6 +214,8 @@ namespace CinematicUnityExplorer.Cinematic
lock (commands)
commands.Clear();
return 0;
}
public UnityIGCSConnector()
@@ -143,8 +240,8 @@ namespace CinematicUnityExplorer.Cinematic
delegates.Add(new SessionCallback(StartSession));
delegates.Add(new SessionCallback(EndSession));
CameraStatus = initFunc((MoveCameraCallback)delegates[0], (SessionCallback)delegates[1], (SessionCallback)delegates[2]);
if (CameraStatus == IntPtr.Zero){
AllCamerasData = initFunc((MoveCameraCallback)delegates[0], (SessionCallback)delegates[1], (SessionCallback)delegates[2]);
if (AllCamerasData == IntPtr.Zero){
// This is actually a InvalidDataException, but some games dont allow you to throw that.
throw new Exception("IGCSDof returned an invalid pointer which means something went wrong");
}
+32 -10
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> Freecam_Camera_Target_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();
@@ -166,7 +171,7 @@ namespace UnityExplorer.Config
"Optional keybind to begin a UI-mode Mouse Inspect.",
KeyCode.None);
CSConsole_Assembly_Blacklist = new("CSharp Console Assembly Blacklist",
CSConsole_Assembly_Blacklist = new("CSharp Console Assembly Blacklist",
"Use this to blacklist Assembly names from being referenced by the C# Console. Requires a Reset of the C# Console.\n" +
"Separate each Assembly with a semicolon ';'." +
"For example, to blacklist Assembly-CSharp, you would add 'Assembly-CSharp;'",
@@ -177,7 +182,7 @@ namespace UnityExplorer.Config
"Seperate signatures with a semicolon ';'.\r\n" +
"For example, to blacklist Camera.main, you would add 'UnityEngine.Camera.main;'",
"");
Reflection_Hide_NativeInfoPtrs = new("Hide NativeMethodInfoPtr_s and NativeFieldInfoPtr_s",
"Use this to blacklist NativeMethodPtr_s and NativeFieldInfoPtrs_s from the class inspector, mainly to reduce clutter.\r\n" +
"For example, this will hide 'Class.NativeFieldInfoPtr_value' for the field 'Class.value'.",
@@ -186,19 +191,23 @@ namespace UnityExplorer.Config
Auto_Scale_UI = new("Make the mod UI automatically scale with resolution",
"Especially useful when running games in high resolutions and you are having a hard time reading the mods menu (requires restart).",
true);
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);
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);
Default_Freecam = new("Default Freecam mode",
"Default type of freecam selected on startup.",
FreeCamPanel.FreeCameraType.New);
Freecam_Camera_Target_Selection = new("Freecam Camera Target 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);
Frameskip = new("Frameskip",
"Skip a frame when the game is paused.",
KeyCode.PageDown);
@@ -252,7 +261,7 @@ namespace UnityExplorer.Config
Left_1 = new("Left 1",
"Move the freecam to the left.",
KeyCode.A);
Left_2 = new("Left 2",
"Move the freecam to the left, alt key.",
KeyCode.LeftArrow);
@@ -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.ForcedMatrix);
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 Freecam Camera Target Selection is enabled.",
"\\");
}
}
}
+25 -1
View File
@@ -131,7 +131,7 @@ namespace UnityExplorer
if (IInputManager.GetKeyDown(ConfigManager.Freecam_Toggle.Value))
{
FreeCamPanel.StartStopButton_OnClick();
FreeCamPanel.ToggleFreecam();
}
if (IInputManager.GetKeyDown(ConfigManager.Block_Freecam_Movement.Value))
@@ -148,6 +148,30 @@ namespace UnityExplorer
{
FreeCamPanel.blockGamesInputOnFreecamToggle.isOn = !FreeCamPanel.blockGamesInputOnFreecamToggle.isOn;
}
// Gamepad controls
bool xButtonPressed = IGamepadInputInterceptor.IsButtonPressed("/gamepad/buttonwest");
bool leftStickButton = IGamepadInputInterceptor.IsButtonPressed("/gamepad/leftstickpress");
bool rightStickButton = IGamepadInputInterceptor.IsButtonPressed("/gamepad/rightstickpress");
// L3 + X: Toggle time scale override
if (xButtonPressed && leftStickButton)
{
if (IGamepadInputInterceptor.WasButtonPressedThisFrame("/gamepad/buttonwest") ||
IGamepadInputInterceptor.WasButtonPressedThisFrame("/gamepad/leftstickpress"))
{
UIManager.GetTimeScaleWidget().ToggleOverride();
}
}
// R3 + X: Toggle freecam
else if (xButtonPressed && rightStickButton)
{
if (IGamepadInputInterceptor.WasButtonPressedThisFrame("/gamepad/buttonwest") ||
IGamepadInputInterceptor.WasButtonPressedThisFrame("/gamepad/rightstickpress"))
{
FreeCamPanel.ToggleFreecam();
}
}
}
void stopFrameSkip(){
+1 -1
View File
@@ -20,7 +20,7 @@ namespace UnityExplorer
public static class ExplorerCore
{
public const string NAME = "CinematicUnityExplorer";
public const string VERSION = "1.3.0";
public const string VERSION = "1.4.0";
public const string AUTHOR = "originalnicodr, Sinai, yukieiji";
public const string GUID = "com.originalnicodr.cinematicunityexplorer";
+3
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.
+45 -7
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);
@@ -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()
@@ -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)}");
}
}
@@ -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 Freecam_Camera_Target_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.Freecam_Camera_Target_Selection.Value = this.Freecam_Camera_Target_Selection;
ConfigManager.Pause.Value = this.Pause;
ConfigManager.Frameskip.Value = this.Frameskip;
+77 -13
View File
@@ -43,6 +43,7 @@ namespace UnityExplorer.UI.Panels
public List<CatmullRom.CatmullRomPoint> controlPoints = new List<CatmullRom.CatmullRomPoint>();
bool closedLoop;
float time = 10;
public int currentSelectedNodeIndex = -1;
public GameObject followObject;
@@ -99,6 +100,8 @@ namespace UnityExplorer.UI.Panels
cell.point = point;
cell.index = index;
cell.indexLabel.text = $"{index}";
cell.indexLabel.color = (index == currentSelectedNodeIndex) ? Color.green : Color.white;
}
// ~~~~~~~~ UI construction / callbacks ~~~~~~~~
@@ -122,10 +125,10 @@ namespace UnityExplorer.UI.Panels
stopButton.ButtonText.fontSize = 20;
stopButton.OnClick += Stop_OnClick;
ButtonRef AddNode = UIFactory.CreateButton(horiGroup, "AddCamNode", "+");
UIFactory.SetLayoutElement(AddNode.GameObject, minWidth: 50, minHeight: 25);
AddNode.ButtonText.fontSize = 20;
AddNode.OnClick += AddNode_OnClick;
ButtonRef AddNodeButton = UIFactory.CreateButton(horiGroup, "AddCamNode", "+");
UIFactory.SetLayoutElement(AddNodeButton.GameObject, minWidth: 50, minHeight: 25);
AddNodeButton.ButtonText.fontSize = 20;
AddNodeButton.OnClick += AddNode;
ButtonRef DeletePath = UIFactory.CreateButton(horiGroup, "DeletePath", "Clear");
UIFactory.SetLayoutElement(DeletePath.GameObject, minWidth: 70, minHeight: 25);
@@ -337,20 +340,22 @@ namespace UnityExplorer.UI.Panels
void StartButton_OnClick()
{
StartPath();
}
public void StartPath(){
if (waitBeforePlay) {
if (startTimer.Enabled){
startTimer.Stop();
}
startTimer.Enabled = true;
} else {
StartPath();
return;
}
EventSystemHelper.SetSelectedGameObject(null);
}
void StartPath(){
if (GetCameraPathsManager()){
UpdateCatmullRomMoverData();
GetCameraPathsManager().StartPath();
@@ -363,7 +368,7 @@ namespace UnityExplorer.UI.Panels
}
}
void TogglePause_OnClick(){
public void TogglePause(){
if(GetCameraPathsManager()){
GetCameraPathsManager().TogglePause();
if (visualizePathToggle.isOn && GetCameraPathsManager().IsPaused()) visualizePathToggle.isOn = false;
@@ -372,7 +377,11 @@ namespace UnityExplorer.UI.Panels
EventSystemHelper.SetSelectedGameObject(null);
}
void Stop_OnClick(){
void TogglePause_OnClick(){
TogglePause();
}
public void StopPath(){
if (GetCameraPathsManager()){
GetCameraPathsManager().Stop();
}
@@ -380,7 +389,11 @@ namespace UnityExplorer.UI.Panels
EventSystemHelper.SetSelectedGameObject(null);
}
void AddNode_OnClick(){
void Stop_OnClick(){
StopPath();
}
public void AddNode(){
EventSystemHelper.SetSelectedGameObject(null);
CatmullRom.CatmullRomPoint point = new CatmullRom.CatmullRomPoint(
@@ -390,6 +403,7 @@ namespace UnityExplorer.UI.Panels
);
controlPoints.Add(point);
currentSelectedNodeIndex = controlPoints.Count - 1;
nodesScrollPool.Refresh(true, false);
MaybeRedrawPath();
}
@@ -528,6 +542,56 @@ namespace UnityExplorer.UI.Panels
File.WriteAllText($"{camPathsFolderPath}\\{filename}.cuepath", serializedData);
}
void NavigateToNode(int nodeIndex)
{
if (nodeIndex < 0 || nodeIndex >= controlPoints.Count)
return;
currentSelectedNodeIndex = nodeIndex;
CatmullRom.CatmullRomPoint point = controlPoints[nodeIndex];
FreeCamPanel.SetCameraPosition(point.position);
FreeCamPanel.SetCameraRotation(point.rotation);
FreeCamPanel.SetFOV(point.fov);
nodesScrollPool.Refresh(true, false);
}
public void NavigateToNextNode()
{
if (controlPoints.Count == 0)
return;
int nextIndex = (currentSelectedNodeIndex + 1) % controlPoints.Count;
NavigateToNode(nextIndex);
}
public void NavigateToPreviousNode()
{
if (controlPoints.Count == 0)
return;
int prevIndex = currentSelectedNodeIndex - 1;
if (prevIndex < 0)
prevIndex = controlPoints.Count - 1;
NavigateToNode(prevIndex);
}
public void OnNodeDeleted(int deletedIndex)
{
// If the current selected index is out of bounds, adjust it
if (currentSelectedNodeIndex >= controlPoints.Count)
{
currentSelectedNodeIndex = controlPoints.Count - 1;
}
// If we deleted the selected node or a node before it, adjust the index
else if (deletedIndex <= currentSelectedNodeIndex && currentSelectedNodeIndex > 0)
{
currentSelectedNodeIndex--;
}
if (controlPoints.Count == 0)
currentSelectedNodeIndex = -1;
}
private void LoadPath(){
string filename = saveLoadInputField.Component.text;
if (filename.EndsWith(".cuepath") || filename.EndsWith(".CUEPATH")) filename = filename.Substring(filename.Length-7);
@@ -563,12 +627,12 @@ namespace UnityExplorer.UI.Panels
// We enable the freecam so we can use it to spawn the camera path relative to it
if (!FreeCamPanel.inFreeCamMode) {
FreeCamPanel.StartStopButton_OnClick();
FreeCamPanel.ToggleFreecam();
}
controlPoints.Clear();
// The first point will be the camera position, and we adapt the following points from there
AddNode_OnClick();
AddNode();
CatmullRom.CatmullRomPoint startingPoint = controlPoints.ElementAt(0);
CatmullRom.CatmullRomPoint originalStartingPoint = deserializedObj.points.ElementAt(0);
File diff suppressed because it is too large Load Diff
+2 -1
View File
@@ -122,7 +122,8 @@ namespace UnityExplorer.CatmullRom
public void StartPath(){
if(splinePoints == null || splinePoints.Length <= 2)
{
ExplorerCore.Log(splinePoints);
int nodeCount = splinePoints?.Length ?? 0;
ExplorerCore.LogWarning($"Cannot start camera path: Need at least 3 nodes, but only {nodeCount} node(s) exist. Please add more nodes to create a path.");
throw new ArgumentException("Catmull Rom Error: Too few spline points!");
}
else {
+21
View File
@@ -68,6 +68,27 @@ namespace UnityExplorer.UI.Widgets
settingTimeScale = false;
}
public void IncreaseTimeScale()
{
float newValue = Mathf.Min(desiredTime + 0.1f, slider.maxValue);
slider.value = newValue;
if (overrideTimeScaleToggle.isOn)
SetTimeScale(newValue);
}
public void DecreaseTimeScale()
{
float newValue = Mathf.Max(desiredTime - 0.1f, slider.minValue);
slider.value = newValue;
if (overrideTimeScaleToggle.isOn)
SetTimeScale(newValue);
}
public void ToggleOverride()
{
overrideTimeScaleToggle.isOn = !overrideTimeScaleToggle.isOn;
}
// UI event listeners
void OnTimeInputEndEdit(string val)