Created
June 7, 2025 17:50
-
-
Save maidopi-usagi/a0cf407a80c9267d2e037a37664419cf to your computer and use it in GitHub Desktop.
GodotObject / Dictionary / Array to ExpandoObject
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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