Writing Scripts - BenBerke/Tilky-Engine GitHub Wiki
Lua Script Examples
This page explains the basic scripting model used by Tilky Engine.
It covers:
- how to write a script
- how to create public variables
- how to create shared global script state
- how to access the entity this script is attached to
- how to access fields from other components
- how to access another script’s public variables
Core Objects
| Object | Meaning |
|---|---|
Owner |
The entity this script is attached to |
Public |
Variables exposed to the editor and saved per script component instance |
Scripts |
Global shared runtime table visible to all scripts |
1. Basic Script Structure
A Lua script can define two main functions:
function Start()
Debug.Print("Script started")
end
function Update()
Debug.Print("Script updated")
end
Start() is called once when the script becomes active.
Update() is called every frame while the script component is enabled.
2. Owner
Owner is the entity this script is attached to.
function Start()
Debug.Print("Owner entity ID:", Owner.id)
end
Before using a component, check that the entity has it.
function Start()
if Owner.hasTransform then
Debug.Print("Owner has a transform")
end
end
3. Accessing Components
Components are accessed through Owner.
function Start()
local transform = Owner.transform
if not transform then
return
end
Debug.Print("Entity has a transform")
end
The common pattern is:
local component = Owner.componentName
if not component then
return
end
Example:
function Start()
local rb = Owner.rigidbody
if not rb then
Debug.Print("This entity has no rigidbody")
return
end
Debug.Print("This entity has a rigidbody")
end
4. Reading and Writing Component Fields
Once you have a component, you can read or change its exposed fields.
function Start()
local transform = Owner.transform
if not transform then
return
end
Debug.Print("Position X:", transform.position.x)
end
Example of changing a transform field:
function Start()
local transform = Owner.transform
if not transform then
return
end
transform.position.x = 100
end
Example of changing a rigidbody field:
function Start()
local rb = Owner.rigidbody
if not rb then
return
end
rb.velocity.x = 10
end
5. Public Variables
Public variables are declared at the top of a script.
Public.Float("speed", 5.0)
Public.Int("health", 100)
Public.Bool("aggressive", true)
Public.String("displayName", "Enemy")
Public variables are:
- shown in the editor
- saved per entity
- unique for each script component instance
- accessible from other scripts through a script reference
After declaring a public variable, use it through Public.name.
Public.Int("health", 100)
function Start()
Debug.Print("Health:", Public.health)
end
You can also change it during runtime:
Public.Int("health", 100)
function Start()
Public.health = Public.health - 25
Debug.Print("New health:", Public.health)
end
Runtime changes affect the active play session. They do not automatically overwrite the saved level data.
6. Public Variables Are Per Entity
If two entities use the same Lua script, each entity has its own public values.
Example script:
-- Enemy.lua
Public.Int("health", 100)
Public.String("enemyName", "Enemy")
function Start()
Debug.Print(Public.enemyName, "health:", Public.health)
end
Entity A can have:
enemyName = "Guard"
health = 100
Entity B can have:
enemyName = "Boss"
health = 500
Both entities use Enemy.lua, but their Public values are independent.
7. Local Variables
Use local variables for private runtime state that only this script instance needs.
local timesUpdated = 0
function Update()
timesUpdated = timesUpdated + 1
Debug.Print("Updated:", timesUpdated)
end
Local variables are not shown in the editor and are not saved as public script data.
Use local variables for:
- temporary state
- counters
- cached values
- internal script logic
Use Public when the value should be editable from the editor.
8. Shared Global State With Scripts
Scripts is a shared table visible to all scripts during the current runtime session.
Use it for global runtime state.
Scripts.GameStats = Scripts.GameStats or {
enemiesSpawned = 0,
enemiesKilled = 0
}
The or pattern prevents the table from being reset if another script already created it.
Example:
Scripts.GameStats = Scripts.GameStats or {
enemiesSpawned = 0,
enemiesKilled = 0
}
function Start()
Scripts.GameStats.enemiesSpawned =
Scripts.GameStats.enemiesSpawned + 1
Debug.Print("Enemies spawned:", Scripts.GameStats.enemiesSpawned)
end
All scripts see the same Scripts.GameStats.
9. Public vs Scripts
Use Public for data owned by one script component instance.
Public.Int("health", 100)
Public.Float("speed", 5.0)
Use Scripts for shared runtime state.
Scripts.GameStats = Scripts.GameStats or {
enemiesKilled = 0
}
Example:
Public.Int("health", 100)
Scripts.GameStats = Scripts.GameStats or {
enemiesKilled = 0
}
function Update()
if Public.health <= 0 then
Scripts.GameStats.enemiesKilled =
Scripts.GameStats.enemiesKilled + 1
Debug.Print("Enemies killed:", Scripts.GameStats.enemiesKilled)
end
end
If 5 entities use this script:
- each entity has its own
Public.health - all entities share the same
Scripts.GameStats
10. Accessing Another Script’s Public Variables
Public variables can be accessed from another script through an entity reference.
local healthScript = target:GetScript("Health")
This returns a reference to the Health.lua script attached to target.
Always check that the script exists before using it.
local healthScript = target:GetScript("Health")
if not healthScript:IsValid() then
return
end
Then access its public variables through .Public.
healthScript.Public.health = healthScript.Public.health - 25
Full example:
Public.Int("damage", 25)
function Damage(target)
local healthScript = target:GetScript("Health")
if not healthScript:IsValid() then
return
end
healthScript.Public.health =
healthScript.Public.health - Public.damage
Debug.Print("Target health:", healthScript.Public.health)
end
In this example:
Public.damagebelongs to the current scripthealthScript.Public.healthbelongs to the target entity’sHealth.luascript
11. Example: Health Script
-- Health.lua
Public.Int("health", 100)
Public.Int("maxHealth", 100)
function Start()
Debug.Print("Health:", Public.health)
end
function Update()
if Public.health <= 0 then
Debug.Print("Entity died")
Owner:Destroy()
end
end
Other scripts can access this script’s health value with:
local healthScript = someEntity:GetScript("Health")
if healthScript:IsValid() then
Debug.Print(healthScript.Public.health)
end
12. Example: Damage Script
-- DamageDealer.lua
Public.Int("damage", 25)
function Damage(target)
local healthScript = target:GetScript("Health")
if not healthScript:IsValid() then
Debug.Print("Target has no Health script")
return
end
healthScript.Public.health =
healthScript.Public.health - Public.damage
Debug.Print("Damaged target. New health:", healthScript.Public.health)
end
target must be an entity passed to this function by another script or by an engine event.
13. Accessing Another Component on the Same Entity
Use Owner.componentName.
function Start()
local transform = Owner.transform
if not transform then
return
end
transform.position.x = 50
end
Another example:
function Start()
local rb = Owner.rigidbody
if not rb then
return
end
rb.velocity.x = 20
end
14. Accessing Another Component on Another Entity
If you have an entity reference, access its components the same way.
function PrintTargetPosition(target)
local transform = target.transform
if not transform then
Debug.Print("Target has no transform")
return
end
Debug.Print("Target X:", transform.position.x)
end
The entity reference could come from another script, an engine event, or an engine function.
15. Common Patterns
Safe Component Access
local transform = Owner.transform
if not transform then
return
end
Safe Script Access
local script = Owner:GetScript("Health")
if not script:IsValid() then
return
end
Public Variable Declaration
Public.Float("speed", 5.0)
Public.Int("health", 100)
Public.Bool("enabled", true)
Public.String("name", "Entity")
Shared Runtime Table
Scripts.GameState = Scripts.GameState or {
score = 0
}
16. Summary
Use Owner to access the entity this script is attached to.
Owner.transform
Owner.rigidbody
Owner:GetScript("Health")
Use Public for values that should be editable in the editor and saved per entity.
Public.Int("health", 100)
Public.Float("speed", 5.0)
Use Scripts for global runtime state shared between scripts.
Scripts.GameStats.enemiesKilled
Use local variables for private script-only runtime state.
local counter = 0
Access another script’s public variables through an entity reference.
local health = target:GetScript("Health")
if health:IsValid() then
health.Public.health = health.Public.health - 25
end