Repository navigation
Debugging
| Call | Shows up in |
|---|---|
Debug.Print(...) |
The in-game console |
Debug.LogInfo/LogWarning/LogError/LogCritical(...) |
The engine log |
| A runtime error inside your script | Both: printed in red in the in-game console, and logged |
Every logging function accepts any number of arguments of any type and joins them with spaces,
like Lua's print: Debug.Print("hp", hp, "alive", alive).
A script that fails to load (a syntax error) never runs. Its error, including the line number, appears in the console and in the script's inspector panel.
When scripting starts, the engine writes an API description to .luals/tilky_api.lua in the project
folder (next to project.tilky, outside Assets). Open the project folder in an editor that uses
the Lua language server (LuaLS) and it reads it for autocomplete and hover docs, using the
.luarc.json the launcher puts in new projects (see
Getting Started). It
covers the bindings that have documentation registered, so some newer members may be missing from
it.
Attach to: the player (needs a Camera and a PlayerController).
Two hotkeys:
- P prints your position, the sector index you're in, and the frame rate.
- I inspects whatever you're looking at: what kind of thing it is, how far away, and its name and tags.
-- Scripts/Debug/DebugTools.lua (entity script)
public Key infoKey = Key.P
public Key inspectKey = Key.I
public number range = 500
local camera, pc, transform
local function TagList(thing)
local tags = {}
for i = 1, thing.tagCount do tags[#tags + 1] = thing:GetTag(i) end
if #tags == 0 then return "(none)" end
return table.concat(tags, ", ")
end
local function Round(value)
return math.floor(value * 100 + 0.5) / 100
end
local function PrintPlayerInfo()
local p = transform.position
local fps = GameTime.deltaTime > 0 and math.floor(1 / GameTime.deltaTime) or 0
Debug.Print("position", Round(p.x), Round(p.y), Round(p.z),
"| sector index", transform.sectorIndex, "| fps", fps)
end
local function Inspect()
-- A ray from the player's eyes. requireCollider = false: see every entity, not only solid ones.
local hit = camera:Raycast(range, false)
if hit == nil then
Debug.Print("Looking at nothing")
return
end
Debug.Print("Hit", hit.type, "at distance", Round(hit.distance))
if hit.entity ~= nil then
Debug.Print(" Entity '" .. hit.entity.name .. "' id", hit.entity.id,
"tags:", TagList(hit.entity), "scripts:", #hit.entity:GetScripts())
end
if hit.wall ~= nil then
Debug.Print(" Wall", hit.wall.id, "length", Round(hit.wall.length), "tags:", TagList(hit.wall))
end
if hit.sector ~= nil then
local name = hit.sector.name
if name == "" then name = "(unnamed)" end
Debug.Print(" Sector", hit.sector.id, name, "tags:", TagList(hit.sector))
end
end
function Start()
camera = entity.camera
pc = entity.playerController
transform = entity.transform
if camera == nil or pc == nil then
Debug.LogError("DebugTools: " .. entity.name .. " needs a Camera and a PlayerController")
end
end
function Update()
if camera == nil or pc == nil then return end
if Input.GetKeyDown(infoKey) then PrintPlayerInfo() end
if Input.GetKeyDown(inspectKey) then Inspect() end
endNotes
- Sectors, walls, and Entities all have
tagCountandGetTag(i), which is why oneTagListhelper works for all three. - The function keys (
F1...) aren't available to scripts, so debug hotkeys use letters.Input.GetKeyName(Input.GetAnyKeyDown())gives the name of whatever key was pressed this frame (checkGetAnyKeyDown()isn'tnilfirst), which is handy for finding out what a key is called.
Setters and getters in the API throw a Lua error when given something invalid, such as an
out-of-range index, a sector ceiling below its floor, or a reference to something that no longer
exists. Left alone, that error prints in red every frame the code runs. pcall runs a
function and gives you the error instead of raising it:
local ok, result = pcall(function()
return entity:GetTag(99) -- there is no 99th tag
end)
if not ok then
Debug.LogWarning("Could not read tag:", result)
endA full demo you can attach to any Entity:
-- Scripts/Debug/SafeCallDemo.lua (entity script)
function Start()
-- 1. A call that works.
local ok, count = pcall(function() return entity.tagCount end)
Debug.Print("tagCount ok:", ok, "value:", count)
-- 2. A call that fails. pcall returns false plus the error message.
local ok2, err = pcall(function() return entity:GetTag(99) end)
Debug.Print("GetTag(99) ok:", ok2, "error:", err)
-- 3. Check for validity up front instead, which is cheaper and clearer.
if entity.isValid then
Debug.Print(entity.name .. " is valid")
end
endGuidelines
- Prefer checking
isValid(Entities, sectors, walls, floors, behaviours) ornil(components, public-field references) over wrapping everything inpcall. - Use
pcallaround writes that can legitimately fail, like sector heights, and stop the script (or set abrokenflag) when one does. The door scripts in 05_doors.md show the pattern. -
assert(condition, "message")raises an error if the condition is false. That's a good way to say "this script can't work without X" inStart.