Entities and components

Finding entities

Lua
TypeScript
local player = Scene.Find("Player")                  -- the first with this name
local coins = Scene.FindByTag("Coin")                -- every one with this tag
local lights = Scene.FindWithComponent("PointLight") -- every one with this component
local child = self.Entity:FindChild("Head")          -- a direct child
local parent = self.Entity.Parent
local children = self.Entity.Children

Scene.Find raises an error when there is no such entity, so a typo in a name is caught where it is made. Scene.FindByName returns nil in that case, for something that may or may not be there.

Finds return entities in hierarchy order, the same on every run.

Look entities up once in OnStart and keep them in self (this). Finding by name every frame is wasted work.

Components

A component is a field of its entity, under its own name:

Lua
TypeScript
local light = self.Entity.PointLight
if light then
	light.Intensity = 4
	light.Color = Vec3(1, 0.5, 0.2)
end

local body = self.Entity:AddComponent("Rigidbody")
self.Entity:RemoveComponent("SphereCollider")

The field is nil (undefined in TypeScript) when the entity has no such component. A name that is not a component at all, such as a typo, is an error.

Component names are the ones in the API reference: Camera, MeshRenderer, Rigidbody, CharacterController, UIElement and so on. entity:GetComponent("PointLight") does the same with the name as text, for when the name is in a variable.

Directions

An entity faces down its own -Z axis.

Lua
TypeScript
local forward = self.Entity.Forward
local right = self.Entity.Right
local up = self.Entity.Up
local world = self.Entity.WorldPosition   -- with every parent applied

self.Entity:LookAt(player)                -- face an entity or a point
self.Entity:Face(velocity:Flat())         -- face along a direction

Position, Rotation and Scale are relative to the parent. Rotation is Euler angles in degrees.

Vectors

Lua
TypeScript
local a = Vec3(1, 2, 3)
local b = Vec3(2)              -- (2, 2, 2)
local c = a + b * 0.5
local length = c:Length()
local direction = c:Normalized()
local between = Vec3.Lerp(a, b, 0.25)
local apart = a:Distance(b)
local onTheGround = a:Flat()   -- the same with y set to 0

The directions have names: Vec3.Up, Vec3.Down, Vec3.Forward, Vec3.Back, Vec3.Left, Vec3.Right, and Vec3.Zero and Vec3.One.

Colours are Vec3 too, with names and two ways to make one from numbers you already have:

Lua
TypeScript
light.Color = Color.Orange
light.Color = Color.Hex("#ff8000")       -- as the web writes it
light.Color = Color.RGB(255, 128, 0)     -- as a paint program shows it
light.Color = Color.Red * 4              -- brighter than white, for a glow

Numbers have helpers in Lua's own math table: math.clamp(value, min, max), math.lerp(a, b, t), math.sign(value) and math.approach(current, target, step).

Making and destroying

Lua
TypeScript
local entity = Scene.CreateEntity("Marker")
entity.Position = Vec3(0, 1, 0)
local renderer = entity:AddComponent("MeshRenderer")
renderer.Mesh = Assets.Cube
renderer:SetMaterial(1, Assets.Find("Assets/Materials/Red.cairnmat"))

local copy = Scene.Clone(entity)
local enemy = Scene.Instantiate(Assets.Find("Assets/Prefabs/Enemy.cairnprefab"), Vec3(5, 0, 0))

entity:Destroy()
copy:Destroy(2)   -- two seconds from now

Destroy takes effect at the end of the current update, after OnDestroy has run. Until then the entity is still valid.

Switching things off and on

Lua
TypeScript
door.Active = false          -- gone: not drawn, no collision, no sound, no updates
door.Active = true           -- back, as it was

if enemy.ActiveInScene then  -- on, and so is everything above it
end

Switching an entity off switches off everything under it. Active reads what was set on the entity itself. ActiveInScene says whether it is really on, which also needs every parent to be on.

A script on a switched-off entity gets no OnUpdate or OnFixedUpdate. Timers it started keep running.

Talking to other scripts

Lua
TypeScript
-- Call a function on one script of another entity.
local game = Scene.Find("Game"):GetScript("Game")
game:AddScore(10)

-- Call a function on every script of an entity that has it.
enemy:SendMessage("TakeDamage", 5)

Events

When a script should not need to know who is listening, emit an event. The coin says a coin was collected, and the score, the sound and the HUD each hear it:

Lua
TypeScript
-- In the coin's script
Events.Emit("CoinCollected", self.Entity.WorldPosition, 5)

-- In any script that cares
function Hud:OnStart()
	Events.On("CoinCollected", function(position, points)
		self.Score = self.Score + points
	end, self.Entity)
end

The last argument of Events.On is the entity the listener belongs to. Give self.Entity (this.Entity): the listener is then dropped when that entity is destroyed, and waits while it is switched off. Every listener is dropped when the scene stops.

GetScript takes the script's file name without .lua or .ts, and returns the table its functions get as self. In TypeScript its type is any: TypeScript does not know another script's values. You can read its values and call its functions directly.

Assets

Lua
TypeScript
local material = Assets.Find("Assets/Materials/Gold.cairnmat")
local mesh = Assets.Find("Assets/Models/Ship.gltf", "Mesh/0")   -- a part of a model

Assets.Cube, Assets.Sphere and Assets.Plane are the built-in meshes. Assets.Find returns the empty handle when there is no such asset. Check with handle:IsValid().