C#: Inspector and prefab properties
Export, private fields, Range, persistence and value validation in a complete example.
Run the example
Add the five files below to a Reckfell 1.0.0 C# project and build. Create a level rooted at Entity2D, attach ManagedDataDemo2D to the root and run F6. Code creates the children. Ammo starts at 5; Fire once changes it to 4.
ShotSettings.cs
using Reckfell;
[GlobalClass]
public partial class ShotSettings : Resource
{
[Export] public string DisplayName { get; set; } = "Pulse";
[Export(PropertyHint.Range, "1,100,1")] public int Damage { get; set; } = 12;
[Export] public Reckfell.Collections.Array<string> Tags { get; set; } = new() { "weapon" };
public bool TryGetDamage(out int damage)
{
damage = 0;
if (Damage < 1 || Damage > 100) return false;
damage = Damage;
return true;
}
}ExportedWeapon.cs
using Reckfell;
public partial class ExportedWeapon : Entity
{
[Signal] public delegate void FiredEventHandler(int damage);
[Export] public ShotSettings? Settings { get; set; }
[Export(PropertyHint.Range, "0,99,1")] private int _ammo = 5;
public int Ammo => _ammo;
public int TransientValue { get; set; } = 3;
public bool TryFire()
{
if (_ammo <= 0 || !ReckfellObject.IsInstanceValid(Settings) ||
!Settings!.TryGetDamage(out int damage)) return false;
_ammo--;
EmitSignal(SignalName.Fired, damage);
return true;
}
}InventoryLedger.cs
using System;
using System.Collections.Generic;
public sealed class InventoryLedger
{
private readonly Dictionary<string, int> _counts = new(StringComparer.Ordinal);
public int CountOf(string item) => _counts.TryGetValue(item, out int count) ? count : 0;
public bool TryAdd(string item, int amount)
{
if (string.IsNullOrWhiteSpace(item) || amount <= 0) return false;
int current = CountOf(item);
if (current > int.MaxValue - amount) return false;
_counts[item] = current + amount;
return true;
}
public Reckfell.Collections.Dictionary<string, int> Snapshot() => new(_counts);
}VariantCounter.cs
using Reckfell;
public static class VariantCounter
{
public static bool TryRead(Variant value, out int count)
{
count = 0;
if (value.VariantType != Variant.Type.Int) return false;
long raw = value.AsInt64();
if (raw < 0 || raw > 999) return false;
count = (int)raw;
return true;
}
}ManagedDataDemo2D.cs
using Reckfell;
public partial class ManagedDataDemo2D : Entity2D
{
public ExportedWeapon Weapon { get; private set; } = null!;
public InventoryLedger Inventory { get; } = new();
public Label Readout { get; private set; } = null!;
public Button FireButton { get; private set; } = null!;
private bool _ru;
public override void _Ready()
{
_ru = TranslationServer.GetLocale().StartsWith("ru");
var title = new Label { Position = new Vector2(28, 24), Text = _ru ? "C#: настройки и данные" : "C#: settings and data" };
title.AddThemeFontSizeOverride("font_size", 26);
AddChild(title);
AddChild(new Label { Position = new Vector2(28, 74), Text = _ru ? "Resource → свойство → игровая проверка" : "Resource → property → gameplay validation" });
Readout = new Label { Position = new Vector2(28, 126) };
AddChild(Readout);
Weapon = new ExportedWeapon { Settings = new ShotSettings() };
AddChild(Weapon);
Inventory.TryAdd("pulse", 2);
FireButton = new Button { Position = new Vector2(28, 260), Size = new Vector2(230, 48), Text = _ru ? "Сделать выстрел" : "Fire once" };
FireButton.Modulate = new Color(0.35f, 0.55f, 1);
FireButton.Pressed += Fire;
AddChild(FireButton);
AddChild(new Label { Position = new Vector2(28, 336), Text = _ru ? "Range — подсказка. TryFire проверяет данные." : "Range is a hint. TryFire validates the data." });
Refresh();
}
private void Fire()
{
Weapon.TryFire();
Refresh();
}
private void Refresh()
{
Variant sample = 25L;
VariantCounter.TryRead(sample, out int count);
Readout.Text = _ru
? $"Урон: {Weapon.Settings!.Damage}\nБоезапас: {Weapon.Ammo}\nВ инвентаре: {Inventory.CountOf("pulse")}\nVariant.Int: {count}"
: $"Damage: {Weapon.Settings!.Damage}\nAmmo: {Weapon.Ammo}\nInventory: {Inventory.CountOf("pulse")}\nVariant.Int: {count}";
}
}What Export stores
ExportedWeapon.Settings is a Resource reference. The private _ammo field is exported too: private limits C# access, not persistence. Packing this object with _ammo = 9 restores 9. The unexported TransientValue = 42 returns to its initializer 3 in a new instance. Get/Set accessibility alone does not establish persistence.
Level/prefab persistence also follows Owner and PackedEntity rules. Export neither creates a disk savegame nor automatically writes runtime instance changes back to the source asset.
A hint and validation are separate
Range supplies Inspector metadata. Assigning Damage = 1000 or using Set to put −2 in _ammo is not clamped by that range. TryGetDamage and TryFire explicitly reject invalid state before consuming ammo or emitting a signal. Do not use a slider as validation for files, network input or your own code.
Export Variant-compatible fields or readable/writable instance properties. List<int> does not replace Reckfell.Collections.Array<int>. Static, readonly and unsupported types produce RF0101, RF0103 and RF0102 respectively. A Resource cannot export an Entity reference: RF0107; place that reference on an Entity component.
After changing fields, build the C# project first. If Inspector is stale, check build errors, the attached script and exact class/file names. Native persistence and metadata were tested; Inspector mouse actions and saving through its window were not.