1
0
mirror of https://github.com/originalnicodr/CinematicUnityExplorer.git synced 2025-07-18 17:38:01 +08:00

65 Commits

Author SHA1 Message Date
e3584654a6 Added advance setting to select target camera (#108)
Some checks failed
Build CinematicUnityExplorer / build_dotnet (push) Has been cancelled
Build CinematicUnityExplorer / build_connector (push) Has been cancelled
* Added a dropdown to select the camera you want to target. Also improved rendering tweaks error handling.

* Added settings to optionally render the target camera dropdown and to remember the selected target camera across sessions. Also renamed the CUE camera objects for standardization (and filtering) sake.

* Refactored Preferred_Target_Camera into a path to the cam object to make it more robust.

* Added missing config values type changes.

* Refactor build guards by using reflection to cover all builds.

* Replaced Array.IndexOf by a manual iteration because it wasn't working for some reason. Also polished some vertical spaces.
2025-06-20 18:33:11 -03:00
388c9fbc29 Added a new field on the freecam panel to add components to disable when enabling the freecam.
Some checks failed
Build CinematicUnityExplorer / build_dotnet (push) Has been cancelled
Build CinematicUnityExplorer / build_connector (push) Has been cancelled
2025-03-02 14:51:51 -03:00
2f35b9d2c7 Made the slow-down hotkey tilt the camera 90 degrees instead, to tilt vertically in games that don't allow vertical ARs.
Some checks are pending
Build CinematicUnityExplorer / build_dotnet (push) Waiting to run
Build CinematicUnityExplorer / build_connector (push) Waiting to run
2025-03-01 10:58:05 -03:00
b82cc62186 Destroy cinemachine in Cloned Freecam mode as we won't be enabling it back again since the cam will get destroyed anyway, given that in some games the cinemachine cam would still bother us even if it's disabled.
Some checks failed
Build CinematicUnityExplorer / build_dotnet (push) Has been cancelled
Build CinematicUnityExplorer / build_connector (push) Has been cancelled
2025-02-26 00:45:15 -03:00
9429b84cfa Made lights in the lights manager panel and effects in the postprocessing effects panel prioritize focusing on already opened inspector tabs when trying to inspect the corresponding object.
Some checks failed
Build CinematicUnityExplorer / build_dotnet (push) Has been cancelled
Build CinematicUnityExplorer / build_connector (push) Has been cancelled
2025-02-23 22:56:09 -03:00
2719c69753 Added new safeguards when adding and removing event handlers to the rendering pipeline. Also changed the "reset camera on new freecam sessions" option default value to true, as its more intuitive.
Some checks are pending
Build CinematicUnityExplorer / build_dotnet (push) Waiting to run
Build CinematicUnityExplorer / build_connector (push) Waiting to run
2025-02-23 20:27:50 -03:00
9144f89e32 Add Adjust ArrowSize (#101)
Some checks failed
Build CinematicUnityExplorer / build_dotnet (push) Has been cancelled
Build CinematicUnityExplorer / build_connector (push) Has been cancelled
Co-authored-by: originalnicodr <niconavall@gmail.com>
2025-02-07 19:39:21 -03:00
3bc1268444 Updated GitHub action upload-artifact from v3 to v4
Some checks failed
Build CinematicUnityExplorer / build_dotnet (push) Has been cancelled
Build CinematicUnityExplorer / build_connector (push) Has been cancelled
2025-02-02 18:48:04 -03:00
4c100daf83 Fixed bug when disabling orthographic property on freecam 2025-02-02 18:42:30 -03:00
e7d591176e Fixed poses not being saved correctly for some .NET versions.
Some checks failed
Build CinematicUnityExplorer / build_dotnet (push) Has been cancelled
Build CinematicUnityExplorer / build_connector (push) Has been cancelled
2025-01-20 13:21:09 -03:00
ca46607c49 Automatically update the default freecam every time a new freecam type is selected. 2025-01-20 11:51:50 -03:00
7fb67b4e28 Added steps on the freecam enabling to enable/disable the orthographic property, as I believe it doesn't make sense to freecam while that is enabled
(might still be worth making this a setting in the future tho)
2025-01-20 11:21:03 -03:00
c0f42ad983 Enhanced the camera selection logic on the world mouse inspector. Also fixed the camera coordinates not being displayed properly.
Based on the work of @p1xel8ted on https://github.com/yukieiji/UnityExplorer/pull/35.
2025-01-20 10:56:35 -03:00
6111c1743b Avoid processing freecam input movement when there is a focused input field. 2024-12-15 21:13:20 -03:00
b03d81052b Update README.md 2024-11-30 14:28:19 -03:00
173eeb993a Increased version number. 2024-11-30 13:15:09 -03:00
07971489c4 Added checks to avoid the look-at feature from getting in the way of the connector commands. 2024-11-30 12:57:02 -03:00
2604dd8286 Added look at feature in the freecam panel. 2024-11-30 12:41:37 -03:00
ca81bcc5dc Fixed loading cam path relative to the camera with a different rotation. 2024-11-30 11:37:27 -03:00
b95178775c Fix get value from property with reflection. 2024-11-30 09:47:05 -03:00
3928cdc4fe fix: save default lod bias 2024-11-30 09:38:00 -03:00
81d15270fe Fixed mod UI scaling on wide ARs (won't work well on tall ARs). 2024-11-29 09:35:25 -03:00
2b557a8c23 Fixed standalone build 2024-11-26 20:50:43 -03:00
462201534d Added option to reset the freecam transform between freecam sessions. 2024-11-26 20:44:17 -03:00
f3b154658c Added a button in the game object inspector panel to move the object to the camera position (and rotation). Also minor UI adjustment on the CamPaths panel. 2024-11-25 19:18:37 -03:00
7ab907d7b0 Added camera path serialization, and changed extension of saved poses. 2024-11-25 18:38:55 -03:00
736ebdfa1b Added new freecam methods, one for cloning the gameplay camera and another one for replacing the worldToCameraMatrix of the gameplay camera. 2024-11-23 11:52:00 -03:00
c9aa93a6a4 Add FlattenHierarchy to find special case of singletons (#90)
Add FlattenHierarchy to find special case of singletons
2024-10-08 20:46:28 -03:00
78bf2fc634 Add support to 32bit igcsdof. (#91)
Add support to 32bit igcsdof.
2024-10-05 12:56:40 -03:00
6c493aaf7d Moved the bone object inspect button to the right and put the bone name on a label so it could resize correctly. 2024-08-08 18:26:33 -03:00
b6d459b664 Search AnimatorCell-associated meshes again when refreshing the animator's list. Refreshes the bone panel with the new meshes. 2024-08-07 19:39:02 -03:00
7a580d12ed Update individual cell mesh togglers if the global toggler is interacted with. 2024-08-04 21:33:05 -03:00
8f2ec19660 Refactored the animator player toggler into a play/pause button for better UI/UX. Also did some minor campaths panel UI improvements. 2024-08-04 21:14:44 -03:00
f855f6d430 Added search function in the bones panel. 2024-07-28 21:25:13 -03:00
a6446c50e4 Added a helper method to copy cached transform data into a transform and fixed a bug when looking for bones for the bones panel. 2024-07-28 20:31:37 -03:00
f3b48bf069 Added serialization support for poses, which allows you to save and load custom poses. 2024-07-28 19:10:40 -03:00
396e653495 Refactored BoneCell to get rid of a legacy member. Also added TODO on BonesPanel. 2024-07-27 14:52:10 -03:00
68c482df22 Added a label next to the expand/collapse button and increased the minimum height of the bones panel. 2024-07-27 14:40:43 -03:00
a868cc74ac Refactored bone panel to mimic a hierarchical-like structure, so the main bones are displayed first, and you can get more granular control by expanding their children. 2024-07-27 13:24:52 -03:00
343ccb9474 Used base Exception on UnityIGCSConnector despite good practices to increase game compatibility. 2024-07-27 13:15:34 -03:00
48fec535fd When writing down a bigger timescale than the maximum move the slider to the right. 2024-07-21 18:44:13 -03:00
5e9abd33b4 Added setting to toggle menu UI autoscaling off, and allow the timescale input field to accept values above the slider maximum. 2024-07-18 09:41:49 -03:00
6ebf19dfa0 Update README.md 2024-07-18 08:57:44 -03:00
cc34a50e21 Revert "Add controller support (#76)"
This reverts commit b785f76ba3.
2024-07-18 08:57:22 -03:00
954b6f4f1c Updated readme 2024-06-30 16:55:05 -03:00
b785f76ba3 Add controller support (#76)
Added controller support.
2024-06-30 15:51:58 -03:00
d2d22de66c Fixed enabled in BonesPanel, refactored SetMeshesEnabled to not use reflection and to disable meshes consistently with the BonesManager. 2024-06-20 18:52:25 -03:00
af2ad057fb Added mesh togglers for each bone, added non-bone meshes to the BoneManager, captured multiple skinnedMeshRenderers, and restored the mesh scale when enabling the animator again. 2024-06-20 18:30:31 -03:00
ca726e0ae7 Added mesh toggle to the animator panel, and reworked the panel UI. 2024-06-20 00:01:29 -03:00
a02460be33 Added buttons to restore individual bones in the BonesManager. 2024-06-19 18:42:46 -03:00
30b9e6e387 Increased CUE version. 2024-06-18 21:36:16 -03:00
58f477ed0d Adjusted space between Animator cells. 2024-06-18 21:35:06 -03:00
fd672615be Added animation sliders to more precisely choose the animation frame in wich you want to set up a character. 2024-06-18 21:26:18 -03:00
f8886700d9 Initialized slider increments with values that make more sense for each one. 2024-06-18 19:49:26 -03:00
dae6930b43 Turn off the animator toggle on the bones manager when modifying one of the sliders or input fields. 2024-06-18 18:42:57 -03:00
d9ebe5819c Added a "bones panel" in the animator to access and control the bones of a character. 2024-06-18 09:41:07 -03:00
171ee3f0d8 Append new animations instead of discarding the previous ones when refreshing the animators. 2024-06-12 19:00:19 -03:00
448ab66f5b Merge branch 'master' of github.com:originalnicodr/UnityExplorer 2024-06-12 18:35:46 -03:00
b090f68732 Properly disable CinemachineBrain on freecam on IL2CPP games. 2024-06-12 18:35:39 -03:00
13dcef668f IGCSDOF: Go back to the original position after an EndSession. (#74)
* (igcsdof) remove unnecessary buffer.

* (igcsdof) fix: go back to the original positon.

* Move the check of EndSession to UnityIGCSConnector class
2024-05-19 23:14:50 -03:00
1d5a7b8885 Fixed icon extension. 2024-05-08 01:02:37 -03:00
54f20fde95 Made the rest of the panels initialize correctly if UnityIGCSConnector failed to initialize, updated the readme with IGCDOF info, resized icon, Increased version number 2024-05-08 01:01:30 -03:00
66773b300f Improved post-processing logging messages and improved compatibility with Hellpoint and potentially more games. 2024-05-05 23:36:21 -03:00
271261307d actions: Update dotnet.yml (#73)
Add support for C/C++ in the pipeline and upload the newer igcs connector.

It was necessary to create two different build scripts because for some reason
when you install the msvc compiler in a github action everything Just Breaks.

Then we create two different jobs to make sure they're independent instances of
containers that don't mess with other's environments.
2024-05-05 20:27:18 -03:00
f557d3de88 IGCS Support for CUE (#72)
* Initial work on supporting IGCS

* feat: Added callback when starting session, adding missing func.

* chore: tidy up math and cleanup.

* chore: tidy up some verbose code

* Added more callbacks, fixed math.

* fix: use queue command system instead of directly writing to the camera.

* lock camera while igcsdof is active.

* build system shenanigans

Now we can build the project just fine by building separately the C
project and the C# project.

* small syntax changes.

learning c# and stuff

* Refactor executeCameraCommand and improve thread-safety in FreeCamPanel

* woops, correct order of arguments for MessageBoxA

* fix: move the ourCamera check where it's actually needed.

* chore: formatting c file

* rewrite: move all igcsdof logic to its own class.

Since CUE *must* work without igcsdof, I thought it would be convenient
to handle all the logic regarding igcsdof in its own class. The
initialization is a bit of a mess because since most of what it's on
FreeCam is static I had to do some trickery.

I wanted to specifically avoid calling LoadLibrary every time so I
thought on only initializing one and having an internal flag either the
thing loaded correctly or not (`isValid`).

* fix: Pin callbacks to avoid garbage collection

* chore: removed unused code

* chore: fix typo.

* nit: more idiomatic C# usage.

Just applied what rider suggested =)

* fix: remove unnecesary GCHandle, store delegates.

* feat: Update IGCS dof camera status accordingly.

Before, we just left igcsdof always enabled, now we handle the cases
properly, avoiding the posibility of the user using igcsdof with the
freecam disabled.

* nit: fix comments and added some to UnityIGCSConnector.

* feat: Get the transform reference earlier for perf™️

* fix: Properly handle an initialization error.

* chore: remove overly verbose debugging

* fix: make sure we start with a clear queue after we end a session.

This was what maybe was causing the misaligning after you cancel a
session since we never checked if the queue was emptied by the code.

* fix: properly cast a function pointer to use what it returns.
2024-05-04 20:05:12 -03:00
39 changed files with 2962 additions and 260 deletions

View File

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

View File

@ -136,6 +136,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,6 +148,10 @@ 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.
### 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 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!
@ -188,6 +193,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 +210,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 +239,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.
![IGCSDOF demo](img/igcsdof_demo.gif)
[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).

View File

@ -1,6 +1,6 @@
{
"name": "com.originalnicodr.cinematicunityexplorer",
"version": "1.0.0",
"version": "1.3.0",
"displayName": "CinematicUnityExplorer",
"description": "UnityExplorer fork focused on providing tools for creating marketing material for Unity games.",
"unity": "2017.1",

View File

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

View File

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

View File

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

View File

@ -0,0 +1,97 @@
// dllmain.cpp : Defines the entry point for the DLL application.
#include <Windows.h>
#include <stdint.h>
#include <stdio.h>
#define EXPOSE __declspec(dllexport)
// The function that will be the one called from the C# side, that will update the corresponding values.
typedef void (*MoveCameraCallback)(float, float, float, int);
typedef void (*SessionCallback)(void);
typedef uint8_t* (*GetCameraDataFunc)(void);
MoveCameraCallback GlobalCallback = NULL;
SessionCallback GlobalStartSession = NULL;
SessionCallback GlobalEndSession = NULL;
// There are things that only needs to be run once.
static int first_initialization = 1;
EXPOSE int __cdecl IGCS_StartScreenshotSession(uint8_t _ignore) {
if (GlobalStartSession) {
GlobalStartSession();
printf("Called StartSession\n");
}
return 0;
}
EXPOSE void __cdecl IGCS_EndScreenshotSession() {
GlobalEndSession();
printf("Called EndSession\n");
}
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;
// Load IGCS
#ifdef _M_IX86
HMODULE igcs = LoadLibraryA("IgcsConnector.addon32");
#else
HMODULE igcs = LoadLibraryA("IgcsConnector.addon64");
#endif
if (!igcs) {
MessageBoxA(
NULL,
"IgcsConnector.addon64 was not found, make sure it is in the same directory as the executable.",
"Unable to find IgcsConnector",
MB_OK | MB_ICONERROR);
return NULL;
}
FARPROC cameraToolsFunction = GetProcAddress(igcs, "connectFromCameraTools");
GetCameraDataFunc getCameraData = (GetCameraDataFunc)GetProcAddress(igcs, "getDataFromCameraToolsBuffer");
if (first_initialization) {
cameraToolsFunction();
first_initialization = 0;
}
// TODO: move this where it belongs. Maybe at some point we should actually fill in the data.
uint8_t* cameraData = getCameraData();
cameraData[0] = 0;
printf("Camera connected!\n");
return cameraData;
}
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;
}

View File

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

6
build_connector.ps1 Normal file
View File

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

BIN
img/icon_big.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.1 MiB

BIN
img/igcsdof_demo.gif Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.7 MiB

View File

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

View File

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

View File

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

View File

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

View File

@ -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", "▲");

View File

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

View File

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

View File

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

View File

@ -0,0 +1,155 @@
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>;
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 void 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 CameraStatus = IntPtr.Zero;
public void UpdateFreecamStatus(bool enabled)
{
if (CameraStatus == IntPtr.Zero) return;
Marshal.WriteByte(CameraStatus, enabled ? (byte)0x1 : (byte)0x0);
}
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 void StartSession()
{
_isActive = true;
}
// 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 void EndSession()
{
endSessionPosition = position;
position = null;
_isActive = false;
lock (commands)
commands.Clear();
}
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));
CameraStatus = initFunc((MoveCameraCallback)delegates[0], (SessionCallback)delegates[1], (SessionCallback)delegates[2]);
if (CameraStatus == 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;
}
}
}

View File

@ -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> Advanced_Freecam_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);
Advanced_Freecam_Selection = new("Advanced Freecam Selection",
"Enables certain advanced settings on the Freecam panel, in case the user can't get the freecam to work properly (requires game reset).",
false);
Pause = new("Pause",
"Toggle the pause of the game.",
KeyCode.PageUp);
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.New);
Custom_Components_To_Disable = new("Custom components to disable",
"List of custom components to disable when enabling the freecam (gets automatically updated when editing it from the freecam panel).",
"");
Preferred_Target_Camera = new("Preferred Target Camera",
"The camera that will be targeted by the freecam methods.\n" +
"Only used when Advanced Freecam Selection is enabled.",
"\\");
}
}
}

View File

@ -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.3.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;
}
}
}

View File

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

View File

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

View File

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

View File

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

View File

@ -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 Advanced_Freecam_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.Advanced_Freecam_Selection.Value = this.Advanced_Freecam_Selection;
ConfigManager.Pause.Value = this.Pause;
ConfigManager.Frameskip.Value = this.Frameskip;

View File

@ -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();
}
}

View File

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

385
src/UI/Panels/BonesPanel.cs Normal file
View File

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

View File

@ -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>
@ -51,6 +48,8 @@ namespace UnityExplorer.UI.Panels
Toggle visualizePathToggle;
public GameObject pathVisualizer;
Toggle closedLoopToggle;
InputFieldRef TimeInput;
bool unpauseOnPlay;
bool waitBeforePlay;
@ -65,6 +64,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;
@ -107,23 +109,22 @@ 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.ButtonText.fontSize = 20;
AddNode.OnClick += AddNode_OnClick;
ButtonRef DeletePath = UIFactory.CreateButton(horiGroup, "DeletePath", "Clear");
@ -134,14 +135,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 +209,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 +239,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 +250,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 +266,7 @@ namespace UnityExplorer.UI.Panels
tensionCatmullRomSlider.value = tensionCatmullRomValue;
MaybeRedrawPath();
EventSystemHelper.SetSelectedGameObject(null);
}
private void ToggleVisualizePath(bool enable){
@ -365,7 +386,7 @@ namespace UnityExplorer.UI.Panels
CatmullRom.CatmullRomPoint point = new CatmullRom.CatmullRomPoint(
FreeCamPanel.GetCameraPosition(),
FreeCamPanel.GetCameraRotation(),
FreeCamPanel.ourCamera.fieldOfView
FreeCamPanel.GetFreecam().fieldOfView
);
controlPoints.Add(point);
@ -377,7 +398,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 +515,92 @@ 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);
}
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.StartStopButton_OnClick();
}
controlPoints.Clear();
// The first point will be the camera position, and we adapt the following points from there
AddNode_OnClick();
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

File diff suppressed because it is too large Load Diff

View File

@ -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){

View File

@ -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){

View File

@ -183,7 +183,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 +238,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)

View File

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

View File

@ -74,13 +74,26 @@ namespace UnityExplorer.UI.Widgets
{
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");
}
}