Configuration, state and collections
Separate shared configuration from mutable instance state.
Where a value belongs
| Value | Location |
|---|---|
| Maximum health for an enemy type | ActorSettings, possibly a shared Resource |
| One enemy's current health | A separate HealthModel inside its receiver |
| Item order or local computation | A .NET array or List as appropriate |
| Key-based lookup in C# | Dictionary with an intentional key type |
| Collection crossing Export/Variant | A supported engine collection |
| Progress after the application exits | An explicit user:// save schema |
An Array name does not make .NET arrays, List and Reckfell.Collections.Array interchangeable. Account for the API boundary, ordering, lookup and copying. See C# collections for concrete alternatives.
One resource, two states
ActorSettings.cs
using Reckfell;
[GlobalClass]
public partial class ActorSettings : Resource
{
[Export(PropertyHint.Range, "1,10000,1")]
public int MaximumHealth { get; set; } = 100;
}HealthModel.cs
using System;
public interface IDamageable
{
bool TryApplyDamage(int amount);
}
public sealed class HealthModel : IDamageable
{
public int Maximum { get; }
public int Current { get; private set; }
public HealthModel(int maximum)
{
if (maximum <= 0) throw new ArgumentOutOfRangeException(nameof(maximum));
Maximum = maximum;
Current = maximum;
}
public bool TryApplyDamage(int amount)
{
if (amount <= 0 || Current == 0) return false;
Current = amount >= Current ? 0 : Current - amount;
return true;
}
}DamageReceiver.cs
using Reckfell;
public partial class DamageReceiver : Entity, IDamageable
{
[Export] public ActorSettings? Settings { get; set; }
[Signal] public delegate void HealthChangedEventHandler(int current, int maximum);
private HealthModel? _health;
public int CurrentHealth => _health?.Current ?? 0;
public int MaximumHealth => _health?.Maximum ?? 0;
public override void _Ready() => ResetFromSettings();
public bool ResetFromSettings()
{
if (Settings is null || !ReckfellObject.IsInstanceValid(Settings) ||
Settings.MaximumHealth <= 0) return false;
_health = new HealthModel(Settings.MaximumHealth);
EmitSignal(SignalName.HealthChanged, _health.Current, _health.Maximum);
return true;
}
public bool TryApplyDamage(int amount)
{
if (_health is null || !_health.TryApplyDamage(amount)) return false;
EmitSignal(SignalName.HealthChanged, _health.Current, _health.Maximum);
return true;
}
}ResetFromSettings copies MaximumHealth into a fresh model. Later changes to the shared resource do not alter existing models. Calling ResetFromSettings again creates a new full-health model: it is a reset command rather than a silent maximum adjustment.
If the new configuration is invalid, ResetFromSettings returns false and preserves previous health. Inspector Range is an editor hint; the code must still validate values.
Decide what to copy and save
Passing a Resource reference shares data. Creating a HealthModel creates separate state. Duplicating a container is not a guarantee that every nested resource is cloned.
PackedEntity in this example stores exported Settings rather than the ordinary _health field. _Ready creates a fresh model after prefab instantiation. Persist current health through a separate data record with load-time validation.