Skip to content

Writing Scripts

Berke edited this page Jun 23, 2026 · 2 revisions

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.damage belongs to the current script
  • healthScript.Public.health belongs to the target entity’s Health.lua script

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

Clone this wiki locally