Veduta

Entities

Everything in a game is an entity: the hero, a wall, a coin, an invisible trigger, the level builder. An entity has a position, rotation and scale, an optional model and material (what it looks like), tags, a kind (what it does) and a state (its own values).

Entities come from the scene file when a scene loads, or from scene.spawn while the game runs. A Lua value of an entity is always the same value, so a == b tells whether two variables hold the same entity.

Fields

FieldAccessMeaning
idreada number, 1, 2, 3… in load and spawn order
namereadunique in the scene
kindreadthe name of its behaviour
alivereadfalse once despawned
x, y, zread/writeposition, relative to the parent if it has one
visibleread/writedrawn or not; an invisible entity still updates and collides
model, materialread/writeasset names, or nil
layerread/writethe first key of the draw order (see 2D Games)
frameread/writethe frame of a sprite sheet its material cuts into a grid (see 2D Games)
parentread/writethe parent entity or nil; set it to an entity, an entity's name or nil
hitboxread/write{{min x, y, z}, {max x, y, z}} in the entity's own space, or nil
anim, anim_doneread/write, readthe clip it plays (see 2D Games); true once a clip that does not loop has ended
colorread/writea tint multiplying what it draws ("#40a0ff", 0x40a0ff, nil for none), read back as 0xrrggbb; "#rrggbbaa" sets alpha too
alpharead/write0 (invisible) to 1 (default); below 1 it blends over what is behind
bodyread/writea velocity the engine moves each tick, or nil: see Bodies
stateread/writea table of your own values

Setting any other field is an error. Changing frame every few ticks is how a sprite is animated, when its material is a sprite sheet:

kinds.torch = {
  update = function(e)
    e.frame = engine.tick // 4 % 3   -- frames 0, 1, 2, a new one every 4 ticks
  end,
}

Methods

MethodDoes
e:position()returns x, y, z
e:set_position(x, y, z)
e:move(dx, dy, dz)adds to the position
e:world_position()x, y, z in the world, with the parents' transforms applied
e:rotation(), e:set_rotation(x, y, z)Euler angles in degrees
e:scale(), e:set_scale(x, y, z)
e:has_tag(t), e:add_tag(t), e:remove_tag(t), e:tags()tags
e:overlapping([tag])the live entities whose bounds overlap this one's, in id order; only those with tag when given
e:bounds()min x, y, z, max x, y, z in the world, or nil for an entity with no model and no hitbox
e:children()the live entities whose parent is this one, in id order
e:despawn()removes the entity, and its children, at the end of the tick; its kind's on_despawn runs first
e:play(clip), e:clips()starts a clip from its first frame; the clips it can play
e:move_and_slide(dx, dy [, solid [, oneway]])moves in the x, y plane, stopped by entities and map cells tagged solid (default "solid") and sliding along them; oneway ones (default "oneway") stop it only falling onto their top. Returns stopped along x, along y
e:move_toward(x, y, speed [, solid]), e:move_toward(other, speed [, solid])one tick's step at speed metres a second toward a point or an entity, sliding against solid when given; true once there
e:distance_to(x, y), e:distance_to(other)the distance in the x, y plane
e:face(dx)turns a sprite toward the side dx points to by mirroring its x scale; 0 keeps it
e:flash([color [, seconds]])paints it color (white) for seconds (0.1): a hit
e:cooldown(name, seconds)true, and starts the wait, when the cooldown name is over; false while it runs

Rotations are Euler angles in degrees, applied roll about Z first, then pitch about X, then yaw about Y (R = Ry · Rx · Rz), as in scene files.

Kind callbacks

Besides init and update, a kind may have two callbacks the engine calls on events:

kinds.coin = {
  on_touch = function(e, other)     -- e and other started to overlap (end of the tick)
    if other:has_tag("hero") then e:despawn() end
  end,
  on_despawn = function(e)          -- inside e:despawn(), before it goes
    particles.burst{x = e.x, y = e.y, color = "#ffd84a"}
  end,
}

on_touch runs once per new overlap, for each of the two whose kind has it, when the trace records the collision event; it waits while the game is paused. Keep checking e:overlapping for things that last (standing in lava).

Bodies

e.body makes the engine move the entity each tick, after the kinds, as e:move_and_slide does:

kinds.crate = {
  init = function(e)
    e.body = {gravity = 30, max_fall = 15}      -- falls and lands on "solid"
  end,
  update = function(e)
    if e.body.on_ground and #e:overlapping("spring") > 0 then
      e.body.vy = 16                             -- metres a second, up
    end
  end,
}
Body fieldMeaning
vx, vyvelocity, metres a second (+y up)
gravity, max_fallpulls vy down (metres a second²); the fastest fall
solidthe tag that stops it ("solid"; false for none)
onewaythe tag of platforms it lands on from above ("oneway"; false for none)
on_ground, on_ceiling, on_wallset by the engine: what it hit this tick (on_wall −1 left, 1 right, 0 none)

A side it hits zeroes that velocity. e.body = nil stops it. See Game Toolkit for a platformer and a top-down hero.

State

e.state holds the values that belong to one entity: its health, its direction, a timer. Entities of a Lua kind start with an empty table.

kinds.enemy = {
  init = function(e)
    e.state.health = 3
    e.state.dir = 1
  end,
  update = function(e)
    for _, shot in ipairs(e:overlapping("shot")) do
      shot:despawn()
      e.state.health = e.state.health - 1
    end
    if e.state.health <= 0 then
      trace("enemy_down", {name = e.name})
      e:despawn()
    end
  end,
}

The trace records the state with every tick as state.<key>, so a scenario can check it: {"entity": "boss", "path": "state.health", "op": "==", "value": 0}. Keep it to numbers, strings, booleans and tables of them: other values (functions, entities) are recorded as a description only.

Spawning

local coin = scene.spawn{
  kind = "coin",
  name = "coin_7",
  model = "quad",
  material = "coin",
  position = {3, -2, 1},
  rotation = {0, 0, 45},
  scale = {0.5, 0.5, 1},
  tags = {"coin", "pickup"},
  visible = true,
  layer = 0,
  frame = 0,
  parent = nil,                             -- an entity or an entity's name
  hitbox = {{-0.3, -0.3, -0.5}, {0.3, 0.3, 0.5}},
  state = {value = 5},
}

Every field is optional. kind defaults to static (no behaviour). Without a name the entity is named after its kind and id (coin_12); a name already taken gets #id added. state is merged into the state the entity starts with. The new entity's kind init runs at once; its update starts on the next tick.

Spawning is allowed from game.init, game.update and any kind's init or update, including while a scene loads, which is how Your First Game builds its level from a text map.

Parents

An entity with a parent moves, turns and scales with it: its position, rotation and scale are relative to the parent. A sword in the hero's hand, a party member's shadow, a turret on a tank:

local hero = scene.find("hero")
local sword = scene.spawn{name = "sword", model = "sword", material = "steel",
  parent = hero, position = {0.4, 0.2, 0.1}}

-- later: drop it where it is
local x, y, z = sword:world_position()
sword.parent = nil
sword:set_position(x, y, z)

Setting parent keeps the entity's local position, so re-parenting moves it unless you set the position again, as above. A parent that would make a cycle (an entity under its own child) is an error. Despawning a parent despawns its children.

Prefabs

A prefab (assets/prefabs/<name>.vprefab) is a group of entities placed as one: a house with its door, a camp, a room. Worlds place them; scripts can too:

local camp = scene.spawn_prefab("camp", 12, 0, -4, 90, "camp_north")
camp.fire.state.lit = true           -- each entity under its name in the prefab
for _, e in ipairs(camp) do          -- and all of them in prefab order
  e:add_tag("camp")
end

scene.spawn_prefab(name, x, y, z [, rotation [, prefix]]) puts the corner of the prefab's footprint at (x, y, z), turns it by 0, 90, 180 or 270 degrees about +Y, names its entities <prefix>_<entity> (the prefix defaults to the prefab's name) and keeps the parents the prefab gives. The prefab format is in the prefab reference.

Finding entities

FunctionReturns
scene.find(name)the entity, or nil
scene.tagged(tag)a list of the live entities with that tag, in id order
scene.entities()every live entity, in id order
scene.nearest(x, y [, tag [, max]])the nearest live entity (with tag, within max) and its distance, or nil
scene.within(x, y, radius [, tag])the live entities within radius, nearest first
scene.raycast(x, y, dx, dy, distance [, tag])the first entity or map cell tagged tag ("solid") along a ray, or nil (see Scenes)

Lists are ordinary Lua tables, safe to keep for the tick. Across ticks, check e.alive before using an entity you kept: a despawned entity stays a valid value, but it is no longer in the scene.

Collisions

e:overlapping(tag) compares axis-aligned bounding boxes: the model's bounds, or the entity's hitbox when it has one. It uses the bounds computed at the end of the previous tick, so an entity moved this tick is seen where it was.

The engine also records a collision event in the trace when two entities start overlapping (two static entities never do), which scenarios can count, and calls the kinds' [on_touch](#kind-callbacks) then.

Veduta v2 (Lua games, release candidates). This wiki is built from wiki/ in the engine's repository, where its examples are tested, and published with every engine release on https://veduta.roomve.it/docs/.