refactor: cleanup and reorganize Unity scene and assets

main
uc-hoba 3 weeks ago
parent 84d253b227
commit b054a25e71
  1. 12791
      Assets/Main.unity
  2. 8
      Assets/Resources.meta
  3. 39
      Assets/Resources/Output.renderTexture
  4. 8
      Assets/Resources/Output.renderTexture.meta
  5. 55
      Assets/Settings/PC_Display.asset
  6. 8
      Assets/Settings/PC_Display.asset.meta
  7. 1
      Assets/Settings/PC_RPAsset.asset
  8. 8
      Assets/Temp.meta
  9. BIN
      Assets/Temp/Street_2.usdz
  10. 16
      Assets/Temp/Street_2.usdz.meta
  11. 174
      Assets/TiltShift/TiltShiftFeature.cs
  12. 267
      Packages/com.ultracombos.giant-museum/Editor/DebugSettingsGUIEditor.cs
  13. 2
      Packages/com.ultracombos.giant-museum/Editor/DebugSettingsGUIEditor.cs.meta
  14. 2
      Packages/com.ultracombos.giant-museum/Editor/UltraCombos.GiantMuseum.Editor.asmdef
  15. 430
      Packages/com.ultracombos.giant-museum/Runtime/DebugSettingsGUI.cs
  16. 2
      Packages/com.ultracombos.giant-museum/Runtime/DebugSettingsGUI.cs.meta
  17. 2
      Packages/com.ultracombos.giant-museum/Runtime/UltraCombos.GiantMuseum.Runtime.asmdef
  18. 1
      Packages/manifest.json
  19. 28
      Packages/packages-lock.json
  20. 8
      Unity-25014-ReadySetRide.slnx

File diff suppressed because it is too large Load Diff

@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: a7ef01be24be39e4f80509cc98fa5621
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

@ -0,0 +1,39 @@
%YAML 1.1
%TAG !u! tag:unity3d.com,2011:
--- !u!84 &8400000
RenderTexture:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_Name: Output
m_ImageContentsHash:
serializedVersion: 2
Hash: 00000000000000000000000000000000
m_IsAlphaChannelOptional: 0
serializedVersion: 6
m_Width: 1080
m_Height: 1920
m_AntiAliasing: 1
m_MipCount: -1
m_DepthStencilFormat: 94
m_ColorFormat: 48
m_MipMap: 0
m_GenerateMips: 1
m_SRGB: 0
m_UseDynamicScale: 0
m_UseDynamicScaleExplicit: 0
m_BindMS: 0
m_EnableCompatibleFormat: 1
m_EnableRandomWrite: 0
m_TextureSettings:
serializedVersion: 2
m_FilterMode: 1
m_Aniso: 0
m_MipBias: 0
m_WrapU: 1
m_WrapV: 1
m_WrapW: 1
m_Dimension: 2
m_VolumeDepth: 1
m_ShadowSamplingMode: 2

@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: de3934cb10406bb4daa84cabd7a9a4d8
NativeFormatImporter:
externalObjects: {}
mainObjectFileID: 8400000
userData:
assetBundleName:
assetBundleVariant:

@ -0,0 +1,55 @@
%YAML 1.1
%TAG !u! tag:unity3d.com,2011:
--- !u!114 &11400000
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 0}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: de640fe3d0db1804a85f9fc8f5cadab6, type: 3}
m_Name: PC_Display
m_EditorClassIdentifier:
debugShaders:
debugReplacementPS: {fileID: 4800000, guid: cf852408f2e174538bcd9b7fda1c5ae7, type: 3}
hdrDebugViewPS: {fileID: 4800000, guid: 573620ae32aec764abd4d728906d2587, type: 3}
probeVolumeSamplingDebugComputeShader: {fileID: 7200000, guid: 53626a513ea68ce47b59dc1299fe3959, type: 3}
probeVolumeResources:
probeVolumeDebugShader: {fileID: 4800000, guid: e5c6678ed2aaa91408dd3df699057aae, type: 3}
probeVolumeFragmentationDebugShader: {fileID: 4800000, guid: 03cfc4915c15d504a9ed85ecc404e607, type: 3}
probeVolumeOffsetDebugShader: {fileID: 4800000, guid: 53a11f4ebaebf4049b3638ef78dc9664, type: 3}
probeVolumeSamplingDebugShader: {fileID: 4800000, guid: 8f96cd657dc40064aa21efcc7e50a2e7, type: 3}
probeSamplingDebugMesh: {fileID: -3555484719484374845, guid: 57d7c4c16e2765b47a4d2069b311bffe, type: 3}
probeSamplingDebugTexture: {fileID: 2800000, guid: 24ec0e140fb444a44ab96ee80844e18e, type: 3}
probeVolumeBlendStatesCS: {fileID: 7200000, guid: b9a23f869c4fd45f19c5ada54dd82176, type: 3}
m_RendererFeatures: []
m_RendererFeatureMap:
m_UseNativeRenderPass: 1
postProcessData: {fileID: 11400000, guid: 41439944d30ece34e96484bdb6645b55, type: 2}
m_AssetVersion: 3
m_PrepassLayerMask:
serializedVersion: 2
m_Bits: 4294967295
m_OpaqueLayerMask:
serializedVersion: 2
m_Bits: 4294967295
m_TransparentLayerMask:
serializedVersion: 2
m_Bits: 4294967295
m_DefaultStencilState:
overrideStencilState: 0
stencilReference: 1
stencilCompareFunction: 3
passOperation: 2
failOperation: 0
zFailOperation: 0
m_ShadowTransparentReceive: 1
m_RenderingMode: 2
m_DepthPrimingMode: 0
m_CopyDepthMode: 0
m_DepthAttachmentFormat: 0
m_DepthTextureFormat: 0
m_AccurateGbufferNormals: 0
m_IntermediateTextureMode: 0

@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: b1d28fcc729e55442a8d6b5c1d284860
NativeFormatImporter:
externalObjects: {}
mainObjectFileID: 11400000
userData:
assetBundleName:
assetBundleVariant:

@ -17,6 +17,7 @@ MonoBehaviour:
m_RendererType: 1
m_RendererData: {fileID: 0}
m_RendererDataList:
- {fileID: 11400000, guid: b1d28fcc729e55442a8d6b5c1d284860, type: 2}
- {fileID: 11400000, guid: f288ae1f4751b564a96ac7587541f7a2, type: 2}
m_DefaultRendererIndex: 0
m_RequireDepthTexture: 1

@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 9004a800b8b48124abf23910ab4e6e31
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

Binary file not shown.

@ -0,0 +1,16 @@
fileFormatVersion: 2
guid: 2419b212eec718e4c8153284ca64a8a5
ScriptedImporter:
internalIDToNameTable: []
externalObjects: {}
serializedVersion: 2
userData:
assetBundleName:
assetBundleVariant:
script: {fileID: 11500000, guid: 7953a3c9778e49e4fbcdfd6a826af2cf, type: 3}
graph: {fileID: 11400000, guid: 628e2af98bba41b4091287c878e2b3a5, type: 2}
importSettingOverrides: []
isUsdRoot: 0
references:
version: 2
RefIds: []

@ -1,15 +1,14 @@
using UnityEngine;
using UnityEngine.Rendering;
using UnityEngine.Rendering.Universal;
using UnityEngine.Rendering.RenderGraphModule;
using UnityEngine.Rendering.RenderGraphModule.Util;
using UnityEngine.Rendering.Universal;
/// <summary>
/// Tilt-shift (移軸鏡) post-process effect for URP 17 (Unity 6).
/// Tilt-shift post-process effect for URP 17 (Unity 6).
///
/// Two modes:
/// ScreenSpace — blur based on vertical screen position (fast, camera-angle dependent).
/// DepthBased — Scheimpflug tilt-plane CoC using the depth buffer (physically accurate).
/// ScreenSpace — blur based on vertical screen position (fast, no depth buffer needed).
/// DepthBased — Scheimpflug tilt-plane CoC using the depth buffer (physically accurate).
/// </summary>
public class TiltShiftFeature : ScriptableRendererFeature
{
@ -17,9 +16,10 @@ public class TiltShiftFeature : ScriptableRendererFeature
{
/// <summary>Blur based on vertical UV position. No depth buffer required.</summary>
ScreenSpace,
/// <summary>
/// Blur based on distance from a tilted focal plane in world space (Scheimpflug principle).
/// Requires the camera's Depth Texture to be enabled.
/// Requires the camera Depth Texture to be enabled.
/// </summary>
DepthBased
}
@ -51,7 +51,7 @@ public class TiltShiftFeature : ScriptableRendererFeature
public float focusDistance = 10f;
[Tooltip("How much the focal plane depth shifts per screen-height unit.\n" +
"Positive top of screen focuses deeper (typical for bird's-eye view).\n" +
"Positive = top of screen focuses deeper (typical for bird's-eye view).\n" +
"Set to 0 for standard (flat) DoF.")]
[Range(-200f, 200f)]
public float tiltFactor = 30f;
@ -78,10 +78,10 @@ public class TiltShiftFeature : ScriptableRendererFeature
public Settings settings = new Settings();
TiltShiftPass _pass;
Material _material;
private TiltShiftPass renderPass;
private Material blitMaterial;
// ── Lifecycle ─────────────────────────────────────────────────────────────────
// ── Lifecycle ─────────────────────────────────────────────────────────────────
public override void Create()
{
@ -92,65 +92,73 @@ public class TiltShiftFeature : ScriptableRendererFeature
return;
}
_material = CoreUtils.CreateEngineMaterial(shader);
_pass = new TiltShiftPass(_material, settings);
_pass.renderPassEvent = settings.injectionPoint;
blitMaterial = CoreUtils.CreateEngineMaterial(shader);
renderPass = new TiltShiftPass(blitMaterial, settings);
renderPass.renderPassEvent = settings.injectionPoint;
}
public override void AddRenderPasses(ScriptableRenderer renderer, ref RenderingData renderingData)
{
if (_material == null || _pass == null) return;
if (blitMaterial == null || renderPass == null)
{
return;
}
var camType = renderingData.cameraData.cameraType;
if (camType == CameraType.Preview || camType == CameraType.Reflection) return;
if (camType == CameraType.Preview || camType == CameraType.Reflection)
{
return;
}
_pass.UpdateSettings(settings);
_pass.renderPassEvent = settings.injectionPoint;
renderer.EnqueuePass(_pass);
renderPass.UpdateSettings(settings);
renderPass.renderPassEvent = settings.injectionPoint;
renderer.EnqueuePass(renderPass);
}
protected override void Dispose(bool disposing)
{
CoreUtils.Destroy(_material);
CoreUtils.Destroy(blitMaterial);
}
// ── Inner render pass ──────────────────────────────────────────────────────────
sealed class TiltShiftPass : ScriptableRenderPass
private sealed class TiltShiftPass : ScriptableRenderPass
{
// Shader property IDs
static readonly int s_MaxBlurRadius = Shader.PropertyToID("_TiltShift_MaxBlurRadius");
static readonly int s_SampleCount = Shader.PropertyToID("_TiltShift_SampleCount");
static readonly int s_CenterOffset = Shader.PropertyToID("_TiltShift_CenterOffset");
static readonly int s_FocusWidth = Shader.PropertyToID("_TiltShift_FocusWidth");
static readonly int s_FalloffRange = Shader.PropertyToID("_TiltShift_FalloffRange");
static readonly int s_FocusDistance = Shader.PropertyToID("_TiltShift_FocusDistance");
static readonly int s_TiltFactor = Shader.PropertyToID("_TiltShift_TiltFactor");
static readonly int s_FocusBand = Shader.PropertyToID("_TiltShift_FocusBand");
static readonly int s_DepthFalloff = Shader.PropertyToID("_TiltShift_DepthFalloff");
// Shader keyword for mode switch
const string k_DepthModeKeyword = "DEPTH_MODE";
readonly Material _material;
Settings _settings;
private static readonly int propMaxBlurRadius = Shader.PropertyToID("_TiltShift_MaxBlurRadius");
private static readonly int propSampleCount = Shader.PropertyToID("_TiltShift_SampleCount");
private static readonly int propCenterOffset = Shader.PropertyToID("_TiltShift_CenterOffset");
private static readonly int propFocusWidth = Shader.PropertyToID("_TiltShift_FocusWidth");
private static readonly int propFalloffRange = Shader.PropertyToID("_TiltShift_FalloffRange");
private static readonly int propFocusDistance = Shader.PropertyToID("_TiltShift_FocusDistance");
private static readonly int propTiltFactor = Shader.PropertyToID("_TiltShift_TiltFactor");
private static readonly int propFocusBand = Shader.PropertyToID("_TiltShift_FocusBand");
private static readonly int propDepthFalloff = Shader.PropertyToID("_TiltShift_DepthFalloff");
private const string depthModeKeyword = "DEPTH_MODE";
private readonly Material blitMaterial;
private Settings currentSettings;
// PassData carries what the render func needs at execution time.
class PassData
private class PassData
{
public TextureHandle source;
public Material material;
public int passIndex;
public Material material;
public int passIndex;
}
public TiltShiftPass(Material material, Settings settings)
public TiltShiftPass(Material mat, Settings passSettings)
{
_material = material;
_settings = settings;
blitMaterial = mat;
currentSettings = passSettings;
requiresIntermediateTexture = true;
}
public void UpdateSettings(Settings s) => _settings = s;
public void UpdateSettings(Settings s)
{
currentSettings = s;
}
// ── RecordRenderGraph ──────────────────────────────────────────────────────
@ -159,79 +167,89 @@ public class TiltShiftFeature : ScriptableRendererFeature
var resourceData = frameData.Get<UniversalResourceData>();
if (resourceData.isActiveTargetBackBuffer)
{
return;
}
bool useDepth = _settings.blurMode == BlurMode.DepthBased;
var useDepth = currentSettings.blurMode == BlurMode.DepthBased;
// Validate depth texture availability.
if (useDepth && !resourceData.cameraDepthTexture.IsValid())
{
Debug.LogWarning("[TiltShift] DepthBased mode requires the camera's Depth Texture. " +
Debug.LogWarning("[TiltShift] DepthBased mode requires the camera Depth Texture. " +
"Enable it in the URP Renderer asset or Camera component.");
useDepth = false;
}
// ── Push settings to material ──────────────────────────────────────────
_material.SetFloat(s_MaxBlurRadius, _settings.maxBlurRadius);
_material.SetInt (s_SampleCount, _settings.sampleCount);
_material.SetFloat(s_CenterOffset, _settings.centerOffset);
_material.SetFloat(s_FocusWidth, _settings.focusWidth);
_material.SetFloat(s_FalloffRange, _settings.falloffRange);
_material.SetFloat(s_FocusDistance, _settings.focusDistance);
_material.SetFloat(s_TiltFactor, _settings.tiltFactor);
_material.SetFloat(s_FocusBand, _settings.focusBand);
_material.SetFloat(s_DepthFalloff, _settings.depthFalloff);
if (useDepth) _material.EnableKeyword(k_DepthModeKeyword);
else _material.DisableKeyword(k_DepthModeKeyword);
// ── Texture handles ────────────────────────────────────────────────────
// Push settings to material.
blitMaterial.SetFloat(propMaxBlurRadius, currentSettings.maxBlurRadius);
blitMaterial.SetInt(propSampleCount, currentSettings.sampleCount);
blitMaterial.SetFloat(propCenterOffset, currentSettings.centerOffset);
blitMaterial.SetFloat(propFocusWidth, currentSettings.focusWidth);
blitMaterial.SetFloat(propFalloffRange, currentSettings.falloffRange);
blitMaterial.SetFloat(propFocusDistance, currentSettings.focusDistance);
blitMaterial.SetFloat(propTiltFactor, currentSettings.tiltFactor);
blitMaterial.SetFloat(propFocusBand, currentSettings.focusBand);
blitMaterial.SetFloat(propDepthFalloff, currentSettings.depthFalloff);
if (useDepth)
{
blitMaterial.EnableKeyword(depthModeKeyword);
}
else
{
blitMaterial.DisableKeyword(depthModeKeyword);
}
// Texture handles
var source = resourceData.activeColorTexture;
var depth = useDepth ? resourceData.cameraDepthTexture : TextureHandle.nullHandle;
var depth = useDepth ? resourceData.cameraDepthTexture : TextureHandle.nullHandle;
var desc = renderGraph.GetTextureDesc(source);
desc.clearBuffer = false;
desc.name = "TiltShift_Temp";
TextureHandle temp = renderGraph.CreateTexture(desc);
desc.name = "TiltShift_Final";
TextureHandle final = renderGraph.CreateTexture(desc);
desc.name = "TiltShift_Temp";
var temp = renderGraph.CreateTexture(desc);
desc.name = "TiltShift_Final";
var final = renderGraph.CreateTexture(desc);
// ── Pass 0 — Horizontal blur (source → temp) ──────────────────────────
// Pass 0 — horizontal blur (source to temp)
AddBlurPass(renderGraph, "TiltShift_H", source, depth, temp, passIndex: 0, useDepth);
// ── Pass 1 — Vertical blur (temp → final) ────────────────────────────
// Pass 1 — vertical blur (temp to final)
AddBlurPass(renderGraph, "TiltShift_V", temp, depth, final, passIndex: 1, useDepth);
// Redirect camera colour so subsequent passes use our result.
resourceData.cameraColor = final;
}
// ── Helper to record one blur pass ─────────────────────────────────────────
// ── Helper: record one blur pass ───────────────────────────────────────────
void AddBlurPass(
RenderGraph renderGraph,
string passName,
private void AddBlurPass(
RenderGraph renderGraph,
string passName,
TextureHandle source,
TextureHandle depth,
TextureHandle dest,
int passIndex,
bool useDepth)
int passIndex,
bool useDepth)
{
using var builder = renderGraph.AddRasterRenderPass<PassData>(passName, out var passData);
passData.source = source;
passData.material = _material;
passData.source = source;
passData.material = blitMaterial;
passData.passIndex = passIndex;
builder.UseTexture(source, AccessFlags.Read);
// Declare the dependency on the depth texture so the render graph correctly
// orders this pass after URP's CopyDepthPass. We do NOT call SetGlobalTexture
// here — URP's CopyDepthPass already exposes _CameraDepthTexture globally via
// SetGlobalTextureAfterPass, so the shader can sample it without any extra work.
// Declare the depth dependency so the render graph orders this pass after CopyDepthPass.
// We do not call SetGlobalTexture here — URP's CopyDepthPass already exposes
// _CameraDepthTexture globally via SetGlobalTextureAfterPass.
if (useDepth && depth.IsValid())
{
builder.UseTexture(depth, AccessFlags.Read);
}
builder.SetRenderAttachment(dest, 0, AccessFlags.Write);

@ -0,0 +1,267 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using UnityEditor;
using UnityEngine;
namespace UltraCombos
{
[CustomEditor(typeof(DebugSettingsGUI))]
public class DebugSettingsGUIEditor : Editor
{
// ── Supported field types ─────────────────────────────────────────────────
private static readonly HashSet<Type> supportedTypes = new()
{
typeof(bool), typeof(int), typeof(float),
typeof(string), typeof(Vector2), typeof(Vector3), typeof(Color)
};
private static bool IsSupported(Type t) => supportedTypes.Contains(t) || t.IsEnum;
// ── Cached SerializedProperties ───────────────────────────────────────────
private SerializedProperty toggleKeyProp;
private SerializedProperty initialRectProp;
private void OnEnable()
{
toggleKeyProp = serializedObject.FindProperty("toggleKey");
initialRectProp = serializedObject.FindProperty("initialRect");
}
// ── Inspector ─────────────────────────────────────────────────────────────
public override void OnInspectorGUI()
{
var gui = (DebugSettingsGUI)target;
serializedObject.Update();
EditorGUILayout.LabelField("General", EditorStyles.boldLabel);
EditorGUILayout.PropertyField(toggleKeyProp, new GUIContent("Toggle Key"));
EditorGUILayout.PropertyField(initialRectProp, new GUIContent("Initial Rect"));
serializedObject.ApplyModifiedProperties();
EditorGUILayout.Space(10);
EditorGUILayout.LabelField("Entries", EditorStyles.boldLabel);
for (var i = 0; i < gui.entries.Count; i++)
{
var removed = DrawEntry(gui, gui.entries[i], i);
if (removed)
{
Undo.RecordObject(gui, "Remove Entry");
gui.entries.RemoveAt(i);
EditorUtility.SetDirty(gui);
i--;
}
EditorGUILayout.Space(2);
}
EditorGUILayout.Space(4);
if (GUILayout.Button("+ Add Entry", GUILayout.Height(26)))
{
Undo.RecordObject(gui, "Add Entry");
gui.entries.Add(new DebugSettingsGUI.Entry());
EditorUtility.SetDirty(gui);
}
}
// ── Draw one Entry — returns true when the user clicked Remove ─────────────
private static bool DrawEntry(DebugSettingsGUI gui, DebugSettingsGUI.Entry entry, int index)
{
var removed = false;
using (new EditorGUILayout.VerticalScope(EditorStyles.helpBox))
{
// Header row
using (new EditorGUILayout.HorizontalScope())
{
var title = string.IsNullOrEmpty(entry.groupLabel)
? $"Entry {index}"
: $"Entry {index} — {entry.groupLabel}";
EditorGUILayout.LabelField(title, EditorStyles.boldLabel);
GUILayout.FlexibleSpace();
if (GUILayout.Button("✕", GUILayout.Width(22), GUILayout.Height(18)))
{
removed = true;
}
}
// Group label
EditorGUI.BeginChangeCheck();
var newLabel = EditorGUILayout.TextField("Group Label", entry.groupLabel);
if (EditorGUI.EndChangeCheck())
{
Undo.RecordObject(gui, "Edit Group Label");
entry.groupLabel = newLabel;
EditorUtility.SetDirty(gui);
}
// Target object.
// Drag any Component or ScriptableObject here.
// Dropping a GameObject picks its first non-Transform Component automatically.
EditorGUI.BeginChangeCheck();
var newTarget = EditorGUILayout.ObjectField(
new GUIContent("Target",
"Drag any Component or ScriptableObject here.\n" +
"Dropping a GameObject picks its first non-Transform Component."),
entry.target,
typeof(UnityEngine.Object),
allowSceneObjects: true);
if (EditorGUI.EndChangeCheck())
{
Undo.RecordObject(gui, "Set Target");
if (newTarget is GameObject go)
{
var comps = go.GetComponents<Component>();
newTarget = comps.FirstOrDefault(c => c is not Transform) ??
comps.FirstOrDefault() ??
(UnityEngine.Object)go;
}
entry.target = newTarget;
entry.fields.Clear();
EditorUtility.SetDirty(gui);
}
// Field selection
if (entry.target != null)
{
DrawFieldSelection(gui, entry);
}
}
return removed;
}
// ── Field checkboxes ──────────────────────────────────────────────────────
private static void DrawFieldSelection(DebugSettingsGUI gui, DebugSettingsGUI.Entry entry)
{
var type = entry.target.GetType();
var fields = CollectFields(type);
if (fields.Count == 0)
{
EditorGUILayout.HelpBox(
"No displayable fields found on this object.\n" +
"Supported types: bool · int · float · string · Vector2 · Vector3 · Color · Enum\n" +
"Fields must be public or marked [SerializeField].",
MessageType.Info);
return;
}
EditorGUILayout.Space(4);
// Select All / None
using (new EditorGUILayout.HorizontalScope())
{
EditorGUILayout.LabelField("Fields to Display", EditorStyles.boldLabel);
GUILayout.FlexibleSpace();
if (GUILayout.Button("All", GUILayout.Width(36)))
{
Undo.RecordObject(gui, "Select All Fields");
entry.fields = fields.Select(f => f.Name).ToList();
EditorUtility.SetDirty(gui);
}
if (GUILayout.Button("None", GUILayout.Width(38)))
{
Undo.RecordObject(gui, "Deselect All Fields");
entry.fields.Clear();
EditorUtility.SetDirty(gui);
}
}
// Per-field toggles
EditorGUI.indentLevel++;
foreach (var fi in fields)
{
var isSelected = entry.fields.Contains(fi.Name);
var niceName = ObjectNames.NicifyVariableName(fi.Name);
var typeName = FriendlyType(fi.FieldType);
var hasRange = fi.GetCustomAttribute<RangeAttribute>() != null;
var label = new GUIContent(
$"{niceName} ({typeName}{(hasRange ? ", Range" : string.Empty)})",
tooltip: $"Field: {fi.Name}\nType: {fi.FieldType.FullName}");
EditorGUI.BeginChangeCheck();
var next = EditorGUILayout.ToggleLeft(label, isSelected);
if (EditorGUI.EndChangeCheck())
{
Undo.RecordObject(gui, next ? "Add Field" : "Remove Field");
if (next)
{
entry.fields.Add(fi.Name);
}
else
{
entry.fields.Remove(fi.Name);
}
EditorUtility.SetDirty(gui);
}
}
EditorGUI.indentLevel--;
}
// ── Reflection helpers ────────────────────────────────────────────────────
private static List<FieldInfo> CollectFields(Type type)
{
const BindingFlags flags =
BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance;
var result = new List<FieldInfo>();
for (var t = type; t != null && t != typeof(UnityEngine.Object); t = t.BaseType)
{
foreach (var fi in t.GetFields(flags | BindingFlags.DeclaredOnly))
{
var exposed = fi.IsPublic || fi.GetCustomAttribute<SerializeField>() != null;
if (!exposed)
{
continue;
}
if (fi.GetCustomAttribute<HideInInspector>() != null)
{
continue;
}
if (!IsSupported(fi.FieldType))
{
continue;
}
if (!result.Any(r => r.Name == fi.Name))
{
result.Add(fi);
}
}
}
return result;
}
private static string FriendlyType(Type t)
{
if (t == typeof(float)) { return "float"; }
if (t == typeof(int)) { return "int"; }
if (t == typeof(bool)) { return "bool"; }
if (t == typeof(string)) { return "string"; }
if (t == typeof(Vector2)) { return "Vector2"; }
if (t == typeof(Vector3)) { return "Vector3"; }
if (t == typeof(Color)) { return "Color"; }
if (t.IsEnum) { return t.Name; }
return t.Name;
}
}
}

@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 374031d85af94bb4f89f5a7346ffcd4c

@ -1,6 +1,6 @@
{
"name": "UltraCombos.GiantMuseum.Editor",
"rootNamespace": "UltraCombos.GiantMuseum",
"rootNamespace": "UltraCombos",
"references": [
"GUID:b2e08e75d81bb8b408397e7998130fbb"
],

@ -0,0 +1,430 @@
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Reflection;
using System.Text;
using UnityEngine;
namespace UltraCombos
{
/// <summary>
/// Runtime settings panel. Press the configured toggle key (default F1) to show or hide.
/// Drag any Component into the Entries list via the Inspector, select the fields to expose,
/// and the panel lets you tweak them live at runtime.
/// </summary>
public class DebugSettingsGUI : MonoBehaviour
{
// ── Data ──────────────────────────────────────────────────────────────────
[Serializable]
public class Entry
{
public UnityEngine.Object target;
public string groupLabel = string.Empty;
/// <summary>Field names written by the custom Editor.</summary>
public List<string> fields = new();
}
[Header("General")]
public KeyCode toggleKey = KeyCode.F1;
public Rect initialRect = new Rect(10, 10, 360, 520);
[Header("Entries")]
public List<Entry> entries = new();
// ── Runtime state ─────────────────────────────────────────────────────────
private bool isVisible;
private Rect windowRect;
private Vector2 scrollPos;
// Styles — lazy-initialised inside OnGUI so GUI.skin is available.
private bool stylesInitialized;
private GUIStyle headerStyle;
private GUIStyle subBoxStyle;
private GUIStyle labelStyle;
private GUIStyle readonlyStyle;
// ── Unity callbacks ───────────────────────────────────────────────────────
private void Awake()
{
windowRect = initialRect;
}
private void Update()
{
if (Input.GetKeyDown(toggleKey))
{
isVisible = !isVisible;
}
}
private void OnGUI()
{
if (!isVisible)
{
return;
}
EnsureStyles();
windowRect = GUILayout.Window(
GetInstanceID(), windowRect, DrawWindow,
$" Debug Settings [{toggleKey}: toggle]");
}
// ── Window ────────────────────────────────────────────────────────────────
private void DrawWindow(int id)
{
scrollPos = GUILayout.BeginScrollView(scrollPos);
var first = true;
foreach (var e in entries)
{
if (e == null || e.target == null || e.fields.Count == 0)
{
continue;
}
if (!first)
{
GUILayout.Space(6);
}
first = false;
DrawEntry(e);
}
GUILayout.EndScrollView();
GUI.DragWindow(new Rect(0, 0, 10000, 22));
}
private void DrawEntry(Entry entry)
{
if (!string.IsNullOrEmpty(entry.groupLabel))
{
GUILayout.Label(entry.groupLabel, headerStyle);
}
var type = entry.target.GetType();
const BindingFlags flags =
BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance;
foreach (var name in entry.fields)
{
var fi = type.GetField(name, flags);
if (fi == null)
{
continue;
}
try
{
DrawField(entry.target, fi);
}
catch
{
// Skip fields that throw during reflection.
}
}
}
// ── Per-type drawing ──────────────────────────────────────────────────────
private void DrawField(object obj, FieldInfo fi)
{
var val = fi.GetValue(obj);
var ftype = fi.FieldType;
var label = NicifyName(fi.Name);
if (ftype == typeof(bool))
{
DrawBool(obj, fi, (bool)val, label);
}
else if (ftype == typeof(float))
{
DrawFloat(obj, fi, (float)val, label);
}
else if (ftype == typeof(int))
{
DrawInt(obj, fi, (int)val, label);
}
else if (ftype == typeof(string))
{
DrawString(obj, fi, (string)val, label);
}
else if (ftype == typeof(Vector2))
{
DrawVector2(obj, fi, (Vector2)val, label);
}
else if (ftype == typeof(Vector3))
{
DrawVector3(obj, fi, (Vector3)val, label);
}
else if (ftype == typeof(Color))
{
DrawColor(obj, fi, (Color)val, label);
}
else if (ftype.IsEnum)
{
DrawEnum(obj, fi, val, ftype, label);
}
else
{
// Unsupported type — read-only display.
using (new GUILayout.HorizontalScope())
{
GUILayout.Label(label, labelStyle, GUILayout.Width(150));
GUILayout.Label(val?.ToString() ?? "(null)", readonlyStyle);
}
}
}
private void DrawBool(object obj, FieldInfo fi, bool val, string label)
{
using (new GUILayout.HorizontalScope())
{
GUILayout.Label(label, labelStyle, GUILayout.Width(150));
var next = GUILayout.Toggle(val, val ? "✓" : string.Empty);
if (next != val)
{
fi.SetValue(obj, next);
}
}
}
private void DrawFloat(object obj, FieldInfo fi, float val, string label)
{
var range = fi.GetCustomAttribute<RangeAttribute>();
using (new GUILayout.HorizontalScope())
{
GUILayout.Label(label, labelStyle, GUILayout.Width(150));
float next;
if (range != null)
{
next = GUILayout.HorizontalSlider(val, range.min, range.max);
GUILayout.Label(next.ToString("F2"), GUILayout.Width(44));
}
else
{
var s = GUILayout.TextField(val.ToString("G5"));
next = float.TryParse(s, out var p) ? p : val;
}
if (!Mathf.Approximately(next, val))
{
fi.SetValue(obj, next);
}
}
}
private void DrawInt(object obj, FieldInfo fi, int val, string label)
{
var range = fi.GetCustomAttribute<RangeAttribute>();
using (new GUILayout.HorizontalScope())
{
GUILayout.Label(label, labelStyle, GUILayout.Width(150));
int next;
if (range != null)
{
next = Mathf.RoundToInt(GUILayout.HorizontalSlider(val, range.min, range.max));
GUILayout.Label(next.ToString(), GUILayout.Width(32));
}
else
{
var s = GUILayout.TextField(val.ToString());
next = int.TryParse(s, out var p) ? p : val;
}
if (next != val)
{
fi.SetValue(obj, next);
}
}
}
private void DrawString(object obj, FieldInfo fi, string val, string label)
{
using (new GUILayout.HorizontalScope())
{
GUILayout.Label(label, labelStyle, GUILayout.Width(150));
var next = GUILayout.TextField(val ?? string.Empty);
if (next != val)
{
fi.SetValue(obj, next);
}
}
}
private void DrawVector2(object obj, FieldInfo fi, Vector2 val, string label)
{
using (new GUILayout.HorizontalScope())
{
GUILayout.Label(label, labelStyle, GUILayout.Width(150));
GUILayout.Label("X", GUILayout.Width(14));
var x = ParseFloat(GUILayout.TextField(val.x.ToString("G4"), GUILayout.Width(52)), val.x);
GUILayout.Label("Y", GUILayout.Width(14));
var y = ParseFloat(GUILayout.TextField(val.y.ToString("G4"), GUILayout.Width(52)), val.y);
var next = new Vector2(x, y);
if (next != val)
{
fi.SetValue(obj, next);
}
}
}
private void DrawVector3(object obj, FieldInfo fi, Vector3 val, string label)
{
using (new GUILayout.HorizontalScope())
{
GUILayout.Label(label, labelStyle, GUILayout.Width(150));
GUILayout.Label("X", GUILayout.Width(14));
var x = ParseFloat(GUILayout.TextField(val.x.ToString("G4"), GUILayout.Width(40)), val.x);
GUILayout.Label("Y", GUILayout.Width(14));
var y = ParseFloat(GUILayout.TextField(val.y.ToString("G4"), GUILayout.Width(40)), val.y);
GUILayout.Label("Z", GUILayout.Width(14));
var z = ParseFloat(GUILayout.TextField(val.z.ToString("G4"), GUILayout.Width(40)), val.z);
var next = new Vector3(x, y, z);
if (next != val)
{
fi.SetValue(obj, next);
}
}
}
private void DrawColor(object obj, FieldInfo fi, Color val, string label)
{
GUILayout.Label(label, headerStyle);
using (new GUILayout.VerticalScope(subBoxStyle))
{
var r = SliderChannel("R", val.r);
var g = SliderChannel("G", val.g);
var b = SliderChannel("B", val.b);
var a = SliderChannel("A", val.a);
var next = new Color(r, g, b, a);
if (next != val)
{
fi.SetValue(obj, next);
}
}
}
private float SliderChannel(string ch, float val)
{
using (new GUILayout.HorizontalScope())
{
GUILayout.Label(ch, GUILayout.Width(16));
var next = GUILayout.HorizontalSlider(val, 0f, 1f);
GUILayout.Label(next.ToString("F2"), GUILayout.Width(36));
return next;
}
}
private void DrawEnum(object obj, FieldInfo fi, object val, Type type, string label)
{
var names = Enum.GetNames(type);
var values = Enum.GetValues(type);
var cur = Array.IndexOf(values, val);
using (new GUILayout.HorizontalScope())
{
GUILayout.Label(label, labelStyle, GUILayout.Width(150));
if (names.Length <= 4)
{
var next = GUILayout.SelectionGrid(cur, names, names.Length);
if (next != cur)
{
fi.SetValue(obj, values.GetValue(next));
}
}
else
{
// Cycle through with prev / next buttons for long enums.
if (GUILayout.Button("◀", GUILayout.Width(24)))
{
cur = (cur - 1 + names.Length) % names.Length;
}
GUILayout.Label(names[Mathf.Clamp(cur, 0, names.Length - 1)]);
if (GUILayout.Button("▶", GUILayout.Width(24)))
{
cur = (cur + 1) % names.Length;
}
fi.SetValue(obj, values.GetValue(Mathf.Clamp(cur, 0, values.Length - 1)));
}
}
}
// ── Helpers ───────────────────────────────────────────────────────────────
private static float ParseFloat(string s, float fallback)
=> float.TryParse(s, NumberStyles.Float, CultureInfo.InvariantCulture, out var v) ? v : fallback;
/// <summary>Converts a camelCase or _prefixed field name to a readable label.</summary>
private static string NicifyName(string s)
{
// Strip leading underscores and "m_" prefix.
var start = 0;
while (start < s.Length && s[start] == '_')
{
start++;
}
if (s.Length > start + 1 && s[start] == 'm' && s[start + 1] == '_')
{
start += 2;
}
var sb = new StringBuilder();
for (var i = start; i < s.Length; i++)
{
var c = s[i];
if (i == start)
{
sb.Append(char.ToUpper(c));
continue;
}
if (char.IsUpper(c) && !char.IsUpper(s[i - 1]))
{
sb.Append(' ');
}
sb.Append(c);
}
return sb.ToString();
}
// ── Styles — OnGUI-safe lazy initialisation ────────────────────────────────
private void EnsureStyles()
{
if (stylesInitialized)
{
return;
}
stylesInitialized = true;
headerStyle = new GUIStyle(GUI.skin.label)
{
fontStyle = FontStyle.Bold,
normal = { textColor = new Color(1f, 0.85f, 0.3f) }
};
subBoxStyle = new GUIStyle(GUI.skin.box)
{
padding = new RectOffset(6, 6, 3, 3),
margin = new RectOffset(2, 2, 0, 0)
};
labelStyle = new GUIStyle(GUI.skin.label)
{
normal = { textColor = new Color(0.85f, 0.85f, 0.85f) }
};
readonlyStyle = new GUIStyle(GUI.skin.label)
{
normal = { textColor = new Color(0.55f, 0.55f, 0.55f) },
fontStyle = FontStyle.Italic
};
}
}
}

@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: da5159de08551c3438978ca2a98e1a50

@ -1,6 +1,6 @@
{
"name": "UltraCombos.GiantMuseum.Runtime",
"rootNamespace": "UltraCombos.GiantMuseum",
"rootNamespace": "UltraCombos",
"references": [
"GUID:75469ad4d38634e559750d17036d5f7c"
],

@ -11,6 +11,7 @@
"dependencies": {
"com.github-glitchenzo.nugetforunity": "https://github.com/GlitchEnzo/NuGetForUnity.git?path=/src/NuGetForUnity",
"com.unity.ide.visualstudio": "2.0.26",
"com.unity.importer.usd": "1.0.0-pre.2",
"com.unity.inputsystem": "1.19.0",
"com.unity.render-pipelines.universal": "17.3.0",
"com.unity.timeline": "1.8.11",

@ -15,7 +15,7 @@
},
"com.unity.burst": {
"version": "1.8.28",
"depth": 2,
"depth": 1,
"source": "registry",
"dependencies": {
"com.unity.mathematics": "1.2.1",
@ -25,7 +25,7 @@
},
"com.unity.collections": {
"version": "2.6.5",
"depth": 2,
"depth": 1,
"source": "registry",
"dependencies": {
"com.unity.burst": "1.8.27",
@ -51,6 +51,19 @@
},
"url": "https://packages.unity.com"
},
"com.unity.importer.usd": {
"version": "1.0.0-pre.2",
"depth": 0,
"source": "registry",
"dependencies": {
"com.unity.burst": "1.7.3",
"com.unity.usd.core": "1.0.0-pre.1",
"com.unity.collections": "1.3.1",
"com.unity.mathematics": "1.2.6",
"com.unity.shadergraph": "15.0.0"
},
"url": "https://packages.unity.com"
},
"com.unity.inputsystem": {
"version": "1.19.0",
"depth": 0,
@ -69,7 +82,7 @@
},
"com.unity.nuget.mono-cecil": {
"version": "1.11.6",
"depth": 3,
"depth": 2,
"source": "registry",
"dependencies": {},
"url": "https://packages.unity.com"
@ -134,7 +147,7 @@
},
"com.unity.test-framework.performance": {
"version": "3.2.0",
"depth": 3,
"depth": 2,
"source": "registry",
"dependencies": {
"com.unity.test-framework": "1.1.33",
@ -163,6 +176,13 @@
"com.unity.modules.imgui": "1.0.0"
}
},
"com.unity.usd.core": {
"version": "1.0.0-pre.1",
"depth": 1,
"source": "registry",
"dependencies": {},
"url": "https://packages.unity.com"
},
"jp.keijiro.klak.lineargradient": {
"version": "1.0.2",
"depth": 1,

@ -1,18 +1,23 @@
<Solution>
<Project Path="Unity.Timeline.csproj" />
<Project Path="Unity.Importer.csproj" />
<Project Path="Unity.Mathematics.csproj" />
<Project Path="Unity.Timeline.Editor.csproj" />
<Project Path="Unity.Collections.csproj" />
<Project Path="Metamesh.Editor.csproj" />
<Project Path="Unity.PerformanceTesting.Editor.csproj" />
<Project Path="Unity.Burst.csproj" />
<Project Path="Unity.Importer.USD.csproj" />
<Project Path="Unity.VisualStudio.Editor.csproj" />
<Project Path="Unity.InputSystem.csproj" />
<Project Path="Unity.Searcher.Editor.csproj" />
<Project Path="USD.NET.Unity.csproj" />
<Project Path="Unity.Burst.CodeGen.csproj" />
<Project Path="Unity.PerformanceTesting.csproj" />
<Project Path="Unity.Importer.Editor.csproj" />
<Project Path="Unity.Burst.Editor.csproj" />
<Project Path="OscJack.Runtime.csproj" />
<Project Path="Unity.Importer.USD.Editor.csproj" />
<Project Path="NuGetForUnity.csproj" />
<Project Path="Unity.InputSystem.ForUI.csproj" />
<Project Path="Unity.Collections.CodeGen.csproj" />
@ -21,11 +26,12 @@
<Project Path="HBAO.Universal.Runtime.csproj" />
<Project Path="Unity.InputSystem.TestFramework.csproj" />
<Project Path="Assembly-CSharp.csproj" />
<Project Path="UltraCombos.GiantMuseum.Runtime.csproj" />
<Project Path="Metatex.Editor.csproj" />
<Project Path="Klak.Spout.Editor.csproj" />
<Project Path="Unity.USD.Core.csproj" />
<Project Path="Klak.Chromatics.LinearGradient.Runtime.csproj" />
<Project Path="OscJack.Editor.csproj" />
<Project Path="UltraCombos.GiantMuseum.Runtime.csproj" />
<Project Path="Unity.InputSystem.DocCodeSamples.csproj" />
<Project Path="Sirenix.OdinInspector.Modules.UnityMathematics.csproj" />
<Project Path="UltraCombos.GiantMuseum.Editor.csproj" />

Loading…
Cancel
Save