Repository navigation
Health and Damage
Berke edited this page Oct 10, 2026
·
1 revision
Tilky has no built-in health system, so this is a script. It also shows the most useful pattern in the scripting API: one script calling functions and reading fields of another.
local health = target:GetScript("Health") -- a Behaviour (reference to that script)
if health.isValid then
health:TakeDamage(10) -- calls TakeDamage inside that script
Debug.Print(health.currentHealth) -- reads a variable from that script
end-
GetScript("Health")matches the script by file name, ignoring folders. - A Behaviour reference has three built-in members:
isValid,entity,enabled. Any other name is looked up in the target script's own variables and functions. - Functions you want others to call take
selffirst, because the call uses a colon.
Attach to: anything that can be hurt: the player, enemies, breakable props.
-- Scripts/Combat/Health.lua (entity script)
public number maxHealth = 100
public number currentHealth = 100
public bool destroyOnDeath = true
public number invulnerableTime = 0.0
public Behaviour deathListener = nil
local dead = false
local invulnerable = 0.0
local function Die()
dead = true
Debug.Print(entity.name .. " died")
-- Tell the listener script about it, if it has an OnDeath function.
if deathListener ~= nil and deathListener.isValid and type(deathListener.OnDeath) == "function" then
deathListener:OnDeath(entity)
end
if destroyOnDeath then
entity:Destroy()
else
entity.enabled = false
end
end
-- Public API used by other scripts ----------------------------------------
function TakeDamage(self, amount)
if dead or invulnerable > 0 then return end
currentHealth = math.max(0, currentHealth - amount)
invulnerable = invulnerableTime
if currentHealth <= 0 then Die() end
end
function Heal(self, amount)
if dead then return end
currentHealth = math.min(maxHealth, currentHealth + amount)
end
function IsDead(self)
return dead
end
-- Brings a dead (disabled) Entity back to full health. See the respawner in 13_utilities.md.
function Revive(self)
dead = false
invulnerable = 0.0
currentHealth = maxHealth
end
function GetHealthFraction(self)
return currentHealth / maxHealth
end
-- Lifecycle ----------------------------------------------------------------
function Start()
currentHealth = math.min(currentHealth, maxHealth)
end
function Update()
if invulnerable > 0 then invulnerable = invulnerable - GameTime.deltaTime end
endNotes
-
entity:Destroy()is queued and happens at the end of the frame, so it's safe to call in the middle of anUpdate. - With
destroyOnDeathoff, the Entity is disabled instead. That's a good choice for the player, so you can still readIsDead()afterwards from another script. -
currentHealthis a public field, so it can be set per-instance in the inspector, and other scripts can read it directly (health.currentHealth). -
deathListeneris the hook for reacting to a death. See the kill counter in 17_level_flow.md.
Attach to: a sector.
Every interval seconds, hurts everything inside the sector that has a Health script. Pair it
with RisingFloor from 06_lifts_and_platforms.md for a flood that
hurts.
-- Scripts/Combat/DamageZone.lua (sector script)
public number damage = 5
public number interval = 0.5
public string requiredTag = ""
local timer = 0.0
function Update()
timer = timer - GameTime.deltaTime
if timer > 0 then return end
timer = interval
for i = 1, sector.entityCount do
local entity = sector:GetEntity(i)
local tagOk = requiredTag == "" or entity:HasTag(requiredTag)
if tagOk and entity:HasScriptNamed("Health") then
entity:GetScript("Health"):TakeDamage(damage)
end
end
endNotes
-
HasScriptNamed("Health")guards against entities that just happen to be in the sector (props, pickups) and have no Health script. Without it,GetScriptwould return an invalid reference. - Set
requiredTagtoPlayerto hurt only the player, orEnemyto hurt only enemies.
Attach to: the same Entity as Health. It is a separate script that talks to it.
Heals a little each second, but only after not having been hurt for a while.
-- Scripts/Combat/HealthRegen.lua (entity script)
public number regenPerSecond = 3
public number delayAfterHit = 5
local health
local lastHealth = 0.0
local sinceHit = 0.0
function Start()
health = entity:GetScript("Health")
if not health.isValid then
Debug.LogError("HealthRegen needs a Health script on " .. entity.name)
return
end
lastHealth = health.currentHealth
end
function Update()
if health == nil or not health.isValid then return end
local dt = GameTime.deltaTime
local now = health.currentHealth
-- Losing health restarts the delay.
if now < lastHealth then sinceHit = 0.0 else sinceHit = sinceHit + dt end
lastHealth = now
if sinceHit >= delayAfterHit and now < health.maxHealth and not health:IsDead() then
health:Heal(regenPerSecond * dt)
lastHealth = health.currentHealth
end
endNotes
- If the
Healthscript isn't found,health.isValidisfalse, and this script quietly stops instead of erroring every frame. - Because
Healis called withregenPerSecond * dt, the healing amount is frame-rate independent.