so, im making a zombie survival game and i decided to make my own rocket launcher tool for the game, you simply use it to fire a projectile that explodes on impact.
however roblox has a weird touch system so whenever the rocket colides, the explosion it makes is not on the exact position it touched, so i decided to use raycast to track the exact touch position, i have used raycast before but i have no idea on how to make a ray that goes to the rocket projectile’s facing surface direction. can anyone help me out?
In this case, you will have to use either RightVector, LookVector, or UpVector. (Which you will use will require testing) The end point of the ray should be either on one of those vectors * a number (should be a low number), then just get the hit position from the ray
You can look into this. Making a combat game with ranged weapons? FastCast may be the module for you!
You would need to know to trajectory over time and raycast between time frames and move the projectile also.
If you don’t want to use FastCast I can write out the formula I personally use to simulate trajectories.
What you can do is make the server and client have a ray simulation trajectory, but the client will show a visual of the projectile to make it seem seamless.
Ray simulations can be done with the following:
StartPos | Gun muzzle or Player’s RootPart position
Direction | Camera Direction/LookVector
An end position is not needed here, we just need a direction and start position!
If you’re going to use a end position, then the direction should be: (endPos - startPos).Unit
Code:
-- Client | RunService.RenderStepped
-- Server | RunService.Heartbeat
local projectileSpeed = 3
local pos = rootPart.Position -- Start position
local direction = Camera.CFrame.LookVector.Unit * projectileSpeed-- Direction
--If you're going to use a end position,
--then the direction should be: (endPos - startPos).Unit * projectileSpeed
local conn
conn = Services.RunService.RenderStepped:Connect(function(dt)
local ray = workspace:Raycast(pos, direction)
if ray then
conn:Disconnect()
conn = nil
else
pos += direction
--visual projectile below
visual.CFrame = CFrame.new(pos)
visual.CFrame *= CFrame.Angles(0, math.rad(math.pi/2), 0)
end
end)
Now that our trajectory & collision is done, we can work on our explosion!
We can use raycasts for the explosion, but how can we get the targets?
The solution to this question is workspace:GetPartBoundsInBox()
!
With GetPartBoundsInBox, we can get the targets with the trajectory’s ray.
Once we get our targets, we check if they’re a Model and see if they contain a Humanoid & RootPart.
If successful, we raycast from the explosion to the target to confirm that they’re in cover or not.
Code:
-- Client | RunService.RenderStepped
-- Server | RunService.Heartbeat
local projectileSpeed = 3
local pos = rootPart.Position -- Start position
local direction = Camera.CFrame.LookVector * projectileSpeed-- Direction
--If you're going to use a end position,
--then direction should be: (endPos - startPos).Unit * projectileSpeed
local conn
local function getInstance(parent, name)
local _, returned = pcall(function()
return parent[name]
end)
-- Sometimes if the protected call is unsuccessful, it will return an error string, so we turn it to nil.
if typeof(returned) == "string" then returned = nil end
return returned
end
local HitboxSize = 20
local baseDamage = 100
local baseForce = 30
-- This function is used to check if a target is under cover such as behind an obstacle to avoid getting damage from the explosion.
local function checkifTargetCover(pos, direction, ignore)
-- Ignore should be a table
local ignoreParams = RaycastParams.new()
ignoreParams.FilterDescendantsInstances = {table.unpack(ignore)}
ignoreParams.FilterType = Enum.RaycastFilterType.Blacklist
local ray = workspace:Raycast(pos, direction, ignoreParams)
if ray then
return true
end
return false
end
function explosion(Raycast)
local normal = Raycast.Normal
local position = Raycast.Position
local hitCF = CFrame.new(position, position+normal) -- This is very useful for bullet particles/sparks.
local overlapParams = OverlapParams.new()
overlapParams.FilterDescendantsInstances = {}
overlapParams.FilterType = Enum.RaycastFilterType.Blacklist
overlapParams.MaxParts = 5000
-- OverlapParams is very similar to RaycastParams, but will not have ignoreWater and instead have a MaxParts setting.
local hit = workspace:GetPartBoundsInBox(hitCF, Vector3.one*HitboxSize, overlapParams)
if hit and #hit > 0 then
for i, inst in ipairs(hit) do
local rig = (inst.Parent ~= nil and inst.Parent:IsA("Model"))
if rig then
local Humanoid = getInstance(rig, "Humanoid")
local RootPart = getInstance(rig, "HumanoidRootPart")
-- I heard that FindFirstChild is 20% slower than WaitForChild, so I made my own function to get an object instead of using FindFirstChild
-- You can replace getInstance with FindFirstChild if you want
-- rig:FindFirstChild("Humanoid"), rig:FindFirstChild("HumanoidRootPart")
if Humanoid and RootPart then
local vector3 = (hitCF.Position - RootPart.Position)
-- vector3 will be used for force, damage, and distance!
local distance = vector3.Magnitude
local direction = vector3.Unit
-- The reason why Unit is used in most direction vectors, is because it normalizes it, making it's magnitude 1.
if not checkifTargetCover(RootPart.Position, direction * distance, {rig}) then
local Damage = math.floor(baseDamage - distance)
local Force = math.floor(baseForce - distance)/2
-- math.floor will help round up our numbers
Humanoid.Health -= Damage
RootPart.AssemblyLinearVelocity += direction * Force
end
end
end
end
end
end
conn = Services.RunService.RenderStepped:Connect(function(dt)
local ray = workspace:Raycast(pos, direction)
if ray then
explosion(ray)
conn:Disconnect()
conn = nil
else
pos += direction
--visual projectile below
visual.CFrame = CFrame.new(pos)
visual.CFrame *= CFrame.Angles(0, math.rad(math.pi/2), 0)
end
end)
I hope this lazy guide is helpful, feel free to modify the code!
I have ran into this issue myself. The explosion would be centered on the middle of the projectile instead of the exact point of impact in which the difference can be significant. So I used your idea of raycasting to come up with a workable solution. However, I encountered a problem. The problem is that physics can make the projectile bounce, so the CFrame.LookAt vector becomes useless. The way that I solved this was in the weapon script on the server, add an attribute to the projectile part itself called “FlightDir”. That’s a Vector3 value that holds the original direction of the projectile’s flight path. Here’s some code:
local flightDir = projectile:GetAttribute("FlightDir")
-- Creates an explosion and removes the projectile.
projectile.Touched:Connect(function(hit)
-- Get the position as early as possible.
local position = projectile.Position
-- Check to make sure that we should explode or
-- fly through.
if not weaponsMod.checkHitIntangible(hit) then
-- Execute Effects
if flySound ~= nil then
flySound:Stop()
end
-- Raycast the impact so we know the exact point of impact.
-- @JPO2006roblox came up with this idea.
local result = game.Workspace:Raycast(position, flightDir * 20)
if result ~= nil then
position = result.Position
end
-- Generate Explosion
explosion.Position = position
explosion.Parent = game.Workspace
-- We reparent the script to the tool instance
-- so it will continue to execute when the
-- projectile is destroyed.
debrisService:AddItem(script, 10)
script.Parent = toolInstance
projectile:Destroy()
end
end)
Don’t worry about some of the module calls because those are specific to my weapons system. Also, I created the explosion beforehand to reduce processing inside the event handler. Feel free to use this.