I am trying to anchor a bullet when it hits something to see how reliable the collision detection is.
the issue is that it’s not as reliable as I hoped. anchoring the bullet way before or way later than it should.
roblox’s touched event isn’t reliable at all, so I’m using rays in order to detect collisions, drawing a ray right in front of the bullet, I have tried this with look vector and attachments in front of the bullet both getting the same exact results.
current code:
local connection
bullet = script.Parent
connection = game:GetService("RunService").Heartbeat:Connect(function()
local ray = Ray.new(bullet.Position, bullet.RayAttachment.WorldPosition)
local hitPart, hitPos = workspace:FindPartOnRay(ray)
if hitPart and hitPart.CanCollide and hitPart ~= bullet then
print(hitPart)
bullet.Anchored = true
connection:Disconnect()
end
end)
code with LookVector ray
local connection
bullet = script.Parent
connection = game:GetService("RunService").Heartbeat:Connect(function()
local ray = Ray.new(bullet.Position, bullet.CFrame.LookVector+Vector3.new(0,0,-2))
local hitPart, hitPos = workspace:FindPartOnRay(ray)
if hitPart and hitPart.CanCollide and hitPart ~= bullet then
print(hitPart)
bullet.Anchored = true
connection:Disconnect()
end
end)
both with the same results
attachment:
I have tried putting the attachment further and even making attachments (each with its
own ray) around the bullet to form a sphere-like hitbox since when the collision of the bullet is detected too late it would flip around a lot.
here you will see the bullets detecting either too late and flipping around or too early.
I’m fairly certain there’s no way to do it with the physics system, there’s just no way to get the exact position of the hit. You can however use raycasts to see if the bullet is about to hit something, and then when the Touched event fires you can simple move the bullet to be at the last detected hit location.
Another approach is to entirely ignore the physics system and instead do your own physics simulation to get the bullet to move in a ballistic trajectory, and do raycasts along the way to get an exact hit location and have full control over the movement that way.
I see, I’ve got a little inspired by the Wii tank game and tiny tanks on roblox, where collisions with bullets work just fine. I’m trying to achieve just that but have no clue how, but tiny tanks made it some how possible.
It’s definitely possible to get good bullet mechanics, but IMO that’s just not something the physics system can do well. Here’s a super simple simulation to get you started:
local bullets = {}
function newBullet(p0: Vector3, v0: Vector3)
local bullet = {Position = p0, Velocity = v0, Model = game.ReplicatedStorage.Bullet:Clone()}
updateBullet(bullet, 0)
table.insert(bullets, bullet)
end
function destroyBullet(bullet)
end
function updateBullet(bullet, dt)
bullet.Velocity += Vector3.new(0, -game.Workspace.Gravity, 0) * dt
local raycastResult = game.Workspace:Raycast(bullet.Position, bullet.Velocity * dt, bulletRaycastParams)
if raycastResult then
bullet.Position = raycastResult.Position
else
bullet.Position += bullet.Velocity * dt
end
bullet.Model:SetPrimaryPartCFrame(CFrame.new(bullet.Position, bullet.Position + bullet.Velocity))
if raycastResult then
bulletHit(bullet, raycastResult)
end
end
function bulletHit(bullet, raycastResult)
--Deal damage, make bullet hole, whatever else
destroyBullet(bullet)
end
game:GetService("RunService").Stepped:Connect(function(_, dt)
for _, bullet in ipairs(bullets) do
updateBullet(bullet, dt)
end
end)
This is meant to run all on the server, but you might want to not have any visuals handled on the server, like the actual bullet model, and instead send a signal to all players that a bullet has been fired with whatever parameters, and then recreate a purely visual bullet on each client that gets simulated every RenderStepped instead, to make the bullet appear to move more smoothly.
that’s a possible solution, only RS.Stepped is only client-side, not that I couldn’t change that to heartbeat instead. but for me it doesn’t change much else from what i have now:
local connection
bullet = script.Parent
connection = game:GetService("RunService").Heartbeat:Connect(function()
local ray = Ray.new(bullet.Position, bullet.RayAttachment.WorldPosition)
local hitPart, hitPos = workspace:FindPartOnRay(ray)
local ray2 = Ray.new(bullet.Position, bullet.RayAttachment2.WorldPosition)
local hitPart2, hitPos2 = workspace:FindPartOnRay(ray2)
local ray3 = Ray.new(bullet.Position, bullet.RayAttachment3.WorldPosition)
local hitPart3, hitPos3 = workspace:FindPartOnRay(ray3)
local ray4 = Ray.new(bullet.Position, bullet.RayAttachment4.WorldPosition)
local hitPart4, hitPos4 = workspace:FindPartOnRay(ray4)
local ray5 = Ray.new(bullet.Position, bullet.RayAttachment5.WorldPosition)
local hitPart5, hitPos5 = workspace:FindPartOnRay(ray5)
if hitPart and hitPart.CanCollide and hitPart ~= bullet then
hit(hitPart)
end
if hitPart2 and hitPart2.CanCollide and hitPart2 ~= bullet then
hit(hitPart2)
end
if hitPart3 and hitPart3.CanCollide and hitPart3 ~= bullet then
hit(hitPart3)
end
if hitPart4 and hitPart4.CanCollide and hitPart4 ~= bullet then
hit(hitPart4)
end
if hitPart5 and hitPart5.CanCollide and hitPart5 ~= bullet then
hit(hitPart5)
end
end)
function hit(hitPart)
print(hitPart)
bullet.Anchored = true
connection:Disconnect()
end
function destroyBullet(bullet)
for i, _bullet in ipairs(bullets) do
if _bullet == bullet then
table.remove(bullets, i)
break
end
end
end
The red transparent parts are just a trail to show the different positions it ends up at. As you cansee, the bullet ends up perfectly embedded in the part it hit, without “bouncing back” before the Touched event has time to fire so that your script can react by setting Anchored to true. Since we have full-ish control over how the bullet moves, we can ensure that it doesn’t bounce before hitting. It seems to work every time:
local TagS = game:GetService("CollectionService")
local bullets = {}
bulletRaycastParams = RaycastParams.new()
bulletRaycastParams.FilterType = Enum.RaycastFilterType.Blacklist
function newBullet(p0: Vector3, v0: Vector3)
local bullet = {Position = p0, Velocity = v0, Model = game.ReplicatedStorage.Bullet:Clone()}
bullet.Model:SetPrimaryPartCFrame(CFrame.new(bullet.Position, bullet.Position + bullet.Velocity))
bullet.Model.Parent = game.Workspace
table.insert(bullets, bullet)
end
function destroyBullet(bullet)
for i, _bullet in ipairs(bullets) do
if _bullet == bullet then
table.remove(bullets, i)
break
end
end
end
function updateBullet(bullet, dt)
local ghostBullet = bullet.Model:Clone()
ghostBullet.PrimaryPart.Transparency = 0.4
ghostBullet.Parent = game.Workspace
bullet.Velocity += Vector3.new(0, -game.Workspace.Gravity, 0) * dt
local raycastResult = game.Workspace:Raycast(bullet.Position, bullet.Velocity * dt, bulletRaycastParams)
if raycastResult then
bullet.Position = raycastResult.Position
else
bullet.Position += bullet.Velocity * dt
end
bullet.Model:SetPrimaryPartCFrame(CFrame.new(bullet.Position, bullet.Position + bullet.Velocity))
if raycastResult then
bulletHit(bullet, raycastResult)
end
end
function bulletHit(bullet, raycastResult)
print(raycastResult.Instance)
--Deal damage, make bullet hole, whatever else
bullet.Model.PrimaryPart.Color = Color3.fromHSV(.5, 1, 1)
destroyBullet(bullet)
end
game:GetService("RunService").Stepped:Connect(function(_, dt)
for _, bullet in ipairs(bullets) do
updateBullet(bullet, dt)
end
end)
local gun = game.Workspace.Gun
local muzzleSpeed = 3000
while wait(0.75) do
newBullet(
gun.Position,
(gun.CFrame * CFrame.Angles(
math.random(-100,100)/1000,
math.random(-100,100)/1000,
0
)).LookVector * muzzleSpeed
)
bulletRaycastParams.FilterDescendantsInstances = TagS:GetTagged("BulletIgnore")
end