Here’s a really simple version that you can work from:
-- Bullet ModuleScript
local RunS = game:GetService("RunService")
local MAX_LIFETIME = 5 --seconds
local GRAVITY = Vector3.new(0, -game.Workspace.Gravity, 0) --studs/second^2
local HIT_DETECT_RAYCAST_PARAMS = RaycastParams.new() --Set this up how you want it
function newBullet(startPosition, startVelocity)
local onHitEventObject = Instance.new("BindableEvent")
local onTimeoutEventObject = Instance.new("BindableEvent")
local bullet = {
position = startPosition,
velocity = startVelocity,
onHit = onHitEventObject,
onTimeout = onTimeoutEventObject,
lifeTime = 0,
}
bullet.updateConnection = RunS.Heartbeat:Connect(function(dt)
updateBullet(bullet, dt)
end)
return bullet
end
function updateBullet(bullet, dt)
--Move the bullet along the parabola
local oldPosition = bullet.position
local newPosition = oldPosition + bullet.velocity * dt + GRAVITY * dt * dt
bullet.position = newPosition
bullet.velocity = bullet.velocity + GRAVITY * dt
--Check if the bullet hit something
local hitResult = hitDetect(oldPosition, newPosition)
if hitResult then
bullet.onHitEventObject.Event:Fire(hitResult)
destroyBullet(bullet)
return
end
--Destroy the bullet if it doesn't hit anything
bullet.lifeTime += dt
if (bullet.lifeTime > MAX_LIFETIME) then
destroyBullet(bullet)
end
end
function hitDetect(pointFrom, pointTo)
return game.Workspace:Raycast(pointFrom, pointTo - pointFrom, HIT_DETECT_RAYCAST_PARAMS)
end
function destroyBullet(bullet)
--Removed references to allow garbage collection
bullet.onHit:Destroy() --Allows BindableEvent to be GC'ed, and breaks any connections to it
bullet.onTimeout:Destroy() --Same
bullet.updateConnection:Disconnect()
end
return {
newBullet = newBullet,
}
-- Gun controller script
local BULLET_SPEED = 100 --studs/second
function shootGun(muzzleCFrame, bulletVelocity)
local bullet = newBullet(muzzleCFrame.Position, muzzleCFrame.LookVector * BULLET_SPEED)
bullet.onHit.Event:Connect(function(hitResult)
local character = hitResult.Instance.Parent
local humanoid = character:FindFirstChildWhichIsA("Humanoid")
if humanoid then
humanoid.Health -= 50
end
end)
end
Here’s an illustration explaining the math/physics: