parent
84d253b227
commit
b054a25e71
20 changed files with 2497 additions and 11353 deletions
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: |
||||
@ -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: [] |
||||
@ -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 |
||||
@ -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 |
||||
Loading…
Reference in new issue