Skip to content
Berke edited this page Oct 3, 2026 · 1 revision

Model

Inspector name: Model · Lua: entity.model · Public field type: Model

A Model draws a 3D mesh at the entity's position: crates, furniture, weapons on a table, a statue. Unlike a Sprite, it's real 3D geometry that looks right from every angle.

How it works

File formats

Supported files: .glb, .gltf, .obj, .fbx, .dae, .3ds, .ply, .stl. .glb is the easiest, because textures are packed inside the one file. Put models anywhere under Assets, for example Assets/Models/crate.glb.

A model can bring its own textures, either embedded in the file or as separate image files next to it. The inspector's Textures & Files list shows what the model needs, marking each one embedded or missing. Rescan Dependencies checks again after you've added files.

Loading

  • A file is loaded the first time an entity needs it, then shared: a hundred crates using crate.glb load it once.
  • If a file fails to load, the error is logged once and the entity draws nothing. The engine doesn't retry that file while running.
  • Changing fileName from a script swaps the model on the next frame. Swapping to a file that isn't loaded yet loads it at that moment, which can cause a small hitch the first time.

Position, rotation and scale

When a model is loaded, the engine normalises it, whatever size or origin it had in your modelling tool:

  • it is shrunk or grown so its longest side is exactly 1 unit, keeping its proportions;
  • it is moved so its bottom centre sits at the origin.

Then it is drawn from its Transform:

  • The bottom centre sits at the Transform position, the feet, so models stand on the floor without any adjustment.
  • The Transform's rotation turns it.
  • The Transform's scale is the model's size in units, on each axis. New entities start at scale 32, so any model shows up with its longest side 32 units long. A crate at 16, 16, 16 is 16 units across. A tall statue whose longest side is its height becomes that many units tall.

The units a model was authored in don't matter. Only the Transform scale decides how big it is.

Lighting

The model's own colours and textures are shaded by the sector's light: each channel is multiplied by light / 255. Pixels whose texture alpha is below 0.1 are cut out.

Collision

A Model has no collision of its own. To make a model solid, add a Collider roughly the size of the model.

In the editor

Field Lua Notes
Model File fileName Path relative to Assets, with extension. Drag a model from the Asset Browser.
Textures & Files What the model depends on.
Rescan Dependencies Looks for missing files again.

Scripting

Property Type Description
isValid boolean read-only false if the entity or its Model is gone.
fileName string read/write Model path, e.g. "Models/crate.glb". Empty draws nothing.
Method Description
clearFileName() Empties fileName, so nothing is drawn.

Everything else about how a model looks is set through its Transform.

Examples

Spinning pickup

-- Scripts/Models/Spin.lua
---@field degreesPerSecond number @ Spin Speed
degreesPerSecond = 120

local angle = 0

function Update()
    angle = angle + degreesPerSecond * GameTime.deltaTime
    local half = mathT.DegToRad(angle) * 0.5
    entity.transform.rotation = Vector4(0, mathT.Sin(half), 0, mathT.Cos(half))
end

Breakable object

-- Scripts/Models/Breakable.lua
-- Swap to a broken model after enough hits: crate:GetScript("Breakable"):Hit()
---@field brokenModel string @ Broken Model
brokenModel = "Models/crate_broken.glb"

---@field hitsToBreak int @ Hits To Break
hitsToBreak = 3

local hits = 0

function Hit(self)
    hits = hits + 1
    if hits == hitsToBreak then
        entity.model.fileName = brokenModel
        if entity.collider ~= nil then entity.collider.isActive = false end
    end
end

Pop in with a scale animation

-- Scripts/Models/PopIn.lua
---@field duration number @ Seconds
duration = 0.4

local finalScale
local t = 0

function Start()
    finalScale = entity.transform.scale
    entity.transform.scale = Vector3(0, 0, 0)
end

function Update()
    if t >= duration then return end

    t = math.min(t + GameTime.deltaTime, duration)
    local k = mathT.SmoothStep(0, 1, t / duration)
    entity.transform.scale = finalScale * k
end

Clone this wiki locally