Recipes

Short answers to common tasks. Each is a whole script or the part that matters.

Move with the keyboard

Lua
TypeScript
local Mover = {}
Mover.Fields = { Speed = 5 }

function Mover:OnUpdate(deltaTime)
	local move = Vec3(Input.GetValue("MoveX"), 0, Input.GetValue("MoveZ"))
	self.Entity:Move(move * self.Speed * deltaTime)
end

return Mover

This passes through walls. For something that collides, use a CharacterController and call Move on it.

Do something later, or again and again

Lua
TypeScript
Timer.After(2, function() Log.Info("two seconds on") end)

local ticking = Timer.Every(0.5, function() self:Tick() end)
Timer.Cancel(ticking)

Timers follow game time, so they stop while the game is paused.

A sequence of steps

A coroutine reads like a list of things to do. In TypeScript, give Timer.Start an arrow function, so this is still the script:

Lua
TypeScript
function Door:Open()
	Timer.Start(function()
		Tween.To(self.Entity, "Position", self.OpenPosition, 1)
		Timer.Wait(3)
		Tween.To(self.Entity, "Position", self.ClosedPosition, 1)
	end)
end

Pick something up

Put a collider with Is Trigger on the pickup:

Lua
TypeScript
local Coin = {}

function Coin:OnTriggerEnter(other)
	if self.Taken or other.Tag ~= "Player" then
		return
	end
	self.Taken = true
	Scene.Find("Game"):GetScript("Game"):AddCoin()
	self.Entity:Destroy()
end

return Coin

The Taken flag matters: the entity lives until the end of the update, so the trigger can fire again before it is gone.

Spawn things over time

Lua
TypeScript
function Spawner:OnStart()
	self.Enemy = Assets.Find("Assets/Prefabs/Enemy.cairnprefab")
	Timer.Every(2, function()
		local angle = math.random() * math.pi * 2
		Scene.Instantiate(self.Enemy, Vec3(math.cos(angle) * 15, 0, math.sin(angle) * 15))
	end)
end

Follow something with the camera

Lua
TypeScript
local Follow = {}
Follow.Fields = { Height = 8, Back = 10 }

function Follow:OnStart()
	self.Target = Scene.Find("Player")
end

function Follow:OnUpdate()
	self.Entity.Position = self.Target.Position + Vec3(0, self.Height, self.Back)
end

return Follow

The third person template has a fuller camera that turns with the mouse and comes closer when a wall is in the way.

Face the way something moves

Lua
TypeScript
if move:Length() > 0.1 then
	self.Entity:Face(move)
end

To face a thing and not a direction, use self.Entity:LookAt(target), or this.Entity.LookAt(target) in TypeScript.

Find what the mouse is over

Lua
TypeScript
function Picker:OnStart()
	self.Camera = Scene.Find("Main Camera").Camera
end

function Picker:OnUpdate()
	if Input.WasPressed("Fire") then
		local origin, direction = self.Camera:ScreenToRay(Input.GetViewPosition())
		local hit = Physics.Raycast(origin, direction, 100)
		if hit then
			Log.Info("Clicked " .. hit.Entity.Name)
		end
	end
end

For a game seen from above, ScreenToGround gives the place on the ground under the mouse, to walk to or aim at:

Lua
TypeScript
local ground = self.Camera:ScreenToGround(Input.GetViewPosition())
if ground then
	self.Entity:Face((ground - self.Entity.Position):Flat())
end

Put a marker over something

WorldToScreen says where a point of the world is in the picture, from (0, 0) at the top left to (1, 1) at the bottom right:

Lua
TypeScript
local x, y, visible = self.Camera:WorldToScreen(enemy.Position + Vec3(0, 2, 0))
self.Marker.Visible = visible
self.Marker:SetPosition(x * 1280, y * 720)

See what a script is doing

Debug draws lines in the editor's viewport while the game plays. Each lasts one frame, so draw it every update for as long as it should show:

Lua
TypeScript
function Guard:OnUpdate()
	local origin = self.Entity.WorldPosition + Vec3.Up
	local hit = Physics.Raycast(origin, self.Entity.Forward, self.Sight, self.Entity)
	Debug.Ray(origin, self.Entity.Forward * self.Sight, hit and Color.Red or Color.Green)
	Debug.Sphere(self.Entity.WorldPosition, self.HearingRange, Color.Yellow)
end

There are Debug.Line, Debug.Ray, Debug.Sphere and Debug.Box. In an exported game they do nothing, so they can stay in the script.

Pause the game

Lua
TypeScript
function Game:SetPaused(paused)
	Time.Scale = paused and 0 or 1
	self.PauseMenu.Visible = paused
end

At a time scale of 0, timers, tweens and fixed updates stop. The UI keeps working, so a pause menu's buttons can still be pressed.

Keep a high score

Lua
TypeScript
function Game:OnStart()
	local best = SaveData.Load("Best")
	self.Best = best and best.Score or 0
end

function Game:End()
	if self.Score > self.Best then
		SaveData.Save("Best", { Score = self.Score })
	end
end

Restart the level

Lua
TypeScript
if Input.WasPressed("Restart") then
	Scene.Load(Scene.GetCurrent())
end