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

45 Commits

Author SHA1 Message Date
8c2c06aff4 Replaced Array.IndexOf by a manual iteration because it wasn't working for some reason. Also polished some vertical spaces. 2025-06-20 18:08:15 -03:00
676f412a60 Refactor build guards by using reflection to cover all builds. 2025-06-20 17:21:13 -03:00
7785f9c51a Added missing config values type changes. 2025-06-20 15:41:57 -03:00
a9f89a0341 Refactored Preferred_Target_Camera into a path to the cam object to make it more robust. 2025-06-18 00:59:44 -03:00
98fb915be1 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. 2025-06-16 16:42:29 -03:00
f0f5d98922 Added a dropdown to select the camera you want to target. Also improved rendering tweaks error handling. 2025-06-16 12:48:34 -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
31 changed files with 1849 additions and 264 deletions

View File

@ -29,79 +29,79 @@ jobs:
# Upload artifacts
- name: Upload BepInEx.IL2CPP
uses: actions/upload-artifact@v3
uses: actions/upload-artifact@v4
with:
name: CinematicUnityExplorer.BepInEx.IL2CPP.zip
path: ./Release/CinematicUnityExplorer.BepInEx.IL2CPP/
- name: Upload BepInEx.IL2CPP.CoreCLR
uses: actions/upload-artifact@v3
uses: actions/upload-artifact@v4
with:
name: CinematicUnityExplorer.BepInEx.IL2CPP.CoreCLR.zip
path: ./Release/CinematicUnityExplorer.BepInEx.IL2CPP.CoreCLR/
- name: Upload BepInEx.Unity.IL2CPP.CoreCLR
uses: actions/upload-artifact@v3
uses: actions/upload-artifact@v4
with:
name: CinematicUnityExplorer.BepInEx.Unity.IL2CPP.CoreCLR.zip
path: ./Release/CinematicUnityExplorer.BepInEx.Unity.IL2CPP.CoreCLR/
- name: Upload BepInEx5.Mono
uses: actions/upload-artifact@v3
uses: actions/upload-artifact@v4
with:
name: CinematicUnityExplorer.BepInEx5.Mono.zip
path: ./Release/CinematicUnityExplorer.BepInEx5.Mono/
- name: Upload BepInEx6.Mono
uses: actions/upload-artifact@v3
uses: actions/upload-artifact@v4
with:
name: CinematicUnityExplorer.BepInEx6.Mono.zip
path: ./Release/CinematicUnityExplorer.BepInEx6.Mono/
- name: Upload BepInEx6.Unity.Mono
uses: actions/upload-artifact@v3
uses: actions/upload-artifact@v4
with:
name: CinematicUnityExplorer.BepInEx6.Unity.Mono.zip
path: ./Release/CinematicUnityExplorer.BepInEx6.Unity.Mono/
- name: Upload MelonLoader.IL2CPP
uses: actions/upload-artifact@v3
uses: actions/upload-artifact@v4
with:
name: CinematicUnityExplorer.MelonLoader.IL2CPP.zip
path: ./Release/CinematicUnityExplorer.MelonLoader.IL2CPP/
- name: Upload MelonLoader.IL2CPP.net6preview
uses: actions/upload-artifact@v3
uses: actions/upload-artifact@v4
with:
name: CinematicUnityExplorer.MelonLoader.IL2CPP.net6preview.zip
path: ./Release/CinematicUnityExplorer.MelonLoader.IL2CPP.net6preview/
- name: Upload MelonLoader.IL2CPP.CoreCLR
uses: actions/upload-artifact@v3
uses: actions/upload-artifact@v4
with:
name: CinematicUnityExplorer.MelonLoader.IL2CPP.CoreCLR.zip
path: ./Release/CinematicUnityExplorer.MelonLoader.IL2CPP.CoreCLR/
- name: Upload MelonLoader.Mono
uses: actions/upload-artifact@v3
uses: actions/upload-artifact@v4
with:
name: CinematicUnityExplorer.MelonLoader.Mono.zip
path: ./Release/CinematicUnityExplorer.MelonLoader.Mono/
- name: Upload Standalone.IL2CPP
uses: actions/upload-artifact@v3
uses: actions/upload-artifact@v4
with:
name: CinematicUnityExplorer.Standalone.IL2CPP.zip
path: ./Release/CinematicUnityExplorer.Standalone.IL2CPP/
- name: Upload Standalone.Mono
uses: actions/upload-artifact@v3
uses: actions/upload-artifact@v4
with:
name: CinematicUnityExplorer.Standalone.Mono.zip
path: ./Release/CinematicUnityExplorer.Standalone.Mono/
- name: Upload Editor
uses: actions/upload-artifact@v3
uses: actions/upload-artifact@v4
with:
name: CinematicUnityExplorer.Editor.zip
path: ./UnityEditorPackage/
@ -127,7 +127,13 @@ jobs:
run: ./build_connector.ps1
- name: Upload Unity IGCS Connector
uses: actions/upload-artifact@v3
uses: actions/upload-artifact@v4
with:
name: UnityIGCSConnector.dll
path: ./Release/UnityIGCSConnector.dll
- name: Upload Unity IGCS Connector 32bit
uses: actions/upload-artifact@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.
@ -212,6 +218,8 @@ Alongside all of this, you can also open each character game object by clicking
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.

View File

@ -1,6 +1,6 @@
{
"name": "com.originalnicodr.cinematicunityexplorer",
"version": "1.2.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

@ -76,13 +76,19 @@
<PostBuildEventUseInBuild>false</PostBuildEventUseInBuild>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
<TargetName>UnityIGCSConnector</TargetName>
<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</TargetName>
<TargetName>UnityIGCSConnector.32</TargetName>
<OutDir>$(SolutionDir)..\$(Configuration)\</OutDir>
<PostBuildEventUseInBuild>false</PostBuildEventUseInBuild>
</PropertyGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
<ClCompile>
@ -90,7 +96,7 @@
<SDLCheck>true</SDLCheck>
<PreprocessorDefinitions>WIN32;_DEBUG;UNITYIGCSCONNECTOR_EXPORTS;_WINDOWS;_USRDLL;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<ConformanceMode>true</ConformanceMode>
<PrecompiledHeader>Use</PrecompiledHeader>
<PrecompiledHeader>NotUsing</PrecompiledHeader>
<PrecompiledHeaderFile>pch.h</PrecompiledHeaderFile>
</ClCompile>
<Link>
@ -107,7 +113,7 @@
<SDLCheck>true</SDLCheck>
<PreprocessorDefinitions>WIN32;NDEBUG;UNITYIGCSCONNECTOR_EXPORTS;_WINDOWS;_USRDLL;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<ConformanceMode>true</ConformanceMode>
<PrecompiledHeader>Use</PrecompiledHeader>
<PrecompiledHeader>NotUsing</PrecompiledHeader>
<PrecompiledHeaderFile>pch.h</PrecompiledHeaderFile>
</ClCompile>
<Link>
@ -164,4 +170,4 @@
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
<ImportGroup Label="ExtensionTargets">
</ImportGroup>
</Project>
</Project>

View File

@ -19,7 +19,7 @@ SessionCallback GlobalEndSession = NULL;
// There are things that only needs to be run once.
static int first_initialization = 1;
EXPOSE int IGCS_StartScreenshotSession(uint8_t _ignore) {
EXPOSE int __cdecl IGCS_StartScreenshotSession(uint8_t _ignore) {
if (GlobalStartSession) {
GlobalStartSession();
printf("Called StartSession\n");
@ -27,12 +27,12 @@ EXPOSE int IGCS_StartScreenshotSession(uint8_t _ignore) {
return 0;
}
EXPOSE void IGCS_EndScreenshotSession() {
EXPOSE void __cdecl IGCS_EndScreenshotSession() {
GlobalEndSession();
printf("Called EndSession\n");
}
EXPOSE uint8_t* U_IGCS_Initialize(MoveCameraCallback cb, SessionCallback start_cb, SessionCallback end_cb) {
EXPOSE uint8_t* __cdecl U_IGCS_Initialize(MoveCameraCallback cb, SessionCallback start_cb, SessionCallback end_cb) {
AllocConsole();
printf("Initializing callback\n");
GlobalCallback = cb;
@ -40,7 +40,11 @@ EXPOSE uint8_t* U_IGCS_Initialize(MoveCameraCallback cb, SessionCallback start_c
GlobalEndSession = end_cb;
// Load IGCS
#ifdef _M_IX86
HMODULE igcs = LoadLibraryA("IgcsConnector.addon32");
#else
HMODULE igcs = LoadLibraryA("IgcsConnector.addon64");
#endif
if (!igcs) {
MessageBoxA(
@ -69,9 +73,9 @@ EXPOSE uint8_t* U_IGCS_Initialize(MoveCameraCallback cb, SessionCallback start_c
}
EXPOSE void IGCS_MoveCameraPanorama() {}
EXPOSE void __cdecl IGCS_MoveCameraPanorama() {}
EXPOSE void IGCS_MoveCameraMultishot(float step_left, float step_up, float fov, int from_start) {
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;
}

View File

@ -1,3 +1,6 @@
# ----------- Build UnityIGCSConnector ------------
msbuild.exe UnityIGCSConnector/UnityIGCSConnector.sln -p:Configuration=Release
msbuild.exe UnityIGCSConnector/UnityIGCSConnector.sln -p:Configuration=Release -p:Platform=x64
msbuild.exe UnityIGCSConnector/UnityIGCSConnector.sln -p:Configuration=Release -p:Platform=x86

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,7 +20,7 @@ namespace UnityExplorer.UI.Panels
public AnimatorPlayer animatorPlayer;
public Toggle IgnoreMasterToggle;
public Toggle AnimatorToggle;
public AnimatorPausePlayButton animatorToggler;
public Toggle MeshToggle;
public ButtonRef inspectButton;
public Dropdown animatorDropdown;
@ -42,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();
}
@ -95,10 +96,8 @@ namespace UnityExplorer.UI.Panels
UIFactory.SetLayoutElement(inspectButton.GameObject, minWidth: 200, minHeight: 25);
inspectButton.OnClick += () => InspectorManager.Inspect(animatorPlayer.animator.gameObject);
GameObject AnimatorToggleObj = UIFactory.CreateToggle(UIRoot, "AnimatorToggle", out AnimatorToggle, out Text animatorToggleText);
UIFactory.SetLayoutElement(AnimatorToggleObj, minHeight: 30);
AnimatorToggle.isOn = animatorPlayer != null && animatorPlayer.animator.speed == 1;
AnimatorToggle.onValueChanged.AddListener(EnableAnimation);
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);
@ -165,7 +164,7 @@ namespace UnityExplorer.UI.Panels
animationTimeline.maxValue = 1;
animationTimeline.onValueChanged.AddListener((float val) => {
animatorPlayer.PlayOverridingAnimation(val);
AnimatorToggle.isOn = false;
animatorToggler.isOn = false;
});
playButton = UIFactory.CreateButton(UIRoot, "PlayButton", "Play", new Color(0.2f, 0.26f, 0.2f));
@ -188,6 +187,10 @@ 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;

View File

@ -48,8 +48,12 @@ namespace UnityExplorer.UI.Panels
this.favAnimations = new List<IAnimationClip>();
this.skinnedMeshes.AddRange(this.animator.wrappedObject.gameObject.GetComponentsInChildren<SkinnedMeshRenderer>(false));
this.extraMeshes.AddRange(this.animator.wrappedObject.gameObject.GetComponentsInChildren<MeshRenderer>(false));
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
@ -125,25 +129,30 @@ namespace UnityExplorer.UI.Panels
}
}
private List<Transform> GetMeshes(){
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, GetMeshes(), animator);
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;
@ -153,6 +162,11 @@ namespace UnityExplorer.UI.Panels
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

View File

@ -8,7 +8,7 @@ namespace UnityExplorer.UI.Panels
{
public class BonesCell : ICell
{
public Transform bone;
public BoneTree boneTree;
public TransformControls transformControls;
ComponentControl positionControl;
@ -16,7 +16,13 @@ namespace UnityExplorer.UI.Panels
ComponentControl scaleControl;
public AxisComponentControl CurrentSlidingAxisControl { get; set; }
public BonesManager Owner;
private ButtonRef inspectButton;
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;
@ -27,20 +33,69 @@ namespace UnityExplorer.UI.Panels
public void Enable() => UIRoot.SetActive(true);
public void Disable() => UIRoot.SetActive(false);
public void SetBone(Transform bone, BonesManager bonesManager){
this.bone = bone;
inspectButton.ButtonText.text = bone.name;
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("BoneCell", parent, new Vector2(25, 25));
Rect = UIRoot.GetComponent<RectTransform>();
UIFactory.SetLayoutGroup<VerticalLayoutGroup>(UIRoot, false, false, true, true, 3);
UIFactory.SetLayoutElement(UIRoot, minHeight: 25, minWidth: 50, flexibleWidth: 9999);
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);
GameObject header = UIFactory.CreateUIObject("BoneHeader", UIRoot);
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);
@ -48,31 +103,45 @@ namespace UnityExplorer.UI.Panels
UIFactory.SetLayoutElement(MeshToggleObj, minHeight: 30);
MeshToggle.onValueChanged.AddListener(SetBoneEnabled);
inspectButton = UIFactory.CreateButton(header, "InspectButton", "");
UIFactory.SetLayoutElement(inspectButton.GameObject, minWidth: 150, minHeight: 25);
inspectButton.OnClick += () => InspectorManager.Inspect(bone.gameObject);
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, UIRoot, "Local Position", TransformType.LocalPosition, 0.01f);
rotationControl = ComponentControl.Create(this, UIRoot, "Rotation", TransformType.Rotation, 10f);
scaleControl = ComponentControl.Create(this, UIRoot, "Scale", TransformType.Scale, 0.1f);
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(bone.name);
Owner.RestoreBoneState(boneTree.obj.name);
}
private void SetBoneEnabled(bool value){
bone.gameObject.SetActive(value);
boneTree.obj.SetActive(value);
}
// TransformControls-like functions
@ -103,7 +172,7 @@ namespace UnityExplorer.UI.Panels
public void AxisControlOperation(float value, ComponentControl parent, int axis)
{
Transform transform = bone;
Transform transform = boneTree.obj.transform;
Vector3 vector = parent.Type switch
{
@ -146,7 +215,7 @@ namespace UnityExplorer.UI.Panels
public class ComponentControl
{
public BonesCell Owner { get; }
public Transform Transform => Owner.bone;
public Transform Transform => Owner.boneTree.obj.transform;
public TransformType Type { get; }
public InputFieldRef MainInput { get; }
@ -275,7 +344,7 @@ namespace UnityExplorer.UI.Panels
uniformScaleControl.minValue = 0.0001f;
uniformScaleControl.maxValue = 10;
uniformScaleControl.value = 1;
uniformScaleControl.onValueChanged.AddListener((float val) => { cell.bone.localScale = new Vector3(val, val, val); });
uniformScaleControl.onValueChanged.AddListener((float val) => { cell.boneTree.obj.transform.localScale = new Vector3(val, val, val); });
}
return control;

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

@ -22,8 +22,13 @@ namespace CinematicUnityExplorer.Cinematic
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.
@ -118,10 +123,11 @@ namespace CinematicUnityExplorer.Cinematic
public UnityIGCSConnector()
{
var lib = NativeMethods.LoadLibrary(@"UnityIGCSConnector.dll");
var libraryName = IntPtr.Size == 8 ? @"UnityIGCSConnector.dll" : @"UnityIGCSConnector.32.dll";
var lib = NativeMethods.LoadLibrary(libraryName);
if (lib == IntPtr.Zero)
{
ExplorerCore.LogWarning("UnityIGCSConnector.dll was not found so IGCSDof will not be available");
ExplorerCore.LogWarning($"{libraryName} was not found so IGCSDof will not be available");
return;
}
@ -139,7 +145,8 @@ namespace CinematicUnityExplorer.Cinematic
CameraStatus = initFunc((MoveCameraCallback)delegates[0], (SessionCallback)delegates[1], (SessionCallback)delegates[2]);
if (CameraStatus == IntPtr.Zero){
throw new InvalidDataException("IGCSDof returned an invalid pointer which means something went wrong");
// 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
{
@ -30,8 +31,10 @@ namespace UnityExplorer.Config
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;
@ -59,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();
@ -164,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;'",
@ -175,7 +182,7 @@ namespace UnityExplorer.Config
"Seperate signatures with a semicolon ';'.\r\n" +
"For example, to blacklist Camera.main, you would add 'UnityEngine.Camera.main;'",
"");
Reflection_Hide_NativeInfoPtrs = new("Hide NativeMethodInfoPtr_s and NativeFieldInfoPtr_s",
"Use this to blacklist NativeMethodPtr_s and NativeFieldInfoPtrs_s from the class inspector, mainly to reduce clutter.\r\n" +
"For example, this will hide 'Class.NativeFieldInfoPtr_value' for the field 'Class.value'.",
@ -185,14 +192,22 @@ namespace UnityExplorer.Config
"Especially useful when running games in high resolutions and you are having a hard time reading the mods menu (requires restart).",
true);
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.",
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);
@ -246,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);
@ -294,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.2.0";
public const string VERSION = "1.3.0";
public const string AUTHOR = "originalnicodr, Sinai, yukieiji";
public const string GUID = "com.originalnicodr.cinematicunityexplorer";
@ -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

@ -30,7 +30,7 @@ namespace UnityExplorer.UI.Panels
public override bool NavButtonWanted => true;
public override bool ShouldSaveActiveState => true;
Toggle masterAnimatorToggle;
AnimatorPausePlayButton masterAnimatorPlayer;
Toggle masterMeshToggle;
private static ScrollPool<AnimatorCell> animatorScrollPool;
@ -52,7 +52,7 @@ namespace UnityExplorer.UI.Panels
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();
@ -76,6 +76,10 @@ namespace UnityExplorer.UI.Panels
animators[i].animations.Add(animationClip);
}
newAnimators[newAnimatorsIndex] = animators[i];
// Reset meshes
newAnimators[newAnimatorsIndex].SearchMeshes();
newAnimators[newAnimatorsIndex].MaybeResetBonesPanel();
}
}
}
@ -92,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();
@ -120,7 +126,7 @@ namespace UnityExplorer.UI.Panels
}
public void HotkeyToggleAnimators(){
masterAnimatorToggle.isOn = !masterAnimatorToggle.isOn;
masterAnimatorPlayer.isOn = !masterAnimatorPlayer.isOn;
}
// ~~~~~~~~ UI construction / callbacks ~~~~~~~~
@ -139,10 +145,11 @@ namespace UnityExplorer.UI.Panels
masterMeshToggle.onValueChanged.AddListener(value => MasterToggleMeshes(value));
masterMeshText.text = "Master Mesh Toggler";
GameObject animatorObj = UIFactory.CreateToggle(firstGroup, "Master Animation Toggle", out masterAnimatorToggle, out Text masterAnimatorText);
UIFactory.SetLayoutElement(animatorObj, minHeight: 25);
masterAnimatorToggle.onValueChanged.AddListener(value => MasterToggleAnimators(value));
masterAnimatorText.text = "Master Animator 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);
@ -193,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;
}
}
}
}

View File

@ -1,4 +1,6 @@
using UniverseLib.UI;
using UnityExplorer.Serializers;
using UniverseLib.UI;
using UniverseLib.UI.Models;
using UniverseLib.UI.Panels;
using UniverseLib.UI.Widgets.ScrollView;
@ -8,25 +10,75 @@ namespace UnityExplorer.UI.Panels
{
public override string Name => $"Bones Manager";
public override int MinWidth => 1000;
public override int MinHeight => 400;
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();
private ScrollPool<BonesCell> boneScrollPool;
public int ItemCount => bones.Count;
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}";
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)
@ -43,15 +95,59 @@ namespace UnityExplorer.UI.Panels
protected override void ConstructPanelContent()
{
skeletonName = UIFactory.CreateLabel(ContentRoot, $"SkeletonName", "");
UIFactory.SetLayoutElement(skeletonName.gameObject, minWidth: 100, minHeight: 25);
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;
GameObject turnOffAnimatorToggleObj = UIFactory.CreateToggle(ContentRoot, "Animator toggle", out turnOffAnimatorToggle, out Text turnOffAnimatorToggleText);
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);
@ -63,10 +159,7 @@ namespace UnityExplorer.UI.Panels
// 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){
CachedBonesTransform CachedBonesTransform = bonesOriginalState[bone.name];
bone.localPosition = CachedBonesTransform.position;
bone.localEulerAngles = CachedBonesTransform.angles;
bone.localScale = CachedBonesTransform.scale;
bonesOriginalState[bone.name].CopyToTransform(bone);
// We assume these were on before. If not we should save its state beforehand.
bone.gameObject.SetActive(true);
}
@ -83,25 +176,97 @@ namespace UnityExplorer.UI.Panels
{
foreach (Transform bone in bones){
if (bone.name == boneName){
CachedBonesTransform CachedBonesTransform = bonesOriginalState[boneName];
bone.localPosition = CachedBonesTransform.position;
bone.localEulerAngles = CachedBonesTransform.angles;
bone.localScale = CachedBonesTransform.scale;
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 >= bones.Count)
if (index >= boneTrees.Count)
{
cell.Disable();
return;
}
Transform bone = bones[index];
cell.SetBone(bone, this);
BoneTree boneTree = boneTrees[index];
cell.SetBoneTree(boneTree, this);
cell.UpdateTransformControlValues(true);
}
@ -119,17 +284,102 @@ namespace UnityExplorer.UI.Panels
}
}
struct CachedBonesTransform
public class BoneTree
{
public CachedBonesTransform(Vector3 position, Vector3 angles, Vector3 scale)
{
this.position = position;
this.angles = angles;
this.scale = scale;
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();
}
public readonly Vector3 position;
public readonly Vector3 angles;
public readonly Vector3 scale;
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

@ -48,6 +48,7 @@ namespace UnityExplorer.UI.Panels
Toggle HighLodToggle;
object qualitySettings = 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();
@ -122,9 +123,9 @@ namespace UnityExplorer.UI.Panels
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 : 1, null);
lodBias.SetValue(null, areHighLodsOn ? 10000 : _defaultLodBias, null);
}
private void ToggleAllMeshesCastAndRecieveShadows(bool enable){

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