Data Prefabs
An .axprefab is a versioned JSON asset that describes a hierarchy of GameObjects, transforms, tags, and components. Prefabs contain no executable code. Component IDs resolve through explicit registrations, and scene dependency injection still constructs every component.
Use prefabs for repeatable object composition while keeping behavior in components. Prefab instantiation does not replace scenes, save games, or package GameObject factories.
Create a prefab
{
"formatVersion": 1,
"root": {
"id": "crate",
"name": "Crate",
"active": true,
"tags": ["destructible"],
"transform": {
"position": { "x": 0, "y": 0 },
"rotation": 0,
"scale": { "x": 1, "y": 1 }
},
"components": [
{
"type": "axolotl.sprite-renderer",
"enabled": true,
"data": {
"sprite": { "texture": "crate" }
}
},
{
"type": "axolotl.physics-body",
"data": {
"bodyType": "dynamic",
"shapes": [
{
"type": "box",
"size": { "x": 64, "y": 64 },
"density": 1,
"friction": 0.6,
"restitution": 0.1
}
]
}
}
],
"children": []
}
}
All angles are radians. Positions, sizes, and physics dimensions use the same pixel-based world units as the runtime APIs. Component data must be a JSON object.
The root and each child support:
| Property | Default | Meaning |
|---|---|---|
id |
None | Optional prefab-local ID used by component references |
name |
GameObject |
Runtime GameObject name |
active |
true |
Initial GameObject activation |
tags |
[] |
Unique, case-sensitive tags |
transform.position |
{ "x": 0, "y": 0 } |
Local position |
transform.rotation |
0 |
Local rotation |
transform.scale |
{ "x": 1, "y": 1 } |
Local scale |
components |
[] |
Components in attachment order |
children |
[] |
Child GameObjects |
IDs must be unique inside one prefab. The loader rejects unknown structural properties, duplicate IDs and tags, non-finite transform values, excessive nesting, and unreasonably large object or component counts.
Load and instantiate
Load standalone files during game initialization:
await assets.LoadFileAsync<PrefabAsset>(
"crate", "Assets/Prefabs/crate.axprefab", cancellationToken);
Instantiate from an active scene:
var crate = Instantiate(assets.Get<PrefabAsset>("crate"));
crate.Transform.LocalPosition = new Vector2(300, 120);
The optional name argument overrides only the root name:
var bossCrate = Instantiate(prefab, "Boss crate");
Instantiation creates the complete GameObject hierarchy first. It then attaches components in declaration order and resolves deferred object references. If any component ID, data, or reference is invalid, every object created by that call is scheduled for destruction and the exception is propagated.
Asset references such as texture and font are keys in AssetManager. Preload those assets before instantiating the prefab. This keeps prefab instantiation synchronous and consistent with normal scene construction.
Package prefabs in .axpkg
Declare prefabs like any other module asset:
<AxolotlAsset Include="Assets\Prefabs\crate.axprefab"
Name="prefabs/crate" />
The built-in MSBuild importer validates the prefab and records it as PrefabAsset with content type application/vnd.axolotl2d.prefab+json. Load it after mounting the package:
await assets.LoadPackageAsync<PrefabAsset>(
"crate", "my.game.content", "prefabs/crate", cancellationToken);
Content-only packages may contain prefabs. They can use any component IDs registered by the host game. A trusted executable module may additionally register its own prefab components.
Custom components
A component can own its data contract by implementing IPrefabDataReceiver:
public sealed record HealthPrefabData(int Maximum, int Current);
public sealed class Health(GameObject gameObject)
: Component(gameObject), IPrefabDataReceiver
{
public int Maximum { get; private set; }
public int Current { get; private set; }
public void LoadPrefabData(JsonElement data, PrefabLoadContext context)
{
var values = context.Deserialize<HealthPrefabData>(data);
Maximum = values.Maximum;
Current = values.Current;
}
}
Register its stable ID while configuring the game:
services.UseSceneManagerGameHost<MyGame>();
services.AddPrefabComponent<Health>("mygame.health");
Then use it in data:
{
"type": "mygame.health",
"data": { "maximum": 100, "current": 75 }
}
PrefabLoadContext.Deserialize<T>() uses camel-case JSON, string enums, and rejects unknown properties. It also exposes GetAsset<T>(), GetObject(id), and GetComponent<T>(objectId).
Use Defer() when data refers to a component that might appear later in the prefab:
public void LoadPrefabData(JsonElement data, PrefabLoadContext context)
{
var values = context.Deserialize<FollowPrefabData>(data);
context.Defer(() => Target = context.GetObject(values.Target).Transform);
}
Components receive prefab data before Awake and the initial OnEnable. A component may therefore validate required prefab values in Awake. Components required by Awake must appear earlier on the same GameObject. Start still runs after the entire prefab has been instantiated.
For a component that should not implement the interface, register a typed loader:
services.AddPrefabComponent<Health, HealthPrefabData>(
"mygame.health",
static (health, values, _) => health.Set(values.Maximum, values.Current));
A trusted module registers an interface-based component from IAxolotlModule.Initialize:
context.RegisterPrefabComponent<ModuleEnemy>("my.module.enemy");
Package component IDs are removed when their package is unloaded. Prefix custom IDs with the game or package ID to avoid collisions. Prefab JSON never names CLR types and never enables code from a content-only package.
Built-in component IDs
Enums use camel-case names. Colors accept #RRGGBB and the built-in names transparent, white, black, red, green, blue, yellow, cyan, magenta, gray, darkgray, lightgray, orange, and brown.
| ID | Component | Main data properties |
|---|---|---|
axolotl.sprite-renderer |
SpriteRenderer |
sprite, tint, space, depth, lightingLayer |
axolotl.sprite-animator |
SpriteAnimator |
texture, frameWidth, frameHeight, margin, spacing, animations, play |
axolotl.physics-body |
PhysicsBody |
bodyType, damping, gravity, bullet flag, shapes |
axolotl.box-collider |
BoxCollider |
size, offset, material, sensor flag, category/mask/group filter |
axolotl.circle-collider |
CircleCollider |
radius, offset, material, sensor flag, category/mask/group filter |
axolotl.capsule-collider |
CapsuleCollider |
point1, point2, radius, material, sensor flag, category/mask/group filter |
axolotl.polygon-collider |
PolygonCollider |
points, material, sensor flag, category/mask/group filter |
axolotl.segment-collider |
SegmentCollider |
point1, point2, material, sensor flag, category/mask/group filter |
axolotl.distance-joint |
DistanceJoint |
connected body ID, local anchors, length, spring, limits, motor |
axolotl.revolute-joint |
RevoluteJoint |
connected body ID, local anchors, spring, angular limits, motor |
axolotl.light |
Light2D |
kind, color, intensity, radius, height, falloff, spot angle, layers, shadows |
axolotl.shadow-caster |
ShadowCaster2D |
points, layerMask |
axolotl.particle-emitter |
ParticleEmitter |
The public emitter settings, optional sprite, and randomSeed |
axolotl.ui-transform |
UITransform |
Anchors, pivot, position, size, offsets, size limits, optional parent object ID |
axolotl.ui-visual |
UIVisual |
sprite, primitive, color, thickness, depth |
axolotl.ui-text |
UIText |
font key, text, font size, color, alignment, depth |
axolotl.ui-layout |
UILayoutGroup |
direction, alignment, padding, spacing, child expansion |
axolotl.ui-clip |
UIClip |
No data |
axolotl.ui-button |
UIButton |
pointer button, interactable, navigation order, depth |
axolotl.ui-toggle |
UIToggle |
Selectable settings and initial value |
axolotl.ui-slider |
UISlider |
Selectable settings, minimum, maximum, step, value |
axolotl.ui-progress-bar |
UIProgressBar |
value, background color, fill color, depth |
axolotl.ui-scroll-view |
UIScrollView |
content object ID, content size, wheel speed, enabled axes |
Sprite data
Components that accept sprite use this shared shape:
{
"texture": "logo",
"normalMap": "logo-normal",
"source": { "x": 0, "y": 0, "width": 64, "height": 64 },
"origin": { "x": 0.5, "y": 0.5 }
}
Only texture is required.
Physics shapes
bodyType is static, kinematic, or dynamic. Shapes are box, circle, capsule, polygon, or segment:
"shapes": [
{ "type": "box", "size": { "x": 80, "y": 40 } },
{ "type": "circle", "radius": 20, "restitution": 0.5 },
{ "type": "capsule", "point1": { "x": 0, "y": -20 }, "point2": { "x": 0, "y": 20 }, "radius": 12 },
{ "type": "polygon", "points": [{ "x": -20, "y": 20 }, { "x": 0, "y": -20 }, { "x": 20, "y": 20 }] },
{ "type": "segment", "point1": { "x": -50, "y": 0 }, "point2": { "x": 50, "y": 0 } }
]
Each shape also accepts density, friction, and restitution with the same defaults and validation as PhysicsBody.
shapes is optional when the GameObject declares a separate collider component. Collider components add sensor behavior, 64-bit categoryBits and maskBits, and groupIndex while keeping the inline shape format compatible. Polygons accept three to eight finite points and are reduced to a valid convex hull.
Joint connectedBody values refer to a prefab object ID. anchorA is local to the joint's GameObject and anchorB is local to the connected body's GameObject. The loader resolves the body reference after the complete prefab hierarchy exists, so moving or rotating the instantiated prefab before Start preserves the joint geometry:
{
"type": "axolotl.distance-joint",
"data": {
"connectedBody": "anchor",
"anchorA": { "x": 0, "y": 0 },
"anchorB": { "x": 0, "y": 0 },
"length": 180,
"maximumLength": 180,
"enableSpring": true
}
}
Sprite animations
The animator slices one texture into a uniform sheet. Omitting frames uses every frame in that sheet:
{
"texture": "run",
"frameWidth": 255,
"frameHeight": 255,
"animations": [
{
"name": "run",
"framesPerSecond": 20,
"playback": "loop",
"frames": [
{ "index": 0 },
{ "index": 1, "duration": 0.08, "marker": "footstep" },
{ "index": 2 }
]
}
],
"play": "run"
}
The animator requires an axolotl.sprite-renderer on the same GameObject.