Quick answer: Use .As<T>() on each Variant value. Manually map dictionary keys to Resource fields; Godot won’t auto-convert between Dictionary and a typed Resource.
A save system loads game state into a Godot.Collections.Dictionary, then tries to populate a PlayerData Resource. Direct assignment throws InvalidCastException — Variant doesn’t auto-unwrap into custom Resource types.
Manual Field Mapping
public static PlayerData FromDict(Godot.Collections.Dictionary dict)
{
return new PlayerData {
Hp = dict["hp"].As<int>(),
Name = dict["name"].As<string>(),
Position = dict["position"].AsVector3(),
Inventory = dict["inventory"].As<Godot.Collections.Array<string>>(),
};
}
Explicit per-key conversion. Type-safe; compile-time errors if you misspell a property.
Safe Key Access
if (dict.TryGetValue("hp", out Variant hpVar))
{
Hp = hpVar.As<int>();
}
else
{
Hp = 100; // default
}
For data from disk where keys may be missing (old saves), guard each access. Provides defaults for new fields.
Nested Resources
WeaponData = WeaponData.FromDict(dict["weapon"].As<Godot.Collections.Dictionary>())
Recurse: each nested type implements its own FromDict. Composes cleanly.
Round-Trip via JSON
For pure-data Resources, JSON skips Variant entirely:
var json = Json.Stringify(dict);
var data = System.Text.Json.JsonSerializer.Deserialize<PlayerData>(json);
Newtonsoft / System.Text.Json handle nested types automatically. Use when Resource is mostly POCO.
Verifying
Load saved dict; resource fields populated. Edit a field; save; reload; values match. No InvalidCastException.
“Variant is typed at runtime, not compile time. Convert explicitly and defensively.”
For large data models, generate FromDict / ToDict code via a source generator — reduces boilerplate and stays type-safe.