Prefabs and Spawning
Prefabs are reusable scene hierarchies stored as prefab items. At runtime you load the prefab item and create an SgPrefab scene object to instantiate it in the active scene. This guide covers spawning, positioning, cleanup, and common patterns.
Prefab items vs SgPrefab scene objects
| Prefab item (via Loader) | The saved hierarchy in /resources/prefabs/... |
|---|---|
| SgPrefab (scene object) | A live scene object that references and expands a prefab item in the running scene |
Basic spawn
local Loader = require 'engine/loader'
local SgPrefab = require 'engine/sceneobjects/sgprefab'
local Transform = require 'engine/math/transform'
local Vector3 = require 'engine/math/vector3'
local Spawner = Class.new(Behaviour)
function Spawner:init()
self.enemyPrefab = await(Loader.loadPrefab("/resources/prefabs/enemy"))
end
function Spawner:spawnAt(position)
SgPrefab.create({
name = "Enemy",
prefab = self.enemyPrefab,
transform = Transform.new(position),
}):next(function(instance)
instance:setActive(true)
end)
end
return Spawner
Spawn under a parent
Pass a parent SceneObject as the last argument to keep the hierarchy tidy:
SgPrefab.create({
prefab = self.enemyPrefab,
transform = Transform.new(Vector3.new(x, 0, z)),
}, self.spawnRoot):next(function(instance)
instance:addTag("Enemy")
end)
Using a Property.Link
If the prefab is fixed at design time, expose it as a link property instead of loading by path:
Spawner:addProperty("enemyPrefab", Property.Link, nil, {
allowedTypes = { "Prefab" },
})
function Spawner:spawnAt(position)
SgPrefab.create({
prefab = self.enemyPrefab,
transform = Transform.new(position),
}):next(function(instance)
-- ...
end)
end
The link is resolved to a loaded Item handle before init() runs.
Procedural level assembly
Combine prefab spawning with primitives and lights for procedural content:
local SgLightsource = require 'engine/sceneobjects/sglightsource'
local Color = require 'engine/data/color'
function LevelBuilder:init()
self.floor = await(SgBox.create({
name = "Floor",
width = 20, height = 0.2, depth = 20,
transform = Transform.new(Vector3.new(0, -0.1, 0)),
}))
await(SgLightsource.create({
type = SgLightsource.LightType.Directional,
intensity = 3,
color = Color.new(255, 255, 255),
}))
for i = 1, 5 do
await(SgPrefab.create({
prefab = self.treePrefab,
transform = Transform.new(Vector3.new(i * 3, 0, 0)),
}, self.floor))
end
end
Accessing spawned content
After spawn, find child nodes by name inside the instance hierarchy:
Or store the returned SgPrefab handle on the spawner behaviour.
Cleanup and pooling
Delete instances you no longer need:
Simple pool — reuse instead of recreating:
function Spawner:getFromPool()
for _, obj in ipairs(self.pool) do
if not obj:getActive() then
obj:setActive(true)
return obj
end
end
local obj = await(SgPrefab.create({ prefab = self.enemyPrefab }))
table.insert(self.pool, obj)
return obj
end
function Spawner:releaseToPool(obj)
obj:setActive(false)
end
Async pitfalls
- Do not call
await(Loader.loadPrefab(...))every frame — cache the item ininit(). - Prefer
:next()for spawns triggered during gameplay (player actions, waves). - Use
waitUntilFinished = truewhen the prefab must be fully ready before gameplay logic runs (see Asset Loading).
Split and dynamic meshes
For one-off geometry rather than saved prefabs, create typed scene objects directly (SgMesh, SgSphere, …). See Scene Graph and Procedural Content for building scenes without prefab items.
Further reading
- Asset Loading — Loader paths and options
- Scene Graph — transforms, parenting, tags
- SgPrefab — SgPrefab API
- Prefab — prefab item type