Skip to content

Projectile

Projectile is a specialized Object which moves through the air in a parabolic shape and impacts other objects. To spawn a Projectile, use Projectile.Spawn().

Properties

Property Name Return Type Description Tags
owner Player The Player who fired this Projectile. Setting this property ensures the Projectile does not impact the owner or their allies. This will also change the color of the Projectile if teams are being used in the game. Read-Write
sourceAbility Ability Reference to the Ability from which the Projectile was created. Read-Write
speed number Centimeters per second movement. Default 5000. Read-Write
maxSpeed number Max cm/s. Default 0. Zero means no limit. Read-Write
gravityScale number How much drop. Default 1. 1 means normal gravity. Zero can be used to make a Projectile go in a straight line. Read-Write
drag number Deceleration. Important for homing Projectiles (try a value around 5). Negative drag will cause the Projectile to accelerate. Default 0. Read-Write
bouncesRemaining integer Number of bounces remaining before it dies. Default 0. Read-Write
bounciness number Velocity % maintained after a bounce. Default 0.6. Read-Write
lifeSpan number Max seconds the Projectile will exist. Default 10. Read-Write
shouldBounceOnPlayers boolean Determines if the Projectile should bounce off players or be destroyed, when bouncesRemaining is used. Default false. Read-Write
piercesRemaining integer Number of objects that will be pierced before it dies. A piercing Projectile loses no velocity when going through objects, but still fires the impactEvent event. If combined with bounces, all piercesRemaining are spent before bouncesRemaining are counted. Default 0. Read-Write
capsuleRadius number Shape of the Projectile's collision. Default 22. Read-Write
capsuleLength number Shape of the Projectile's collision. A value of zero will make it shaped like a Sphere. Default 44. Read-Write
homingTarget CoreObject The projectile accelerates towards its target. Homing targets are meant to be used with spawned projectiles and will not work with weapons. Read-Write
homingAcceleration number Magnitude of acceleration towards the target. Default 10,000. Read-Write
shouldDieOnImpact boolean If true, the Projectile is automatically destroyed when it hits something, unless it has bounces remaining. Default true. Read-Write

Functions

Function Name Return Type Description Tags
Destroy() Object Immediately destroys the object. None
GetWorldTransform() Transform Transform data for the Projectile in world space. None
GetWorldPosition() Vector3 Position of the Projectile in world space. None
SetWorldPosition(Vector3) None Position of the Projectile in world space. None
GetVelocity() Vector3 Current direction and speed vector of the Projectile. Speed is expressed in centimeters per second. None
SetVelocity(Vector3) None Current direction and speed vector of the Projectile. Speed is expressed in centimeters per second. None

Class Functions

Class Function Name Return Type Description Tags
Projectile.Spawn(string childTemplateId, Vector3 startPosition, Vector3 direction) Projectile Spawns a Projectile with a child that is an instance of a template. None

Events

Event Name Return Type Description Tags
impactEvent Event<Projectile projectile, Object other, HitResult pointOfContact> Fired when the Projectile collides with something. Impacted object parameter will be either of type CoreObject or Player, but can also be nil. The HitResult describes the point of contact between the Projectile and the impacted object. None
lifeSpanEndedEvent Event<Projectile> Fired when the Projectile reaches the end of its lifespan. Fired before it is destroyed. None
homingFailedEvent Event<Projectile> Fired when the target is no longer valid, for example the Player disconnected from the game or the object was destroyed somehow. None

Examples

Example using:

Spawn

lifeSpanEndedEvent

lifeSpan

Like CoreObjects, Projectiles have a lifeSpan property, which is the maximum number of seconds a projectile can be kept around. Once that time is up, the projectile is automatically destroyed by the engine.

When projectiles reach the end of their lifespan, they trigger a lifeSpanEndedEvent event. This event fires before the projectile is destroyed, so it is still valid to reference it in the event handler.

In this example, we fire a projectile straight up, so its lifeSpan runs out before it collides with anything. When it does, the lifeSpanEndedEvent fires.

-- A template of a basic cube, attached to the script as a custom property:
local propCubeTemplate = script:GetCustomProperty("CubeTemplate")

-- Fire this projectile straight up so it doesn't hit anything:
local mySlowProjectile = Projectile.Spawn(propCubeTemplate,
            Vector3.New(1000, 0, 200), -- starting position
            Vector3.UP)                -- direction

mySlowProjectile.lifeSpan = 1

local function OnLifeSpanEnded(projectile)
    print("Projectile lifespan over")
end

mySlowProjectile.lifeSpanEndedEvent:Connect(OnLifeSpanEnded)

mySlowProjectile:SetVelocity(Vector3.New(0, 0, 1000))

See also: Projectile.SetVelocity | CoreObject.GetCustomProperty | Vector3.New | Event.Connect | CoreLua.print


Example using:

homingFailedEvent

If a projectile has its homingTarget set, and then the target disappears for some reason, it will fire a HomingFailedEvent. This is usually because the CoreObject that the projectile is following was Destroyed, or the player it was following logged out.

In this example, we spawn an object, fire a projectile at it, (and set the homingTarget property) and then immediately remove the target, leaving the projectile to feel dejected and confused.

-- A template of a basic cube, attached to the script as a custom property:
local propCubeTemplate = script:GetCustomProperty("CubeTemplate")

local objectInWorld = World.SpawnAsset(propCubeTemplate)
objectInWorld:SetWorldPosition(Vector3.New(1000, 0, 0))

local objectHomingProjectile = Projectile.Spawn(propCubeTemplate,
            Vector3.New(1000, 1000, 1000), -- starting position
            Vector3.New(0, 0, 0))          -- direction
objectHomingProjectile.speed = 0
objectHomingProjectile.gravityScale = 0
objectHomingProjectile.homingTarget = objectInWorld
objectHomingProjectile.drag = 5
objectHomingProjectile.homingAcceleration = 5000

local function OnHomingFailed(projectile)
    print("Target lost!")
end

objectHomingProjectile.homingFailedEvent:Connect(OnHomingFailed)

Task.Wait(0.5)
objectInWorld:Destroy()
-- The event should fire now and the "target lost" message should be displayed.

See also: Projectile.Spawn | CoreObject.GetCustomProperty | World.SpawnAsset | Vector3.New | Event.Connect | CoreLua.print | Task.Wait


Example using:

impactEvent

When a projectile hits a surface, it triggers an impactEvent, which is given various information about exactly what collided with what, and where.

Specifically, the event receives a reference to the projectile that did the impacting, a reference to whatever object or player it hit, and a hitResult object that can be used to determine things like the position and angle of the collision.

A very common pattern is to use the Object:IsA("Player") function to determine if the projectile has hit a player or not.

In this example, the projectile will hit something and print out information about what it hit.

-- A template of a basic cube, attached to the script as a custom property:
local propCubeTemplate = script:GetCustomProperty("CubeTemplate")

-- Fire this projectile straight down so it hits the ground:
local myProjectile = Projectile.Spawn(propCubeTemplate,
            Vector3.New(1000, 0, 200), -- starting position
            Vector3.New(0, 0, -1))     -- direction

local function OnImpacted(projectile, other, hitresult)
    print("Hit object: " .. other.name .. " with an impact normal of " .. tostring(hitresult:GetImpactNormal()))
    if other and other:IsA("Player") then
        print("We hit player " .. other.name .. "!!!")
    end
end

myProjectile.impactEvent:Connect(OnImpacted)

See also: Projectile.Spawn | CoreObject.name | HitResult.GetImpactNormal | other.IsA | Vector3.New | CoreLua.print | Event.Connect


Example using:

Destroy

Sometimes you will want to remove a projectile from the game even if it hasn't hit any targets yet. When this is the case, the Destroy() function does what you need - it does exactly what the name implies - the projectile is immediately removed from the game and no events are generated.

We can test if an object still exists via the Object:IsValid() function. This can be useful because sometimes things other than program code can remove an object from our game. (Existing for longer than the lifeSpan, or colliding with an object, in the case of projectiles.)

-- A template of a basic cube, attached to the script as a custom property:
local propCubeTemplate = script:GetCustomProperty("CubeTemplate")

-- Fire this projectile straight up, so it will be in the air for a while.
local myProjectile = Projectile.Spawn(propCubeTemplate,
            Vector3.New(1000, 0, 200), -- starting position
            Vector3.UP)      -- direction
myProjectile.speed = 50
myProjectile.gravityScale = 0
Task.Wait(1)
print("Is the projectile still around?  " .. tostring(Object.IsValid(myProjectile)))
-- The projectile is still there.
myProjectile:Destroy()
print("How about now?  " .. tostring(Object.IsValid(myProjectile)))
-- The projectile is no longer in the game.

See also: Projectile.Spawn | CoreObject.GetCustomProperty | Object.IsValid | Task.Wait | CoreLua.print | Vector3.New


Example using:

GetWorldTransform

GetWorldPosition

GetVelocity

SetVelocity

We can get various information about a projectile's position and velocity via several functions. GetWorldTransform() and GetWorldPosition() functions can tell us where it is and where it is facing. GetVelocity() tells us where it is moving and how fast. And SetVelocity() allows us to change its direction in mid-flight.

In this sample, we'll fire some more projectiles at the player. But we'll also give them a magic shield that reflects any projectiles that get too close.

-- A template of a basic cube, attached to the script as a custom property:
local propCubeTemplate = script:GetCustomProperty("CubeTemplate")

-- utility function for spawning projectiles aimed at the player
function FireAtPlayer(startPos, player)
    local direction = (player:GetWorldPosition() - startPos):GetNormalized()
    local myProjectile = Projectile.Spawn(propCubeTemplate,
        startPos,
        direction)
    myProjectile.speed = 2000
    myProjectile.gravityScale = 0
    return myProjectile
end

local projectileList = {}
-- Fire the barrage!
for i = -4, 4 do
    projectileList[i] = FireAtPlayer(Vector3.New(1000, 250 * i, 500), targetPlayer)
end

local MagicShieldTask = Task.Spawn(function()
    while true do
        for k, projectile in pairs(projectileList) do
            local projectileToPlayer = targetPlayer:GetWorldPosition() - projectile:GetWorldPosition()
            -- if the projectile is within 200 units of the player...
            if projectileToPlayer.size < 200 then
                local t = projectile:GetWorldTransform()
                -- ... and is generally facing the player ...
                if projectileToPlayer .. t:GetForwardVector() > 0 then
                    -- then shoot it back where it came!
                    projectile:SetVelocity(projectile:GetVelocity() * -0.8)
                end
            end
                return
        end
        Task.Wait()
    end
end)

See also: Projectile.Spawn | CoreObject.GetCustomProperty | Player.GetWorldPosition | Task.Spawn | Transform.GetForwardVector | Vector3.New


Example using:

capsuleLength

capsuleRadius

When Core performs collision checks (to see if a projectile has hit anything) it assumes the projectile is a capsule. That is, a cylinder with a hemisphere on each flat end.

We can change the shape of this capsule by modifying the length and radius of the cylinder. A length of 0 means we have a sphere. (Because there is no space between the two hemispheres on the ends.)

This sample makes a few projectiles of varying shapes and sizes.

Note that this only changes the collision properties of the projectile. The visual representation on screen will be unchanged.

By default, projectiles have a radius of 22, and length of 44.

-- A template of a basic cube, attached to the script as a custom property:
local propCubeTemplate = script:GetCustomProperty("CubeTemplate")

-- Fire this projectile straight up, so it will be in the air for a while.
function FireProjectile()
    local myProjectile = Projectile.Spawn(propCubeTemplate,
                Vector3.New(1000, 0, 200), -- starting position
                Vector3.UP)                -- direction
    myProjectile.speed = 100
    myProjectile.gravityScale = 0
    return myProjectile
end

-- The default projectile is radius 22, and length 44.
local defaultProjectile = FireProjectile()

-- This projectile is very long but narrow.
local longThinProjectile = FireProjectile()
longThinProjectile.capsuleRadius = 10
longThinProjectile.capsuleLength = 100

-- This projectile is short and fat.
local shortFatProjectile = FireProjectile()
shortFatProjectile.capsuleRadius = 50
shortFatProjectile.capsuleLength = 20

-- This projectile's collision volume is a sphere.
local sphereProjectile = FireProjectile()
sphereProjectile.capsuleRadius = 40
sphereProjectile.capsuleLength = 0

See also: Projectile.Spawn | CoreObject.GetCustomProperty | Vector3.New


Example using:

gravityScale

bouncesRemaining

bounciness

shouldBounceOnPlayers

By default, projectiles are destroyed when they impact a surface. If you set their bouncesRemaining though, whenever they hit a surface, they will lose one bouncesRemaining and ricochet off in a new direction. This can be used to simulate grenades, super balls, bouncing lasers, or similar. The amount of energy they lose (or gain!) from impact is controlled via the bounciness property.

gravityScale can be used to change the trajectory of projectiles in flight. Setting it to 0 means that the projectiles are unaffected by gravity, and will simply fly in a straight line until they hit something. Setting it to greater than zero means that the projectile will arc downwards like a normal thrown object. And setting it to less than zero means the projectile will arc upwards like a helium balloon.

In this example, we fire several projectiles, with various properties.

-- A template of a basic cube, attached to the script as a custom property:
local propCubeTemplate = script:GetCustomProperty("CubeTemplate")

function fireProjectile()
    local myProjectile = Projectile.Spawn(propCubeTemplate,
        Vector3.New(500, 0, 200), -- starting position
        Vector3.New(0, 1, 0))     -- direction
    myProjectile.speed = 500
    myProjectile.lifeSpan = 3
    return myProjectile
end

-- this projectile will just fire off in a straight line with no gravity. It should never bounce.
local standardProjectile = fireProjectile()
standardProjectile.gravityScale = 0

-- this projectile will arc and hit the ground.
local arcingProjectile = fireProjectile()
arcingProjectile.gravityScale = 1

-- this projectile will arc upwards and fly off into the sky.
local floatingProjectile = fireProjectile()
floatingProjectile.gravityScale = -1

-- this projectile will arc further, because it has less gravity.
local furtherArcingProjectile = fireProjectile()
furtherArcingProjectile.gravityScale = 0.5

-- this projectile will arc and bounce up to three times.
local bouncingProjectile = fireProjectile()
bouncingProjectile.gravityScale = 0.5
bouncingProjectile.bouncesRemaining = 3

-- this projectile will bounce more times, but with less energy per bounce.
local lessBouncyProjectile = fireProjectile()
lessBouncyProjectile.gravityScale = 0.5
lessBouncyProjectile.bouncesRemaining = 5
lessBouncyProjectile.bounciness = 0.2

See also: Projectile.Spawn | CoreObject.GetCustomProperty | Vector3.New | Event.Connect


Example using:

homingTarget

drag

homingAcceleration

Projectiles can be set to home in on targets, via the homingTarget property. This can be either a player or a CoreObject.

This example spawns an object in the world, and then fires a projectile to home in on it.

The drag and homingAcceleration properties affect how fast the homing projectile can change direction, and how fast it loses velocity due to air resistance.

-- A template of a basic cube, attached to the script as a custom property:
local propCubeTemplate = script:GetCustomProperty("CubeTemplate")

local objectInWorld = World.SpawnAsset(propCubeTemplate)
objectInWorld:SetWorldPosition(Vector3.New(1000, 0, 0))

local function ProjectileImpact(projectile, other, hitresult)
    print("Hit something! " .. other.name)
end

local objectHomingProjectile = Projectile.Spawn(propCubeTemplate,
            Vector3.New(1000, 1000, 1000), -- starting position
            Vector3.New(0, 0, 0))          -- direction
objectHomingProjectile.speed = 0
objectHomingProjectile.gravityScale = 0
objectHomingProjectile.homingTarget = objectInWorld
objectHomingProjectile.drag = 5
objectHomingProjectile.homingAcceleration = 5000

local function OnImpacted(projectile, other, hitresult)
    print("Hit something! " .. other.name)
end

objectHomingProjectile.impactEvent:Connect(OnImpacted)

-- The projectile will hit home towards the target object, and print out a message when it hits.

See also: Projectile.Spawn | CoreObject.GetCustomProperty | World.SpawnAsset | Vector3.New | CoreLua.print | Event.Connect


Example using:

owner

Projectiles have a property, owner, which stores data about who spawned the projectile. This is populated automatically, if the projectile is generated from a weapon interaction. Otherwise, we have to set it ourselves.

Projectiles will never impact their owner, or anyone on the owner's team. They will just pass on through, and not trigger an impactEvent.

In this example, we fire several projectiles at the player, but set them to be owned by the player, so they are unhit.

-- A template of a basic cube, attached to the script as a custom property:
local propCubeTemplate = script:GetCustomProperty("CubeTemplate")

function OnImpact(projectile, other, hitresult)
    --Count how many times each projectile hits the player:
    if other and other:IsA("Player") then
        print("Urk! I've been shot!")
    end
end

-- utility function for spawning projectiles aimed at the player
function FireAtPlayer(startPos, player)
    local direction = (player:GetWorldPosition() - startPos):GetNormalized()
    local myProjectile = Projectile.Spawn(propCubeTemplate,
        startPos,
        direction)
    myProjectile.speed = 1000
    myProjectile.impactEvent:Connect(OnImpact)
    myProjectile.gravityScale = 0
    myProjectile.owner = player
end

-- Fire the barrage!
for i = -4, 4 do
    FireAtPlayer(Vector3.New(1000, 250 * i, 500), targetPlayer)
end
-- Player will not be hit (and the hit message will never be printed) because
-- the projectiles are all owned by the player.

See also: Projectile.Spawn | Player.GetWorldPosition | CoreObject.GetCustomProperty | other.IsA | Vector3.New | CoreLua.print | Event.Connect


Example using:

piercesRemaining

shouldDieOnImpact

Projectiles have the piercesRemaining property, which controls how many times they penetrate objects and keep going. In this sample, we spawn several walls and fire several projectiles at them, with different penetration numbers.

Projectiles also have a property that determines if they should be destroyed when they hit an object - shouldDieOnImpact. One of the projectiles we spawn here does not die on impact. So when it hits a wall, it simply stops and waits for its lifeSpan to run out.

-- A template of a basic cube, attached to the script as a custom property:
local propCubeTemplate = script:GetCustomProperty("CubeTemplate")

function FirePiercingProjectile(pierceCount)
    local myProjectile = Projectile.Spawn(propCubeTemplate,
        Vector3.New(500, 0, 150 + pierceCount * 100), -- starting position
        Vector3.New(1, 0, 0))     -- direction
    myProjectile.speed = 1000
    myProjectile.lifeSpan = 3
    myProjectile.gravityScale = 0
    myProjectile.piercesRemaining = pierceCount
    return myProjectile
end

-- Make some walls for our projectiles to run into:
for i = 1, 8 do
    walls[i] = World.SpawnAsset(propCubeTemplate, {
        position = Vector3.New(500 + i * 500, 0, 250),
        scale = Vector3.New(1, 5, 5)
    })
end

-- this projectile will just fire off in a straight line with no gravity. It should never bounce.
local Pierce_x1 = FirePiercingProjectile(0)

-- This projectile will pierce the first wall and impact the second.
local Pierce_x2 = FirePiercingProjectile(1)

-- This projectile will pierce the first two walls, and impact the third.
local Pierce_x3 = FirePiercingProjectile(2)

-- This projectile will hit the first wall, and then stop, because it is set not to die on impact.
local DontDieOnImpact = FirePiercingProjectile(0)
DontDieOnImpact.shouldDieOnImpact = false

See also: Projectile.Spawn | CoreObject.GetCustomProperty | World.SpawnAsset | Vector3.New | Event.Connect


Example using:

sourceAbility

Projectiles have a field to report what ability spawned them. If the projectile is fired by a weapon, then the weapon automatically populates the sourceAbility property. If you spawn projectiles manually via spawnProjectile, then you are responsible for populating it yourself.

Here is an example of a weapon script that tests if the projectiles came from an ability called "FlameThrower." It is assumed that this is in a script that is a direct child of a weapon object.

function OnImpact(projectile, other, hitresult)
    if other and other:IsA("Player") then
        local damageScale = 1.0
        if projectile.sourceAbility ~= nil
                and projectile.sourceAbility.name == "FlameThrower" then
            local fireResistance = other:GetResource("fireResist")
            damageScale = damageScale * (1.0 - fireResistance)
            if (fireResistance > 0) then
                print("Damage reduced by fire resistance!")
            end
        end
        other:ApplyDamage(Damage.New(10 * damageScale))
    end
end

--Tell each projectile fired what to do when it hits something:
local weapon = script.parent

local function OnProjectileSpawned(weapon, projectile)
    projectile.impactEvent:Connect(OnImpact)
end

weapon.projectileSpawnedEvent:Connect(OnProjectileSpawned)

See also: Ability.name | Player.GetResource | Damage.New | other.IsA | CoreLua.print


Example using:

speed

maxSpeed

You can set the speed of a projectile directly, via the speed property. Note that this does not change the direction of a projectile - only how fast it is moving in whatever direction it is already pointing in.

You can also set a projectile's maxSpeed property, which clamps the speed to a given velocity. This can be useful in situations where the projectile is homing or affected by gravity - you can ensure that the speed never gets above a particular velocity, no matter how long it has been falling/accelerating.

One important thing to note, is that maxSpeed is only checked at the END of the frame, so if you manually set the speed to something large, it will only be clamped after your script has executed and the game has updated with a new 'tick'.

-- A template of a basic cube, attached to the script as a custom property:
local propCubeTemplate = script:GetCustomProperty("CubeTemplate")

-- Fire this projectile straight up, so it will be in the air for a while.
local myProjectile = Projectile.Spawn(propCubeTemplate,
            Vector3.New(1000, 0, 200), -- starting position
            Vector3.UP)                -- direction
myProjectile.speed = 100
myProjectile.maxSpeed = 50
myProjectile.gravityScale = 0
-- The projectile is still going at 100 speed. Max Speed is only checked at the end of the frame.
print("This projectile's speed is " .. tostring(myProjectile.speed))

Task.Wait()    -- So if we wait one frame...
print("This projectile's speed is " .. tostring(myProjectile.speed))
-- It should now be clamped down to the maximum speed.

See also: Projectile.Spawn | CoreObject.GetCustomProperty | Task.Wait | Vector3.New | CoreLua.print



Last update: June 30, 2022