Godot Version
v4.6.3.stable.mono.official [7d41c59c4]
tl;dr
Attempting to create an inspector plugin that handles an array of “arguments”. Having trouble creating said plugin, both in the visual layer and serialization layer.
Background
I am creating a resource that uses Reflection to create pure C# objects. My intention is to have code that is completely engine agnostic for a few reasons. Having a generic resource in which I can select assembly, type, constructor, and arguments to instantiate objects would be incredibly useful. I’ve already got the first three working, having trouble with arguments.
My current solution has been to create a new plugin, using _ParseProperty to create my own EditorProperty for each element:
GodotObject @object,
Variant.Type type,
string name,
PropertyHint hintType,
string hintString,
PropertyUsageFlags usageFlags,
bool wide)
{
ReflectionResource resource = @object as ReflectionResource;
const bool addToEnd = false;
if (name == nameof(ReflectionResource.AssemblyName))
{
AddPropertyEditor(
name,
new AssemblyEditorProperty(resource),
addToEnd,
"Assembly");
return true;
}
if (name == nameof(ReflectionResource.TypeName))
{
AddPropertyEditor(
name,
new TypeEditorProperty(resource),
addToEnd,
"Type");
return true;
}
if (name == nameof(ReflectionResource.ConstructorSignature))
{
AddPropertyEditor(
name,
new ConstructorEditorProperty(resource),
addToEnd,
"Constructor");
return true;
}
if (name == nameof(ReflectionResource.FirstArgument))
{
const int argumentIndex = 0;
if (argumentIndex >= resource.ConstructorSignature.Count)
{
return true;
}
// Repeat for parameters 2-4.
if (name == nameof(ReflectionResource.FifthArgument))
{
const int argumentIndex = 4;
if (argumentIndex >= resource.ConstructorSignature.Count)
{
return true;
}
ParameterData parameter = new(resource.ConstructorSignature[argumentIndex]);
PropertyHint propertyHint = ReflectionUtility.GetPropertyHint(parameter.VariantType);
return base._ParseProperty(@object, parameter.VariantType, name, propertyHint, hintString, usageFlags, wide);
}
return false;
}
Problem
This works technically, but there’s some things I dislike:
- Cannot enforce variant type. The user needs to select the right variant for the argument, in the picture above, that’d be a
boolean. - I have no control over label name.
- I have to set up arguments concretely. Right now 5 arguments is the maximum.
Explrored Solutions
Below are some things I’ve tried to mitigate the above problems:
Argument Array - Per Element Custom PropertyEditor
My first pass at this was to have a Godot.Array property on ReflectionResource instead of typing out each argument property. For each argument, I’d create a new EditorProperty:
#if TOOLS
using Godot;
namespace Flantastico.Godot.ACE.Presenter.Core.Reflection;
internal partial class ArgumentEditorProperty : EditorProperty
{
private readonly ReflectionResource _resource;
private readonly int _parameterIndex;
private bool _isUpdating;
private EditorProperty _editorProperty;
private Variant _currentValue;
public ArgumentEditorProperty(ReflectionResource resource, int parameterIndex)
{
_resource = resource;
_parameterIndex = parameterIndex;
ArgumentData parameter = new(resource.ConstructorSignature[_parameterIndex]);
Variant variant = resource.GetArgumentByIndex(_parameterIndex);
_editorProperty = EditorInspector.InstantiatePropertyEditor(
resource,
parameter.VariantType,
resource.ResourceName,
PropertyHint.None,
string.Empty,
(uint)PropertyUsageFlags.None);
AddChild(_editorProperty);
AddFocusable(_editorProperty);
_editorProperty.PropertyChanged += OnPropertyChanged;
RefreshArgument();
}
public override void _UpdateProperty()
{
var editedProperty = GetEditedProperty();
var editedValue = GetEditedObject().Get(editedProperty);
if (IsMatchingVariant(editedValue, _currentValue))
{
return;
}
_isUpdating = true;
_currentValue = editedValue;
RefreshArgument();
_isUpdating = false;
}
private void RefreshArgument()
{
_editorProperty.UpdateProperty();
}
private void OnPropertyChanged(StringName property, Variant value, StringName field, bool changing)
{
if (_isUpdating)
{
return;
}
_currentValue = value;
EmitChanged(nameof(ReflectionResource.FirstArgument), _currentValue);
}
private static bool IsMatchingVariant(Variant variantA, Variant variantB)
{
if (variantA.Obj == null || variantB.Obj == null)
{
return false;
}
if (variantA.VariantType != variantB.VariantType)
{
return false;
}
if (!variantA.Obj.Equals(variantB.Obj))
{
return false;
}
return true;
}
}
#endif
This EditorProperty would be added as a child of a VBoxContainer, which was added as a control within the plugin. Problem with this solution was that it was having trouble serializing; whenever I’d make a change to an argument in the inspector, it’d revert to the default value. Otherwise, it worked great.
Argument Array - Per Element Default PropertyEditor
When the above didn’t work, I decided to move the code that was in the constructor out to the plugin, using AddPropertyEditor instead of AddCustomControl. This also didn’t work. Unfortunately, don’t have code left over to show but I can try to recreate if folks feel like this is a viable solution.
Argument Per Property
Essentially what I have now; each argument is it’s own Variant property.
Question
I’ve given a few solutions, so I guess my question is which is the best one that has the least amount of downsides. Here’s a general list of inquiries:
- How does one use
AddCustomControlwhile still serializing properties? I never got that to work; I always had to useAddEditorProperty. - Is it possible for an
EditorPropertyto handle a single element of anArrayor is it an all or nothing type of deal? - How do I create the default
EditorPropertyfor aVariant? I only found out how to useEditorInspector.InstantiatePropertyEditor, but this one doesn’t seem to allow me enforce aVariant.Typeunless I’m wrong. - If I am force to have each argument be a separate property, how do I enforce
Variant.Typeand override the shown text. - Tangential, but is there a way to call Godot Inspector’s default property label formatter? I.E. I have argument
isDeactivatedImmediatelyand want to change it toIs Deactivated Immediatelyas the inspector does with variables.
Thanks in advance for your help! Let me know if you need more clarifications. Also, feel free to give solutions in gdScript. I’ll try to then figure out how to translate to C#.

