Prefabs
Prefabs are reusable entity templates that define a set of components. They allow you to define entity archetypes once and instantiate them multiple times with consistent component configurations.
Deprecation Notice: The runtime prefab API (
EntityPrefab,world.RegisterPrefab(),world.SpawnFromPrefab()) is deprecated. Use.keprefabfiles with source-generated spawn methods instead. See Migration Guide below.
Source-Generated Prefabs (Recommended)
The recommended approach is to define prefabs in .keprefab JSON files. These are processed at compile-time by the SceneGenerator to produce type-safe spawn methods.
Creating a .keprefab File
Create a JSON file with the .keprefab extension in your project:
// Prefabs/Enemy.keprefab
{
"name": "Enemy",
"version": 1,
"root": {
"id": "enemy",
"name": "Enemy",
"components": {
"MyGame.Position": { "X": 0, "Y": 0 },
"MyGame.Health": { "Current": 100, "Max": 100 },
"MyGame.Sprite": { "TextureId": "enemy.png", "Layer": 1 },
"MyGame.EnemyTag": {}
}
},
"overridableFields": ["MyGame.Position.X", "MyGame.Position.Y"]
}
Project Configuration
Add the prefab files to your project as AdditionalFiles:
<ItemGroup>
<AdditionalFiles Include="Prefabs\*.keprefab" />
</ItemGroup>
Using Generated Spawn Methods
The generator creates a Scenes class with spawn methods for each prefab:
// Spawn with default values
var enemy = Scenes.SpawnEnemy(world);
// Spawn with override parameters (from overridableFields)
var enemy = Scenes.SpawnEnemy(world,
myGamePositionX: 100,
myGamePositionY: 50);
Benefits of Source-Generated Prefabs
- Compile-time validation: Component types and field names are verified at build time
- Type-safe API: IDE autocomplete for spawn methods and parameters
- Zero runtime overhead: No registration or string lookups at runtime
- Overridable fields: Become typed optional parameters on spawn methods
Prefab File Schema
{
"$schema": "https://keeneyes.dev/schemas/prefab-v1.json",
"name": "PrefabName",
"version": 1,
"root": {
"id": "unique-id",
"name": "EntityName",
"components": {
"Namespace.ComponentType": {
"FieldName": value
}
},
"children": []
},
"children": [],
"overridableFields": ["Namespace.Component.Field"]
}
Listing Available Prefabs
The generated Scenes class provides a list of all prefab names:
foreach (var prefabName in Scenes.All)
{
Console.WriteLine($"Available: {prefabName}");
}
Migration Guide
Step 1: Create .keprefab Files
For each runtime prefab, create a corresponding .keprefab file:
Before (deprecated):
var enemyPrefab = new EntityPrefab()
.With(new Position { X = 0, Y = 0 })
.With(new Health { Current = 100, Max = 100 })
.WithTag<EnemyTag>();
world.RegisterPrefab("Enemy", enemyPrefab);
After (recommended):
// Prefabs/Enemy.keprefab
{
"name": "Enemy",
"root": {
"id": "enemy",
"components": {
"MyGame.Position": { "X": 0, "Y": 0 },
"MyGame.Health": { "Current": 100, "Max": 100 },
"MyGame.EnemyTag": {}
}
}
}
Step 2: Update Project File
Add the prefab files to your project:
<ItemGroup>
<AdditionalFiles Include="Prefabs\*.keprefab" />
</ItemGroup>
Step 3: Replace Spawn Calls
Before (deprecated):
var enemy = world.SpawnFromPrefab("Enemy").Build();
var enemy = world.SpawnFromPrefab("Enemy")
.With(new Position { X = 100, Y = 50 })
.Build();
After (recommended):
var enemy = Scenes.SpawnEnemy(world);
var enemy = Scenes.SpawnEnemy(world,
myGamePositionX: 100,
myGamePositionY: 50);
Step 4: Remove Runtime Registration
Delete all world.RegisterPrefab() calls and EntityPrefab definitions.
Inheritance Note
Prefab inheritance (base field in .keprefab) is not yet implemented. For prefabs that used Extends(), flatten the component definitions into each prefab file.
Runtime Prefabs (Deprecated)
Warning: This API is deprecated and will be removed in a future version. Use Source-Generated Prefabs instead.
Defining a Prefab
#pragma warning disable CS0618 // Suppress deprecation warning
var enemyPrefab = new EntityPrefab()
.With(new Position { X = 0, Y = 0 })
.With(new Health { Current = 100, Max = 100 })
.With(new Velocity { X = 0, Y = 0 })
.WithTag<EnemyTag>();
Registering a Prefab
world.RegisterPrefab("Enemy", enemyPrefab);
Spawning from a Prefab
// Spawn with default prefab values
var enemy1 = world.SpawnFromPrefab("Enemy").Build();
// Spawn with overridden values
var enemy2 = world.SpawnFromPrefab("Enemy")
.With(new Position { X = 100, Y = 50 })
.Build();
Prefab Inheritance (Deprecated)
// Base enemy prefab
var baseEnemyPrefab = new EntityPrefab()
.With(new Position { X = 0, Y = 0 })
.With(new Health { Current = 100, Max = 100 })
.WithTag<EnemyTag>();
world.RegisterPrefab("Enemy", baseEnemyPrefab);
// Flying enemy extends base enemy
var flyingEnemyPrefab = new EntityPrefab()
.Extends("Enemy")
.With(new Velocity { X = 0, Y = -5 })
.WithTag<FlyingTag>();
world.RegisterPrefab("FlyingEnemy", flyingEnemyPrefab);
Prefab Management (Deprecated)
// Check if registered
if (world.HasPrefab("Enemy"))
{
var enemy = world.SpawnFromPrefab("Enemy").Build();
}
// Unregister
world.UnregisterPrefab("Enemy");
// List all prefabs
foreach (var prefabName in world.GetAllPrefabNames())
{
Console.WriteLine($"Registered: {prefabName}");
}
#pragma warning restore CS0618
Performance Considerations
Source-generated prefabs have zero runtime overhead - all work is done at compile time.
Runtime prefabs (deprecated) have the following characteristics:
- Registration: O(1)
- Spawning: O(C * D) where C is total components and D is inheritance depth
- Inheritance resolution happens at spawn time
For performance-critical scenarios, source-generated prefabs are always preferred.