Raycast sometimes gets nil, even though it's right next to a part

  1. What do you want to achieve? Keep it simple and clear!
    I have a ball bouncing script that bounces the ball whenever it hits a part.
  2. What is the issue? Include screenshots / videos if possible!
    Sometimes, the raycast I get is nil, even though it only fires when the ball touches something.
  3. What solutions have you tried so far? Did you look for solutions on the Creator Hub?
    I’ve tried to change the position of where the raycast fires from, but it didn’t work.
    Here’s the function in question:
local spawnBasic = game.ReplicatedStorage.SpawnBasic
local moreSpeed = game.ReplicatedStorage.BasicSpeed
local speed = 0
local blank = Vector3.new(0,15,0)
local RunService = game:GetService("RunService")
--local create = require(game.ReplicatedStorage.Modules.BallSpawn)

local function BasicBall()
	local ball = Instance.new("Part")
	local attachment = Instance.new("Attachment", ball)
	local lv = Instance.new("LinearVelocity", attachment)
	ball.Name = "ball"
	ball.Parent = workspace
	ball.Shape = Enum.PartType.Ball
	ball.CFrame = CFrame.new(math.random(-50,50), math.random(3,20), math.random(-50,50))
	ball.CanCollide = false
	lv.Attachment0 = attachment
	lv.VelocityConstraintMode = Enum.VelocityConstraintMode.Line
	lv.LineDirection = Vector3.new(math.random(-999,999)/1000, math.random(-999,999)/1000, math.random(-999,999)/1000)
	lv.LineVelocity = speed
	ball.Touched:Connect(function() -- IMPORTANT BIT!!!
		local startCFrame = ball.CFrame -- Where we derive our position & normal
		local normal = lv.LineDirection.Unit
		local position = startCFrame.Position
		local result = workspace:Raycast(position, normal*500) -- Cast ray
		if result ~= nil  then
			local reflectedNormal = normal - (2 * normal:Dot(result.Normal) * result.Normal)
			ball.AssemblyLinearVelocity = Vector3.new(0,0,0) -- super important, dont delete!!
			lv.LineDirection = reflectedNormal
			print("bounced!")
		else
			warn("dude, thats nil.")
		end
	end)
	while true do
		if (math.abs(ball.Position.X) > 100) == true then
			ball.Position = blank
		end
		if (math.abs(ball.Position.Y) > 100) == true then
			ball.Position = blank
		end
		if (math.abs(ball.Position.Z) > 100) == true then
			ball.Position = blank
		end
		lv.LineVelocity = speed
		task.wait(1)
	end
end

spawnBasic.OnServerEvent:Connect(BasicBall)

moreSpeed.OnServerEvent:Connect(function() -- this makes already spawned balls faster!!!!
	speed = speed + 10
	print(speed)
end)

Here’s the video of the issue:
https://streamable.com/xiy7lx

2 Likes

Edit:
I’ve came up with a trick that seems to be good enough for now, but doesn’t work that well at higher speeds…
https://streamable.com/0e8h6o

1 Like

Edit again:
I’ve now got an even better trick that works, even at high speed! However, the root problem I’m trying to fix still isn’t solved, so I’m not going to mark this as the solution…
https://streamable.com/t20pbf
And since I forgot to post the code last time, here’s my new fix:

ball.Touched:Connect(function() -- IMPORTANT BIT!!!
		ball.AssemblyLinearVelocity = Vector3.new(0,0,0)
		ball.CFrame = ball.CFrame:Lerp(CFrame.new(0,15,0),speed/10000)
		local startCFrame = ball.CFrame -- Where we derive our position & normal
		local normal = lv.LineDirection.Unit
		local position = startCFrame.Position
		local result = workspace:Raycast(position, normal*500) -- Cast ray
		if result ~= nil  then
			local reflectedNormal = normal - (2 * normal:Dot(result.Normal) * result.Normal)
			lv.LineDirection = reflectedNormal
			print("bounced!")
		else
			ball.AssemblyLinearVelocity = Vector3.new(0,0,0)
			lv.LineDirection = Vector3.new(math.random(-999,999)/1000, math.random(-999,999)/1000, math.random(-999,999)/1000)
			warn("dude, thats nil.")
		end
	end)
	while true do
		if (math.abs(ball.Position.X) > 100) == true then
			ball.Position = blank
		end
		if (math.abs(ball.Position.Y) > 100) == true then
			ball.Position = blank
		end
		if (math.abs(ball.Position.Z) > 100) == true then
			ball.Position = blank
		end
		lv.LineVelocity = speed
		task.wait(1)
	end
1 Like

Judging from title it may be the case becouse raycast is right in the same position as a bounding box of the wall.
If it has 0 distance raycast may pass through sometimes.

1 Like

That’s what I was thinking, but how would I fix that?

with raycasting?
Cant really unless you somehow know surface normal
With other functions like workspace:GetPartBoundsInRadius ? maybe
There is a lot of solutions to a problem but workspace:GetPartBoundsInRadius would be the easiest ig

Well, I rely on raycasting because that seems like the easiest way to get the surface normal. How would I use GetPartBounds for finding the normal?

Normal is a direction surface is facing that has been hitted by a raycast; GetPartBounds does check position.
You can loop through all objects that it found and use (part:GetClosestPointOnSurface(RayStartPosition)-RayStartPosition).Magnitude if it equals 0 or near 0 like 0.00000001 then yeah this part should be right infront of raycast probably

1 Like

Yeah, it’s currently printing 0. However, it’s now bouncing the ball out of bounds sometimes too.
Heres the code I used:

ball.Touched:Connect(function() -- IMPORTANT BIT!!!
		ball.AssemblyLinearVelocity = Vector3.new(0,0,0)
		--ball.CFrame = ball.CFrame:Lerp(CFrame.new(0,15,0),speed/10000)
		local startCFrame = ball.CFrame -- Where we derive our position & normal
		local normal = lv.LineDirection.Unit
		local position = (ball:GetClosestPointOnSurface(ball.Position)-ball.Position) -- changed code
		local magnitude = position.Magnitude
		print(magnitude)
		local result = workspace:Raycast(position, normal*500) -- Cast ray
		if result ~= nil  then
			local reflectedNormal = normal - (2 * normal:Dot(result.Normal) * result.Normal)
			lv.LineDirection = reflectedNormal
			print("bounced!")
		else
			ball.AssemblyLinearVelocity = Vector3.new(0,0,0)
			lv.LineDirection = Vector3.new(math.random(-999,999)/1000, math.random(-999,999)/1000, math.random(-999,999)/1000)
			warn("dude, thats nil.")
		end
	end)
	while true do
		if (math.abs(ball.Position.X) > 100) == true then
			ball.Position = blank
		end
		if (math.abs(ball.Position.Y) > 100) == true then
			ball.Position = blank
		end
		if (math.abs(ball.Position.Z) > 100) == true then
			ball.Position = blank
		end
		lv.LineVelocity = speed
		task.wait(1)
	end

I think you misunderstood what i meant.
You have to loop through nearest models using GetPartBoundsInRadius
like:

for i,part in workspace:GetPartBoundsInRadius(RaycastStartPos,0.001) do 


end

Like this?

	ball.Touched:Connect(function()
		for i,part in workspace:GetPartBoundsInRadius(ball.Position,0.001) do 
			ball.AssemblyLinearVelocity = Vector3.new(0,0,0)
			--ball.CFrame = ball.CFrame:Lerp(CFrame.new(0,15,0),speed/10000)
			local startCFrame = ball.CFrame -- Where we derive our position & normal
			local normal = lv.LineDirection.Unit
			local position = ball.Position
			local magnitude = position.Magnitude
			print(magnitude)
			local result = workspace:Raycast(position, normal*500) -- Cast ray
			if result ~= nil  then
				local reflectedNormal = normal - (2 * normal:Dot(result.Normal) * result.Normal)
				lv.LineDirection = reflectedNormal
				print("bounced!")
			else
				ball.AssemblyLinearVelocity = Vector3.new(0,0,0)
				lv.LineDirection = Vector3.new(math.random(-999,999)/1000, math.random(-999,999)/1000, math.random(-999,999)/1000)
				warn("dude, thats nil.")
			end
		end
	end)
	while true do
		if (math.abs(ball.Position.X) > 100) == true then
			ball.Position = blank
		end
		if (math.abs(ball.Position.Y) > 100) == true then
			ball.Position = blank
		end
		if (math.abs(ball.Position.Z) > 100) == true then
			ball.Position = blank
		end
		lv.LineVelocity = speed
		task.wait(1)
	end
end

No
You should make it local like a “second raycast” if raycast returns nil or prior to making raycast in the first place

So I should put it where the else is, right?

basically like that smth:
and using rayPart if its not nil instead of raycast

ball.Touched:Connect(function() -- IMPORTANT BIT!!!
		ball.AssemblyLinearVelocity = Vector3.new(0,0,0)
		--ball.CFrame = ball.CFrame:Lerp(CFrame.new(0,15,0),speed/10000)
		local startCFrame = ball.CFrame -- Where we derive our position & normal
		local normal = lv.LineDirection.Unit
		local rayPart:BasePart?=nil
for i,part in workspace:GetPartBoundsInRadius(ball.Position,0.001) do
if (ball:GetClosestPointOnSurface(ball.Position)-ball.Position).Magnitude<=0.001
BasePart=part
break
end
end
		local result = workspace:Raycast(position, normal*500) -- Cast ray
		if result ~= nil  then
			local reflectedNormal = normal - (2 * normal:Dot(result.Normal) * result.Normal)
			lv.LineDirection = reflectedNormal
			print("bounced!")
		else
			ball.AssemblyLinearVelocity = Vector3.new(0,0,0)
			lv.LineDirection = Vector3.new(math.random(-999,999)/1000, math.random(-999,999)/1000, math.random(-999,999)/1000)
			warn("dude, thats nil.")
		end
	end)
	while true do
		if (math.abs(ball.Position.X) > 100) == true then
			ball.Position = blank
		end
		if (math.abs(ball.Position.Y) > 100) == true then
			ball.Position = blank
		end
		if (math.abs(ball.Position.Z) > 100) == true then
			ball.Position = blank
		end
		lv.LineVelocity = speed
		task.wait(1)
	end

I have no idea how to use what you just replied with :cold_sweat:

relatable bro i dont know neither
basically you can make a “range cast” lets call it like that

local rayPart:BasePart?=nil
for i,part in workspace:GetPartBoundsInRadius(ball.Position,0.001) do
if (ball:GetClosestPointOnSurface(ball.Position)-ball.Position).Magnitude<=0.001 then
BasePart=part
break
end
end

rayPart is basically like result.Instance

If rayPart equals to nil then just do a regular raycast

Note that code i provided previously was just an ilustration how to make something like that and i probably deleted some important varaibles whoops

I’m not sure if I implemented this right. Did I?

	ball.Touched:Connect(function()
		for i,part in workspace:GetPartBoundsInRadius(ball.Position,0.001) do 
			ball.AssemblyLinearVelocity = Vector3.new(0,0,0)
			--ball.CFrame = ball.CFrame:Lerp(CFrame.new(0,15,0),speed/10000)
			local startCFrame = ball.CFrame -- Where we derive our position & normal
			local normal = lv.LineDirection.Unit
			local position = ball.Position
			local magnitude = position.Magnitude
			local rayPart:BasePart?=nil
			for i,part in workspace:GetPartBoundsInRadius(ball.Position,0.001) do
				if (ball:GetClosestPointOnSurface(ball.Position)-ball.Position).Magnitude<=0.001 then
					BasePart=part
					break
			end
		end
			local result = workspace:Raycast(position, normal*500) -- Cast ray
			if result ~= nil  then
				local reflectedNormal = normal - (2 * normal:Dot(result.Normal) * result.Normal)
				lv.LineDirection = reflectedNormal
				print("bounced!")
			elseif (ball:GetClosestPointOnSurface(ball.Position)-ball.Position).Magnitude<=0.001 then
				for i,part in workspace:GetPartBoundsInRadius(ball.Position,0.001) do
				ball.AssemblyLinearVelocity = Vector3.new(0,0,0)
				lv.LineDirection = Vector3.new(math.random(-999,999)/1000, math.random(-999,999)/1000, math.random(-999,999)/1000)
				warn("dude, thats nil.")
				end
			end
		end
	end)
	while true do
		if (math.abs(ball.Position.X) > 100) == true then
			ball.Position = blank
		end
		if (math.abs(ball.Position.Y) > 100) == true then
			ball.Position = blank
		end
		if (math.abs(ball.Position.Z) > 100) == true then
			ball.Position = blank
		end
		lv.LineVelocity = speed
		task.wait(1)
	end
end

no…
This for loop is just like a workspace:Raycast()
you should not touch it.
It sets a part that is inside the bounds
Also you removed variable making it completelly trapped inside without way of inputing value outside of scope.

Also you should remove that (in the beggining of script)

for i,part in workspace:GetPartBoundsInRadius(ball.Position,0.001) do 

It was never meant to be here in the first place.

It was meant to be used for that only:

local rayPart:BasePart?=nil
for i,part in workspace:GetPartBoundsInRadius(ball.Position,0.001) do
if (ball:GetClosestPointOnSurface(ball.Position)-ball.Position).Magnitude<=0.001 then
rayPart=part
break
end
end

DAMN DAMN DAMN

I meant rayPart=part NOT BasePart=part

Honestly, my brain is completely fried and I have no idea what to do… this is what I have now… I’ll be coming back to this tomorrow…
(also contains some other stuff in the function…)
(also, still getting nil but dont know if thats important)

local function BasicBall()
	local ball = Instance.new("Part")
	local attachment = Instance.new("Attachment", ball)
	local lv = Instance.new("LinearVelocity", attachment)
	ball.Name = "ball"
	ball.Parent = workspace
	ball.Shape = Enum.PartType.Ball
	ball.CFrame = CFrame.new(math.random(-50,50), math.random(3,20), math.random(-50,50))
	ball.CanCollide = false
	lv.Attachment0 = attachment
	lv.VelocityConstraintMode = Enum.VelocityConstraintMode.Line
	lv.LineDirection = Vector3.new(math.random(-999,999)/1000, math.random(-999,999)/1000, math.random(-999,999)/1000)
	lv.LineVelocity = speed
	ball.Touched:Connect(function()
		ball.AssemblyLinearVelocity = Vector3.new(0,0,0)
		--ball.CFrame = ball.CFrame:Lerp(CFrame.new(0,15,0),speed/10000)
		local startCFrame = ball.CFrame -- Where we derive our position & normal
		local normal = lv.LineDirection.Unit
		local position = ball.Position
		local magnitude = position.Magnitude
		local rayPart:BasePart?=nil
		local result = workspace:Raycast(position, normal*500) -- Cast ray
		if result ~= nil  then
			local reflectedNormal = normal - (2 * normal:Dot(result.Normal) * result.Normal)
			lv.LineDirection = reflectedNormal
			print("bounced!")
		else
			warn("dude, thats nil.")
			local rayPart:BasePart?=nil
			for i,part in workspace:GetPartBoundsInRadius(ball.Position,0.001) do
				if (ball:GetClosestPointOnSurface(ball.Position)-ball.Position).Magnitude<=0.001 then
					rayPart=part
					break
				end
			end
		end
end)

My current hack I’ve developed seems to work near perfect, so I’m marking this as the solution. However, I know this probably isn’t the best way to do this. It relies on the UpVector of the part the ball bounces on… :sweat_smile:

local spawnBasic = game.ReplicatedStorage.SpawnBasic
local moreSpeed = game.ReplicatedStorage.BasicSpeed
local speed = 0
local blank = Vector3.new(0,15,0)
local RunService = game:GetService("RunService")
--local create = require(game.ReplicatedStorage.Modules.BallSpawn)

local function BasicBall()
	local ball = Instance.new("Part")
	local attachment = Instance.new("Attachment", ball)
	local lv = Instance.new("LinearVelocity", attachment)
	ball.Name = "ball"
	ball.Parent = workspace
	ball.Shape = Enum.PartType.Ball
	ball.CFrame = CFrame.new(math.random(-50,50), math.random(3,20), math.random(-50,50))
	ball.CanCollide = false
	lv.Attachment0 = attachment
	lv.VelocityConstraintMode = Enum.VelocityConstraintMode.Line
	lv.LineDirection = Vector3.new(math.random(-999,999)/1000, math.random(-999,999)/1000, math.random(-999,999)/1000)
	lv.LineVelocity = speed
	ball.Touched:Connect(function()
		ball.AssemblyLinearVelocity = Vector3.new(0,0,0)
		local startCFrame = ball.CFrame -- Where we derive our position & normal
		local normal = lv.LineDirection.Unit
		local position = ball.Position
		local magnitude = position.Magnitude
		local result = workspace:Raycast(position, normal*500) -- Cast ray
		if result ~= nil then
			local reflectedNormal = normal - (2 * normal:Dot(result.Normal) * result.Normal)
			lv.LineDirection = reflectedNormal
		else -- CHANGED PART
			local swag = workspace:GetPartsInPart(ball,OverlapParams.new())
			local reflectedNormal = normal - (2 * normal:Dot(swag[1].CFrame.UpVector.Unit) * swag[1].CFrame.UpVector.Unit)
			lv.LineDirection = reflectedNormal
		end
end)

	while true do
		if (math.abs(ball.Position.X) > 100) == true then
			ball.Position = blank
		end
		if (math.abs(ball.Position.Y) > 100) == true then
			ball.Position = blank
		end
		if (math.abs(ball.Position.Z) > 100) == true then
			ball.Position = blank
		end
		lv.LineVelocity = speed
		task.wait(0.1)
	end
end

spawnBasic.OnServerEvent:Connect(BasicBall)

moreSpeed.OnServerEvent:Connect(function() -- this makes already spawned balls faster!!!!
	speed = speed + 10
	print(speed)
end)
1 Like