# How can i properly make projectile hitboxes?

This is a proof of concept and should ideally be implemented on the client rather than the server. I’ve written it on the server for now as I currently do not have enough time to write the full thing.

Script Inside ServerScriptService
``````local RunService = game:GetService("RunService")

-- Testing projectile part
local Projectile = Instance.new("Part")
Projectile.Size = Vector3.new(1, 1, 3)
Projectile.Position = Vector3.new(0, 20, 0)
Projectile:SetAttribute("CreationTime", tick()) -- Track the creation time for the interpolation function
Projectile.Anchored = true
Projectile.Parent = workspace

local Force = 2 -- Horizontal movement speed of the projectile
local Gravity = 2 -- Fall speed of the projectile

--- Quadratic interpolation function to calculate the projectile's position
-- @param startPos The starting position of the projectile
-- @param force The force applied to the projectile (used for air resistance)
-- @param currentTime The current time
-- @return The new position of the projectile
local function interpolate(startPos, force, currentTime)
-- Calculate the vertical drop this frame, clamped to a maximum of 5 (terminal velocity)
local drop = math.clamp(currentTime^2 / Gravity / force, 0, 5)

-- Calculate the new position by subtracting the drop from the starting position
local newPos = startPos - Vector3.new(0, drop, 0)

return newPos
end

--- Function called every frame to update the projectile's position
local function heartbeat()
-- Get the creation time of the projectile
local creationTime = Projectile:GetAttribute("CreationTime")

-- Calculate how long the projectile has existed
local timeDifference = tick() - creationTime

-- Set the new position for the projectile
-- interpolate(Projectile.Position, Force, timeDifference) to get the new height
-- Projectile.CFrame.LookVector to get the horizontal movement
-- Projectile.Size.Z to avoid overlapping and skipping positions
-- Increase the part's Z size to make it move faster
Projectile.Position = interpolate(Projectile.Position, Force, timeDifference) + Projectile.CFrame.LookVector * Projectile.Size.Z
end

-- Connect the heartbeat function to the RunService.Heartbeat event
RunService.Heartbeat:Connect(heartbeat)
``````

ProjectileExample.rbxl (52.8 KB)

Unanchored parts would be bad for performance and have unreliable physics when used on the client. I used anchored parts and moved them with a `RunService.Heartbeat` connection.

Create a `Region3` to represent the projectile on the server, then use the interpolation function I provided to move that region forwards. Use `Workspace:GetPartBoundsInBox` to check for collisions.

You could use the interpolation function to visualize the movement of the part by updating its position, or have the server send the `Region3` location to the client, and use that to visualize the part.

Additionally, optimize the collision detection logic and part movement using something like batch processing before using it in production.

1 Like

I went into the project and it works but I have some questions.

Im guessing by remote events?

Also what is this?

Yes

Instead of creating separate `RunService.Heartbeat` connections for each part’s collision detection, create a single connection that loops through a table of all the projectiles and processes them simultaneously. However, consider using `BasePart.Touched` first, as it may be more performant if it works correctly.

1 Like

Yeah it would be my last resort using touched events for heavy physics based projectiles

1 Like

I didnt really find a answer to this question but i found some helpful topics like:

This works for linear projectiles with a predictable path but not really what im looking for

If you do not want it to be linear you can modify the equation.

Currently the equation is this on the Z axis

``````local projectileTime =  (respondedTick - startTickVal)
local positionCFrame = spawnPoint * CFrame.new(0, 0, -45 * projectileTime )
``````

If you want it to randomly start moving diagonally at 5 seconds in linearly you can use an if statement.

``````local projectileTime =  (respondedTick - startTickVal)

if projectileTime > 10 then
--Start moving in X axis instead
local positionCFrame = spawnPoint * CFrame.new( -45 * (projectileTime-10), 0, -45 * projectileTime )

else
local positionCFrame = spawnPoint * CFrame.new(0, 0, -45 * projectileTime )
end
``````

You can even instead of just making it swerve 10 seconds in you can use math.random instead to make it even more unpredictable yet predictable.

``````local positionCFrame = spawnPoint * CFrame.new(0, 0, -45 * projectileTime^2 -5*projectileTime )