问题
I'm working on an OpenGL game engine as a passion project and im using the UI library "Dear ImGUI" to display and debug values similar to Unity's inspector. I'm having trouble thinking of a way to get a reference to the field I'm trying to debug.
Here is the code i have got at the moment but the problem is that its not a reference to the actual field, its just a reference to a local variable (value) and as such, it doesnt actually set the variable that I debug in the GUI. From what i've been able to see, there is no clean cut way to get the reference.
protected override void OnImGUIRender(FrameEventArgs e)
{
ImGui.PushFont(font);
ImGui.ShowDemoWindow();
//Scene Window
{
ImGui.Begin("Scene");
ImGui.BeginTabBar("1");
ImGui.BeginTabItem("Heirachy");
if (ImGui.TreeNode("Scene"))
{
foreach (var obj in (LayerStack.Layers.FirstOrDefault(x => x.GetType() == typeof(GameLayer)) as GameLayer).scene.GameObjects)
{
if (ImGui.Selectable(obj.Name))
selectedGameObject = obj;
}
ImGui.TreePop();
}
ImGui.EndTabItem();
ImGui.EndTabBar();
ImGui.Dummy(new System.Numerics.Vector2(0, 40));
ImGui.BeginTabBar("Properties");
ImGui.BeginTabItem("Properties");
if (selectedGameObject != null)
{
ImGui.Text(selectedGameObject.Name);
foreach (var c in selectedGameObject.components)
{
if (ImGui.TreeNode(c.GetType().Name))
{
var fields = c.GetType().GetFields();
foreach (var field in fields)
{
ImGui.DragFloat3(field.Name, field.refValue); <-- Focused line
}
ImGui.TreePop();
}
}
}
else
ImGui.Text("No Currently Selected Gameobject");
ImGui.EndTabItem();
ImGui.EndTabBar();
ImGui.End();
ImGui.Begin("Debug");
ImGui.Text("Gameobjects: " + LayerStack.GameObjectCount);
ImGui.End();
}
base.OnImGUIRender(e);
}
Is there any way I can get a reference to the actual field that is being looped over in the foreach? In my head I would imagine it looking something like this:
ImGui.DragFloat3(field.Name, field.Reference);
Thanks!
Edit:
I found my personal solution to be in the code below but massive thanks to @pinkfloydx33 for helping me to understand the problem better and provide a high quality answer.
var fields = c.GetType().GetFields();
foreach (var field in fields)
{
var value = (field.FieldType)field.GetValue(c);
ImGui.DragFloat3(field.Name, field.refValue);
field.SetValue(c, value);
}
回答1:
Part of the problem you are experiencing is due to the fact those field values are structs. You only end up operating on copies of them. But we can get around this by building a delegate that accepts as its only parameter an object of the containing type (the type who's fields you are inspecting). This delegate will in turn call the method you are trying to invoke, passing the object's field under the hood with ref
.
This solution below assumes that the methods you want to invoke (ImGui.Drag3
, ImGui.Checkbox
) always have two parameters -- string name
and ref T value
. In other words, a hypothetical method that operated on int
fields would have to be declared as ImGui.DoSomethingToInt(string name, ref int value)
using System.Linq.Expressions;
using System.Reflection;
using System.Collection.Generic;
public static class ComponentHelpers
{
// helper function to get the MethodInfo for the method we want to call
private static MethodInfo GetStaticMethod(Expression<Action> expression)
{
if (expression.Body is MethodCallExpression body && body.Method.IsStatic)
return body.Method;
throw new InvalidOperationException("Expression must represent a static method");
}
// helper field we can use in calls to GetStaticMethod
private static class Ref<T>
{
public static T Value;
}
// Define which method we want to call based on the field's type
// each of these methods must take 2 parameters (string + ref T)
private static readonly Dictionary<Type, MethodInfo> Methods = new Dictionary<Type, MethodInfo>
{
[typeof(Vector3)] = GetStaticMethod(() => ImGui.Drag3(default, ref Ref<Vector3>.Value)),
[typeof(bool)] = GetStaticMethod(() => ImGui.Checkbox(default, ref Ref<bool>.Value))
};
// store the compiled delegates so that we only build/compile them once
private static readonly Dictionary<FieldInfo, Action<Component>> Delegates = new Dictionary<FieldInfo, Action<Component>>();
// this method will either build us a delegate, return one we've already built
// or will return null if we have not defined a method for the specific type
public static Action<Component> GetActionFor(FieldInfo field)
{
if (!Methods.TryGetValue(field.FieldType, out var method))
return null;
if (Delegates.TryGetValue(field, out var del))
return del;
// type the parameter as the base class Component
var param = Expression.Parameter(typeof(Component), "x");
var lambda = Expression.Lambda<Action<Component>>(
Expression.Call(
method,
// Pass the field's name as the first parameter
Expression.Constant(field.Name, typeof(string)),
// pass the field as the second parameter
Expression.Field(
// cast to the actual type so we can access fields of inherited types
Expression.Convert(param, field.ReflectedType),
field
)
),
param
);
return Delegates[field] = lambda.Compile();
}
}
Once we've done that, we can update your main loop to look like the following:
var fields = c.GetType().GetFields();
foreach (var field in fields)
{
var action = ComponentHelpers.GetActionFor(field);
if (action == null) // no method defined
continue;
// invoke the function passing in the object itself
action(c);
}
来源:https://stackoverflow.com/questions/62234090/get-reference-to-field-from-reflection