Skip to content

Instantly share code, notes, and snippets.

@maidopi-usagi
Created June 7, 2025 17:50
Show Gist options
  • Select an option

  • Save maidopi-usagi/a0cf407a80c9267d2e037a37664419cf to your computer and use it in GitHub Desktop.

Select an option

Save maidopi-usagi/a0cf407a80c9267d2e037a37664419cf to your computer and use it in GitHub Desktop.
GodotObject / Dictionary / Array to ExpandoObject
using Godot;
using System.Dynamic;
using Godot.Collections;
public static class GodotDynamicExtensions
{
public static ExpandoObject ToExpandoObject(this Dictionary godotDict)
{
var expando = new ExpandoObject();
var expandoDict = (IDictionary<string, object>)expando;
foreach (var key in godotDict.Keys)
{
var propertyName = key.ToString();
var propertyValue = ConvertVariant(godotDict[key]);
expandoDict[propertyName] = propertyValue;
}
return expando;
}
public static dynamic AsDynamic(this GodotObject obj)
{
return GodotObjectProxy.Create(obj);
}
private static object ConvertVariant(Variant variant)
{
if (variant.Obj is GodotObject godotObject)
{
return new GodotObjectProxy(godotObject);
}
if (variant.Obj is Godot.Collections.Dictionary godotDict)
{
return ToExpandoObject(godotDict);
}
if (variant.Obj is Godot.Collections.Array godotArray)
{
var netList = new List<object>(godotArray.Count);
foreach (var item in godotArray)
{
netList.Add(ConvertVariant(item));
}
return netList;
}
return variant.Obj;
}
public class GodotObjectProxy : DynamicObject
{
private readonly GodotObject _target;
private static readonly System.Collections.Generic.Dictionary<string, StringName> NameCache = new();
public GodotObjectProxy(GodotObject target)
{
_target = target ?? throw new ArgumentNullException(nameof(target));
}
public override bool TryGetMember(GetMemberBinder binder, out object result)
{
try
{
var memberName = GetCachedStringName(binder.Name);
var variantResult = _target.Get(memberName);
result = ConvertVariant(variantResult);
return true;
}
catch (Exception ex)
{
GD.PrintErr($"Failed to get member '{binder.Name}': {ex.Message}");
result = null;
return false;
}
}
public override bool TrySetMember(SetMemberBinder binder, object value)
{
try
{
var memberName = GetCachedStringName(binder.Name);
_target.Set(memberName, VariantFromObject(value));
return true;
}
catch (Exception ex)
{
GD.PrintErr($"Failed to set member '{binder.Name}': {ex.Message}");
return false;
}
}
public override bool TryInvokeMember(InvokeMemberBinder binder, object[] args, out object result)
{
try
{
var methodName = GetCachedStringName(binder.Name);
var variantArgs = args.Select(VariantFromObject).ToArray();
var variantResult = _target.Call(methodName, variantArgs);
result = ConvertVariant(variantResult);
return true;
}
catch (Exception ex)
{
GD.PrintErr($"Failed to call member '{binder.Name}': {ex.Message}");
result = null;
return false;
}
}
public static Variant VariantFromObject(object obj)
{
return obj switch
{
bool b => Variant.From(b),
char c => Variant.From(c),
sbyte sb => Variant.From(sb),
short s => Variant.From(s),
int i => Variant.From(i),
long l => Variant.From(l),
byte b => Variant.From(b),
ushort us => Variant.From(us),
uint ui => Variant.From(ui),
ulong ul => Variant.From(ul),
float f => Variant.From(f),
double d => Variant.From(d),
Vector2 v2 => Variant.From(v2),
Vector2I v2i => Variant.From(v2i),
Rect2 r2 => Variant.From(r2),
Rect2I r2i => Variant.From(r2i),
Transform2D t2 => Variant.From(t2),
Projection proj => Variant.From(proj),
Vector3 v3 => Variant.From(v3),
Vector3I v3i => Variant.From(v3i),
Basis b => Variant.From(b),
Quaternion q => Variant.From(q),
Transform3D t3 => Variant.From(t3),
Vector4 v4 => Variant.From(v4),
Vector4I v4i => Variant.From(v4i),
Aabb a => Variant.From(a),
Color c => Variant.From(c),
Plane p => Variant.From(p),
Callable c => Variant.From(c),
Signal s => Variant.From(s),
string s => Variant.From(s),
byte[] b => Variant.From(b),
int[] i => Variant.From(i),
long[] l => Variant.From(l),
float[] f => Variant.From(f),
double[] d => Variant.From(d),
string[] str => Variant.From(str),
Vector2[] v2 => Variant.From(v2),
Vector3[] v3 => Variant.From(v3),
Vector4[] v4 => Variant.From(v4),
Color[] c => Variant.From(c),
StringName[] str => Variant.From(str),
NodePath[] n => Variant.From(n),
Rid[] r => Variant.From(r),
StringName str => Variant.From(str),
NodePath n => Variant.From(n),
Rid rid => Variant.From(rid),
Godot.Collections.Array g => Variant.From(g),
Godot.Collections.Dictionary d => Variant.From(d),
GodotObject o => Variant.From(o),
_ => new Variant()
};
}
private static StringName GetCachedStringName(string name)
{
if (NameCache.TryGetValue(name, out var stringName))
{
return stringName;
}
stringName = new StringName(name);
NameCache[name] = stringName;
return stringName;
}
public static dynamic Create(GodotObject target)
{
return new GodotObjectProxy(target);
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment