Hey, so I’ve been working on a projectile system and I’ve been having trouble with hit detection. I don’t want to use Region3 or .Touched since they’re either exploitable/ non-performant. I’ve stuck with Raycasts as they’re fairly inexpensive in short distances (afaik), one thing is that I have no clue on how to properly detect the object detected by this method.
Current code to get a detected object:
local function Check(C, Arrow)
-- C is Character
if Arrow and Arrow:IsDescendantOf(workspace) then
local LinearVelocity = Arrow:FindFirstChild("LinearVelocity")
local ArrowOrigin = Arrow.Position
local ArrowCFrame = Arrow.CFrame
local LookVector = ArrowCFrame.LookVector
local ArrowDirection = LookVector*2
--
local Params = RaycastParams.new()
Params.FilterType = Enum.RaycastFilterType.Blacklist
Params.FilterDescendantsInstances = {C, Arrow}
local Raycast = workspace:Raycast(ArrowOrigin, ArrowDirection, Params)
if Raycast then
return Raycast
else
task.wait()
LinearVelocity.VectorVelocity = LinearVelocity.VectorVelocity - Vector3.new(0,.4,0)
Check(C, Arrow)
end
end
end
I’ve looked at all these modules that are a wrapper for a Melee raycasting system, and I can just say that using them in this method wouldn’t be a very good idea at all, as I’m cloning a new Arrow everytime, instead of just one Melee itself. I really don’t think that would be any faster than using Region3 at that point unless it manages to compute much, much faster.
You should use run service. Every frame put a raycast between the projectile position of the previous frame and the current position. If you detect something then do whatever the projectile is supposed to do. If not then let it keep flying.
If the projectile is quite big you may need to put attachments on it and track all the attachments
Yes, that is what I am currently doing, as task.wait() has comparable speeds to RunService.Heartbeat:Wait(), It’s raycasting, but I don’t know the math on how to get it to raycast properly and detect. Also, it’s fairly small and isn’t very large.
I’m considering using this method, but I want to try using Raycasts instead as it’ll be a bit quicker to detect as the Arrow won’t have to be inside of the actual thing detected.
Then you can try using it plus what @lightningstrike30451 also said. Create a Connection and use RunService.Heartbeat to get the Parts, Put them inside a temporary table to avoid possible damage repetitions. Then you can just pretty much do whatever you want with the Parts, Aftermath, Clear the table and Connection.
So, Positions are vectors. Set the origin of the raycast to the previous position, and then the direction to the current position - previous position. Vector subtraction dictates that this creates a vector that goes from previous to current(when you position it at the previous position)
I tried doing this, and it didn’t seem to change anything, do I have to .Unit*2 it or is there something I’m missing?
local function Check(C, Arrow, ArrowDirection)
-- C is Character
if Arrow and Arrow:IsDescendantOf(workspace) then
local LinearVelocity = Arrow:FindFirstChild("LinearVelocity")
local ArrowOrigin = Arrow.Position
--
local Params = RaycastParams.new()
Params.FilterType = Enum.RaycastFilterType.Blacklist
Params.FilterDescendantsInstances = {C, Arrow}
if not ArrowDirection then
ArrowDirection = ArrowOrigin
ArrowDirection = (ArrowOrigin-ArrowDirection)
else
ArrowDirection = (ArrowOrigin-ArrowDirection)
end
local Raycast = workspace:Raycast(ArrowOrigin, ArrowDirection, Params)
if Raycast then
return Raycast
else
task.wait()
LinearVelocity.VectorVelocity = LinearVelocity.VectorVelocity - Vector3.new(0,.4,0)
Check(C, Arrow, ArrowDirection)
end
end
end
If you’re asking about how to do the raycasts reliably, here’s a method I commonly use:
Keep the arrow anchored, with CanCollide, CanTouch, and CanQuery set to false. We won’t use the arrow’s velocity to change its position, and instead do things through code.
Once the arrow is fired, we spawn it into the world with a velocity. This velocity will not be modified by the default physics engine because the the arrow is anchored.
Every physics step, we do a raycast to check and see if something’s been hit. If not, wait for the next physics step.
Here’s a code sample of how you would do something like this. Please let me know about any potential questions you have!
local RunService = game:GetService("RunService")
local arrowTemplate: BasePart = script.Arrow
local function applyPhysics(velocity: Vector3, deltaTime: number): (Vector3)
local newVelocity: Vector3 = velocity * (1 - 0.05 * deltaTime)
-- the above line applies drag to the object
-- drag is represented by '0.05'. The arrow will lose 5% of its speed every second
-- feel free to change/remove the drag!
newVelocity += Vector3.new(0, -workspace.Gravity * deltaTime, 0)
-- the above line will apply gravity to the object, similar to the physics engine
return newVelocity
end
local function spawnArrow(character: Model, position: Vector3, velocity: Vector3): (RaycastResult?)
local arrow: BasePart = arrowTemplate:Clone()
arrow.CFrame = CFrame.new(position, position + velocity) -- the second parameter makes the arrow face the direction it's going
arrow.Velocity = velocity -- we will change this value manually
-- these are the RaycastParams for the raycast
local params: RaycastParams = RaycastParams.new()
params.FilterType = Enum.RaycastFilterType.Blacklist
params.FilterDescendantsInstances = {character, arrow}
-- loop while the arrow isn't in the void.
-- add extra parameters if you'd like!
local deltaTime: number = 0.017 -- the first step value
-- deltaTime will also be set by RunService.Stepped, but we want to cast right now instead of waiting for the next physics step
while arrow.Position.Y > workspace.FallenPartsDestroyHeight do
local result: RaycastResult = workspace:Raycast(arrow.Position, arrow.Velocity * deltaTime)
-- multiplying the velocity by 'deltaTime' returns the distance travelled by the arrow
-- in 'deltaTime' amount of time (seconds)
if result then
-- we hit something!
-- return the RaycastResult
-- if you'd like to know what you hit, do 'result.Instance'
return result
else
-- we didn't hit something
-- lets move the arrow, and modify its velocity as well
-- the following order is important!
arrow.Position += arrow.Velocity * deltaTime
arrow.Velocity = applyPhysics(arrow.Velocity, deltaTime)
-- ...and a lazy way of rotating the arrow
arrow.CFrame = CFrame.new(arrow.Position, arrow.Position + arrow.Velocity)
-- finally, we go back to the start of the loop and wait for the next physics step
deltaTime = RunService.Stepped:Wait()
end
end
-- the 'while' loop exited without hitting something
return nil
end
There are more efficient ways of doing this, but the above code sample is easier to understand. If you’d like the efficient option, I can send you the Lua file I’ve written a few weeks ago.
You should most definitely use raycasts.
I wouldn’t recommend ClientCast for this as it isn’t tailored for ranged hitboxes like bullets, you should still use a simple raycast operation.
Some tips would be to:
Create a RaycastParams object when the bullet is created
In a RunService loop, calculate the bullets next position, and raycast from the bullets current position towards the next.
If there was something hit, stop the bullet and deal damage.
If nothing was hit, move the bullet towards that new position.
EDIT: the answer is in the post above, but to clarify as to why Region3 is worse: it’s much more expensive, it’s axis-locked (ie, no rotation support), and it does not offer any additional data such as surface normal.
Just… don’t break the loop then? Continue going until you either hit a baseplate, or you reach a certain amount of targets that can be hit. Also, this post is 2 years old and I really do suggest you create a new post compared to boosting this one, so you can get the most up to date information regarding stuff like this (albeit though I don’t think much has changed tbh)
A bit off topic, so ill be fine using raycast for projectiles like fireballs. But what method do you recommend for non projectile abilities like a AOE ground smash? Magnitude, etc, or can raycast be used for this (detecting players that are within a area)?