Assets
An asset declaration gives a value with a codec a family name, a path below Assets.zip/Server, parent and tag rules, and a place in the server load transaction. The feature module registers the family and handles validation or projection that depends on the rest of the game.
Asset Declarations
For an imported asset, the schema remains the source of the value shape. Its reviewed overlay adds asset behavior that cannot be inferred safely from the schema.
"assets": {
"examples/ImportedBadgeAsset.json#": {
"storeName": "CodecExampleBadge",
"path": "Examples/Badges",
"parent": "examples/ImportedBadgeAsset.json#/properties/Parent",
"tags": "examples/ImportedBadgeAsset.json#/properties/Tags"
}
}
This declaration generates:
ImportedBadgeAsset.Codecfor ordinary value operationsImportedBadgeAsset.AssetBindingfor module registrationImportedBadgeAsset.PropertyPathsfor load callback queries- a parent aware materializer that removes
ParentandTagsfrom the published value model
Keep module policy out of the overlay. Store ordering, preloads, cross family checks, lookup construction, derived values, and client packets belong to the registering module.
Family Registration
A module registers all its stores during OnSetup, before the Assets module begins loading files. The example keeps registration beside the feature that uses it:
internal static class BadgeAssets
{
public static IReadOnlyList<AssetStoreRegistration> Registrations { get; } =
[
AssetStoreRegistration.Rooted(
ImportedBadgeAsset.AssetBinding,
indexed: true),
];
}
Register the batch under the module ID:
protected override void OnSetup()
{
Services.Resolve<AssetRegistry>().Register(
Context.ModuleId,
BadgeAssets.Registrations);
}
Add Assets to the module dependencies so registration and later reads follow the asset lifecycle.
AssetBinding and AssetDefinition serve different assembly ownership arrangements, so use the generated member exposed by the imported type.
Pack Layout
The family path is relative to Server/ inside Assets.zip. A file at this path publishes the key trailblazer:
Assets.zip
└── Server
└── Examples
└── Badges
└── trailblazer.json
{
"Tags": {
"rarity": ["rare"]
},
"DisplayName": "Trailblazer",
"Description": "Awarded for finding a new route.",
"Variants": [
{
"Texture": "Textures/Badges/Trailblazer.png",
"Priority": 3
}
],
"Weights": {
"gold": 4,
"silver": 2
},
"Mode": "visible"
}
JSON is the default file suffix, so ordinary overlays do not need to repeat it. The file name supplies the asset key, while Parent and Tags remain authoring fields handled during loading.
Published Asset Access
AssetRegistry.Get is available only after the registry reaches its prepared state. A module that depends on Assets can read its family during OnPreloadAsync or later:
protected override Task OnPreloadAsync(CancellationToken _)
{
var badges = Services.Resolve<AssetRegistry>()
.Get(ImportedBadgeAsset.AssetBinding);
Services.Provide(badges);
return Task.CompletedTask;
}
AssetMap<T> is immutable and supports key lookup, stable indices when the registration requested them, and effective tags.
if (badges.TryGet("trailblazer", out var badge))
{
var tags = badges.TagsOf("trailblazer");
var id = badges.IndexOf("trailblazer");
}
Do not retain load contexts or try to mutate the published map. Runtime player or world state belongs in Flecs components, tags, or pairs rather than in an asset definition.
Lifecycle Callbacks
AssetStoreRegistration.Rooted accepts several optional callbacks:
| Callback | Use it for |
|---|---|
validate |
References to Common files, tags, other asset families, and game rules that the value codec cannot check alone. |
resolve |
Stable indices or immutable lookup state built after every family has been validated. |
contribute |
Derived assets that require the complete captured graph before publication. |
buildPacket |
A one way client projection required by an existing protocol packet. |
preloads |
Module supplied roots that authored files may replace. |
The badge example omits these callbacks because its schema constraints are sufficient and it has no external reference or client packet. Empty callbacks would imply lifecycle work that does not exist.
When validation needs another family, read it through context.Registry.GetDependency(...). The registry supplies a complete candidate graph to callbacks and publishes nothing if any phase fails.