How to cast rays through blocks that have cancollide set to false

Something like this would work but you I feel like you need to loop everything in workspace that are non-collidable, how I do this is I put this inside a function, and check the part that has been hit if it’s set to cancollide false, if the part is not collidable I will fire again the function, something like that

(I’m not good at english :confused:)

Try

local InstancePart = raycastResult.Instance -- getting the part that has been hit.

if InstancePart.CanCollide == false then -- checking if it is false.
  -- do stuff
end

Yes something like that, then fire the function that is used to raycast like:

local function rayCast(origin, dir)
 --put your stuff here
  if hitPart.CanColide == false then -- make sure the hitpart doesn't have a humanoid in its parent
     rayCast(hitPosition, CFrame.new(hitPosition, hitPosition - hitNormal).LookVector * range) -- Some bits of my old gun system
  end
end
1 Like

Yes, just like that. Also I didn’t notice I was replying to the wrong person LOL

Would that go through cannot collide parts?
Sorry for the stupidity, Not the best at scripting.

To do this with Ray.new()
Shoot 2 rays,

  local Ignore ={tool.Parent}

This makes the ray ignore the tool also use;

 local fPart, fPosition, fNormal = workspace:FindPartOnRayWithIgnoreList(Ray, Ignore)
 -- "the f in front means FIRST" --
if fPart.CanCollide == false then
Ignore = {tool.Parent; fPart}
else
Ignore = {tool.Parent}
end
local Part, Position, Normal = workspace:FindPartOnRayWithIgnoreList(Ray, Ignore)

This is a deprecated method, however It works fine. :sunny:

1 Like

No, I only converted your old raycast to the new API.

You have 3 options to do that:

  1. Create an array with all CanCollide false parts in your game and set raycastParams.FilterDescendantsInstances to it. You can use workspace:GetDescendants() or CollectionService:GetTagged() (if you tag all CanCollide false parts before that, it can be done in studio edit mode with the command bar) to find all the CanCollide false parts in your game.
  2. Instead of using the CanCollide property, make a new Collision Group which doesn’t collide with Default and set all of the CanCollide false parts’ Collision Group to it.
  3. If your raycast hits a CanCollide false part, raycast again from where the last ray ended (new origin vector). You would also have to decrease the direction vector based on how far from the last origin the ray traveled, so the rays don’t go too far. Repeat this until you get a result which isn’t a CanCollide false part.

The 3rd method is probably the best if your game has a lot of CanCollide false parts, but also the hardest to implement.

@Epic_2219 Hello, unfortunately I am not enough experienced to have a answer, but I would council getting all the RayCast functions, and then use them at there designed place.

Where?

I’ll attempt this, thank you.
I will also implement a the new raycast thing.

And another question, will this create a new ray part? As I need that.

What do you mean by a “ray part”?

As in a block that is streched across the ray length.

And by the way, this is not working.
Is this implemented correctly?


local function fire()
	--//Checks
	if canShoot:InvokeServer() then
		--//Initialize
		
	
		
		--[[local SPREAD = 1000 -- set some value for it
		local spreadPosition = Vector3.new(
			hole.CFrame.p.X + math.random(-SPREAD, SPREAD)/1000,
			hole.CFrame.p.Y + math.random(-SPREAD, SPREAD)/1000,
			hole.CFrame.p.Z + math.random(-SPREAD, SPREAD)/1000
		)]]
		local raycastParams = RaycastParams.new()

		local origin = hole.CFrame.p
		local direction = (mouse.hit.p - hole.CFrame).unit * range.Value

		raycastParams.FilterDescendantsInstances = {player.Character}
		raycastParams.FilterType = Enum.RaycastFilterType.Blacklist
		raycastParams.IgnoreWater = true

		local raycastResult = workspace:Raycast(origin, direction, raycastParams)
		local position

		if raycastResult then
			local touch = raycastResult.Instance

			position = raycastResult.Position

			hitRemote:FireServer(touch)
		else
			position = origin + direction
		end
	end
end

OLD:

local function fire()
	--//Checks
	if canShoot:InvokeServer() then
		--//Initialize
		
	
		
		--[[local SPREAD = 1000 -- set some value for it
		local spreadPosition = Vector3.new(
			hole.CFrame.p.X + math.random(-SPREAD, SPREAD)/1000,
			hole.CFrame.p.Y + math.random(-SPREAD, SPREAD)/1000,
			hole.CFrame.p.Z + math.random(-SPREAD, SPREAD)/1000
		)]]
		local ray = Ray.new(hole.CFrame.p, (mouse.hit.p - hole.CFrame.p).unit * range.Value)
		local touch, position = workspace:FindPartOnRay(ray, player.Character, false, true)
		
		--//Hit detection
		if touch then
			hitRemote:FireServer(touch)
		end
		shootRemote:FireServer()
		
		--//Trace
		if allowTracing.Value then
			--//Create
			local trace = Instance.new("Part")
			trace.Anchored = trace
			trace.CanCollide = false
			trace.Transparency = .5
			trace.BrickColor = BrickColor.new("Medium blue")
			trace.Material = Enum.Material.Neon
			
			--//Calculate
			local distance = (hole.CFrame.p - position).magnitude
			trace.Size = Vector3.new(.05, .05, distance)
			trace.CFrame = CFrame.new(hole.CFrame.p, position) * CFrame.new(0, 0, -distance/2)
			trace.Parent = workspace
			remotes.Oof:FireServer(hole.CFrame.p, mouse.hit.p
			)
			
			--//Clean-up
			game:GetService("Debris"):AddItem(trace, 0.02)
			
			wait(.02)
			
		
		end
	end
end

Oh, my bad. I missed a “.p” for the direction variable. Should have been:

local direction = (mouse.hit.p - hole.CFrame.p).unit * range.Value

I only changed the top of your old code to show you how you can use the new Raycasting API with your code. The rest of the code should still work, I didn’t bother copy pasting it (the shootRemote:FireServer() part and anything below).

Alright, so the ray cast works. Now to cast them through non-solid blocks or massless blocks, we would have to do what?

Could you perhaps explain how to do the third method? Sorry, again I’m not the best at scripting (especially raycasting.)

1 Like

Something like this should work, thought I didn’t test it thoroughly:

local function RaycastWithACondition(condition, origin, direction, raycastParams) -- condition is a function which returns true or false
	local raycastResult = workspace:Raycast(origin, direction, raycastParams)

	if raycastResult == nil or condition(raycastResult.Instance) then -- this is to ensure we dont call condition(nil)
		return raycastResult -- returns nil or RaycastResult
	end

	-- if the condition didnt pass

	local intersection = raycastResult.Position

	direction = direction - (intersection - origin) -- previous direction reduced by the distance traveled from the previous origin to the intersection
	origin = intersection -- the intersection is the new origin of the next ray

	return RaycastWithACondition(condition, origin, direction, raycastParams) -- recursion, back to the top of the function with new origin and direction
end

I implemented the above one with recursion, you can also use a while loop:

local function RaycastWithACondition(condition, origin, direction, raycastParams) -- condition is a function which returns true or false
	while true do
		local raycastResult = workspace:Raycast(origin, direction, raycastParams)

		if raycastResult == nil or condition(raycastResult.Instance) then -- this is to ensure we dont call condition(nil)
			return raycastResult -- returns nil or RaycastResult
		end

		-- if the condition didnt pass

		local intersection = raycastResult.Position

		direction = direction - (intersection - origin) -- previous direction reduced by the distance traveled from the previous origin to the intersection
		origin = intersection -- the intersection is the new origin of the next ray
	end
end

You should probably put this in some module script and return it (or put it in a table and return the table), so any script can access it.

Here is an example of the condition function:

local function condition(instance)
    return (instance.Transparency < 1 and instance.CanCollide) -- true if a part's Transparency is below 1 and CanCollide is true, otherwise false
end

Some implementation notes:

  1. I tested it and it seems like if you cast a ray that originates from the intersection of the previous ray and a part, it won’t hit the same part again. Basically, a ray that starts from inside a part and goes through it, doesn’t intersect with it. This might not be true for some kinds of unions/meshes with complicated geometries, but the function should still work. This way it’s simpler and we don’t have to mess with tables/Blacklists. From what I know, RaycastParams.FilterDescendantsInstances copies any table you set it to, so it would essentially be creating a new table every time, which wouldn’t be good.
  2. I also tested for the edge case of the ray hitting a part which doesn’t pass the condition at exactly origin + direction. I couldn’t get it to happen. In that case, it would attempt a useless raycast with a direction vector of Vector3.new(0, 0, 0). It would still correctly return a nil, but we could technically prevent the extra workspace.Raycast call by checking if direction == Vector3.new() or if direction.Magnitude == 0. I found this check unnecessary, since it seems like rays don’t detect an intersection at the end of the ray. I tested this by casting a ray with an origin of Vector3.new(0, 0, 0) and a direction of Vector3.new(0, 1, 0). It intersected a part above it at Vector3.new(0, 0.5, 0). Then I tested the same scenario with a direction of Vector3.new(0, 0.5, 0) and there was no intersection.
4 Likes

Just coming back I would use a while loop because of callstack overflow.

Hey people, forgot to put a solution on this. Roblox now has a new property called ‘CanQuery’ which manages this exact thing. Therefore, this thread is now deprecated. Sorry everyone!

https://developer.roblox.com/en-us/api-reference/property/BasePart/CanQuery

3 Likes