Skip to content

Instantly share code, notes, and snippets.

@anatawa12
Last active February 13, 2025 05:19
Show Gist options
  • Save anatawa12/299d87404c99a12cafb7311c586912ae to your computer and use it in GitHub Desktop.
Save anatawa12/299d87404c99a12cafb7311c586912ae to your computer and use it in GitHub Desktop.
An inspector that can see all fields with reflection
/*
* Reflection Explorer
* An inspector that can see all fields with reflection
* https://gist.github.com/anatawa12/299d87404c99a12cafb7311c586912ae
*
* `Tools/anatawa12 gists/Reflection Explorer` to open this window.
*
* MIT License
*
* Copyright (c) 2025 anatawa12, Planeta K.K.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
#if UNITY_EDITOR && (!ANATAWA12_GISTS_VPM_PACKAGE || GIST_299d87404c99a12cafb7311c586912ae)
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using JetBrains.Annotations;
using UnityEditor;
using UnityEngine;
using Object = UnityEngine.Object;
namespace anatawa12.gists
{
class ReflectionExplorer : EditorWindow
{
private const string GIST_NAME = "Reflection Explorer";
[MenuItem("Tools/anatawa12 gists/" + GIST_NAME)]
static void ShowWindow() => GetWindow(typeof(ReflectionExplorer)).Show();
[CanBeNull]
Object obj = null;
[NonSerialized]
FetchedObject[] fetchedObjectStack = Array.Empty<FetchedObject>();
private void OnGUI()
{
obj = EditorGUILayout.ObjectField("Object", obj, typeof(Object), true);
if (obj == null) fetchedObjectStack = Array.Empty<FetchedObject>();
else
{
if (fetchedObjectStack.Length == 0 || fetchedObjectStack[0].obj != obj)
{
fetchedObjectStack = new FetchedObject[1]
{
new FetchedObject("", obj)
};
}
}
if (fetchedObjectStack.Length != 0)
{
var stackTop = fetchedObjectStack[^1];
EditorGUILayout.BeginHorizontal();
EditorGUI.BeginDisabledGroup(fetchedObjectStack.Length == 1);
if (GUILayout.Button("<", GUILayout.Width(20), GUILayout.ExpandWidth(false)))
{
fetchedObjectStack = fetchedObjectStack.Take(fetchedObjectStack.Length - 1).ToArray();
}
EditorGUI.EndDisabledGroup();
EditorGUI.BeginDisabledGroup(true);
EditorGUILayout.TextField(stackTop.path);
EditorGUI.EndDisabledGroup();
EditorGUILayout.EndHorizontal();
if (GUILayout.Button("Refetch"))
stackTop.FetchValues();
EditorGUILayout.LabelField("Fields", EditorStyles.boldLabel);
EditorGUI.BeginDisabledGroup(true);
foreach (var field in stackTop.fields)
{
if (field.value == null)
{
EditorGUILayout.LabelField(field.name, "null");
}
else
{
var type = field.value.GetType();
if (type == typeof(bool)) EditorGUILayout.Toggle(field.name, (bool)field.value);
else if (type == typeof(char)) EditorGUILayout.TextField(field.name, ((char)field.value).ToString());
else if (type == typeof(byte)) EditorGUILayout.LongField(field.name, (byte)field.value);
else if (type == typeof(sbyte)) EditorGUILayout.LongField(field.name, (sbyte)field.value);
else if (type == typeof(short)) EditorGUILayout.LongField(field.name, (short)field.value);
else if (type == typeof(ushort)) EditorGUILayout.LongField(field.name, (ushort)field.value);
else if (type == typeof(int)) EditorGUILayout.LongField(field.name, (int)field.value);
else if (type == typeof(uint)) EditorGUILayout.LongField(field.name, (uint)field.value);
else if (type == typeof(long)) EditorGUILayout.LongField(field.name, (long)field.value);
else if (type == typeof(ulong)) EditorGUILayout.LabelField(field.name, field.value.ToString());
else if (type == typeof(float)) EditorGUILayout.FloatField(field.name, (float)field.value);
else if (type == typeof(double)) EditorGUILayout.DoubleField(field.name, (double)field.value);
else if (type == typeof(string)) EditorGUILayout.TextField(field.name, (string)field.value);
else if (type == typeof(Color)) EditorGUILayout.ColorField(field.name, (Color)field.value);
else if (type == typeof(Color32)) EditorGUILayout.ColorField(field.name, (Color32)field.value);
else if (type == typeof(Rect)) EditorGUILayout.RectField(field.name, (Rect)field.value);
else if (type == typeof(Vector2)) EditorGUILayout.Vector2Field(field.name, (Vector2)field.value);
else if (type == typeof(Vector3)) EditorGUILayout.Vector3Field(field.name, (Vector3)field.value);
else if (type == typeof(Vector4)) EditorGUILayout.Vector4Field(field.name, (Vector4)field.value);
else if (type == typeof(Vector2Int)) EditorGUILayout.Vector2IntField(field.name, (Vector2Int)field.value);
else if (type == typeof(Bounds)) EditorGUILayout.BoundsField(field.name, (Bounds)field.value);
else if (type.IsEnum) EditorGUILayout.EnumPopup(field.name, (Enum)field.value);
else if (type.IsSubclassOf(typeof(Object)))
{
EditorGUILayout.BeginHorizontal();
EditorGUILayout.ObjectField(field.name, (Object)field.value, type, true);
EditorGUI.EndDisabledGroup();
if (GUILayout.Button(">", GUILayout.Width(20), GUILayout.ExpandWidth(false)))
{
obj = (Object)field.value;
}
EditorGUI.BeginDisabledGroup(true);
EditorGUILayout.EndHorizontal();
}
else
{
EditorGUILayout.BeginHorizontal();
EditorGUILayout.LabelField(field.name, $"({type.Name})");
EditorGUI.EndDisabledGroup();
if (GUILayout.Button(">", GUILayout.Width(20), GUILayout.ExpandWidth(false)))
{
fetchedObjectStack = fetchedObjectStack.Concat(new[] { new FetchedObject(stackTop.path + " > " + field.name, field.value) }).ToArray();
}
EditorGUI.BeginDisabledGroup(true);
EditorGUILayout.EndHorizontal();
}
}
}
EditorGUI.EndDisabledGroup();
}
}
class FetchedObject
{
private static Dictionary<Type, FieldInfo[]> objectFields = new Dictionary<Type, FieldInfo[]>();
public string path;
public object obj;
public Field[] fields;
private readonly FieldInfo[] fieldInfos;
public FetchedObject(string path, object obj)
{
this.path = path;
this.obj = obj;
if (!objectFields.TryGetValue(obj.GetType(), out fieldInfos))
objectFields.Add(obj.GetType(), fieldInfos = ComputeFields(obj.GetType()));
FetchValues();
}
private FieldInfo[] ComputeFields(Type type)
{
var fields = type.GetFields(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic).ToList();
for (var current = type.BaseType; current != null && current != typeof(object); current = current.BaseType)
fields.AddRange(current.GetFields(BindingFlags.Instance | BindingFlags.NonPublic).Where(x => x.IsPrivate));
return fields.ToArray();
}
public void FetchValues()
{
fields = fieldInfos
.Select(x => new Field(x.Name, x.GetValue(obj)))
.ToArray();
}
}
struct Field
{
public string name;
public object value;
public Field(string name, object value)
{
this.name = name;
this.value = value;
}
}
}
}
#endif
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment