Creature & Brain
A Creature is any living entity in the world — player character or NPC.
A Brain is the decision-making layer attached to a Creature. The two are
always separate: swapping the Brain transfers control without changing the
Creature's world state.
Design overview
This mirrors Unreal's Pawn / Controller separation. Key benefits:
- Possession — give the player control of any Creature at runtime with one call.
- Cutscene takeover — detach the player brain, run scripted movement, reattach.
- AI testing — attach a
CreatureBrainto the player's character to verify AI. - Spectator — possess a camera-only creature with a
PlayerBrain.
Spawning creatures
function on_init()
-- Spawn at a marker node in the scene
local player = Engine.world.spawn_creature("player_start")
player:set_brain(Engine.world.make_player_brain())
local guard = Engine.world.spawn_creature("guard_spawn_01")
guard:set_brain(Engine.world.make_creature_brain("scripts://entities/guard.lua"))
end
| Function | Description |
|---|---|
Engine.world.spawn_creature(marker) | Spawn at a named marker node; returns Creature |
Engine.world.find_creature(name) | Look up an already-spawned creature by name; returns Creature|nil |
Engine.world.make_player_brain() | Create a PlayerBrain ready to attach |
Engine.world.make_creature_brain(uri) | Create a CreatureBrain backed by a Lua script |
Brain swap
creature:set_brain(Engine.world.make_player_brain())
-- Give control to an AI script
creature:set_brain(Engine.world.make_creature_brain("scripts://entities/boss.lua"))
-- Remove all control — creature idles
creature:set_brain(nil)
-- Query what is currently attached
local kind = creature:get_brain_type() -- "player" | "ai" | "none"
Reading state
local x, y, z = creature:get_position()
local p, yaw, r = creature:get_rotation()
local vx, vy, vz = creature:get_velocity()
local hp = creature:get_health()
local maxHp = creature:get_max_health()
local alive = creature:is_alive()
local ground = creature:is_grounded()
local mode = creature:get_camera_mode() -- "first_person" | "third_person"
Writing state
creature:set_health(80)
creature:set_move_speed(5.0) -- world units per second
creature:set_camera_mode("third_person")
creature:set_visible(false)
Actions
-- Combat
creature:take_damage(25, attacker) -- attacker is a Creature or nil
creature:heal(10)
creature:kill() -- instant death, fires "death" event
-- Physics
creature:apply_impulse(0, 8, 0) -- jump-like upward boost
creature:teleport(10, 0, -5) -- instant position change, no physics
-- Animation
creature:play_animation("run")
-- Abilities
creature:give_ability("fireball")
creature:use_ability("fireball")
creature:remove_ability("fireball")
Events
Subscribe to creature-local events with creature:on(event, callback).
These fire only for this specific creature instance, not all creatures.
creature:on("damage", function(amount, source)
Engine.log.info("Hit for " .. amount)
if source then
Engine.log.info("by " .. source.name)
end
end)
creature:on("death", function(killer)
Engine.events.fire("player_died")
end)
creature:on("healed", function(amount) end)
creature:on("grounded", function() end)
creature:on("airborne", function() end)
creature:on("ability_used", function(ability_id) end)
Unsubscribe with creature:off(event).
Camera rig
Every Creature owns a camera rig. It is activated automatically when a
PlayerBrain takes control and deactivated when the brain is swapped away.
You can also configure it explicitly:
local cam = creature:get_camera()
cam:set_mode("third_person") -- or "first_person"
cam:set_distance(4.0) -- spring arm length (third-person only)
cam:set_height_offset(1.5) -- pivot height above creature origin
cam:set_fov(75.0) -- vertical FOV in degrees
-- Activate / deactivate manually (e.g. for split-screen or cutscene cameras)
cam:activate()
cam:deactivate()
local active = cam:is_active()
local mode = cam:get_mode()
The spring arm automatically slides away from geometry in third-person mode, so the camera never clips through walls.
Minimal player setup
-- scripts/entities/player.lua
local M = {}
function M.init(marker_name)
local p = Engine.world.spawn_creature(marker_name)
p:set_brain(Engine.world.make_player_brain())
p:set_move_speed(5.0)
local cam = p:get_camera()
cam:set_mode("third_person")
cam:set_distance(4.0)
cam:set_fov(75.0)
p:on("death", function()
Engine.events.fire("player_died")
end)
return p
end
return M
-- scripts/main.lua
local player = require("entities.player")
function on_init()
player.init("player_start")
end
Minimal AI creature
The script passed to make_creature_brain is loaded into the same Lua state
and called as if it were main.lua, but scoped to that single creature.
The creature is passed as self:
-- scripts/entities/guard.lua
local patrol_points = { "patrol_a", "patrol_b", "patrol_c" }
local current = 1
local wait = 0.0
function on_update(dt)
wait = wait - dt
if wait > 0 then return end
local target = Engine.scene.find(patrol_points[current])
if target then
local tx, ty, tz = target:get_position()
self:teleport(tx, ty, tz)
self:play_animation("walk")
end
current = current % #patrol_points + 1
wait = 2.0
end