mirror of
https://github.com/originalnicodr/CinematicUnityExplorer.git
synced 2026-10-10 13:03:15 +08:00
Compare commits
76 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 361f10704e | |||
| e6bcbcd0cc | |||
| 1b93dd67f8 | |||
| 733887ea50 | |||
| d042771896 | |||
| df5a1542ba | |||
| 27851a88ea | |||
| 5c556d99c5 | |||
| 91e5bcb38c | |||
| a08a299a8f | |||
| 32bf1a6246 | |||
| e3584654a6 | |||
| 388c9fbc29 | |||
| 2f35b9d2c7 | |||
| b82cc62186 | |||
| 9429b84cfa | |||
| 2719c69753 | |||
| 9144f89e32 | |||
| 3bc1268444 | |||
| 4c100daf83 | |||
| e7d591176e | |||
| ca46607c49 | |||
| 7fb67b4e28 | |||
| c0f42ad983 | |||
| 6111c1743b | |||
| b03d81052b | |||
| 173eeb993a | |||
| 07971489c4 | |||
| 2604dd8286 | |||
| ca81bcc5dc | |||
| b95178775c | |||
| 3928cdc4fe | |||
| 81d15270fe | |||
| 2b557a8c23 | |||
| 462201534d | |||
| f3b154658c | |||
| 7ab907d7b0 | |||
| 736ebdfa1b | |||
| c9aa93a6a4 | |||
| 78bf2fc634 | |||
| 6c493aaf7d | |||
| b6d459b664 | |||
| 7a580d12ed | |||
| 8f2ec19660 | |||
| f855f6d430 | |||
| a6446c50e4 | |||
| f3b48bf069 | |||
| 396e653495 | |||
| 68c482df22 | |||
| a868cc74ac | |||
| 343ccb9474 | |||
| 48fec535fd | |||
| 5e9abd33b4 | |||
| 6ebf19dfa0 | |||
| cc34a50e21 | |||
| 954b6f4f1c | |||
| b785f76ba3 | |||
| d2d22de66c | |||
| af2ad057fb | |||
| ca726e0ae7 | |||
| a02460be33 | |||
| 30b9e6e387 | |||
| 58f477ed0d | |||
| fd672615be | |||
| f8886700d9 | |||
| dae6930b43 | |||
| d9ebe5819c | |||
| 171ee3f0d8 | |||
| 448ab66f5b | |||
| b090f68732 | |||
| 13dcef668f | |||
| 1d5a7b8885 | |||
| 54f20fde95 | |||
| 66773b300f | |||
| 271261307d | |||
| f557d3de88 |
@@ -8,7 +8,7 @@ on:
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
build:
|
||||
build_dotnet:
|
||||
runs-on: windows-latest
|
||||
if: "!contains(github.event.head_commit.message, '-noci')"
|
||||
|
||||
@@ -29,79 +29,111 @@ 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/
|
||||
|
||||
|
||||
build_connector:
|
||||
runs-on: windows-latest
|
||||
if: "!contains(github.event.head_commit.message, '-noci')"
|
||||
|
||||
steps:
|
||||
- name: Checkout latest
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
submodules: true
|
||||
|
||||
- name: Setup C/C++
|
||||
uses: TheMrMilchmann/setup-msvc-dev@v3.0.0
|
||||
with:
|
||||
arch: x64
|
||||
|
||||
# Run build script
|
||||
- name: Build Unity IGCS Connector
|
||||
run: ./build_connector.ps1
|
||||
|
||||
- name: Upload Unity IGCS Connector
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: UnityIGCSConnector.dll
|
||||
path: ./Release/UnityIGCSConnector.dll
|
||||
|
||||
- name: Upload Unity IGCS Connector 32bit
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: UnityIGCSConnector.32.dll
|
||||
path: ./Release/UnityIGCSConnector.32.dll
|
||||
|
||||
@@ -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 [](../../releases)
|
||||
|
||||
[](../../releases/latest) [](https://github.com/originalnicodr/CinematicUnityExplorer/actions) [](../../releases/latest)
|
||||
[](../../releases/latest) [](https://github.com/originalnicodr/CinematicUnityExplorer/actions) [](../../releases/latest) [](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
|
||||
|
||||
@@ -136,6 +150,7 @@ The original Unity Explorer had a Freecam feature, but even if it was useful at
|
||||
- Added tilt support.
|
||||
- Added FOV control support.
|
||||
- Added near and far clip sliders on the Freecam panel, to cut out objects near the camera out of view, or include more scene objects on sight.
|
||||
- Added new freecam methods to increase compatibility across games.
|
||||
- Automatically disable the Cinemachine component if present when using the gameplay camera, to avoid the game from trying to take control.
|
||||
- Added a toggle to block the freecam from moving with user input.
|
||||
- Unlocked the mouse on freecam even when the mod UI is hidden.
|
||||
@@ -147,8 +162,12 @@ You can click on the "Follow object" button on the panel and select the object y
|
||||
|
||||
By default the camera only follows the object's position, but you can also make it follow its rotation as if the camera was physically bound to the object by checking the "Follow Object Rotation" toggle. Should be useful for mimicking a car camera, a character POV, or creating motion blur.
|
||||
|
||||
### 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!
|
||||
### Look At Object
|
||||
|
||||
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 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.
|
||||
@@ -188,6 +207,7 @@ It allows you to create nodes to build camera paths for videos and cinematics. F
|
||||
- Wait 3 seconds before start toggler.
|
||||
- Move nodes up or down on the list.
|
||||
- Control over the tension and alpha values of the path, which are curve constants that change the resulting path created from the nodes. Their effect can be clearly seen when visualizing the path, as explained below.
|
||||
- Saving and loading camera paths to reuse them in different sessions.
|
||||
|
||||
As a side note, the mod UI will be disabled once the path starts, to ease video recording.
|
||||
|
||||
@@ -204,10 +224,16 @@ Allows you to manually play characters and NPC animations in a scene. This shoul
|
||||
|
||||
Favorite animations so they appear first on the dropdown list by clicking on the star button with the animation selected.
|
||||
|
||||
The Animator Panel also allows you to freeze all characters in a scene all at once, alongside giving you control over which characters should ignore the master toggler. That way you can make the playable character avoid getting frozen, or avoid un-freezing NPCs or enemies that already have the animations you want them to have.
|
||||
The Animator Panel also allows you to freeze all characters in a scene all at once, alongside giving you control over which characters should ignore the master toggler. Each animator also comes with a toggle to be able to hide a character meshes from a scene.
|
||||
|
||||
Alongside all of this, you can also open each character game object by clicking on their names, so you can move, rotate, scale them around, disable them, or further edit their properties and child objects.
|
||||
|
||||
### Bones Panel
|
||||
|
||||
For each animator, you can also spawn a Bones Panel. This panel will list all of the character's bones and meshes, and provide easy-to-access toggles to disable them and sliders to move them around, allowing you to pose a character to your liking.
|
||||
|
||||
You can also save and load poses across different characters (if they have the same skeleton) and sessions.
|
||||
|
||||
## Misc Panel
|
||||
- HUD toggle.
|
||||
- Force high LODs toggle. This means that the highest models possible will be forced on all meshes.
|
||||
@@ -227,6 +253,29 @@ Alongside all of this, you can also open each character game object by clicking
|
||||
- Added assignable hotkeys on the Options panel, all of which are displayed on the freecam panel.
|
||||
- Made the mod UI scale with higher resolutions.
|
||||
|
||||
# IGCSDOF Support
|
||||
|
||||
The mod also supports [IGCSConnector](https://github.com/FransBouma/IgcsConnector/releases), and therefore [IGCSDOF](https://opm.fransbouma.com/igcsdof.htm), the best modded DOF available. This is an accumulated DOF solution similar to Forza Horizon's or other offline rendering software. This accumulated solution brings some advantages compared to real-time solutions, such as:
|
||||
- Accurate near-plane bleed.
|
||||
- Particle & alpha effects in DOF.
|
||||
- Depth-accurate reflections.
|
||||
- Layered defocusing of transparent and translucent materials.
|
||||
|
||||

|
||||
|
||||
[Before and after](https://framedsc.com/ReshadeGuides/Addons/MSADOF.htm#advantages) showing the advantages of accumulated DOFs over real-time DOFs.
|
||||
|
||||
You can download it, read how to install it, and how to use it [here](https://opm.fransbouma.com/igcsdof.htm).
|
||||
|
||||
>[!IMPORTANT]
|
||||
> To be able to use IGCSDOF, besides following the instructions above, make sure to download [UnityIGCSConnector.dll](https://github.com/originalnicodr/CinematicUnityExplorer/releases/latest/download/UnityIGCSConnector.dll) and put it in the same folder as the games .exe.
|
||||
> This is a necessary middleware so the Reshade add-on can communicate with the mod.
|
||||
|
||||
>[!IMPORTANT]
|
||||
> Be sure to select `Classic (slower)` mode instead of `Fast`, as the latter one seems to render the image out of focus. However `Fast` might still work in some games.
|
||||
|
||||
Huge shout out to [etra0](https://github.com/etra0) for implementing this!
|
||||
|
||||
# Why a fork instead of making a new mod?
|
||||
|
||||
It wasn't the original intention to develop this fork to its current state. I just wanted to make it easier for me and my friends to take screenshots of Unity games and it rolled from there. It is in fact true that me modifying an already existing generic mod streamlined things instead of doing everything from zero, but it's also true that some of UnityExplorer vanilla functionality is useful for doing marketing-related stuff as well (as well as allowing some features to work by letting the user get their hands dirty, e.g. unlocking the gameplay freecam).
|
||||
|
||||
Binary file not shown.
Binary file not shown.
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "com.originalnicodr.cinematicunityexplorer",
|
||||
"version": "1.0.0",
|
||||
"version": "1.4.0",
|
||||
"displayName": "CinematicUnityExplorer",
|
||||
"description": "UnityExplorer fork focused on providing tools for creating marketing material for Unity games.",
|
||||
"unity": "2017.1",
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
|
||||
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||
# Visual Studio Version 17
|
||||
VisualStudioVersion = 17.9.34723.18
|
||||
MinimumVisualStudioVersion = 10.0.40219.1
|
||||
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "UnityIGCSConnector", "UnityIGCSConnector.vcxproj", "{676E7EC4-E40E-4D59-9587-2E2CD04BC562}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|x64 = Debug|x64
|
||||
Debug|x86 = Debug|x86
|
||||
Release|x64 = Release|x64
|
||||
Release|x86 = Release|x86
|
||||
EndGlobalSection
|
||||
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||
{676E7EC4-E40E-4D59-9587-2E2CD04BC562}.Debug|x64.ActiveCfg = Debug|x64
|
||||
{676E7EC4-E40E-4D59-9587-2E2CD04BC562}.Debug|x64.Build.0 = Debug|x64
|
||||
{676E7EC4-E40E-4D59-9587-2E2CD04BC562}.Debug|x86.ActiveCfg = Debug|Win32
|
||||
{676E7EC4-E40E-4D59-9587-2E2CD04BC562}.Debug|x86.Build.0 = Debug|Win32
|
||||
{676E7EC4-E40E-4D59-9587-2E2CD04BC562}.Release|x64.ActiveCfg = Release|x64
|
||||
{676E7EC4-E40E-4D59-9587-2E2CD04BC562}.Release|x64.Build.0 = Release|x64
|
||||
{676E7EC4-E40E-4D59-9587-2E2CD04BC562}.Release|x86.ActiveCfg = Release|Win32
|
||||
{676E7EC4-E40E-4D59-9587-2E2CD04BC562}.Release|x86.Build.0 = Release|Win32
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
EndGlobalSection
|
||||
GlobalSection(ExtensibilityGlobals) = postSolution
|
||||
SolutionGuid = {CCC0598B-A93B-4F40-B29D-43164253230A}
|
||||
EndGlobalSection
|
||||
EndGlobal
|
||||
@@ -0,0 +1,173 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<ItemGroup Label="ProjectConfigurations">
|
||||
<ProjectConfiguration Include="Debug|Win32">
|
||||
<Configuration>Debug</Configuration>
|
||||
<Platform>Win32</Platform>
|
||||
</ProjectConfiguration>
|
||||
<ProjectConfiguration Include="Release|Win32">
|
||||
<Configuration>Release</Configuration>
|
||||
<Platform>Win32</Platform>
|
||||
</ProjectConfiguration>
|
||||
<ProjectConfiguration Include="Debug|x64">
|
||||
<Configuration>Debug</Configuration>
|
||||
<Platform>x64</Platform>
|
||||
</ProjectConfiguration>
|
||||
<ProjectConfiguration Include="Release|x64">
|
||||
<Configuration>Release</Configuration>
|
||||
<Platform>x64</Platform>
|
||||
</ProjectConfiguration>
|
||||
</ItemGroup>
|
||||
<PropertyGroup Label="Globals">
|
||||
<VCProjectVersion>17.0</VCProjectVersion>
|
||||
<Keyword>Win32Proj</Keyword>
|
||||
<ProjectGuid>{676e7ec4-e40e-4d59-9587-2e2cd04bc562}</ProjectGuid>
|
||||
<RootNamespace>UnityIGCSConnector</RootNamespace>
|
||||
<WindowsTargetPlatformVersion>10.0</WindowsTargetPlatformVersion>
|
||||
</PropertyGroup>
|
||||
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'" Label="Configuration">
|
||||
<ConfigurationType>DynamicLibrary</ConfigurationType>
|
||||
<UseDebugLibraries>true</UseDebugLibraries>
|
||||
<PlatformToolset>v143</PlatformToolset>
|
||||
<CharacterSet>Unicode</CharacterSet>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'" Label="Configuration">
|
||||
<ConfigurationType>DynamicLibrary</ConfigurationType>
|
||||
<UseDebugLibraries>false</UseDebugLibraries>
|
||||
<PlatformToolset>v143</PlatformToolset>
|
||||
<WholeProgramOptimization>true</WholeProgramOptimization>
|
||||
<CharacterSet>Unicode</CharacterSet>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'" Label="Configuration">
|
||||
<ConfigurationType>DynamicLibrary</ConfigurationType>
|
||||
<UseDebugLibraries>true</UseDebugLibraries>
|
||||
<PlatformToolset>v143</PlatformToolset>
|
||||
<CharacterSet>Unicode</CharacterSet>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'" Label="Configuration">
|
||||
<ConfigurationType>DynamicLibrary</ConfigurationType>
|
||||
<UseDebugLibraries>false</UseDebugLibraries>
|
||||
<PlatformToolset>v143</PlatformToolset>
|
||||
<WholeProgramOptimization>true</WholeProgramOptimization>
|
||||
<CharacterSet>Unicode</CharacterSet>
|
||||
</PropertyGroup>
|
||||
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
|
||||
<ImportGroup Label="ExtensionSettings">
|
||||
</ImportGroup>
|
||||
<ImportGroup Label="Shared">
|
||||
</ImportGroup>
|
||||
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
|
||||
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
|
||||
</ImportGroup>
|
||||
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
|
||||
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
|
||||
</ImportGroup>
|
||||
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
|
||||
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
|
||||
</ImportGroup>
|
||||
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
|
||||
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
|
||||
</ImportGroup>
|
||||
<PropertyGroup Label="UserMacros" />
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
|
||||
<TargetName>UnityIGCSConnector</TargetName>
|
||||
<OutDir>$(SolutionDir)..\$(Configuration)\</OutDir>
|
||||
<PostBuildEventUseInBuild>false</PostBuildEventUseInBuild>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
|
||||
<TargetName>UnityIGCSConnector.32</TargetName>
|
||||
<OutDir>$(SolutionDir)..\$(Configuration)\</OutDir>
|
||||
<PostBuildEventUseInBuild>false</PostBuildEventUseInBuild>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
|
||||
<TargetName>UnityIGCSConnector</TargetName>
|
||||
<OutDir>$(SolutionDir)..\$(Configuration)\</OutDir>
|
||||
<PostBuildEventUseInBuild>false</PostBuildEventUseInBuild>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
|
||||
<TargetName>UnityIGCSConnector.32</TargetName>
|
||||
<OutDir>$(SolutionDir)..\$(Configuration)\</OutDir>
|
||||
<PostBuildEventUseInBuild>false</PostBuildEventUseInBuild>
|
||||
</PropertyGroup>
|
||||
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
|
||||
<ClCompile>
|
||||
<WarningLevel>Level3</WarningLevel>
|
||||
<SDLCheck>true</SDLCheck>
|
||||
<PreprocessorDefinitions>WIN32;_DEBUG;UNITYIGCSCONNECTOR_EXPORTS;_WINDOWS;_USRDLL;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
<ConformanceMode>true</ConformanceMode>
|
||||
<PrecompiledHeader>NotUsing</PrecompiledHeader>
|
||||
<PrecompiledHeaderFile>pch.h</PrecompiledHeaderFile>
|
||||
</ClCompile>
|
||||
<Link>
|
||||
<SubSystem>Windows</SubSystem>
|
||||
<GenerateDebugInformation>true</GenerateDebugInformation>
|
||||
<EnableUAC>false</EnableUAC>
|
||||
</Link>
|
||||
</ItemDefinitionGroup>
|
||||
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
|
||||
<ClCompile>
|
||||
<WarningLevel>Level3</WarningLevel>
|
||||
<FunctionLevelLinking>true</FunctionLevelLinking>
|
||||
<IntrinsicFunctions>true</IntrinsicFunctions>
|
||||
<SDLCheck>true</SDLCheck>
|
||||
<PreprocessorDefinitions>WIN32;NDEBUG;UNITYIGCSCONNECTOR_EXPORTS;_WINDOWS;_USRDLL;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
<ConformanceMode>true</ConformanceMode>
|
||||
<PrecompiledHeader>NotUsing</PrecompiledHeader>
|
||||
<PrecompiledHeaderFile>pch.h</PrecompiledHeaderFile>
|
||||
</ClCompile>
|
||||
<Link>
|
||||
<SubSystem>Windows</SubSystem>
|
||||
<EnableCOMDATFolding>true</EnableCOMDATFolding>
|
||||
<OptimizeReferences>true</OptimizeReferences>
|
||||
<GenerateDebugInformation>true</GenerateDebugInformation>
|
||||
<EnableUAC>false</EnableUAC>
|
||||
</Link>
|
||||
</ItemDefinitionGroup>
|
||||
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
|
||||
<ClCompile>
|
||||
<WarningLevel>Level3</WarningLevel>
|
||||
<SDLCheck>true</SDLCheck>
|
||||
<PreprocessorDefinitions>_DEBUG;UNITYIGCSCONNECTOR_EXPORTS;_WINDOWS;_USRDLL;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
<ConformanceMode>true</ConformanceMode>
|
||||
<PrecompiledHeader>NotUsing</PrecompiledHeader>
|
||||
<PrecompiledHeaderFile>pch.h</PrecompiledHeaderFile>
|
||||
</ClCompile>
|
||||
<Link>
|
||||
<SubSystem>Windows</SubSystem>
|
||||
<GenerateDebugInformation>true</GenerateDebugInformation>
|
||||
<EnableUAC>false</EnableUAC>
|
||||
</Link>
|
||||
</ItemDefinitionGroup>
|
||||
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
|
||||
<ClCompile>
|
||||
<WarningLevel>Level3</WarningLevel>
|
||||
<FunctionLevelLinking>true</FunctionLevelLinking>
|
||||
<IntrinsicFunctions>true</IntrinsicFunctions>
|
||||
<SDLCheck>true</SDLCheck>
|
||||
<PreprocessorDefinitions>NDEBUG;UNITYIGCSCONNECTOR_EXPORTS;_WINDOWS;_USRDLL;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
<ConformanceMode>true</ConformanceMode>
|
||||
<PrecompiledHeader>NotUsing</PrecompiledHeader>
|
||||
<PrecompiledHeaderFile>pch.h</PrecompiledHeaderFile>
|
||||
<LanguageStandard_C>stdc11</LanguageStandard_C>
|
||||
</ClCompile>
|
||||
<Link>
|
||||
<SubSystem>Windows</SubSystem>
|
||||
<EnableCOMDATFolding>true</EnableCOMDATFolding>
|
||||
<OptimizeReferences>true</OptimizeReferences>
|
||||
<GenerateDebugInformation>true</GenerateDebugInformation>
|
||||
<EnableUAC>false</EnableUAC>
|
||||
</Link>
|
||||
<PostBuildEvent>
|
||||
<Command>xcopy /y /d "$(SolutionDir)$(Platform)\$(Configuration)\UnityIGCSConnector.dll" "$(SolutionDir)..\$(Configuration)\UnityIGCSConnector.dll"</Command>
|
||||
</PostBuildEvent>
|
||||
</ItemDefinitionGroup>
|
||||
<ItemGroup>
|
||||
<ClCompile Include="dllmain.c">
|
||||
<DeploymentContent Condition="'$(Configuration)|$(Platform)'=='Release|x64'">true</DeploymentContent>
|
||||
</ClCompile>
|
||||
</ItemGroup>
|
||||
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
|
||||
<ImportGroup Label="ExtensionTargets">
|
||||
</ImportGroup>
|
||||
</Project>
|
||||
@@ -0,0 +1,22 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<ItemGroup>
|
||||
<Filter Include="Source Files">
|
||||
<UniqueIdentifier>{4FC737F1-C7A5-4376-A066-2A32D752A2FF}</UniqueIdentifier>
|
||||
<Extensions>cpp;c;cc;cxx;c++;cppm;ixx;def;odl;idl;hpj;bat;asm;asmx</Extensions>
|
||||
</Filter>
|
||||
<Filter Include="Header Files">
|
||||
<UniqueIdentifier>{93995380-89BD-4b04-88EB-625FBE52EBFB}</UniqueIdentifier>
|
||||
<Extensions>h;hh;hpp;hxx;h++;hm;inl;inc;ipp;xsd</Extensions>
|
||||
</Filter>
|
||||
<Filter Include="Resource Files">
|
||||
<UniqueIdentifier>{67DA6AB6-F800-4c08-8B7A-83BB121AAD01}</UniqueIdentifier>
|
||||
<Extensions>rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms</Extensions>
|
||||
</Filter>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClCompile Include="dllmain.c">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
@@ -0,0 +1,127 @@
|
||||
// dllmain.cpp : Defines the entry point for the DLL application.
|
||||
#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 int (*SessionCallback)(void);
|
||||
|
||||
typedef uint8_t* (*GetCameraDataFunc)(void);
|
||||
|
||||
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 SessionStatus __cdecl IGCS_StartScreenshotSession(uint8_t _ignore) {
|
||||
if (GlobalStartSession) {
|
||||
printf("Called StartSession\n");
|
||||
return (SessionStatus)GlobalStartSession();
|
||||
}
|
||||
return SESSION_STATUS_CAMERA_FEAT_NOT_AVAILABLE;
|
||||
}
|
||||
|
||||
EXPOSE void __cdecl IGCS_EndScreenshotSession() {
|
||||
GlobalEndSession();
|
||||
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");
|
||||
GlobalCallback = cb;
|
||||
GlobalStartSession = start_cb;
|
||||
GlobalEndSession = end_cb;
|
||||
|
||||
if (first_initialization) {
|
||||
|
||||
if (connect_all_tools()) {
|
||||
printf("Error connecting cameras\n");
|
||||
return 0;
|
||||
}
|
||||
first_initialization = 0;
|
||||
}
|
||||
|
||||
printf("Camera connected!\n");
|
||||
|
||||
return &U_IGCS_CameraData[0];
|
||||
|
||||
}
|
||||
|
||||
EXPOSE void __cdecl IGCS_MoveCameraPanorama() {}
|
||||
|
||||
EXPOSE void __cdecl IGCS_MoveCameraMultishot(float step_left, float step_up, float fov, int from_start) {
|
||||
GlobalCallback(step_left, step_up, fov, from_start);
|
||||
return;
|
||||
}
|
||||
|
||||
BOOL WINAPI DllMain(HMODULE hModule,
|
||||
DWORD ul_reason_for_call,
|
||||
LPVOID lpReserved
|
||||
)
|
||||
{
|
||||
switch (ul_reason_for_call)
|
||||
{
|
||||
case DLL_PROCESS_ATTACH:
|
||||
case DLL_THREAD_ATTACH:
|
||||
case DLL_THREAD_DETACH:
|
||||
case DLL_PROCESS_DETACH:
|
||||
break;
|
||||
}
|
||||
return TRUE;
|
||||
}
|
||||
+1
-1
Submodule UniverseLib updated: 3dc1aba123...7bf319557c
@@ -2,7 +2,6 @@ cd UniverseLib
|
||||
.\build.ps1
|
||||
cd ..
|
||||
|
||||
|
||||
# ----------- MelonLoader IL2CPP (net6) -----------
|
||||
dotnet build src/CinematicUnityExplorer.sln -c Release_ML_Cpp_net6preview
|
||||
$Path = "Release\CinematicUnityExplorer.MelonLoader.IL2CPP.net6preview"
|
||||
@@ -215,4 +214,4 @@ $Path2 = "UnityEditorPackage/Runtime"
|
||||
Copy-Item $Path1/CinematicUnityExplorer.STANDALONE.Mono.dll -Destination $Path2
|
||||
Copy-Item $Path1/UniverseLib.Mono.dll -Destination $Path2
|
||||
Remove-Item Release/CinematicUnityExplorer.Editor.zip -ErrorAction SilentlyContinue
|
||||
compress-archive .\UnityEditorPackage\* Release/CinematicUnityExplorer.Editor.zip
|
||||
compress-archive .\UnityEditorPackage\* Release/CinematicUnityExplorer.Editor.zip
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
# ----------- Build UnityIGCSConnector ------------
|
||||
msbuild.exe UnityIGCSConnector/UnityIGCSConnector.sln -p:Configuration=Release -p:Platform=x64
|
||||
|
||||
msbuild.exe UnityIGCSConnector/UnityIGCSConnector.sln -p:Configuration=Release -p:Platform=x86
|
||||
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 4.1 MiB After Width: | Height: | Size: 42 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 4.1 MiB |
Binary file not shown.
|
After Width: | Height: | Size: 8.7 MiB |
@@ -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
|
||||
|
||||
@@ -20,14 +20,17 @@ namespace UnityExplorer.UI.Panels
|
||||
public AnimatorPlayer animatorPlayer;
|
||||
|
||||
public Toggle IgnoreMasterToggle;
|
||||
public Toggle AnimatorToggle;
|
||||
public AnimatorPausePlayButton animatorToggler;
|
||||
public Toggle MeshToggle;
|
||||
public ButtonRef inspectButton;
|
||||
ButtonRef playButton;
|
||||
public Dropdown animatorDropdown;
|
||||
ButtonRef favAnimation;
|
||||
Slider animationTimeline;
|
||||
ButtonRef playButton;
|
||||
ButtonRef openBonesPanelButton;
|
||||
|
||||
// ICell
|
||||
public float DefaultHeight => 25f;
|
||||
public float DefaultHeight => 30f;
|
||||
public GameObject UIRoot { get; set; }
|
||||
public RectTransform Rect { get; set; }
|
||||
|
||||
@@ -39,7 +42,8 @@ namespace UnityExplorer.UI.Panels
|
||||
this.animatorPlayer = animatorPlayer;
|
||||
inspectButton.ButtonText.text = animatorPlayer.animator.name;
|
||||
IgnoreMasterToggle.isOn = animatorPlayer.shouldIgnoreMasterToggle;
|
||||
AnimatorToggle.isOn = animatorPlayer.animator.speed != 0;
|
||||
animatorToggler.isOn = animatorPlayer.animator.speed != 0;
|
||||
MeshToggle.isOn = animatorPlayer.IsMeshHidden();
|
||||
|
||||
UpdateDropdownOptions();
|
||||
}
|
||||
@@ -64,7 +68,7 @@ namespace UnityExplorer.UI.Panels
|
||||
}
|
||||
|
||||
private void PlayButton_OnClick(){
|
||||
animatorPlayer.PlayOverridingAnimation();
|
||||
animatorPlayer.PlayOverridingAnimation(0);
|
||||
// Needed for the new animation to play for some reason
|
||||
EnableAnimation(false);
|
||||
EnableAnimation(true);
|
||||
@@ -72,31 +76,34 @@ namespace UnityExplorer.UI.Panels
|
||||
|
||||
public virtual GameObject CreateContent(GameObject parent)
|
||||
{
|
||||
GameObject AnimatorToggleObj = UIFactory.CreateToggle(parent, $"AnimatorToggle", out AnimatorToggle, out Text animatorToggleText);
|
||||
UIFactory.SetLayoutElement(AnimatorToggleObj, minHeight: 25);
|
||||
AnimatorToggle.isOn = animatorPlayer != null && animatorPlayer.animator.speed == 1;
|
||||
AnimatorToggle.onValueChanged.AddListener(EnableAnimation);
|
||||
UIRoot = UIFactory.CreateUIObject("AnimatorCell", parent);
|
||||
UIFactory.SetLayoutGroup<HorizontalLayoutGroup>(UIRoot, false, false, true, true, 4, childAlignment: TextAnchor.MiddleLeft);
|
||||
UIFactory.SetLayoutElement(UIRoot, minHeight: 25, flexibleWidth: 9999);
|
||||
|
||||
UIRoot = AnimatorToggleObj;
|
||||
UIRoot.SetActive(false);
|
||||
GameObject MeshToggleObj = UIFactory.CreateToggle(UIRoot, "MeshToggle", out MeshToggle, out Text MeshToggleText);
|
||||
UIFactory.SetLayoutElement(MeshToggleObj, minHeight: 30);
|
||||
MeshToggle.onValueChanged.AddListener(EnableMesh);
|
||||
|
||||
Rect = UIRoot.GetComponent<RectTransform>();
|
||||
Rect.anchorMin = new Vector2(0, 1);
|
||||
Rect.anchorMax = new Vector2(0, 1);
|
||||
Rect.pivot = new Vector2(0.5f, 1);
|
||||
Rect.sizeDelta = new Vector2(25, 25);
|
||||
Rect.sizeDelta = new Vector2(30, 30);
|
||||
|
||||
UIFactory.SetLayoutElement(UIRoot, minWidth: 100, flexibleWidth: 9999, minHeight: 25, flexibleHeight: 0);
|
||||
UIFactory.SetLayoutElement(UIRoot, minWidth: 100, flexibleWidth: 9999, minHeight: 30, flexibleHeight: 0);
|
||||
|
||||
inspectButton = UIFactory.CreateButton(UIRoot, "InspectButton", "");
|
||||
UIFactory.SetLayoutElement(inspectButton.GameObject, minWidth: 200, minHeight: 25);
|
||||
inspectButton.OnClick += () => InspectorManager.Inspect(animatorPlayer.animator.gameObject);
|
||||
|
||||
animatorToggler = new AnimatorPausePlayButton(UIRoot, animatorPlayer != null && animatorPlayer.animator.speed == 1);
|
||||
animatorToggler.OnClick += ButtonEnableAnimation;
|
||||
|
||||
ButtonRef resetAnimation = UIFactory.CreateButton(UIRoot, "Reset Animation", "Reset");
|
||||
UIFactory.SetLayoutElement(resetAnimation.GameObject, minWidth: 50, minHeight: 25);
|
||||
resetAnimation.OnClick += ResetAnimation;
|
||||
|
||||
GameObject ignoresMasterTogglerObj = UIFactory.CreateToggle(UIRoot, $"AnimatorIgnoreMasterToggle", out IgnoreMasterToggle, out Text ignoreMasterToggleText);
|
||||
GameObject ignoresMasterTogglerObj = UIFactory.CreateToggle(UIRoot, "AnimatorIgnoreMasterToggle", out IgnoreMasterToggle, out Text ignoreMasterToggleText);
|
||||
UIFactory.SetLayoutElement(ignoresMasterTogglerObj, minHeight: 25, minWidth: 155);
|
||||
IgnoreMasterToggle.isOn = false;
|
||||
IgnoreMasterToggle.onValueChanged.AddListener(IgnoreMasterToggle_Clicked);
|
||||
@@ -112,7 +119,7 @@ namespace UnityExplorer.UI.Panels
|
||||
favAnimation.ButtonText.text = animatorPlayer.IsAnimationFaved(animatorPlayer.overridingAnimation) ? "★" : "☆";
|
||||
};
|
||||
|
||||
GameObject overridingAnimationObj = UIFactory.CreateDropdown(UIRoot, $"Animations_Dropdown", out animatorDropdown, null, 14, (idx) => {
|
||||
GameObject overridingAnimationObj = UIFactory.CreateDropdown(UIRoot, "Animations_Dropdown", out animatorDropdown, null, 14, (idx) => {
|
||||
if (animatorPlayer.animator.wrappedObject == null)
|
||||
return;
|
||||
animatorPlayer.overridingAnimation = idx < animatorDropdown.options.Count ? animatorPlayer.animations.Find(a => a.name == animatorDropdown.options[idx].text) : animatorPlayer.overridingAnimation;
|
||||
@@ -151,10 +158,24 @@ namespace UnityExplorer.UI.Panels
|
||||
//UpdateDropdownOptions();
|
||||
};
|
||||
|
||||
GameObject animationTimelineObj = UIFactory.CreateSlider(UIRoot, "AnimationTimelineSlider", out animationTimeline);
|
||||
UIFactory.SetLayoutElement(animationTimelineObj, minHeight: 25, minWidth: 200, flexibleHeight: 0);
|
||||
animationTimeline.minValue = 0;
|
||||
animationTimeline.maxValue = 1;
|
||||
animationTimeline.onValueChanged.AddListener((float val) => {
|
||||
animatorPlayer.PlayOverridingAnimation(val);
|
||||
animatorToggler.isOn = false;
|
||||
});
|
||||
|
||||
playButton = UIFactory.CreateButton(UIRoot, "PlayButton", "Play", new Color(0.2f, 0.26f, 0.2f));
|
||||
UIFactory.SetLayoutElement(playButton.Component.gameObject, minHeight: 25, minWidth: 90);
|
||||
playButton.OnClick += PlayButton_OnClick;
|
||||
|
||||
openBonesPanelButton = UIFactory.CreateButton(UIRoot, "OpenBonesPanelButton", "Open Bones Panel");
|
||||
UIFactory.SetLayoutElement(openBonesPanelButton.Component.gameObject, minWidth: 150, minHeight: 25, flexibleWidth: 0, flexibleHeight: 0);
|
||||
|
||||
openBonesPanelButton.OnClick += () => { animatorPlayer.OpenBonesPanel(); };
|
||||
|
||||
return UIRoot;
|
||||
}
|
||||
|
||||
@@ -166,9 +187,17 @@ namespace UnityExplorer.UI.Panels
|
||||
animatorPlayer.shouldIgnoreMasterToggle = value;
|
||||
}
|
||||
|
||||
internal void ButtonEnableAnimation(){
|
||||
EnableAnimation(animatorToggler.isOn);
|
||||
}
|
||||
|
||||
internal void EnableAnimation(bool value){
|
||||
if (animatorPlayer.animator.wrappedObject != null)
|
||||
animatorPlayer.animator.speed = value ? 1 : 0;
|
||||
}
|
||||
|
||||
internal void EnableMesh(bool value){
|
||||
animatorPlayer.SetMeshesEnabled(value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,6 +20,11 @@ namespace UnityExplorer.UI.Panels
|
||||
public List<IAnimationClip> animations = new List<IAnimationClip>();
|
||||
public bool shouldIgnoreMasterToggle = false;
|
||||
|
||||
BonesManager bonesManager;
|
||||
private List<Transform> bones = new List<Transform>();
|
||||
private List<SkinnedMeshRenderer> skinnedMeshes = new();
|
||||
private List<MeshRenderer> extraMeshes = new();
|
||||
|
||||
public IAnimationClip overridingAnimation;
|
||||
private IAnimationClip lastCurrentAnimation;
|
||||
private IRuntimeAnimatorController originalAnimatorController;
|
||||
@@ -42,6 +47,13 @@ namespace UnityExplorer.UI.Panels
|
||||
this.overridingAnimation = lastCurrentAnimation != null ? lastCurrentAnimation : (animations.Count > 0 ? animations[0] : null);
|
||||
|
||||
this.favAnimations = new List<IAnimationClip>();
|
||||
|
||||
SearchMeshes();
|
||||
}
|
||||
|
||||
public void SearchMeshes(){
|
||||
skinnedMeshes = new List<SkinnedMeshRenderer>(animator.wrappedObject.gameObject.GetComponentsInChildren<SkinnedMeshRenderer>(false));
|
||||
extraMeshes = new List<MeshRenderer>(animator.wrappedObject.gameObject.GetComponentsInChildren<MeshRenderer>(false));
|
||||
}
|
||||
|
||||
// Include the animations being played in other layers
|
||||
@@ -54,6 +66,7 @@ namespace UnityExplorer.UI.Panels
|
||||
}
|
||||
|
||||
public void ResetAnimation(){
|
||||
if (bonesManager != null) bonesManager.turnOffAnimatorToggle.isOn = true;
|
||||
// Let the game change animations again
|
||||
animator.StopPlayback();
|
||||
if (originalAnimatorController != null && animator.wrappedObject != null){
|
||||
@@ -65,7 +78,7 @@ namespace UnityExplorer.UI.Panels
|
||||
}
|
||||
}
|
||||
|
||||
public void PlayOverridingAnimation(){
|
||||
public void PlayOverridingAnimation(float normalizedTime){
|
||||
ResetAnimation();
|
||||
if (animatorOverrideController == null){
|
||||
animatorOverrideController = new IAnimatorOverrideController();
|
||||
@@ -84,7 +97,7 @@ namespace UnityExplorer.UI.Panels
|
||||
|
||||
IAnimationClip currentAnimation = animator.GetCurrentAnimatorClipInfo(0)[0].clip;
|
||||
animatorOverrideController[currentAnimation] = overridingAnimation;
|
||||
animator.Play(currentAnimation.name);
|
||||
animator.Play(currentAnimation.name, normalizedTime);
|
||||
|
||||
lastCurrentAnimation = currentAnimation;
|
||||
}
|
||||
@@ -115,6 +128,45 @@ namespace UnityExplorer.UI.Panels
|
||||
animator.enabled = value;
|
||||
}
|
||||
}
|
||||
|
||||
private List<Transform> GetMeshesTransforms(){
|
||||
List<Transform> meshes = new List<Transform>();
|
||||
|
||||
foreach (SkinnedMeshRenderer skinnedMesh in skinnedMeshes) {
|
||||
meshes.AddRange(skinnedMesh.bones);
|
||||
}
|
||||
meshes.AddRange(extraMeshes.Select(m => m.transform));
|
||||
meshes.RemoveAll(item => item == null);
|
||||
return meshes.GroupBy(b => b.name).Select(b => b.First()).ToList().OrderBy(b => b.name).ToList();
|
||||
}
|
||||
|
||||
public void OpenBonesPanel(){
|
||||
if (skinnedMeshes.Count == 0 && extraMeshes.Count == 0) return;
|
||||
if (bonesManager == null){
|
||||
bonesManager = new BonesManager(UIManager.GetPanel<UnityExplorer.UI.Panels.AnimatorPanel>(UIManager.Panels.AnimatorPanel).Owner, GetMeshesTransforms(), animator);
|
||||
}
|
||||
bonesManager.SetActive(true);
|
||||
}
|
||||
|
||||
public void MaybeResetBonesPanel(){
|
||||
if (bonesManager == null) return;
|
||||
bonesManager.RefreshBones(GetMeshesTransforms());
|
||||
}
|
||||
|
||||
public void SetMeshesEnabled(bool value){
|
||||
foreach (SkinnedMeshRenderer skinnedMesh in skinnedMeshes) {
|
||||
skinnedMesh.TryCast<Renderer>().enabled = value;
|
||||
}
|
||||
|
||||
foreach (MeshRenderer meshRenderer in extraMeshes) {
|
||||
meshRenderer.gameObject.SetActive(value);
|
||||
}
|
||||
}
|
||||
|
||||
public bool IsMeshHidden(){
|
||||
// Could maybe save a variable to set on SetMeshesEnabled instead
|
||||
return skinnedMeshes.Any( m => m.TryCast<Renderer>().enabled) || extraMeshes.Any( m => m.gameObject.activeSelf);
|
||||
}
|
||||
}
|
||||
|
||||
public class IAnimator
|
||||
@@ -209,9 +261,9 @@ namespace UnityExplorer.UI.Panels
|
||||
}
|
||||
}
|
||||
|
||||
public void Play(string animatorClip){
|
||||
public void Play(string animatorClip, float normalizedTime){
|
||||
MethodInfo play = realType.GetMethod("Play", new Type[] {typeof(string), typeof(int), typeof(float)});
|
||||
play.Invoke(_animator.TryCast(), new object[] {animatorClip, -1, 0f});
|
||||
play.Invoke(_animator.TryCast(), new object[] {animatorClip, -1, normalizedTime});
|
||||
}
|
||||
|
||||
public float speed
|
||||
|
||||
@@ -0,0 +1,411 @@
|
||||
using UnityExplorer.UI.Widgets;
|
||||
using UniverseLib.Input;
|
||||
using UniverseLib.UI;
|
||||
using UniverseLib.UI.Models;
|
||||
using UniverseLib.UI.Widgets.ScrollView;
|
||||
|
||||
namespace UnityExplorer.UI.Panels
|
||||
{
|
||||
public class BonesCell : ICell
|
||||
{
|
||||
public BoneTree boneTree;
|
||||
public TransformControls transformControls;
|
||||
|
||||
ComponentControl positionControl;
|
||||
ComponentControl rotationControl;
|
||||
ComponentControl scaleControl;
|
||||
public AxisComponentControl CurrentSlidingAxisControl { get; set; }
|
||||
public BonesManager Owner;
|
||||
private Text boneName;
|
||||
|
||||
private GameObject expandBonesRow;
|
||||
private ButtonRef expandBonesButton;
|
||||
private Text expandBonesText;
|
||||
private LayoutElement spaceLayout;
|
||||
static private int TREE_LEVEL_IDENTATION = 20;
|
||||
|
||||
// ICell
|
||||
public float DefaultHeight => 25f;
|
||||
public GameObject UIRoot { get; set; }
|
||||
public RectTransform Rect { get; set; }
|
||||
|
||||
public bool Enabled => UIRoot.activeSelf;
|
||||
public void Enable() => UIRoot.SetActive(true);
|
||||
public void Disable() => UIRoot.SetActive(false);
|
||||
|
||||
public void SetBoneTree(BoneTree boneTree, BonesManager bonesManager){
|
||||
this.boneTree = boneTree;
|
||||
boneName.text = boneTree.obj.name;
|
||||
Owner = bonesManager;
|
||||
|
||||
if (boneTree.childTrees.Count == 0){
|
||||
expandBonesRow.SetActive(false);
|
||||
} else {
|
||||
expandBonesRow.SetActive(true);
|
||||
bool isTreeExpanded = IsTreeExpanded();
|
||||
expandBonesButton.ButtonText.text = isTreeExpanded ? "▼" : "▶";
|
||||
expandBonesText.text = isTreeExpanded ? "Collapse bones" : "Expand bones";
|
||||
}
|
||||
|
||||
spaceLayout.minWidth = TREE_LEVEL_IDENTATION * boneTree.level;
|
||||
}
|
||||
|
||||
private void ExpandOrCollapseBoneTree(){
|
||||
if (IsTreeExpanded()){
|
||||
// Collapse
|
||||
List<BoneTree> treesToRemove = boneTree.childTrees.Select(t => t.flatten()).SelectMany(l => l).ToList();
|
||||
Owner.boneTrees = Owner.boneTrees.Except(treesToRemove).ToList();
|
||||
expandBonesButton.ButtonText.text = "▶";
|
||||
expandBonesText.text = "Expand bones";
|
||||
}
|
||||
else {
|
||||
// Expand
|
||||
int index = Owner.boneTrees.FindIndex(t => t == boneTree);
|
||||
if (index == -1) return;
|
||||
|
||||
Owner.boneTrees.InsertRange(index + 1, boneTree.childTrees);
|
||||
expandBonesButton.ButtonText.text = "▼";
|
||||
expandBonesText.text = "Collapse bones";
|
||||
}
|
||||
|
||||
Owner.boneScrollPool.Refresh(true, false);
|
||||
}
|
||||
|
||||
private bool IsTreeExpanded(){
|
||||
if (boneTree == null) return true;
|
||||
return boneTree.childTrees.Any(t1 => Owner.boneTrees.Any(t2 => t2 == t1));
|
||||
}
|
||||
|
||||
public virtual GameObject CreateContent(GameObject parent)
|
||||
{
|
||||
UIRoot = UIFactory.CreateUIObject("CellRoot", parent, new Vector2(25, 25));
|
||||
UIFactory.SetLayoutGroup<HorizontalLayoutGroup>(UIRoot, false, false, true, true, 4, childAlignment: TextAnchor.MiddleRight);
|
||||
UIFactory.SetLayoutElement(UIRoot, minHeight: 25, flexibleWidth: 9999, flexibleHeight: 800);
|
||||
|
||||
Rect = UIRoot.GetComponent<RectTransform>();
|
||||
Rect.anchorMin = new Vector2(0, 1);
|
||||
Rect.anchorMax = new Vector2(0, 1);
|
||||
Rect.pivot = new Vector2(0.5f, 1);
|
||||
Rect.sizeDelta = new Vector2(30, 30);
|
||||
|
||||
GameObject spacer = UIFactory.CreateUIObject("Spacer", UIRoot);
|
||||
spaceLayout = UIFactory.SetLayoutElement(spacer, minWidth: 0, flexibleWidth: 0);
|
||||
|
||||
GameObject baseCell = UIFactory.CreateUIObject("BaseCell", UIRoot);
|
||||
UIFactory.SetLayoutGroup<VerticalLayoutGroup>(baseCell, false, false, true, true, 3);
|
||||
UIFactory.SetLayoutElement(baseCell, minHeight: 25, minWidth: 50, flexibleWidth: 9999);
|
||||
|
||||
GameObject header = UIFactory.CreateUIObject("BoneHeader", baseCell);
|
||||
UIFactory.SetLayoutGroup<HorizontalLayoutGroup>(header, false, false, true, true, 4, childAlignment: TextAnchor.MiddleLeft);
|
||||
UIFactory.SetLayoutElement(header, minHeight: 25, flexibleWidth: 9999, flexibleHeight: 800);
|
||||
|
||||
GameObject MeshToggleObj = UIFactory.CreateToggle(header, "MeshToggle", out Toggle MeshToggle, out Text MeshToggleText);
|
||||
UIFactory.SetLayoutElement(MeshToggleObj, minHeight: 30);
|
||||
MeshToggle.onValueChanged.AddListener(SetBoneEnabled);
|
||||
|
||||
boneName = UIFactory.CreateLabel(header, $"BoneName", "", fontSize: 18);
|
||||
UIFactory.SetLayoutElement(boneName.gameObject, minWidth: 100, minHeight: 25, flexibleWidth: 9999);
|
||||
|
||||
GameObject headerButtons = UIFactory.CreateUIObject("BoneHeader", header);
|
||||
UIFactory.SetLayoutGroup<HorizontalLayoutGroup>(headerButtons, false, false, true, true, 4, childAlignment: TextAnchor.MiddleRight);
|
||||
UIFactory.SetLayoutElement(headerButtons, minHeight: 25, flexibleWidth: 9999, flexibleHeight: 800);
|
||||
|
||||
ButtonRef inspectButton = UIFactory.CreateButton(headerButtons, "InspectButton", "Inspect");
|
||||
UIFactory.SetLayoutElement(inspectButton.GameObject, minWidth: 75, minHeight: 25);
|
||||
inspectButton.OnClick += () => InspectorManager.Inspect(boneTree.obj);
|
||||
|
||||
ButtonRef restoreBoneStateButton = UIFactory.CreateButton(headerButtons, "RestoreBoneState", "Restore State");
|
||||
UIFactory.SetLayoutElement(restoreBoneStateButton.GameObject, minWidth: 125, minHeight: 25);
|
||||
restoreBoneStateButton.OnClick += RestoreBoneState;
|
||||
|
||||
positionControl = ComponentControl.Create(this, baseCell, "Local Position", TransformType.LocalPosition, 0.01f);
|
||||
rotationControl = ComponentControl.Create(this, baseCell, "Rotation", TransformType.Rotation, 10f);
|
||||
scaleControl = ComponentControl.Create(this, baseCell, "Scale", TransformType.Scale, 0.1f);
|
||||
|
||||
expandBonesRow = UIFactory.CreateUIObject("ExpandBonesRow", baseCell);
|
||||
UIFactory.SetLayoutGroup<HorizontalLayoutGroup>(expandBonesRow, false, false, true, true, 4, childAlignment: TextAnchor.MiddleLeft);
|
||||
UIFactory.SetLayoutElement(expandBonesRow, minHeight: 25, flexibleWidth: 9999, flexibleHeight: 800);
|
||||
|
||||
expandBonesButton = UIFactory.CreateButton(expandBonesRow, "ExpandBonesButton", IsTreeExpanded() ? "⯆" : "▶", new Color(0.05f, 0.05f, 0.05f));
|
||||
UIFactory.SetLayoutElement(expandBonesButton.Component.gameObject, minHeight: 25, minWidth: 25);
|
||||
expandBonesButton.OnClick += ExpandOrCollapseBoneTree;
|
||||
|
||||
expandBonesText = UIFactory.CreateLabel(expandBonesRow, $"ExpandBonesText", IsTreeExpanded() ? "Collapse bones" : "Expand bones");
|
||||
UIFactory.SetLayoutElement(expandBonesText.gameObject, minWidth: 100, minHeight: 25);
|
||||
|
||||
return UIRoot;
|
||||
}
|
||||
|
||||
private void RestoreBoneState(){
|
||||
Owner.RestoreBoneState(boneTree.obj.name);
|
||||
}
|
||||
|
||||
private void SetBoneEnabled(bool value){
|
||||
boneTree.obj.SetActive(value);
|
||||
}
|
||||
|
||||
// TransformControls-like functions
|
||||
public void UpdateTransformControlValues(bool force){
|
||||
positionControl.Update(force);
|
||||
rotationControl.Update(force);
|
||||
scaleControl.Update(force);
|
||||
}
|
||||
|
||||
public void UpdateVectorSlider()
|
||||
{
|
||||
AxisComponentControl control = CurrentSlidingAxisControl;
|
||||
|
||||
if (control == null)
|
||||
return;
|
||||
|
||||
if (!IInputManager.GetMouseButton(0))
|
||||
{
|
||||
control.slider.value = 0f;
|
||||
control = null;
|
||||
return;
|
||||
}
|
||||
|
||||
AxisControlOperation(control.slider.value, control.parent, control.axis);
|
||||
|
||||
if (Owner.turnOffAnimatorToggle.isOn) Owner.turnOffAnimatorToggle.isOn = false;
|
||||
}
|
||||
|
||||
public void AxisControlOperation(float value, ComponentControl parent, int axis)
|
||||
{
|
||||
Transform transform = boneTree.obj.transform;
|
||||
|
||||
Vector3 vector = parent.Type switch
|
||||
{
|
||||
TransformType.Position => transform.position,
|
||||
TransformType.LocalPosition => transform.localPosition,
|
||||
TransformType.Rotation => transform.localEulerAngles,
|
||||
TransformType.Scale => transform.localScale,
|
||||
_ => throw new NotImplementedException()
|
||||
};
|
||||
|
||||
// apply vector value change
|
||||
switch (axis)
|
||||
{
|
||||
case 0:
|
||||
vector.x += value; break;
|
||||
case 1:
|
||||
vector.y += value; break;
|
||||
case 2:
|
||||
vector.z += value; break;
|
||||
}
|
||||
|
||||
// set vector back to transform
|
||||
switch (parent.Type)
|
||||
{
|
||||
case TransformType.Position:
|
||||
transform.position = vector; break;
|
||||
case TransformType.LocalPosition:
|
||||
transform.localPosition = vector; break;
|
||||
case TransformType.Rotation:
|
||||
transform.localEulerAngles = vector; break;
|
||||
case TransformType.Scale:
|
||||
transform.localScale = vector; break;
|
||||
}
|
||||
|
||||
UpdateTransformControlValues(false);
|
||||
}
|
||||
}
|
||||
|
||||
// Duplication of Vector3Control class
|
||||
public class ComponentControl
|
||||
{
|
||||
public BonesCell Owner { get; }
|
||||
public Transform Transform => Owner.boneTree.obj.transform;
|
||||
public TransformType Type { get; }
|
||||
|
||||
public InputFieldRef MainInput { get; }
|
||||
|
||||
public AxisComponentControl[] axisComponentControl { get; } = new AxisComponentControl[3];
|
||||
|
||||
public InputFieldRef IncrementInput { get; set; }
|
||||
public float Increment { get; set; } = 0.1f;
|
||||
|
||||
Vector3 lastValue;
|
||||
|
||||
Vector3 CurrentValue => Type switch
|
||||
{
|
||||
TransformType.Position => Transform.position,
|
||||
TransformType.LocalPosition => Transform.localPosition,
|
||||
TransformType.Rotation => Transform.localEulerAngles,
|
||||
TransformType.Scale => Transform.localScale,
|
||||
_ => throw new NotImplementedException()
|
||||
};
|
||||
|
||||
public ComponentControl(BonesCell cell, TransformType type, InputFieldRef input)
|
||||
{
|
||||
this.Owner = cell;
|
||||
this.Type = type;
|
||||
this.MainInput = input;
|
||||
}
|
||||
|
||||
public void Update(bool force)
|
||||
{
|
||||
// Probably not needed
|
||||
if (Transform == null) return;
|
||||
|
||||
Vector3 currValue = CurrentValue;
|
||||
if (force || (!MainInput.Component.isFocused && !lastValue.Equals(currValue)))
|
||||
{
|
||||
MainInput.Text = ParseUtility.ToStringForInput<Vector3>(currValue);
|
||||
lastValue = currValue;
|
||||
}
|
||||
}
|
||||
|
||||
void OnTransformInputEndEdit(TransformType type, string input)
|
||||
{
|
||||
switch (type)
|
||||
{
|
||||
case TransformType.Position:
|
||||
{
|
||||
if (ParseUtility.TryParse(input, out Vector3 val, out _))
|
||||
Transform.position = val;
|
||||
}
|
||||
break;
|
||||
case TransformType.LocalPosition:
|
||||
{
|
||||
if (ParseUtility.TryParse(input, out Vector3 val, out _))
|
||||
Transform.localPosition = val;
|
||||
}
|
||||
break;
|
||||
case TransformType.Rotation:
|
||||
{
|
||||
if (ParseUtility.TryParse(input, out Vector3 val, out _))
|
||||
Transform.localEulerAngles = val;
|
||||
}
|
||||
break;
|
||||
case TransformType.Scale:
|
||||
{
|
||||
if (ParseUtility.TryParse(input, out Vector3 val, out _))
|
||||
Transform.localScale = val;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
Owner.UpdateTransformControlValues(true);
|
||||
if (Owner.Owner.turnOffAnimatorToggle.isOn) Owner.Owner.turnOffAnimatorToggle.isOn = false;
|
||||
}
|
||||
|
||||
void IncrementInput_OnEndEdit(string value)
|
||||
{
|
||||
if (!ParseUtility.TryParse(value, out float increment, out _))
|
||||
IncrementInput.Text = ParseUtility.ToStringForInput<float>(Increment);
|
||||
else
|
||||
{
|
||||
Increment = increment;
|
||||
foreach (AxisComponentControl slider in axisComponentControl)
|
||||
{
|
||||
slider.slider.minValue = -increment;
|
||||
slider.slider.maxValue = increment;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static ComponentControl Create(BonesCell cell, GameObject transformGroup, string title, TransformType type, float increment)
|
||||
{
|
||||
GameObject rowObj = UIFactory.CreateUIObject($"Row_{title}", transformGroup);
|
||||
UIFactory.SetLayoutGroup<HorizontalLayoutGroup>(rowObj, false, false, true, true, 5, 0, 0, 0, 0, default);
|
||||
UIFactory.SetLayoutElement(rowObj, minHeight: 25, flexibleWidth: 9999);
|
||||
|
||||
Text titleLabel = UIFactory.CreateLabel(rowObj, "PositionLabel", title, TextAnchor.MiddleRight, Color.grey);
|
||||
UIFactory.SetLayoutElement(titleLabel.gameObject, minHeight: 25, minWidth: 110);
|
||||
|
||||
InputFieldRef inputField = UIFactory.CreateInputField(rowObj, "InputField", "...");
|
||||
UIFactory.SetLayoutElement(inputField.Component.gameObject, minHeight: 25, minWidth: 100, flexibleWidth: 999);
|
||||
|
||||
ComponentControl control = new(cell, type, inputField);
|
||||
|
||||
inputField.Component.GetOnEndEdit().AddListener((string value) => { control.OnTransformInputEndEdit(type, value); });
|
||||
control.Increment = increment;
|
||||
|
||||
control.axisComponentControl[0] = AxisComponentControl.Create(rowObj, "X", 0, control);
|
||||
control.axisComponentControl[1] = AxisComponentControl.Create(rowObj, "Y", 1, control);
|
||||
control.axisComponentControl[2] = AxisComponentControl.Create(rowObj, "Z", 2, control);
|
||||
|
||||
control.IncrementInput = UIFactory.CreateInputField(rowObj, "IncrementInput", "...");
|
||||
control.IncrementInput.Text = increment.ToString();
|
||||
UIFactory.SetLayoutElement(control.IncrementInput.GameObject, minWidth: 30, flexibleWidth: 0, minHeight: 25);
|
||||
control.IncrementInput.Component.GetOnEndEdit().AddListener(control.IncrementInput_OnEndEdit);
|
||||
|
||||
if (type == TransformType.Scale){
|
||||
GameObject extraRowObj = UIFactory.CreateUIObject("Row_UniformScale", transformGroup);
|
||||
UIFactory.SetLayoutGroup<HorizontalLayoutGroup>(extraRowObj, false, false, true, true, 5, 0, 0, 0, 0, default);
|
||||
UIFactory.SetLayoutElement(extraRowObj, minHeight: 25, flexibleWidth: 9999);
|
||||
|
||||
Text uniformScaleTitleLabel = UIFactory.CreateLabel(extraRowObj, "UniformScaleLabel", "Uniform Scale:", TextAnchor.MiddleRight, Color.grey);
|
||||
UIFactory.SetLayoutElement(uniformScaleTitleLabel.gameObject, minHeight: 25, minWidth: 110);
|
||||
|
||||
GameObject uniformScaleControlObj = UIFactory.CreateSlider(extraRowObj, "UniformScaleSlider", out Slider uniformScaleControl);
|
||||
UIFactory.SetLayoutElement(uniformScaleControlObj, minHeight: 25, minWidth: 200, flexibleHeight: 0);
|
||||
uniformScaleControl.minValue = 0.0001f;
|
||||
uniformScaleControl.maxValue = 10;
|
||||
uniformScaleControl.value = 1;
|
||||
uniformScaleControl.onValueChanged.AddListener((float val) => { cell.boneTree.obj.transform.localScale = new Vector3(val, val, val); });
|
||||
}
|
||||
|
||||
return control;
|
||||
}
|
||||
}
|
||||
|
||||
// // Duplication of AxisControl class
|
||||
public class AxisComponentControl
|
||||
{
|
||||
public readonly ComponentControl parent;
|
||||
|
||||
public readonly int axis;
|
||||
public readonly Slider slider;
|
||||
|
||||
public AxisComponentControl(int axis, Slider slider, ComponentControl parentControl)
|
||||
{
|
||||
this.parent = parentControl;
|
||||
this.axis = axis;
|
||||
this.slider = slider;
|
||||
}
|
||||
|
||||
void OnVectorSliderChanged(float value)
|
||||
{
|
||||
parent.Owner.CurrentSlidingAxisControl = value == 0f ? null : this;
|
||||
}
|
||||
|
||||
void OnVectorMinusClicked()
|
||||
{
|
||||
parent.Owner.AxisControlOperation(-this.parent.Increment, this.parent, this.axis);
|
||||
}
|
||||
|
||||
void OnVectorPlusClicked()
|
||||
{
|
||||
parent.Owner.AxisControlOperation(this.parent.Increment, this.parent, this.axis);
|
||||
}
|
||||
|
||||
public static AxisComponentControl Create(GameObject parent, string title, int axis, ComponentControl owner)
|
||||
{
|
||||
Text label = UIFactory.CreateLabel(parent, $"Label_{title}", $"{title}:", TextAnchor.MiddleRight, Color.grey);
|
||||
UIFactory.SetLayoutElement(label.gameObject, minHeight: 25, minWidth: 30);
|
||||
|
||||
GameObject sliderObj = UIFactory.CreateSlider(parent, $"Slider_{title}", out Slider slider);
|
||||
UIFactory.SetLayoutElement(sliderObj, minHeight: 25, minWidth: 75, flexibleWidth: 0);
|
||||
slider.m_FillImage.color = Color.clear;
|
||||
|
||||
slider.minValue = -owner.Increment;
|
||||
slider.maxValue = owner.Increment;
|
||||
|
||||
AxisComponentControl sliderControl = new(axis, slider, owner);
|
||||
|
||||
slider.onValueChanged.AddListener(sliderControl.OnVectorSliderChanged);
|
||||
|
||||
ButtonRef minusButton = UIFactory.CreateButton(parent, "MinusIncrementButton", "-");
|
||||
UIFactory.SetLayoutElement(minusButton.GameObject, minWidth: 20, flexibleWidth: 0, minHeight: 25);
|
||||
minusButton.OnClick += sliderControl.OnVectorMinusClicked;
|
||||
|
||||
ButtonRef plusButton = UIFactory.CreateButton(parent, "PlusIncrementButton", "+");
|
||||
UIFactory.SetLayoutElement(plusButton.GameObject, minWidth: 20, flexibleWidth: 0, minHeight: 25);
|
||||
plusButton.OnClick += sliderControl.OnVectorPlusClicked;
|
||||
|
||||
return sliderControl;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -43,8 +43,11 @@ namespace UnityExplorer.UI.Panels
|
||||
ButtonRef copyFovButton = UIFactory.CreateButton(UIRoot, "Copy Camera FoV", "Copy Camera FoV");
|
||||
UIFactory.SetLayoutElement(copyFovButton.GameObject, minWidth: 100, minHeight: 25, flexibleWidth: 9999);
|
||||
copyFovButton.OnClick += () => {
|
||||
point.fov = FreeCamPanel.ourCamera.fieldOfView;
|
||||
GetCamPathsPanel().controlPoints[index] = point;
|
||||
Camera freecam = FreeCamPanel.GetFreecam();
|
||||
if (freecam != null) {
|
||||
point.fov = freecam.fieldOfView;
|
||||
GetCamPathsPanel().controlPoints[index] = point;
|
||||
}
|
||||
};
|
||||
|
||||
ButtonRef moveToPointButton = UIFactory.CreateButton(UIRoot, "Move Cam to Node", "Move Cam to Node");
|
||||
@@ -52,7 +55,7 @@ namespace UnityExplorer.UI.Panels
|
||||
moveToPointButton.OnClick += () => {
|
||||
FreeCamPanel.SetCameraRotation(point.rotation);
|
||||
FreeCamPanel.SetCameraPosition(point.position);
|
||||
FreeCamPanel.ourCamera.fieldOfView = point.fov;
|
||||
FreeCamPanel.SetFOV(point.fov);
|
||||
};
|
||||
|
||||
ButtonRef moveUpButton = UIFactory.CreateButton(UIRoot, "MoveUp", "▲");
|
||||
@@ -84,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();
|
||||
};
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using UnityEngine;
|
||||
using UnityExplorer.Config;
|
||||
using UniverseLib.UI;
|
||||
using UniverseLib.UI.Models;
|
||||
using UniverseLib.UI.Widgets.ScrollView;
|
||||
@@ -29,7 +30,7 @@ namespace UnityExplorer.UI.Panels
|
||||
|
||||
//Active toggle
|
||||
Toggle toggleLight;
|
||||
GameObject toggleObj = UIFactory.CreateToggle(UIRoot, "UseGameCameraToggle", out toggleLight, out label);
|
||||
GameObject toggleObj = UIFactory.CreateToggle(UIRoot, "ActivateLightToggle", out toggleLight, out label);
|
||||
UIFactory.SetLayoutElement(toggleObj, minHeight: 25, flexibleWidth: 9999);
|
||||
toggleLight.onValueChanged.AddListener(value => { light.SetActive(value); });
|
||||
toggleLight.isOn = true;
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -90,7 +94,7 @@ namespace UnityExplorer.UI.Panels
|
||||
}
|
||||
|
||||
public static void CopyFreeCamTransform(GameObject obj){
|
||||
Camera freeCam = FreeCamPanel.ourCamera;
|
||||
Camera freeCam = FreeCamPanel.GetFreecam();
|
||||
|
||||
if (freeCam != null) {
|
||||
obj.transform.position = freeCam.transform.position;
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,89 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Xml.Serialization;
|
||||
|
||||
namespace UnityExplorer.Serializers
|
||||
{
|
||||
public class BonesSerializer
|
||||
{
|
||||
public static string Serialize(Dictionary<string, List<CachedBonesTransform>> dict)
|
||||
{
|
||||
SerializableDictionary serializableDict = new SerializableDictionary(dict);
|
||||
|
||||
var serializer = new XmlSerializer(typeof(SerializableDictionary));
|
||||
using (var writer = new StringWriter())
|
||||
{
|
||||
serializer.Serialize(writer, serializableDict);
|
||||
return writer.ToString();
|
||||
}
|
||||
}
|
||||
|
||||
public static Dictionary<string, List<CachedBonesTransform>> Deserialize(string xml)
|
||||
{
|
||||
var serializer = new XmlSerializer(typeof(SerializableDictionary));
|
||||
using (var reader = new StringReader(xml))
|
||||
{
|
||||
return ((SerializableDictionary)serializer.Deserialize(reader)).ToDictionary();
|
||||
}
|
||||
}
|
||||
|
||||
// Old versions of .net can't natively serialize dictionaries, therefore we make a couple of classes to do it ourselves
|
||||
[XmlRoot("Dictionary")]
|
||||
public class SerializableDictionary
|
||||
{
|
||||
[XmlElement("Item")]
|
||||
public List<DictionaryItem> Items { get; set; } = new List<DictionaryItem>();
|
||||
|
||||
public SerializableDictionary() { }
|
||||
|
||||
public SerializableDictionary(Dictionary<string, List<CachedBonesTransform>> dictionary)
|
||||
{
|
||||
foreach (var kvp in dictionary)
|
||||
{
|
||||
Items.Add(new DictionaryItem { Key = kvp.Key, Value = kvp.Value });
|
||||
}
|
||||
}
|
||||
|
||||
public Dictionary<string, List<CachedBonesTransform>> ToDictionary()
|
||||
{
|
||||
var dictionary = new Dictionary<string, List<CachedBonesTransform>>();
|
||||
foreach (var item in Items)
|
||||
{
|
||||
dictionary[item.Key] = item.Value;
|
||||
}
|
||||
return dictionary;
|
||||
}
|
||||
}
|
||||
|
||||
public class DictionaryItem
|
||||
{
|
||||
[XmlAttribute("Key")]
|
||||
public string Key { get; set; }
|
||||
|
||||
[XmlElement("Value")]
|
||||
public List<CachedBonesTransform> Value { get; set; }
|
||||
}
|
||||
}
|
||||
|
||||
public struct CachedBonesTransform
|
||||
{
|
||||
public CachedBonesTransform(Vector3 position, Vector3 angles, Vector3 scale)
|
||||
{
|
||||
this.position = position;
|
||||
this.angles = angles;
|
||||
this.scale = 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;
|
||||
transform.localEulerAngles = angles;
|
||||
transform.localScale = scale;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Xml.Serialization;
|
||||
|
||||
namespace UnityExplorer.Serializers
|
||||
{
|
||||
public class CamPathSerializer
|
||||
{
|
||||
public static string Serialize(List<CatmullRom.CatmullRomPoint> points, float time, float alpha, float tension, bool closePath, string sceneName)
|
||||
{
|
||||
CamPathSerializeObject serializeObject = new CamPathSerializeObject(points, time, alpha, tension, closePath, sceneName);
|
||||
var serializer = new XmlSerializer(typeof(CamPathSerializeObject));
|
||||
using (var writer = new StringWriter())
|
||||
{
|
||||
serializer.Serialize(writer, serializeObject);
|
||||
return writer.ToString();
|
||||
}
|
||||
}
|
||||
|
||||
public static CamPathSerializeObject Deserialize(string xml)
|
||||
{
|
||||
var serializer = new XmlSerializer(typeof(CamPathSerializeObject));
|
||||
using (var reader = new StringReader(xml))
|
||||
{
|
||||
return ((CamPathSerializeObject)serializer.Deserialize(reader));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public struct CamPathSerializeObject
|
||||
{
|
||||
public CamPathSerializeObject(List<CatmullRom.CatmullRomPoint> points, float time, float alpha, float tension, bool closePath, string sceneName)
|
||||
{
|
||||
this.points = points;
|
||||
this.time = time;
|
||||
this.tension = tension;
|
||||
this.alpha = alpha;
|
||||
this.closePath = closePath;
|
||||
this.sceneName = sceneName;
|
||||
}
|
||||
|
||||
public readonly List<CatmullRom.CatmullRomPoint> points;
|
||||
public readonly float time;
|
||||
public readonly float alpha;
|
||||
public readonly float tension;
|
||||
public readonly bool closePath;
|
||||
public readonly string sceneName;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,252 @@
|
||||
using MonoMod.Utils;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Text;
|
||||
using UnityExplorer;
|
||||
|
||||
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")]
|
||||
public static extern IntPtr LoadLibrary(string dll);
|
||||
|
||||
[DllImport("kernel32.dll")]
|
||||
public static extern IntPtr GetProcAddress(IntPtr hModule, string procName);
|
||||
}
|
||||
public class UnityIGCSConnector
|
||||
{
|
||||
// UnityIGCSConnector.dll definitions.
|
||||
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
|
||||
private delegate void MoveCameraCallback(float step_left, float step_up, float fov, int from_start);
|
||||
|
||||
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
|
||||
private delegate int SessionCallback();
|
||||
|
||||
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
|
||||
private delegate IntPtr U_IGCS_Initialize(MoveCameraCallback callback, SessionCallback start_cb, SessionCallback end_cb);
|
||||
|
||||
// Store the initial position when a session start in IGCSDof.
|
||||
Mono.CSharp.Tuple<Vector3, Quaternion> position = null;
|
||||
|
||||
// When we end a session, we need to make sure to go back to the position when the session ends.
|
||||
private Mono.CSharp.Tuple<Vector3, Quaternion> endSessionPosition = null;
|
||||
|
||||
private readonly bool isValid = false;
|
||||
private bool _isActive = false;
|
||||
public bool IsActive => isValid && _isActive;
|
||||
|
||||
// Delegate holder for the MoveCamera, StartSession and EndSession.
|
||||
private readonly List<System.Delegate> delegates = new();
|
||||
|
||||
// Since some games use multi-threaded, in order to make sure we're only moving things during
|
||||
// the main thread is executing, we use this Queue to enqueue the move commands and dequeue them in the Update function.
|
||||
// This object *must* be used with a Lock.
|
||||
private readonly Queue<StepCommand> commands = new();
|
||||
|
||||
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 (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)
|
||||
{
|
||||
var transform = cam.transform;
|
||||
|
||||
// Check whether we should go back to the original position despite being active or not
|
||||
this.ShouldMoveToOriginalPosition(transform);
|
||||
|
||||
if (!_isActive || position == null)
|
||||
{
|
||||
position = new(transform.position, transform.rotation);
|
||||
}
|
||||
|
||||
if (!_isActive || position == null) { return; }
|
||||
|
||||
StepCommand c = null;
|
||||
|
||||
lock (commands)
|
||||
{
|
||||
if (commands.Count <= 0) return;
|
||||
c = commands.Dequeue();
|
||||
}
|
||||
|
||||
transform.position = position.Item1;
|
||||
transform.rotation = position.Item2;
|
||||
transform.Translate(c.Item1, c.Item2, 0.0f);
|
||||
}
|
||||
|
||||
private void MoveCamera(float stepLeft, float stepUp, float fov, int fromStart)
|
||||
{
|
||||
lock (commands)
|
||||
{
|
||||
commands.Enqueue(new StepCommand(stepLeft, stepUp));
|
||||
}
|
||||
}
|
||||
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
|
||||
// then move the camera back to the original position, because the queue gets cleaned as soon as the session
|
||||
// ends.
|
||||
public void ShouldMoveToOriginalPosition(Transform transform)
|
||||
{
|
||||
if (!isValid) return;
|
||||
if (endSessionPosition == null) return;
|
||||
|
||||
transform.position = endSessionPosition.Item1;
|
||||
transform.rotation = endSessionPosition.Item2;
|
||||
|
||||
endSessionPosition = null;
|
||||
}
|
||||
|
||||
private int EndSession()
|
||||
{
|
||||
endSessionPosition = position;
|
||||
position = null;
|
||||
_isActive = false;
|
||||
|
||||
lock (commands)
|
||||
commands.Clear();
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
public UnityIGCSConnector()
|
||||
{
|
||||
var libraryName = IntPtr.Size == 8 ? @"UnityIGCSConnector.dll" : @"UnityIGCSConnector.32.dll";
|
||||
var lib = NativeMethods.LoadLibrary(libraryName);
|
||||
if (lib == IntPtr.Zero)
|
||||
{
|
||||
ExplorerCore.LogWarning($"{libraryName} was not found so IGCSDof will not be available");
|
||||
return;
|
||||
}
|
||||
|
||||
var func = NativeMethods.GetProcAddress(lib, @"U_IGCS_Initialize");
|
||||
if (func == IntPtr.Zero)
|
||||
{
|
||||
throw new EntryPointNotFoundException("Failed to find 'U_IGCS_Initialize' which means you can have a corrupt UnityIGCSConnector.dll.");
|
||||
}
|
||||
|
||||
var initFunc = (U_IGCS_Initialize)Marshal.GetDelegateForFunctionPointer(func, typeof(U_IGCS_Initialize));
|
||||
|
||||
delegates.Add(new MoveCameraCallback(MoveCamera));
|
||||
delegates.Add(new SessionCallback(StartSession));
|
||||
delegates.Add(new SessionCallback(EndSession));
|
||||
|
||||
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");
|
||||
}
|
||||
|
||||
isValid = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
using UnityExplorer.UI;
|
||||
using UnityExplorer.UI.Panels;
|
||||
|
||||
namespace UnityExplorer.Config
|
||||
{
|
||||
@@ -29,8 +30,11 @@ namespace UnityExplorer.Config
|
||||
public static ConfigElement<string> CSConsole_Assembly_Blacklist;
|
||||
public static ConfigElement<string> Reflection_Signature_Blacklist;
|
||||
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<bool> Default_Gameplay_Freecam;
|
||||
public static ConfigElement<KeyCode> Pause;
|
||||
public static ConfigElement<KeyCode> Frameskip;
|
||||
public static ConfigElement<KeyCode> Screenshot;
|
||||
@@ -58,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();
|
||||
@@ -163,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;'",
|
||||
@@ -174,20 +182,32 @@ 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'.",
|
||||
false);
|
||||
|
||||
Default_Gameplay_Freecam = new("Default Gameplay Freecam",
|
||||
"Turn this on if you want the default gameplay freecam toggle on the Freecam panel to be on on startup.",
|
||||
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.",
|
||||
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);
|
||||
|
||||
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);
|
||||
@@ -241,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);
|
||||
@@ -289,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.",
|
||||
"\\");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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(){
|
||||
|
||||
+4
-2
@@ -20,7 +20,7 @@ namespace UnityExplorer
|
||||
public static class ExplorerCore
|
||||
{
|
||||
public const string NAME = "CinematicUnityExplorer";
|
||||
public const string VERSION = "1.0.0";
|
||||
public const string VERSION = "1.4.0";
|
||||
public const string AUTHOR = "originalnicodr, Sinai, yukieiji";
|
||||
public const string GUID = "com.originalnicodr.cinematicunityexplorer";
|
||||
|
||||
@@ -80,7 +80,7 @@ namespace UnityExplorer
|
||||
|
||||
KeypressListener.Setup();
|
||||
|
||||
MakeUEUIScale();
|
||||
if (ConfigManager.Auto_Scale_UI.Value) MakeUEUIScale();
|
||||
}
|
||||
|
||||
internal static void Update()
|
||||
@@ -205,6 +205,8 @@ namespace UnityExplorer
|
||||
foreach (CanvasScaler scaler in canvasScalers)
|
||||
{
|
||||
scaler.uiScaleMode = CanvasScaler.ScaleMode.ScaleWithScreenSize;
|
||||
scaler.screenMatchMode = CanvasScaler.ScreenMatchMode.MatchWidthOrHeight;
|
||||
scaler.matchWidthOrHeight = 0f;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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)}");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -8,6 +8,7 @@ using System.Text;
|
||||
using UnityEngine;
|
||||
using UnityExplorer.Config;
|
||||
using UnityExplorer.UI;
|
||||
using UnityExplorer.UI.Panels;
|
||||
using UniverseLib;
|
||||
|
||||
namespace UnityExplorer.Loader.Standalone
|
||||
@@ -26,6 +27,13 @@ namespace UnityExplorer.Loader.Standalone
|
||||
public bool Force_Unlock_Mouse = true;
|
||||
public KeyCode Force_Unlock_Toggle;
|
||||
public bool Disable_EventSystem_Override;
|
||||
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;
|
||||
@@ -80,6 +88,14 @@ namespace UnityExplorer.Loader.Standalone
|
||||
ConfigManager.Force_Unlock_Mouse.Value = this.Force_Unlock_Mouse;
|
||||
ConfigManager.Force_Unlock_Toggle.Value = this.Force_Unlock_Toggle;
|
||||
ConfigManager.Disable_EventSystem_Override.Value = this.Disable_EventSystem_Override;
|
||||
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;
|
||||
|
||||
@@ -163,7 +163,7 @@ namespace UnityExplorer.ObjectExplorer
|
||||
if (!string.IsNullOrEmpty(input))
|
||||
nameFilter = input;
|
||||
|
||||
BindingFlags flags = BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static;
|
||||
BindingFlags flags = BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static | BindingFlags.FlattenHierarchy;
|
||||
|
||||
foreach (Assembly asm in AppDomain.CurrentDomain.GetAssemblies())
|
||||
{
|
||||
@@ -181,7 +181,7 @@ namespace UnityExplorer.ObjectExplorer
|
||||
}
|
||||
}
|
||||
|
||||
return instances;
|
||||
return instances.Distinct().ToList();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+126
-20
@@ -23,14 +23,15 @@ namespace UnityExplorer.UI.Panels
|
||||
|
||||
public override string Name => "Animator";
|
||||
public override UIManager.Panels PanelType => UIManager.Panels.AnimatorPanel;
|
||||
public override int MinWidth => 900;
|
||||
public override int MinWidth => 1300;
|
||||
public override int MinHeight => 200;
|
||||
public override Vector2 DefaultAnchorMin => new(0.4f, 0.4f);
|
||||
public override Vector2 DefaultAnchorMax => new(0.6f, 0.6f);
|
||||
public override bool NavButtonWanted => true;
|
||||
public override bool ShouldSaveActiveState => true;
|
||||
|
||||
Toggle masterAnimatorToggle = new Toggle();
|
||||
AnimatorPausePlayButton masterAnimatorPlayer;
|
||||
Toggle masterMeshToggle;
|
||||
|
||||
private static ScrollPool<AnimatorCell> animatorScrollPool;
|
||||
internal List<AnimatorPlayer> animators = new List<AnimatorPlayer>();
|
||||
@@ -47,13 +48,11 @@ namespace UnityExplorer.UI.Panels
|
||||
animatorScrollPool.Initialize(this);
|
||||
DoneScrollPoolInit = true;
|
||||
}
|
||||
|
||||
animatorScrollPool.Refresh(true, false);
|
||||
}
|
||||
|
||||
private void FindAllAnimators(){
|
||||
// Enable all animators on refresh
|
||||
masterAnimatorToggle.isOn = true; // Will also trigger "MasterToggleAnimators(true)"
|
||||
masterAnimatorPlayer.isOn = true; // Will also trigger "MasterToggleAnimators(true)"
|
||||
|
||||
Type searchType = ReflectionUtility.GetTypeByName("UnityEngine.Animator");
|
||||
searchType = searchType is Type type ? type : searchType.GetActualType();
|
||||
@@ -68,8 +67,20 @@ namespace UnityExplorer.UI.Panels
|
||||
{
|
||||
if (animators[i].animator.wrappedObject != null){
|
||||
int newAnimatorsIndex = newAnimators.FindIndex(a => a.animator.wrappedObject == animators[i].animator.wrappedObject);
|
||||
if (newAnimatorsIndex != -1)
|
||||
if (newAnimatorsIndex != -1) {
|
||||
// If refreshing the animator gives us new animations, add them to the already existing ones.
|
||||
// Might break stuff.
|
||||
foreach (IAnimationClip animationClip in newAnimators[newAnimatorsIndex].animations) {
|
||||
// TODO: Refactor AnimatorPlayer.animations from List<IAnimationClip> to HashSet<IAnimationClip> to avoid checking this
|
||||
if (!animators[i].animations.Contains(animationClip))
|
||||
animators[i].animations.Add(animationClip);
|
||||
}
|
||||
newAnimators[newAnimatorsIndex] = animators[i];
|
||||
|
||||
// Reset meshes
|
||||
newAnimators[newAnimatorsIndex].SearchMeshes();
|
||||
newAnimators[newAnimatorsIndex].MaybeResetBonesPanel();
|
||||
}
|
||||
}
|
||||
}
|
||||
animators = newAnimators;
|
||||
@@ -85,7 +96,9 @@ namespace UnityExplorer.UI.Panels
|
||||
}
|
||||
}
|
||||
|
||||
public void MasterToggleAnimators(bool enable){
|
||||
public void MasterToggleAnimators(){
|
||||
bool enable = masterAnimatorPlayer.isOn;
|
||||
|
||||
// Load animators for the first time if there are not any
|
||||
if (animators.Count == 0) FindAllAnimators();
|
||||
|
||||
@@ -99,8 +112,21 @@ namespace UnityExplorer.UI.Panels
|
||||
animatorScrollPool.Refresh(true, false);
|
||||
}
|
||||
|
||||
public void MasterToggleMeshes(bool enable){
|
||||
// Load animators for the first time if there are not any
|
||||
if (animators.Count == 0) FindAllAnimators();
|
||||
|
||||
foreach (AnimatorPlayer animatorPlayer in animators){
|
||||
if (!animatorPlayer.shouldIgnoreMasterToggle){
|
||||
animatorPlayer.SetMeshesEnabled(enable);
|
||||
}
|
||||
}
|
||||
|
||||
animatorScrollPool.Refresh(true, false);
|
||||
}
|
||||
|
||||
public void HotkeyToggleAnimators(){
|
||||
masterAnimatorToggle.isOn = !masterAnimatorToggle.isOn;
|
||||
masterAnimatorPlayer.isOn = !masterAnimatorPlayer.isOn;
|
||||
}
|
||||
|
||||
// ~~~~~~~~ UI construction / callbacks ~~~~~~~~
|
||||
@@ -110,25 +136,38 @@ namespace UnityExplorer.UI.Panels
|
||||
GameObject firstGroup = UIFactory.CreateHorizontalGroup(ContentRoot, "MainOptions", false, false, true, true, 3,
|
||||
default, new Color(1, 1, 1, 0), TextAnchor.MiddleLeft);
|
||||
UIFactory.SetLayoutElement(firstGroup, minHeight: 25, flexibleWidth: 9999);
|
||||
//UIElements.Add(horiGroup);
|
||||
|
||||
ButtonRef updateAnimators = UIFactory.CreateButton(firstGroup, "RefreshAnimators", "Refresh Animators");
|
||||
UIFactory.SetLayoutElement(updateAnimators.GameObject, minWidth: 150, minHeight: 25);
|
||||
updateAnimators.OnClick += FindAllAnimators;
|
||||
GameObject headerSpace1 = UIFactory.CreateUIObject("HeaderSpace1", firstGroup);
|
||||
UIFactory.SetLayoutElement(headerSpace1, minWidth: 0, flexibleWidth: 0);
|
||||
|
||||
GameObject meshObj = UIFactory.CreateToggle(firstGroup, "Master Mesh Toggle", out masterMeshToggle, out Text masterMeshText);
|
||||
UIFactory.SetLayoutElement(meshObj, minHeight: 25, minWidth: 230);
|
||||
masterMeshToggle.onValueChanged.AddListener(value => MasterToggleMeshes(value));
|
||||
masterMeshText.text = "Master Mesh Toggler";
|
||||
|
||||
masterAnimatorPlayer = new AnimatorPausePlayButton(firstGroup);
|
||||
masterAnimatorPlayer.OnClick += MasterToggleAnimators;
|
||||
|
||||
Text masterAnimatorPlayerText = UIFactory.CreateLabel(firstGroup, "MasterAnimatorToggleLabel", "Master Animator Toggler", TextAnchor.MiddleRight);
|
||||
UIFactory.SetLayoutElement(masterAnimatorPlayerText.gameObject, flexibleWidth: 0, minHeight: 25);
|
||||
|
||||
GameObject headerSpace2 = UIFactory.CreateUIObject("HeaderSpace2", firstGroup);
|
||||
UIFactory.SetLayoutElement(headerSpace2, minWidth: 10, flexibleWidth: 0);
|
||||
|
||||
ButtonRef resetAnimators = UIFactory.CreateButton(firstGroup, "ResetAnimators", "Reset Animators");
|
||||
UIFactory.SetLayoutElement(resetAnimators.GameObject, minWidth: 150, minHeight: 25);
|
||||
resetAnimators.OnClick += ResetAllAnimators;
|
||||
|
||||
GameObject animatorObj = UIFactory.CreateToggle(firstGroup, $"Master Animation Toggle", out masterAnimatorToggle, out Text masterAnimatorText);
|
||||
UIFactory.SetLayoutElement(animatorObj, minHeight: 25);
|
||||
masterAnimatorToggle.isOn = true;
|
||||
masterAnimatorToggle.onValueChanged.AddListener(value => MasterToggleAnimators(value));
|
||||
masterAnimatorText.text = "Master Toggler";
|
||||
GameObject secondGroup = UIFactory.CreateHorizontalGroup(firstGroup, "HeaderRight", false, false, true, true, 3,
|
||||
default, new Color(1, 1, 1, 0), TextAnchor.MiddleRight);
|
||||
UIFactory.SetLayoutElement(secondGroup, minHeight: 25, flexibleWidth: 9999);
|
||||
|
||||
GameObject secondGroup = UIFactory.CreateHorizontalGroup(ContentRoot, "MainOptions", false, false, true, true, 3,
|
||||
default, new Color(1, 1, 1, 0), TextAnchor.MiddleLeft);
|
||||
UIFactory.SetLayoutElement(firstGroup, minHeight: 25, flexibleWidth: 9999);
|
||||
ButtonRef updateAnimators = UIFactory.CreateButton(secondGroup, "RefreshAnimators", "Refresh Animators");
|
||||
UIFactory.SetLayoutElement(updateAnimators.GameObject, minWidth: 150, minHeight: 25);
|
||||
updateAnimators.OnClick += FindAllAnimators;
|
||||
|
||||
GameObject headerSpaceRight = UIFactory.CreateUIObject("HeaderSpaceRight", firstGroup);
|
||||
UIFactory.SetLayoutElement(headerSpaceRight, minWidth: 25, flexibleWidth: 0);
|
||||
|
||||
animatorScrollPool = UIFactory.CreateScrollPool<AnimatorCell>(ContentRoot, "AnimatorsList", out GameObject scrollObj,
|
||||
out GameObject scrollContent, new Color(0.03f, 0.03f, 0.03f));
|
||||
@@ -161,4 +200,71 @@ namespace UnityExplorer.UI.Panels
|
||||
|
||||
public void OnCellBorrowed(AnimatorCell cell) { }
|
||||
}
|
||||
|
||||
// ButtonRef wrapper to act as a toggle with clearer UI
|
||||
public class AnimatorPausePlayButton
|
||||
{
|
||||
private ButtonRef innerButton;
|
||||
public Button Component => innerButton.Component;
|
||||
public GameObject GameObject => innerButton.GameObject;
|
||||
private bool isPlaying;
|
||||
|
||||
public AnimatorPausePlayButton(GameObject ui, bool state = true)
|
||||
{
|
||||
innerButton = UIFactory.CreateButton(ui, "InnerAnimatorPlayButton", "");
|
||||
UIFactory.SetLayoutElement(innerButton.GameObject, minHeight: 25, minWidth: 25);
|
||||
innerButton.OnClick += SetToggleButtonState;
|
||||
isPlaying = state;
|
||||
UpdateButton();
|
||||
}
|
||||
|
||||
void OnPlay(){
|
||||
innerButton.ButtonText.text = "❚❚";
|
||||
RuntimeHelper.SetColorBlockAuto(innerButton.Component, new(0.4f, 0.2f, 0.2f));
|
||||
}
|
||||
|
||||
void OnPause(){
|
||||
innerButton.ButtonText.text = "►";
|
||||
RuntimeHelper.SetColorBlockAuto(innerButton.Component, new(0.2f, 0.4f, 0.2f));
|
||||
}
|
||||
|
||||
void UpdateButton(){
|
||||
if (isPlaying)
|
||||
{
|
||||
OnPlay();
|
||||
}
|
||||
else
|
||||
{
|
||||
OnPause();
|
||||
}
|
||||
}
|
||||
|
||||
void SetToggleButtonState()
|
||||
{
|
||||
isPlaying = !isPlaying;
|
||||
UpdateButton();
|
||||
}
|
||||
|
||||
public bool isOn
|
||||
{
|
||||
get {
|
||||
return isPlaying;
|
||||
}
|
||||
set {
|
||||
if (value != isPlaying){
|
||||
SetToggleButtonState();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public Action OnClick
|
||||
{
|
||||
get {
|
||||
return innerButton.OnClick;
|
||||
}
|
||||
set {
|
||||
innerButton.OnClick = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,385 @@
|
||||
using UnityExplorer.Serializers;
|
||||
using UniverseLib.UI;
|
||||
using UniverseLib.UI.Models;
|
||||
using UniverseLib.UI.Panels;
|
||||
using UniverseLib.UI.Widgets.ScrollView;
|
||||
|
||||
namespace UnityExplorer.UI.Panels
|
||||
{
|
||||
public class BonesManager : PanelBase, ICellPoolDataSource<BonesCell>
|
||||
{
|
||||
public override string Name => $"Bones Manager";
|
||||
public override int MinWidth => 1000;
|
||||
public override int MinHeight => 800;
|
||||
public override Vector2 DefaultAnchorMin => Vector2.zero;
|
||||
public override Vector2 DefaultAnchorMax => Vector2.zero;
|
||||
public Toggle turnOffAnimatorToggle;
|
||||
|
||||
private IAnimator animator;
|
||||
private Text skeletonName;
|
||||
private InputFieldRef saveLoadinputField;
|
||||
private InputFieldRef searchBoneNameInput;
|
||||
private List<Transform> bones = new List<Transform>();
|
||||
private Dictionary<string, CachedBonesTransform> bonesOriginalState = new();
|
||||
|
||||
public List<BoneTree> boneTrees = new();
|
||||
public ScrollPool<BonesCell> boneScrollPool;
|
||||
public int ItemCount => boneTrees.Count;
|
||||
private bool DoneScrollPoolInit;
|
||||
|
||||
public BonesManager(UIBase owner, List<Transform> bones, IAnimator animator) : base(owner)
|
||||
{
|
||||
this.bones = bones;
|
||||
this.animator = animator;
|
||||
skeletonName.text = $"Skeleton: {animator?.name} ";
|
||||
BuildBoneTrees();
|
||||
}
|
||||
|
||||
public void RefreshBones(List<Transform> bones) {
|
||||
this.bones = bones;
|
||||
boneTrees.Clear();
|
||||
BuildBoneTrees();
|
||||
boneScrollPool.Refresh(true, true);
|
||||
}
|
||||
|
||||
private void BuildBoneTrees(){
|
||||
BoneTree root = new BoneTree(animator.wrappedObject.gameObject, bones);
|
||||
if (root.obj != null){
|
||||
root.AssignLevels();
|
||||
boneTrees.Add(root);
|
||||
} else {
|
||||
foreach(BoneTree childTree in root.childTrees){
|
||||
childTree.AssignLevels();
|
||||
boneTrees.Add(childTree);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void CollapseBoneTrees(){
|
||||
boneTrees.Clear();
|
||||
BuildBoneTrees();
|
||||
boneScrollPool.Refresh(true, true);
|
||||
}
|
||||
|
||||
private void ExpandBoneTrees(){
|
||||
// We collapse before expanding to start from scratch and dont duplicate nodes
|
||||
CollapseBoneTrees();
|
||||
|
||||
List<BoneTree> newBoneTrees = new();
|
||||
|
||||
foreach(BoneTree childTree in boneTrees){
|
||||
newBoneTrees.AddRange(childTree.flatten());
|
||||
}
|
||||
|
||||
boneTrees = newBoneTrees;
|
||||
boneScrollPool.Refresh(true, false);
|
||||
}
|
||||
|
||||
private void SearchBones(){
|
||||
ExpandBoneTrees();
|
||||
boneTrees = boneTrees.Where(tree => tree.obj.name.IndexOf(searchBoneNameInput.Component.text, 0, StringComparison.OrdinalIgnoreCase) >= 0).ToList();
|
||||
boneScrollPool.Refresh(true, true);
|
||||
}
|
||||
|
||||
public override void SetActive(bool active)
|
||||
{
|
||||
base.SetActive(active);
|
||||
|
||||
if (active && !DoneScrollPoolInit)
|
||||
{
|
||||
LayoutRebuilder.ForceRebuildLayoutImmediate(this.Rect);
|
||||
boneScrollPool.Initialize(this);
|
||||
DoneScrollPoolInit = true;
|
||||
}
|
||||
}
|
||||
|
||||
protected override void ConstructPanelContent()
|
||||
{
|
||||
GameObject bonesPanelHeader = UIFactory.CreateHorizontalGroup(ContentRoot, "BonesPanelHeader", false, false, true, true, 3,
|
||||
default, new Color(1, 1, 1, 0), TextAnchor.MiddleLeft);
|
||||
UIFactory.SetLayoutElement(bonesPanelHeader, minHeight: 25, flexibleWidth: 9999);
|
||||
|
||||
skeletonName = UIFactory.CreateLabel(bonesPanelHeader, $"SkeletonName", "");
|
||||
UIFactory.SetLayoutElement(skeletonName.gameObject, minWidth: 100, minHeight: 25, flexibleWidth: 9999);
|
||||
skeletonName.fontSize = 16;
|
||||
|
||||
saveLoadinputField = UIFactory.CreateInputField(bonesPanelHeader, $"FileNameInput", "File name");
|
||||
UIFactory.SetLayoutElement(saveLoadinputField.GameObject, minWidth: 380, minHeight: 25);
|
||||
|
||||
GameObject spacer1 = UIFactory.CreateUIObject("Spacer", bonesPanelHeader);
|
||||
LayoutElement spaceLayout1 = UIFactory.SetLayoutElement(spacer1, minWidth: 20, flexibleWidth: 0);
|
||||
|
||||
ButtonRef savePose = UIFactory.CreateButton(bonesPanelHeader, "SavePoseButton", "Save pose");
|
||||
UIFactory.SetLayoutElement(savePose.GameObject, minWidth: 100, minHeight: 25);
|
||||
savePose.OnClick += SaveBones;
|
||||
|
||||
ButtonRef loadPose = UIFactory.CreateButton(bonesPanelHeader, "LoadPoseButton", "Load pose");
|
||||
UIFactory.SetLayoutElement(loadPose.GameObject, minWidth: 100, minHeight: 25);
|
||||
loadPose.OnClick += LoadBones;
|
||||
|
||||
GameObject header = UIFactory.CreateHorizontalGroup(ContentRoot, "BonesPanelHeader", false, false, true, true, 3,
|
||||
default, new Color(1, 1, 1, 0), TextAnchor.MiddleLeft);
|
||||
UIFactory.SetLayoutElement(header, minHeight: 35, flexibleWidth: 9999);
|
||||
|
||||
GameObject turnOffAnimatorToggleObj = UIFactory.CreateToggle(header, "Animator toggle", out turnOffAnimatorToggle, out Text turnOffAnimatorToggleText);
|
||||
UIFactory.SetLayoutElement(turnOffAnimatorToggleObj, minHeight: 25, flexibleWidth: 9999);
|
||||
turnOffAnimatorToggle.onValueChanged.AddListener(OnTurnOffAnimatorToggle);
|
||||
turnOffAnimatorToggleText.text = "Toggle animator (needs to be off to move bones)";
|
||||
|
||||
searchBoneNameInput = UIFactory.CreateInputField(header, $"SearchBoneInput", "Search bone name");
|
||||
UIFactory.SetLayoutElement(searchBoneNameInput.GameObject, minWidth: 225, minHeight: 25);
|
||||
|
||||
ButtonRef searchButton = UIFactory.CreateButton(header, "SearchButton", "Search");
|
||||
UIFactory.SetLayoutElement(searchButton.GameObject, minWidth: 75, minHeight: 25);
|
||||
searchButton.OnClick += SearchBones;
|
||||
|
||||
ButtonRef resetButton = UIFactory.CreateButton(header, "ResetSearchButton", "Reset");
|
||||
UIFactory.SetLayoutElement(resetButton.GameObject, minWidth: 75, minHeight: 25);
|
||||
resetButton.OnClick += () => { CollapseBoneTrees(); searchBoneNameInput.Component.text = ""; };
|
||||
|
||||
GameObject spacer2 = UIFactory.CreateUIObject("Spacer", header);
|
||||
LayoutElement spaceLayout2 = UIFactory.SetLayoutElement(spacer2, minWidth: 20, flexibleWidth: 0);
|
||||
|
||||
ButtonRef collapseAllButton = UIFactory.CreateButton(header, "CollapseAllButton", "Collapse all");
|
||||
UIFactory.SetLayoutElement(collapseAllButton.GameObject, minWidth: 100, minHeight: 25);
|
||||
collapseAllButton.OnClick += CollapseBoneTrees;
|
||||
|
||||
ButtonRef expandAllButton = UIFactory.CreateButton(header, "ExpandAllButton", "Expand all");
|
||||
UIFactory.SetLayoutElement(expandAllButton.GameObject, minWidth: 100, minHeight: 25);
|
||||
expandAllButton.OnClick += ExpandBoneTrees;
|
||||
|
||||
boneScrollPool = UIFactory.CreateScrollPool<BonesCell>(ContentRoot, "BonesList", out GameObject scrollObj,
|
||||
out GameObject scrollContent, new Color(0.06f, 0.06f, 0.06f));
|
||||
UIFactory.SetLayoutElement(scrollObj, flexibleWidth: 9999, flexibleHeight: 9999);
|
||||
}
|
||||
|
||||
private void OnTurnOffAnimatorToggle(bool value)
|
||||
{
|
||||
if (value){
|
||||
// Restore meshes manually in case some are not part of a skeleton and won't get restored automatically.
|
||||
// Besides, this restores the scale, which the animator doesn't.
|
||||
foreach (Transform bone in bones){
|
||||
bonesOriginalState[bone.name].CopyToTransform(bone);
|
||||
// We assume these were on before. If not we should save its state beforehand.
|
||||
bone.gameObject.SetActive(true);
|
||||
}
|
||||
} else {
|
||||
bonesOriginalState.Clear();
|
||||
foreach (Transform bone in bones){
|
||||
bonesOriginalState[bone.name] = new CachedBonesTransform(bone.localPosition, bone.localEulerAngles, bone.localScale);
|
||||
}
|
||||
}
|
||||
animator.enabled = value;
|
||||
}
|
||||
|
||||
public void RestoreBoneState(string boneName)
|
||||
{
|
||||
foreach (Transform bone in bones){
|
||||
if (bone.name == boneName){
|
||||
bonesOriginalState[boneName].CopyToTransform(bone);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void SaveBones(){
|
||||
Dictionary<string, List<CachedBonesTransform>> bonesTreeCache = new();
|
||||
// Get the list of bones based on the hierarchy order so we can deserialize it in the same order, instead of just using the bones list.
|
||||
List<BoneTree> allBoneTrees = new();
|
||||
foreach(BoneTree tree in boneTrees) {
|
||||
allBoneTrees.AddRange(tree.flatten());
|
||||
}
|
||||
|
||||
foreach(BoneTree tree in allBoneTrees){
|
||||
if (!bonesTreeCache.ContainsKey(tree.obj.name)){
|
||||
bonesTreeCache.Add(tree.obj.name, new List<CachedBonesTransform>());
|
||||
}
|
||||
CachedBonesTransform entry = new CachedBonesTransform(tree.obj.transform.localPosition, tree.obj.transform.localEulerAngles, tree.obj.transform.localScale);
|
||||
bonesTreeCache[tree.obj.name].Add(entry);
|
||||
}
|
||||
|
||||
string filename = saveLoadinputField.Component.text;
|
||||
if (filename.EndsWith(".cuepose") || filename.EndsWith(".CUEPOSE")) filename = filename.Substring(filename.Length-7);
|
||||
if (string.IsNullOrEmpty(filename)) filename = $"{animator?.name}-{DateTime.Now.ToString("yyyy-M-d HH-mm-ss")}";
|
||||
string posesPath = Path.Combine(ExplorerCore.ExplorerFolder, "Poses");
|
||||
System.IO.Directory.CreateDirectory(posesPath);
|
||||
|
||||
// Serialize
|
||||
string serializedData = BonesSerializer.Serialize(bonesTreeCache);
|
||||
File.WriteAllText($"{posesPath}\\{filename}.cuepose", serializedData);
|
||||
}
|
||||
|
||||
private void LoadBones(){
|
||||
string filename = saveLoadinputField.Component.text;
|
||||
if (filename.EndsWith(".cuepose") || filename.EndsWith(".CUEPOSE")) filename = filename.Substring(filename.Length-7);
|
||||
if (string.IsNullOrEmpty(filename)){
|
||||
ExplorerCore.LogWarning("Empty file name. Please write the name of the file to load.");
|
||||
return;
|
||||
}
|
||||
|
||||
string posesPath = Path.Combine(ExplorerCore.ExplorerFolder, "Poses");
|
||||
string xml;
|
||||
try {
|
||||
xml = File.ReadAllText($"{posesPath}\\{filename}.cuepose");
|
||||
}
|
||||
catch (Exception ex) {
|
||||
ExplorerCore.LogWarning(ex);
|
||||
return;
|
||||
}
|
||||
Dictionary<string, List<CachedBonesTransform>> deserializedDict;
|
||||
try {
|
||||
deserializedDict = BonesSerializer.Deserialize(xml);
|
||||
}
|
||||
catch (Exception ex) {
|
||||
ExplorerCore.LogWarning(ex);
|
||||
return;
|
||||
}
|
||||
|
||||
turnOffAnimatorToggle.isOn = false;
|
||||
foreach(Transform boneTransform in bones) {
|
||||
List<CachedBonesTransform> cachedTransformList;
|
||||
deserializedDict.TryGetValue(boneTransform.name, out cachedTransformList);
|
||||
if (cachedTransformList != null && cachedTransformList.Count > 0){
|
||||
CachedBonesTransform cachedTransform = cachedTransformList[0];
|
||||
cachedTransform.CopyToTransform(boneTransform);
|
||||
|
||||
cachedTransformList.RemoveAt(0);
|
||||
if (cachedTransformList.Count == 0) {
|
||||
deserializedDict.Remove(boneTransform.name);
|
||||
} else {
|
||||
deserializedDict[boneTransform.name] = cachedTransformList;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (deserializedDict.Count > 0) {
|
||||
ExplorerCore.LogWarning($"Couldn't apply every bone in the pose. Wrong entity?");
|
||||
}
|
||||
}
|
||||
|
||||
public void SetCell(BonesCell cell, int index)
|
||||
{
|
||||
if (index >= boneTrees.Count)
|
||||
{
|
||||
cell.Disable();
|
||||
return;
|
||||
}
|
||||
|
||||
BoneTree boneTree = boneTrees[index];
|
||||
cell.SetBoneTree(boneTree, this);
|
||||
cell.UpdateTransformControlValues(true);
|
||||
}
|
||||
|
||||
public void OnCellBorrowed(BonesCell cell) {
|
||||
cell.UpdateVectorSlider();
|
||||
}
|
||||
|
||||
public override void Update()
|
||||
{
|
||||
base.Update();
|
||||
|
||||
foreach(BonesCell boneCell in boneScrollPool.CellPool) {
|
||||
boneCell.UpdateVectorSlider();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public class BoneTree
|
||||
{
|
||||
public GameObject obj;
|
||||
public int level;
|
||||
public List<BoneTree> childTrees = new();
|
||||
|
||||
public BoneTree(GameObject obj, List<Transform> bones){
|
||||
// For some reason comparing GameObjects isn't working as intended in IL2CPP games, therefore we use their instance hash.
|
||||
#if CPP
|
||||
if (bones.Any(bone => bone.gameObject.GetInstanceID() == obj.GetInstanceID())) {
|
||||
this.obj = obj;
|
||||
}
|
||||
#else
|
||||
if (bones.Contains(obj.transform)) {
|
||||
this.obj = obj;
|
||||
}
|
||||
#endif
|
||||
for (int i = 0; i < obj.transform.childCount; i++)
|
||||
{
|
||||
Transform child = obj.transform.GetChild(i);
|
||||
if (child.gameObject.activeSelf){
|
||||
childTrees.Add(new BoneTree(child.gameObject, bones));
|
||||
}
|
||||
}
|
||||
|
||||
Trim();
|
||||
childTrees = childTrees.OrderBy(b => b.obj.name).ToList();
|
||||
}
|
||||
|
||||
private void Trim(){
|
||||
List<BoneTree> newList = new();
|
||||
foreach (BoneTree childTree in childTrees)
|
||||
{
|
||||
if (childTree.obj == null){
|
||||
newList.AddRange(childTree.childTrees);
|
||||
} else {
|
||||
newList.Add(childTree);
|
||||
}
|
||||
}
|
||||
|
||||
this.childTrees = newList;
|
||||
}
|
||||
|
||||
// TODO: refactor BoneTree so we don't need to call this after creating an instance.
|
||||
public void AssignLevels(){
|
||||
AssignLevel(0);
|
||||
}
|
||||
|
||||
private void AssignLevel(int distanceFromRoot){
|
||||
level = distanceFromRoot;
|
||||
foreach (BoneTree childTree in childTrees)
|
||||
{
|
||||
childTree.AssignLevel(distanceFromRoot + 1);
|
||||
}
|
||||
}
|
||||
|
||||
public override string ToString(){
|
||||
string return_string = "";
|
||||
if (obj != null){
|
||||
return_string = $"{obj.name} lvl: {level} - ";
|
||||
}
|
||||
|
||||
foreach (BoneTree childTree in childTrees)
|
||||
{
|
||||
return_string = return_string + childTree.ToString();
|
||||
}
|
||||
|
||||
return return_string;
|
||||
}
|
||||
|
||||
public List<GameObject> getGameObjects(){
|
||||
List<GameObject> return_list = new();
|
||||
if (obj != null){
|
||||
return_list.Add(obj);
|
||||
}
|
||||
|
||||
foreach (BoneTree childTree in childTrees)
|
||||
{
|
||||
return_list.AddRange(childTree.getGameObjects());
|
||||
}
|
||||
|
||||
return return_list;
|
||||
}
|
||||
|
||||
public List<BoneTree> flatten(){
|
||||
List<BoneTree> return_list = new();
|
||||
if (obj != null){
|
||||
return_list.Add(this);
|
||||
}
|
||||
|
||||
foreach (BoneTree childTree in childTrees)
|
||||
{
|
||||
return_list.AddRange(childTree.flatten());
|
||||
}
|
||||
|
||||
return return_list;
|
||||
}
|
||||
}
|
||||
}
|
||||
+200
-29
@@ -1,4 +1,6 @@
|
||||
using UniverseLib.Input;
|
||||
using UnityEngine.SceneManagement;
|
||||
using UnityExplorer.Serializers;
|
||||
using UniverseLib.Input;
|
||||
using UniverseLib.UI;
|
||||
using UniverseLib.UI.Models;
|
||||
#if UNHOLLOWER
|
||||
@@ -9,11 +11,6 @@ using Il2CppInterop.Runtime.Injection;
|
||||
#endif
|
||||
using UniverseLib.UI.Widgets.ScrollView;
|
||||
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
using System;
|
||||
using System.Collections;
|
||||
|
||||
namespace UnityExplorer.UI.Panels
|
||||
{
|
||||
public class CamPaths : UEPanel, ICellPoolDataSource<CamPathNodeCell>
|
||||
@@ -46,11 +43,14 @@ 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;
|
||||
|
||||
Toggle visualizePathToggle;
|
||||
public GameObject pathVisualizer;
|
||||
Toggle closedLoopToggle;
|
||||
InputFieldRef TimeInput;
|
||||
|
||||
bool unpauseOnPlay;
|
||||
bool waitBeforePlay;
|
||||
@@ -65,6 +65,9 @@ namespace UnityExplorer.UI.Panels
|
||||
Slider tensionCatmullRomSlider;
|
||||
float tensionCatmullRomValue = 0;
|
||||
|
||||
private InputFieldRef saveLoadInputField;
|
||||
private Toggle loadPathOnCamToggle;
|
||||
|
||||
public ScrollPool<CamPathNodeCell> nodesScrollPool;
|
||||
public int ItemCount => controlPoints.Count;
|
||||
private static bool DoneScrollPoolInit;
|
||||
@@ -97,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 ~~~~~~~~
|
||||
@@ -107,24 +112,23 @@ namespace UnityExplorer.UI.Panels
|
||||
default, new Color(1, 1, 1, 0), TextAnchor.MiddleLeft);
|
||||
UIFactory.SetLayoutElement(horiGroup, minHeight: 25, flexibleWidth: 9999);
|
||||
|
||||
ButtonRef startButton = UIFactory.CreateButton(horiGroup, "Start", "▶");
|
||||
ButtonRef startButton = UIFactory.CreateButton(horiGroup, "Start", "►", new Color(0.2f, 0.4f, 0.2f));
|
||||
UIFactory.SetLayoutElement(startButton.GameObject, minWidth: 50, minHeight: 25);
|
||||
startButton.ButtonText.fontSize = 20;
|
||||
startButton.OnClick += StartButton_OnClick;
|
||||
|
||||
ButtonRef pauseContinueButton = UIFactory.CreateButton(horiGroup, "Pause/Continue", "❚❚/▶");
|
||||
ButtonRef pauseContinueButton = UIFactory.CreateButton(horiGroup, "Pause/Continue", "❚❚/►");
|
||||
UIFactory.SetLayoutElement(pauseContinueButton.GameObject, minWidth: 50, minHeight: 25);
|
||||
pauseContinueButton.ButtonText.fontSize = 20;
|
||||
pauseContinueButton.OnClick += TogglePause_OnClick;
|
||||
|
||||
ButtonRef stopButton = UIFactory.CreateButton(horiGroup, "Stop", "■");
|
||||
ButtonRef stopButton = UIFactory.CreateButton(horiGroup, "Stop", "■", new Color(0.4f, 0.2f, 0.2f));
|
||||
UIFactory.SetLayoutElement(stopButton.GameObject, minWidth: 50, minHeight: 25);
|
||||
stopButton.ButtonText.fontSize = 20;
|
||||
stopButton.OnClick += Stop_OnClick;
|
||||
|
||||
ButtonRef AddNode = UIFactory.CreateButton(horiGroup, "AddCamNode", "+");
|
||||
UIFactory.SetLayoutElement(AddNode.GameObject, minWidth: 50, minHeight: 25);
|
||||
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);
|
||||
@@ -134,14 +138,13 @@ namespace UnityExplorer.UI.Panels
|
||||
MaybeRedrawPath();
|
||||
};
|
||||
|
||||
Toggle closedLoopToggle = new Toggle();
|
||||
closedLoopToggle = new Toggle();
|
||||
GameObject toggleClosedLoopObj = UIFactory.CreateToggle(horiGroup, "Close path in a loop", out closedLoopToggle, out Text toggleClosedLoopText);
|
||||
UIFactory.SetLayoutElement(toggleClosedLoopObj, minHeight: 25, flexibleWidth: 9999);
|
||||
closedLoopToggle.isOn = false;
|
||||
closedLoopToggle.onValueChanged.AddListener((isClosedLoop) => {closedLoop = isClosedLoop; MaybeRedrawPath(); EventSystemHelper.SetSelectedGameObject(null);});
|
||||
toggleClosedLoopText.text = "Close path in a loop";
|
||||
|
||||
InputFieldRef TimeInput = null;
|
||||
AddInputField("Time", "Path time (in seconds at 60fps):", $"Default: {time}", out TimeInput, Time_OnEndEdit, 50, horiGroup);
|
||||
TimeInput.Text = time.ToString();
|
||||
|
||||
@@ -209,6 +212,29 @@ namespace UnityExplorer.UI.Panels
|
||||
MaybeRedrawPath();
|
||||
});
|
||||
|
||||
GameObject fourthRow = UIFactory.CreateHorizontalGroup(ContentRoot, "SerializationOptions", false, false, true, true, 3,
|
||||
default, new Color(1, 1, 1, 0), TextAnchor.MiddleLeft);
|
||||
UIFactory.SetLayoutElement(fourthRow, minHeight: 25, flexibleWidth: 9999);
|
||||
|
||||
saveLoadInputField = UIFactory.CreateInputField(fourthRow, "PathName", "File name");
|
||||
UIFactory.SetLayoutElement(saveLoadInputField.GameObject, minWidth: 320, minHeight: 25);
|
||||
|
||||
GameObject spacer1 = UIFactory.CreateUIObject("Spacer", fourthRow);
|
||||
LayoutElement spaceLayout1 = UIFactory.SetLayoutElement(spacer1, minWidth: 20, flexibleWidth: 0);
|
||||
|
||||
ButtonRef savePath = UIFactory.CreateButton(fourthRow, "SavePathButton", "Save path");
|
||||
UIFactory.SetLayoutElement(savePath.GameObject, minWidth: 100, minHeight: 25);
|
||||
savePath.OnClick += SavePath;
|
||||
|
||||
ButtonRef loadPath = UIFactory.CreateButton(fourthRow, "LoadPathButton", "Load path");
|
||||
UIFactory.SetLayoutElement(loadPath.GameObject, minWidth: 100, minHeight: 25);
|
||||
loadPath.OnClick += LoadPath;
|
||||
|
||||
GameObject loadPathOnCamObj = UIFactory.CreateToggle(fourthRow, "Load path on cam", out loadPathOnCamToggle, out Text loadPathOnCamText);
|
||||
UIFactory.SetLayoutElement(loadPathOnCamObj, minHeight: 25, flexibleWidth: 9999);
|
||||
loadPathOnCamToggle.isOn = false;
|
||||
loadPathOnCamText.text = "Load path starting on current camera state";
|
||||
|
||||
nodesScrollPool = UIFactory.CreateScrollPool<CamPathNodeCell>(ContentRoot, "NodeList", out GameObject scrollObj,
|
||||
out GameObject scrollContent, new Color(0.03f, 0.03f, 0.03f));
|
||||
UIFactory.SetLayoutElement(scrollObj, flexibleWidth: 9999, flexibleHeight: 9999);
|
||||
@@ -216,8 +242,6 @@ namespace UnityExplorer.UI.Panels
|
||||
|
||||
void AlphaCatmullRom_OnEndEdit(string input)
|
||||
{
|
||||
EventSystemHelper.SetSelectedGameObject(null);
|
||||
|
||||
if (!ParseUtility.TryParse(input, out float parsed, out Exception parseEx))
|
||||
{
|
||||
ExplorerCore.LogWarning($"Could not parse value: {parseEx.ReflectionExToString()}");
|
||||
@@ -229,12 +253,11 @@ namespace UnityExplorer.UI.Panels
|
||||
alphaCatmullRomSlider.value = alphaCatmullRomValue;
|
||||
|
||||
MaybeRedrawPath();
|
||||
EventSystemHelper.SetSelectedGameObject(null);
|
||||
}
|
||||
|
||||
void TensionCatmullRom_OnEndEdit(string input)
|
||||
{
|
||||
EventSystemHelper.SetSelectedGameObject(null);
|
||||
|
||||
if (!ParseUtility.TryParse(input, out float parsed, out Exception parseEx))
|
||||
{
|
||||
ExplorerCore.LogWarning($"Could not parse value: {parseEx.ReflectionExToString()}");
|
||||
@@ -246,6 +269,7 @@ namespace UnityExplorer.UI.Panels
|
||||
tensionCatmullRomSlider.value = tensionCatmullRomValue;
|
||||
|
||||
MaybeRedrawPath();
|
||||
EventSystemHelper.SetSelectedGameObject(null);
|
||||
}
|
||||
|
||||
private void ToggleVisualizePath(bool enable){
|
||||
@@ -316,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();
|
||||
@@ -342,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;
|
||||
@@ -351,7 +377,11 @@ namespace UnityExplorer.UI.Panels
|
||||
EventSystemHelper.SetSelectedGameObject(null);
|
||||
}
|
||||
|
||||
void Stop_OnClick(){
|
||||
void TogglePause_OnClick(){
|
||||
TogglePause();
|
||||
}
|
||||
|
||||
public void StopPath(){
|
||||
if (GetCameraPathsManager()){
|
||||
GetCameraPathsManager().Stop();
|
||||
}
|
||||
@@ -359,16 +389,21 @@ 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(
|
||||
FreeCamPanel.GetCameraPosition(),
|
||||
FreeCamPanel.GetCameraRotation(),
|
||||
FreeCamPanel.ourCamera.fieldOfView
|
||||
FreeCamPanel.GetFreecam().fieldOfView
|
||||
);
|
||||
|
||||
controlPoints.Add(point);
|
||||
currentSelectedNodeIndex = controlPoints.Count - 1;
|
||||
nodesScrollPool.Refresh(true, false);
|
||||
MaybeRedrawPath();
|
||||
}
|
||||
@@ -377,7 +412,7 @@ namespace UnityExplorer.UI.Panels
|
||||
{
|
||||
EventSystemHelper.SetSelectedGameObject(null);
|
||||
|
||||
if (!ParseUtility.TryParse(input, out int parsed, out Exception parseEx))
|
||||
if (!ParseUtility.TryParse(input, out float parsed, out Exception parseEx))
|
||||
{
|
||||
ExplorerCore.LogWarning($"Could not parse value: {parseEx.ReflectionExToString()}");
|
||||
return;
|
||||
@@ -494,6 +529,142 @@ namespace UnityExplorer.UI.Panels
|
||||
|
||||
GetCameraPathsManager().CalculateLookahead();
|
||||
}
|
||||
|
||||
private void SavePath(){
|
||||
string filename = saveLoadInputField.Component.text;
|
||||
if (filename.EndsWith(".cuepath") || filename.EndsWith(".CUEPATH")) filename = filename.Substring(filename.Length-7);
|
||||
if (string.IsNullOrEmpty(filename)) filename = $"{DateTime.Now.ToString("yyyy-M-d HH-mm-ss")}";
|
||||
string camPathsFolderPath = Path.Combine(ExplorerCore.ExplorerFolder, "CamPaths");
|
||||
System.IO.Directory.CreateDirectory(camPathsFolderPath);
|
||||
|
||||
// Serialize
|
||||
string serializedData = CamPathSerializer.Serialize(controlPoints, time, alphaCatmullRomValue, tensionCatmullRomValue, closedLoop, SceneManager.GetActiveScene().name);
|
||||
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);
|
||||
if (string.IsNullOrEmpty(filename)){
|
||||
ExplorerCore.LogWarning("Empty file name. Please write the name of the file to load.");
|
||||
return;
|
||||
}
|
||||
|
||||
string camPathsFolderPath = Path.Combine(ExplorerCore.ExplorerFolder, "CamPaths");
|
||||
string pathFile;
|
||||
try {
|
||||
pathFile = File.ReadAllText($"{camPathsFolderPath}\\{filename}.cuepath");
|
||||
}
|
||||
catch (Exception ex) {
|
||||
ExplorerCore.LogWarning(ex);
|
||||
return;
|
||||
}
|
||||
CamPathSerializeObject deserializedObj;
|
||||
try {
|
||||
deserializedObj = CamPathSerializer.Deserialize(pathFile);
|
||||
}
|
||||
catch (Exception ex) {
|
||||
ExplorerCore.LogWarning(ex);
|
||||
return;
|
||||
}
|
||||
|
||||
if (deserializedObj.sceneName != SceneManager.GetActiveScene().name && !loadPathOnCamToggle.isOn) {
|
||||
loadPathOnCamToggle.isOn = true;
|
||||
ExplorerCore.LogWarning("Loaded a path on a different scene than the one it was saved on. Spawning it starting from the current camera state.");
|
||||
}
|
||||
|
||||
if (loadPathOnCamToggle.isOn) {
|
||||
|
||||
// We enable the freecam so we can use it to spawn the camera path relative to it
|
||||
if (!FreeCamPanel.inFreeCamMode) {
|
||||
FreeCamPanel.ToggleFreecam();
|
||||
}
|
||||
|
||||
controlPoints.Clear();
|
||||
// The first point will be the camera position, and we adapt the following points from there
|
||||
AddNode();
|
||||
|
||||
CatmullRom.CatmullRomPoint startingPoint = controlPoints.ElementAt(0);
|
||||
CatmullRom.CatmullRomPoint originalStartingPoint = deserializedObj.points.ElementAt(0);
|
||||
// We only want to use the camera pos and rotation, not the fov
|
||||
startingPoint.fov = originalStartingPoint.fov;
|
||||
controlPoints[0] = startingPoint;
|
||||
|
||||
foreach (CatmullRom.CatmullRomPoint point in deserializedObj.points.Skip(1)) {
|
||||
CatmullRom.CatmullRomPoint newPoint = point;
|
||||
|
||||
Quaternion offsetRot = startingPoint.rotation * Quaternion.Inverse(originalStartingPoint.rotation);
|
||||
newPoint.position = offsetRot * (newPoint.position - originalStartingPoint.position) + startingPoint.position;
|
||||
newPoint.rotation = offsetRot * newPoint.rotation;
|
||||
|
||||
controlPoints.Add(newPoint);
|
||||
}
|
||||
} else {
|
||||
controlPoints = deserializedObj.points;
|
||||
}
|
||||
|
||||
TimeInput.Text = deserializedObj.time.ToString("0.00");
|
||||
Time_OnEndEdit(TimeInput.Text);
|
||||
alphaCatmullRomInput.Text = deserializedObj.alpha.ToString("0.00");
|
||||
AlphaCatmullRom_OnEndEdit(alphaCatmullRomInput.Text);
|
||||
tensionCatmullRomInput.Text = deserializedObj.tension.ToString("0.00");
|
||||
TensionCatmullRom_OnEndEdit(tensionCatmullRomInput.Text);
|
||||
closedLoopToggle.isOn = deserializedObj.closePath;
|
||||
|
||||
// Update nodes
|
||||
nodesScrollPool.Refresh(true, false);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public class CamPointsUpdater : MonoBehaviour
|
||||
|
||||
+1150
-138
File diff suppressed because it is too large
Load Diff
+16
-32
@@ -41,13 +41,14 @@ namespace UnityExplorer.UI.Panels
|
||||
List<Canvas> disabledCanvases;
|
||||
Toggle HUDToggle;
|
||||
|
||||
CacheMethod captureScreenshotFunction = null;
|
||||
MethodInfo captureScreenshotFunction = null;
|
||||
int superSizeValue = 2;
|
||||
public ScreenshotState screenshotStatus;
|
||||
|
||||
Toggle HighLodToggle;
|
||||
object qualitySettings = null;
|
||||
CacheProperty lodBias = null;
|
||||
PropertyInfo lodBias = null;
|
||||
float _defaultLodBias = 1;
|
||||
|
||||
// We save the current properties of the Renderers and Lights to restore them after editing them with togglers
|
||||
internal Dictionary<Renderer, bool> renderersReceiveShadows = new();
|
||||
@@ -92,56 +93,39 @@ namespace UnityExplorer.UI.Panels
|
||||
}
|
||||
|
||||
private void TakeScreenshot(){
|
||||
MethodInfo methodInfo = captureScreenshotFunction.MethodInfo;
|
||||
string filename = DateTime.Now.ToString("yyyy-M-d HH-mm-ss");
|
||||
|
||||
string screenshotsPath = Path.Combine(ExplorerCore.ExplorerFolder, "Screenshots");
|
||||
System.IO.Directory.CreateDirectory(screenshotsPath);
|
||||
|
||||
object[] args = {$"{screenshotsPath}\\{filename}.png", superSizeValue};
|
||||
|
||||
methodInfo.Invoke(captureScreenshotFunction.DeclaringInstance, args);
|
||||
try {
|
||||
captureScreenshotFunction.Invoke(qualitySettings, args);
|
||||
}
|
||||
catch { ExplorerCore.LogWarning("Failed to take a screenshot. Chances are the method has been stripped."); }
|
||||
}
|
||||
|
||||
|
||||
private void FindCaptureScreenshotFunction(){
|
||||
try {
|
||||
object screenCaptureClass = ReflectionUtility.GetTypeByName("UnityEngine.ScreenCapture");
|
||||
Type screenCaptureType = screenCaptureClass is Type type ? type : screenCaptureClass.GetActualType();
|
||||
ReflectionInspector inspector = Pool<ReflectionInspector>.Borrow();
|
||||
List<CacheMember> members = CacheMemberFactory.GetCacheMembers(screenCaptureType, inspector);
|
||||
foreach (CacheMember member in members){
|
||||
if (member is CacheMethod methodMember){
|
||||
if (methodMember.NameForFiltering == "ScreenCapture.CaptureScreenshot(string, int)"){
|
||||
captureScreenshotFunction = methodMember;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
captureScreenshotFunction = screenCaptureType.GetMethod("CaptureScreenshot", new Type[] {typeof(string), typeof(int)});
|
||||
}
|
||||
catch { ExplorerCore.Log("Couldn't find the ScreenCapture class.");}
|
||||
catch { ExplorerCore.Log("Couldn't find the ScreenCapture class."); }
|
||||
}
|
||||
|
||||
private void FindQualitySettings(){
|
||||
qualitySettings = ReflectionUtility.GetTypeByName("UnityEngine.QualitySettings");
|
||||
}
|
||||
|
||||
private void FindLodBias(){
|
||||
Type qualitySettingsType = qualitySettings is Type type ? type : qualitySettings.GetActualType();
|
||||
ReflectionInspector inspector = Pool<ReflectionInspector>.Borrow();
|
||||
List<CacheMember> members = CacheMemberFactory.GetCacheMembers(qualitySettingsType, inspector);
|
||||
foreach (CacheMember member in members){
|
||||
if (member is CacheProperty propertyMember && propertyMember.NameForFiltering == "QualitySettings.lodBias"){
|
||||
lodBias = propertyMember;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void ToogleHighLods(bool areHighLodsOn){
|
||||
if (qualitySettings == null) FindQualitySettings();
|
||||
if (lodBias == null) FindLodBias();
|
||||
|
||||
lodBias.TrySetUserValue(areHighLodsOn ? 10000 : 1);
|
||||
if (lodBias == null){
|
||||
Type qualitySettingsType = qualitySettings is Type type ? type : qualitySettings.GetActualType();
|
||||
lodBias = qualitySettingsType.GetProperty("lodBias");
|
||||
_defaultLodBias = (float)lodBias.GetValue(null, null);
|
||||
}
|
||||
lodBias.SetValue(null, areHighLodsOn ? 10000 : _defaultLodBias, null);
|
||||
}
|
||||
|
||||
private void ToggleAllMeshesCastAndRecieveShadows(bool enable){
|
||||
|
||||
@@ -31,6 +31,7 @@ namespace UnityExplorer.UI.Panels
|
||||
public static Dictionary<string,List<PPEffect>> postProcessingEffects = null;
|
||||
static ButtonRef updateEffects;
|
||||
List<GameObject> UIElements = new List<GameObject>();
|
||||
public bool foundAnyEffect;
|
||||
|
||||
public class PPEffect
|
||||
{
|
||||
@@ -46,8 +47,6 @@ namespace UnityExplorer.UI.Panels
|
||||
List<CacheMember> members = CacheMemberFactory.GetCacheMembers(objType, inspector);
|
||||
|
||||
foreach (CacheMember member in members){
|
||||
//ExplorerCore.LogWarning(entry.NameForFiltering);
|
||||
|
||||
if (member.NameForFiltering.EndsWith(".active")){
|
||||
member.Evaluate();
|
||||
Active = member;
|
||||
@@ -68,6 +67,8 @@ namespace UnityExplorer.UI.Panels
|
||||
}
|
||||
|
||||
public void UpdatePPElements(){
|
||||
foundAnyEffect = false;
|
||||
|
||||
if(postProcessingEffects != null){
|
||||
// We turn the effects we had back on so they get captured again on refresh
|
||||
foreach (List<PPEffect> effects in postProcessingEffects.Values){
|
||||
@@ -168,6 +169,10 @@ namespace UnityExplorer.UI.Panels
|
||||
catch {}
|
||||
}
|
||||
|
||||
if (!foundAnyEffect){
|
||||
ExplorerCore.Log("Couldn't find any standard post-processing effect classes.");
|
||||
}
|
||||
|
||||
BuildEffectTogglers();
|
||||
}
|
||||
|
||||
@@ -189,8 +194,12 @@ namespace UnityExplorer.UI.Panels
|
||||
postProcessingEffects[effect].Add(entry);
|
||||
}
|
||||
}
|
||||
|
||||
foundAnyEffect = true;
|
||||
}
|
||||
catch {
|
||||
// ExplorerCore.Log($"Couldn't find {baseClass}.{effect}");
|
||||
}
|
||||
catch { ExplorerCore.Log($"Couldn't find {baseClass}.{effect}");}
|
||||
}
|
||||
|
||||
private void SetEffect(bool value, List<PPEffect> effects){
|
||||
|
||||
@@ -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 {
|
||||
@@ -183,7 +184,7 @@ namespace UnityExplorer.CatmullRom
|
||||
Vector3 camPos = FreeCamPanel.GetCameraPosition();
|
||||
Quaternion camRot = FreeCamPanel.GetCameraRotation();
|
||||
|
||||
return new CatmullRomPoint(camPos, camRot, FreeCamPanel.ourCamera.fieldOfView);
|
||||
return new CatmullRomPoint(camPos, camRot, FreeCamPanel.GetFreecam().fieldOfView);
|
||||
}
|
||||
|
||||
private void AdvanceMover(float dt){
|
||||
@@ -238,14 +239,14 @@ namespace UnityExplorer.CatmullRom
|
||||
|
||||
FreeCamPanel.SetCameraRotation(CatmullRomPoint.Vector4ToQuaternion(newRot));
|
||||
FreeCamPanel.SetCameraPosition(FreeCamPanel.GetCameraPosition() + direction.position);
|
||||
FreeCamPanel.ourCamera.fieldOfView += direction.fov * actual / room;
|
||||
FreeCamPanel.SetFOV(FreeCamPanel.GetFreecam().fieldOfView + direction.fov * actual / room);
|
||||
}
|
||||
|
||||
public void MoveCameraToPoint(CatmullRomPoint newPoint){
|
||||
FreeCamPanel.SetCameraRotation(newPoint.rotation);
|
||||
FreeCamPanel.SetCameraPosition(newPoint.position);
|
||||
|
||||
FreeCamPanel.ourCamera.fieldOfView = newPoint.fov;
|
||||
FreeCamPanel.SetFOV(newPoint.fov);
|
||||
}
|
||||
|
||||
private CatmullRomPoint GetPointFromPath(float d)
|
||||
|
||||
@@ -19,6 +19,7 @@ namespace UnityExplorer.UI.Widgets
|
||||
|
||||
ButtonRef ViewParentButton;
|
||||
ButtonRef FollowObjectButton;
|
||||
ButtonRef LookAtObjectButton;
|
||||
InputFieldRef PathInput;
|
||||
|
||||
InputFieldRef NameInput;
|
||||
@@ -295,6 +296,11 @@ namespace UnityExplorer.UI.Widgets
|
||||
UIFactory.SetLayoutElement(FollowObjectButton.Component.gameObject, minHeight: 25, minWidth: 100);
|
||||
FollowObjectButton.OnClick += () => FreeCamPanel.FollowObjectAction(this.Target.gameObject);
|
||||
|
||||
LookAtObjectButton = UIFactory.CreateButton(firstRow, "LookAtObjectButton", "Look at object with Freecam", new Color(0.2f, 0.2f, 0.2f));
|
||||
LookAtObjectButton.ButtonText.fontSize = 13;
|
||||
UIFactory.SetLayoutElement(LookAtObjectButton.Component.gameObject, minHeight: 25, minWidth: 100);
|
||||
LookAtObjectButton.OnClick += () => FreeCamPanel.LookAtObjectAction(this.Target.gameObject);
|
||||
|
||||
this.PathInput = UIFactory.CreateInputField(firstRow, "PathInput", "...");
|
||||
PathInput.Component.textComponent.color = Color.grey;
|
||||
PathInput.Component.textComponent.fontSize = 14;
|
||||
@@ -324,6 +330,19 @@ namespace UnityExplorer.UI.Widgets
|
||||
NameInput.Component.textComponent.fontSize = 15;
|
||||
NameInput.Component.GetOnEndEdit().AddListener((string val) => { OnNameEndEdit(val); });
|
||||
|
||||
ButtonRef MoveToCameraButton = UIFactory.CreateButton(titleRow, "MoveToCameraButton", "Move to Camera", new Color(0.2f, 0.2f, 0.2f));
|
||||
MoveToCameraButton.ButtonText.fontSize = 13;
|
||||
UIFactory.SetLayoutElement(MoveToCameraButton.Component.gameObject, minHeight: 25, minWidth: 120);
|
||||
MoveToCameraButton.OnClick += () => {
|
||||
if (FreeCamPanel.inFreeCamMode) {
|
||||
Transform freecamTransform = FreeCamPanel.GetFreecam().transform;
|
||||
this.Target.gameObject.transform.position = freecamTransform.position;
|
||||
this.Target.gameObject.transform.rotation = freecamTransform.rotation;
|
||||
} else {
|
||||
ExplorerCore.LogWarning("Enable freecam before trying to move an object to the camera!");
|
||||
}
|
||||
};
|
||||
|
||||
// second row (toggles, instanceID, tag, buttons)
|
||||
|
||||
GameObject secondRow = UIFactory.CreateUIObject("ParentRow", topInfoHolder);
|
||||
|
||||
@@ -68,19 +68,53 @@ 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)
|
||||
{
|
||||
if (float.TryParse(val, out float f))
|
||||
{
|
||||
if (f < slider.minValue || f > slider.maxValue){
|
||||
if (f < slider.minValue){
|
||||
ExplorerCore.LogWarning("Error, new time scale value outside of margins.");
|
||||
timeInput.Text = desiredTime.ToString("0.00");
|
||||
return;
|
||||
}
|
||||
|
||||
slider.value = f; // Will update the desiredTime value and extra things
|
||||
// Allow registering timescale values above the slider max value
|
||||
if (f >= slider.maxValue) {
|
||||
// Move the slider to the right
|
||||
slider.value = slider.maxValue;
|
||||
|
||||
desiredTime = f;
|
||||
pause = false;
|
||||
previousDesiredTime = desiredTime;
|
||||
}
|
||||
else {
|
||||
slider.value = f; // Will update the desiredTime value and extra things
|
||||
}
|
||||
|
||||
timeInput.Text = f.ToString("0.00");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user