Skip to content

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)

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:

local weapon = instance:findByName("Weapon")
if weapon then
    -- configure spawned child
end

Or store the returned SgPrefab handle on the spawner behaviour.

Cleanup and pooling

Delete instances you no longer need:

instance:delete()

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 in init().
  • Prefer :next() for spawns triggered during gameplay (player actions, waves).
  • Use waitUntilFinished = true when 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