using Godot; using System; using System.Collections.Generic; using System.Linq; using System.Reflection; [Tool] public partial class PODNode : Node where T : class, new() { private T _data = new T(); // We can't export a generic type 'T', but we can still access it. public T Data { get => _data; set { _data = value ?? new T(); // If the data object is replaced, tell the editor to refresh the inspector. if( Engine.IsEditorHint() ) { NotifyPropertyListChanged(); } } } public override Godot.Collections.Array _GetPropertyList() { var properties = new Godot.Collections.Array(); //if (Data == null) return properties; // Use reflection to find all writable properties in our POD. PropertyInfo[] podProperties = typeof( T ).GetProperties( BindingFlags.Public | BindingFlags.Instance ) .Where( p => p.CanRead && p.CanWrite ).ToArray(); foreach( var prop in podProperties ) { // Try to map the C# type to a Godot Variant type. var (variantType, hint, hintString) = CSharpTypeToVariant( prop.PropertyType ); if( variantType == Variant.Type.Nil ) continue; // Skip unsupported types. // Create the dictionary that Godot's Inspector understands. properties.Add( new Godot.Collections.Dictionary { { "name", $"{prop.Name}" }, // Use a category for organization. { "type", (int)variantType }, { "usage", (int)PropertyUsageFlags.Default }, { "hint", (int)hint }, { "hint_string", hintString } } ); } return properties; } public override Variant _Get( StringName property ) { string propName = property.ToString(); if( !propName.StartsWith( "Data/" ) ) return base._Get( property ); string podPropName = propName.Remove( 0, 5 ); // Remove "Data/" prefix. PropertyInfo propInfo = typeof( T ).GetProperty( podPropName ); if( propInfo != null ) { try { // Ensure the property can be read. if( !propInfo.CanRead ) { GD.PrintErr( $"Property '{podPropName}' is not readable." ); return Variant.From( null ); } var value = Variant.From( propInfo.GetValue( Data ) ); return value; } catch( Exception ex ) { GD.PrintErr( $"Failed to access property '{podPropName}': {ex.Message}" ); return Variant.From( null ); } } return base._Get( property ); } public override bool _Set( StringName property, Variant value ) { string propName = property.ToString(); //if (!propName.StartsWith("Data/")) return base._Set(property, value); string podPropName = propName.Remove( 0, 5 ); // Remove "Data/" prefix. PropertyInfo propInfo = typeof( T ).GetProperty( podPropName ); if( propInfo != null ) { try { // Convert Godot's Variant back to the correct C# type. object convertedValue = value.AsType( propInfo.PropertyType ); propInfo.SetValue( Data, convertedValue ); return true; } catch( Exception ex ) { GD.PrintErr( $"Failed to set property '{podPropName}': {ex.Message}" ); return false; } } return base._Set( property, value ); } // Helper to map C# types to Godot's Variant types and property hints. private (Variant.Type, PropertyHint, string) CSharpTypeToVariant( Type type ) { if( type == typeof( int ) || type == typeof( long ) ) return (Variant.Type.Int, PropertyHint.None, ""); if( type == typeof( uint ) || type == typeof( ulong ) ) return (Variant.Type.Int, PropertyHint.None, ""); if( type == typeof( float ) || type == typeof( double ) ) return (Variant.Type.Float, PropertyHint.None, ""); if( type == typeof( string ) ) return (Variant.Type.String, PropertyHint.None, ""); if( type == typeof( bool ) ) return (Variant.Type.Bool, PropertyHint.None, ""); if( type == typeof( Vector2 ) ) return (Variant.Type.Vector2, PropertyHint.None, ""); if( type == typeof( Vector3 ) ) return (Variant.Type.Vector3, PropertyHint.None, ""); if( type == typeof( Color ) ) return (Variant.Type.Color, PropertyHint.None, ""); if( type.IsEnum ) { // Create a comma-separated string of enum names for a dropdown list. string hintString = string.Join( ",", Enum.GetNames( type ) ); return (Variant.Type.Int, PropertyHint.Enum, hintString); } // Add more type mappings as needed. return (Variant.Type.Nil, PropertyHint.None, ""); // Unsupported type. } }