Skip to content

DamageableObject

DamageableObject is a CoreObject which implements the Damageable interface.

Properties

Property Name Return Type Description Tags
hitPoints number Current amount of hit points. Read-Write
maxHitPoints number Maximum amount of hit points. Read-Write
isDead boolean True if the object is dead, otherwise false. Death occurs when damage is applied which reduces hit points to 0, or when the Die() function is called. Read-Only
isImmortal boolean When set to true, this object cannot die. Read-Write
isInvulnerable boolean When set to true, this object does not take damage. Read-Write
destroyOnDeath boolean When set to true, this object will automatically be destroyed when it dies. Read-Only
destroyOnDeathDelay number Delay in seconds after death before this object is destroyed, if destroyOnDeath is set to true. Defaults to 0. Read-Only
destroyOnDeathClientTemplateId string Optional asset ID of a template to be spawned on clients when this object is automatically destroyed on death. Read-Only
destroyOnDeathNetworkedTemplateId string Optional asset ID of a networked template to be spawned on the server when this object is automatically destroyed on death. Read-Only

Functions

Function Name Return Type Description Tags
ApplyDamage(Damage) None Damages the object, unless it is invulnerable. If its hit points reach 0 and it is not immortal, it dies. Server-Only
Die([Damage]) None Kills the object, unless it is immortal. The optional Damage parameter is a way to communicate cause of death. Server-Only

Events

Event Name Return Type Description Tags
damagedEvent Event<DamageableObject object, Damage damage> Fired when the object takes damage. Server-Only
diedEvent Event<DamageableObject object, Damage damage> Fired when the object dies. Server-Only
hitPointsChangedEvent Event<DamageableObject object, number previousHitPoints> Fired when there's a change in hit points on the object. Server-Only

Hooks

Hook Name Return Type Description Tags
damageHook Hook<DamageableObject object, Damage damage> Hook called when applying damage from a call to ApplyDamage(). The incoming damage may be modified or prevented by modifying properties on the damage parameter. Server-Only

Examples

Example using:

damagedEvent

diedEvent

Vehicles implement the DamageableObject interface. As such, they have all the properties and events from that type. In this example, we add a script to a vehicle that causes it to pass to the driver some of the damage it receives. By default, a vehicle's damageable properties are configured to make them immune-- Change them for this example to work.

local VEHICLE = script:FindAncestorByType("Vehicle")
local CHANCE_TO_PASS_DAMAGE = 0.5
local DAMAGE_REDUCTION = 0.2
local ON_DEATH_DIRECT_DAMAGE_TO_DRIVER = 75

function ApplyDamageToDriver(newAmount, vehicleDamage)
    -- Create new damage object for the player
    local damage = Damage.New(newAmount)
    -- Copy properties from the vehicle's damage object
    damage.reason = vehicleDamage.reason
    damage.sourceAbility = vehicleDamage.sourceAbility
    damage.sourcePlayer = vehicleDamage.sourcePlayer
    damage:SetHitResult(vehicleDamage:GetHitResult())

    local player = VEHICLE.driver
    -- If we think the player will die from this damage, eject them and
    -- wait a bit, so they will ragdoll correctly
    if player.hitPoints <= damage.amount then
        VEHICLE:RemoveDriver()
        Task.Wait(0.15)
    end
    -- Apply it
    player:ApplyDamage(damage)
end

function OnDamaged(_, damage)
    if damage.amount <= 0 then return end
    if not Object.IsValid(VEHICLE.driver) then return end

    -- Chance to apply damage to the player or prevent it completely
    if math.random() >= CHANCE_TO_PASS_DAMAGE then return end

    -- Reduction of the original damage amount
    local newAmount = damage.amount * (1 - DAMAGE_REDUCTION)
    newAmount = math.ceil(newAmount)

    -- Apply reduced damage
    ApplyDamageToDriver(newAmount, damage)
end

function OnDied(_, damage)
    if not Object.IsValid(VEHICLE.driver) then return end
    local player = VEHICLE.driver

    -- Apply the on-death damage
    ApplyDamageToDriver(ON_DEATH_DIRECT_DAMAGE_TO_DRIVER, damage)
end

VEHICLE.damagedEvent:Connect(OnDamaged)
VEHICLE.diedEvent:Connect(OnDied)

See also: Vehicle.driver | CoreObject.FindAncestorByType | Damage.New | Player.ApplyDamage | Object.IsValid


Example using:

damagedEvent

isInvulnerable

hitPoints

maxHitPoints

In this example, when the health of an NPC reaches a certain threshold, the NPC will become invulnerable to all damage for a certain amount of time. While the NPC is invulnerable, it will regenerate health. The NPC can only regenerate the health once so that it can be killed.

-- Place script as a child of a Damageable Object.

-- Reference to the Damageable Object
local DAMAGEABLE_OBJECT = script.parent

-- At what point should the object start regenerating health
local invulnerableThreshold = 0.25 -- 25 percent

-- Only allow the regen to happen once
local hasRegened = false

-- Regenerate the health of the object passed in by adding 25 health
-- every .5 seconds.
local function regenHealth(obj)
    local counter = 0

    -- Spawn a repeating task that runs 6 times. Each iteration is counted
    -- so that the object can be vulnerable again at >= 6
    local regenTask = Task.Spawn(function()

        -- Prevent hitPoints being bigger than maxHitPoints
        obj.hitPoints = math.min(obj.hitPoints + 15, obj.maxHitPoints)

        -- At 6 or more iterations, make the object vulnerable so it can be killed
        if counter >= 6 then
            obj.isInvulnerable = false
        end

        counter = counter + 1
    end)

    regenTask.repeatCount = 6
    regenTask.repeatInterval = .5
end

local function OnDamaged(obj, damage)

    -- Check the object is valid
    if Object.IsValid(obj) then
        local currentHealth = obj.hitPoints
        local maxHealth = obj.maxHitPoints
        local percentageLeft = currentHealth / maxHealth

        -- Check if the health is less than or equal to the threshold and make sure
        -- the object hasn't already regened health
        if percentageLeft <= invulnerableThreshold and not hasRegened then
            hasRegened = true
            obj.isInvulnerable = true

            regenHealth(obj)
        end
    end
end

-- Print out to the Event Log the current health of the damageable object.
-- This is for debugging so you can see the health going down, and stops
-- when the health threshold is reached.
function Tick()
    print("Current Health: ", DAMAGEABLE_OBJECT.hitPoints)
    Task.Wait(.5)
end

-- Listen for when the damage is received.
DAMAGEABLE_OBJECT.damagedEvent:Connect(OnDamaged)

See also: Task.Spawn | Object.IsValid | CoreObject.parent


Example using:

diedEvent

In Core, many gameplay objects are kitbashed from various Static Meshes. In this example an object explodes, sending all its kitbashed parts flying. A pair of server/client scripts are added to the hierarchy of the DamageableObject. The server script detects the death and forwards that event to all clients by use of a broadcast. The client script then searches its local hierarchy for all Static Meshes and enables debris physics on them. Because the Damageable Object is most likely being destroyed, we need to move the meshes to a new parent that will outlive it: For the script to work, add a Client-Context group to the hierarchy and rename it to "DebrisParent".

-- Server Script:
local DAMAGEABLE = script:FindAncestorByType("Damageable")

function OnDied(_, _)
    -- Detects the death and forwards it to clients
    Events.BroadcastToAllPlayers("Scatter"..DAMAGEABLE.id)
end
DAMAGEABLE.diedEvent:Connect(OnDied)

-- Client Script:
local DAMAGEABLE = script:FindAncestorByType("Damageable")
local EXPLOSION_POWER = 2000
local RNG = RandomStream.New()

function OnDied()
    -- The client script receive the death event
    -- Finds all Static Meshes in the local hierarchy
    local childMeshes = script.parent:FindDescendantsByType("StaticMesh")
    -- Finds the new parent, a Client-context named "DebrisParent"
    local clientContext = World.FindObjectByName("DebrisParent")
    for _,mesh in ipairs(childMeshes) do
        -- Change parent, as we assume the old one is being destroyed
        mesh.parent = clientContext
        -- Enable debris physics
        mesh.isSimulatingDebrisPhysics = true
        -- Some Static Meshes don't support debris physics, so we must check
        if mesh.isSimulatingDebrisPhysics then
            -- Additional collision settings
            mesh.collision = Collision.FORCE_ON
            mesh.cameraCollision = Collision.FORCE_OFF
            -- Set a life span, so the mesh destroys itself after a few seconds
            mesh.lifeSpan = RNG:GetNumber(3, 5)
            -- Give a random velocity to the mesh, away from ground
            local vel = RNG:GetVector3FromCone(Vector3.UP, 90) * EXPLOSION_POWER
            mesh:SetVelocity(vel)
        else
            -- Destroy meshes immediately if they don't support debris physics
            mesh:Destroy()
        end
    end
end

Events.Connect("Scatter"..DAMAGEABLE.id, OnDied)

See also: StaticMesh.isSimulatingDebrisPhysics | CoreObject.id | Events.BroadcastToAllPlayers | RandomStream.GetVector3FromCone | World.FindObjectByName


Example using:

diedEvent

In this example, 2 teams battle it out by destroying objects that match their team color. Everytime a Damageable Object is destroyed, it respawns a new one with a random team color by using the property "Destroy on Death Networked Template ID".

-- Place script as a child of a Damageable Object

-- Set the "Destroy on Death Networked Template ID" on the Damageable Object
-- with a copy of the template that contains this script so that it gets
-- respawned each time the Damageable Object has died

-- Reference to the damageable object
local DAMAGEABLE_OBJECT = script.parent

-- Reference to the static mesh which will be colored either red or blue
-- Enable the property "Is Team Color Used" on the static mesh
local MESH_OBJECT = script:GetCustomProperty("MeshObj"):WaitForObject()

-- Amount of score to give to the team for destroying an object
-- that matches the team they are on
local score = 5

local function OnDied(obj, damage)
    if Object.IsValid(obj) then

        -- Get the static mesh team number
        local meshObjTeam = MESH_OBJECT.team

        -- Get the team number from the player who destroyed the object
        local team = damage.sourcePlayer.team

        -- Compare the static mesh team value against the player team value
        if meshObjTeam == team then
            Game.IncreaseTeamScore(team, score)
        else

            -- Print to the Event log when a player destroys an object
            -- for the other team
            print("Wrong color", team, meshObjTeam)
        end

        -- Print out the team scores to the Event Log after each object
        -- is destroyed.
        print("Team 1 Score: ", Game.GetTeamScore(1))
        print("Team 2 Score: ", Game.GetTeamScore(2))
        print("------------------------------------")
    end
end

-- Connect the died event that will fire when the object is destroyed
DAMAGEABLE_OBJECT.diedEvent:Connect(OnDied)

-- Set to a random team number between 1 and 2 (both inclusive)
MESH_OBJECT.team = math.random(1, 2)

See also: Object.IsValid | CoreObject.parent | Game.IncreaseTeamScore | Damage.sourcePlayer | Player.team


Example using:

ApplyDamage

In this example, damage can be applied to all objects in an area, by calling Explosion(), passing the epicenter of the explosion. First, all damageable objects that are inside the area are found with GetDamageablesInArea(). Then, a damage object is created and applied to each one.

local RADIUS = 500
local DAMAGE_AMOUNT = 50

function Explosion(center)
    local objects = GetDamageablesInArea(center, RADIUS)
    for _,obj in ipairs(objects) do
        local damage = Damage.New(DAMAGE_AMOUNT)
        obj:ApplyDamage(damage)
    end
end

function GetDamageablesInArea(center, radius)
    --CPU optimization, to avoid square root later
    local radiusSquared = radius * radius

    local result = {}
    local allDamageables = World.FindObjectsByType("DamageableObject")
    for _,obj in ipairs(allDamageables) do
        local position = obj:GetWorldPosition()
        local v = position - center
        if v.sizeSquared <= RADIUS_SQUARED then
            table.insert(result, obj)
        end
    end
    return result
end

See also: Damage.New | Vector3.sizeSquared | World.FindObjectsByType | CoreObject.GetWorldPosition


Example using:

Die

In this example, the function ApplyPoison() can be called, passing any damageable object as parameter. Each object keeps track of how much poison they have, using the serverUserData table. When an object receives 10 or more poison they are killed.

function ApplyPoison(damageableObject)
    if damageableObject.serverUserData.poisonCount then
        damageableObject.serverUserData.poisonCount = damageableObject.serverUserData.poisonCount + 1

        if damageableObject.serverUserData.poisonCount >= 10 then
            damageableObject:Die()
        end
    else
        damageableObject.serverUserData.poisonCount = 1
    end
end

See also: CoreObject.serverUserData


Example using:

isDead

This example shows how to search the game for all damageables and then filter them by some criteria. In this case, we create a function that returns all dead objects by checking the isDead property.

function GetAllDeadObjects()
    local result = {}
    local allDamageables = World.FindObjectsByType("Damageable")
    for _,obj in ipairs(allDamageables) do
        if obj.isDead then
            table.insert(result, obj)
        end
    end
    return result
end

See also: World.FindObjectsByType


Example using:

isImmortal

In this example, whenever a Damageable object overlaps a trigger it temporarily gains immortality. We keep track of the immortalCount in the case where the object would gain immortality a second time before the wait period completes from the first overlap. This could happen if the object went back and forth and hit the trigger multiple times, or if there are multiple triggers near each other with this same script.

local TRIGGER = script:GetCustomProperty("Trigger"):WaitForObject()
local IMMORTAL_DURATION = 2.5

function OnTriggerOverlap(_, other)
    if other:IsA("Damageable") then
        other.isImmortal = true

        if not other.serverUserData.immortalCount then
            other.serverUserData.immortalCount = 0
        end
        other.serverUserData.immortalCount = other.serverUserData.immortalCount + 1

        Task.Wait(IMMORTAL_DURATION)

        other.serverUserData.immortalCount = other.serverUserData.immortalCount - 1
        if other.serverUserData.immortalCount <= 0 then
            other.isImmortal = false
        end
    end
end

TRIGGER.beginOverlapEvent:Connect(OnTriggerOverlap)

See also: CoreObject.serverUserData | CoreObjectReference.WaitForObject | Other.IsA | Task.Wait



Last update: April 13, 2023