How would I check if something isn't being obstructed by another part?

Hi. So I’m making an interaction system, and so far it’s going good.

There is 1 issue though: I can interact with interactable parts even if there is something in front of them, as long as they’re within reach (which I set to 8 studs).

This is a problem, as I don’t want players to be able to interact with stuff they can’t exactly “see” ( cam:WorldToViewportPoint() still returns true, as long as its within screen bounds even if its obstructed by other parts or objects which is a problem)

I’ve tried searching on the dev forum, but all the solutions that I came across didn’t work and are from years ago.

So is there any way to know if something specific is being obstructed by other objects when it’s on the screen? Any help is appreciated.

2 Likes

Have you tried using raycasting?

1 Like

Yes, and to be honest it’s really messing up the whole thing. How would I use it correctly? What I would like is a function that I pass an object to, it takes that object and checks if theres anything obstructing the view between the camera and the object itself, and if there is return false otherwise return true.

1 Like

Try raycasting towards the center of the object. If you hit anything except the object, you know that it (atleast the center) is obstructed.

If you want a somewhat more precise version, perhaps raycast to the 4 corners instead (so that it wont be considered obstructed unless all 4 are blocked)

1 Like
local rayParams = RaycastParams.new()
rayParams.FilterType = Enum.RaycastFilterType.Exclude
local function isObstructed(targetPart)
    rayParams.FilterDescendantsInstances = {game.Players.LocalPlayer.Character}
	local castResult = workspace:Raycast(workspace.CurrentCamera.CFrame.Position, (targetPart.Position-workspace.CurrentCamera.CFrame.Position)*2, rayParams)
	return not (castResult and castResult.Instance == targetPart)
end

If you want to exclude instances like the players character from hitting the ray, you should include RaycastParams

2 Likes

Yes that is what I tried to do, but it messes up and doesn’t let me interact with objects that aren’t even obstructed

1 Like

is your game first person? if it is then you will need to include the player character in the FilterDescendants table

1 Like

Yes the player is always in first person. I included all character descendants and made dedicated functions for adding or removing the descendant from a table if it is destroyed/added to the character.

1 Like

could you explain what you’re doing here and what the function is returning?

1 Like

the function is raycasting from the camera position to a target part, if there is an obstruction the function will return true, if there isnt an obstruction it will return false

i have updated the script to correctly exclude the character

1 Like

pretty sure your code would also return True if it hits the targetPart

1 Like

if the target part is hit, castResult is not nil (casts to true in the expression), and castResult.Instance == targetPart.
so not (true and true) is false

1 Like

Oh wait

yea my brain aint working

1 Like

I will try this and get back to you.

1 Like

Okay so this works fine, except now I can’t interact with models. I think it’s because the ray wouldn’t be able to hit the “Model” object itself, thus returning false on castResult.Instance == obj. Do you know how I would fix that?
Also, would this account for whenever something gets deleted or added to the character?

1 Like

When I made a system similar to this, I gave every interactable model a tag, and then when the raycast hit a part I would find the first ancestor of the hit instance which has the tag.
This does account for any changes to the character, since we are making sure the FilterDescendants is pointing to an updated reference of the character each time we raycast.

this was the function i made to find the first tagged ancestor:

local function findTaggedAncestor(instance, tag)
	local anc
	while instance.Parent and instance.Parent ~= game do
		instance = instance.Parent
		if CS:HasTag(instance, tag) then
			anc = instance
			break
		end
	end
	return anc
end

it will return nil if no ancestor has the tag

1 Like

I dont understand this line, but the rest seems fine
So what I would do is when i send the ray and it hits something, I could look if any of it’s ancestors has the tag, and if they do I return it?

if the isObstructed function returns false, you would do something like
local interactModel = findTaggedAncestor(targetPart, “Interactable”)
which would give you the interactable model or nil

also i used raycast in a heartbeat event from the player camera, in the cameras lookdirection to scan for models to interact with

1 Like

I don’t really understand, but let me clarify:
I have the “Interactable” tag on a model, a part, and a union (for testing purposes).
Right now, only the union and the part are actually working, and the model isn’t working at all.
When I run this block of code

local function isObstructed(obj)--
	print(obj)
    rayParams.FilterDescendantsInstances = {game.Players.LocalPlayer.Character}
	local castResult = workspace:Raycast(camera.CFrame.Position, (getPosition(obj)-camera.CFrame.Position)*REACH, rayParams)
	print(castResult, castResult.Instance)
	return not (castResult and castResult.Instance == obj)
end

on the model, it gives me “Model” as the obj, then RaycastResult{Part @ -118.3256, 3.93352389, -15.8457928; normal = 0, 0, 1; material = Plastic} as the castResult, and then “Part” as castResult.Instance which is a part of the model.

1 Like

in the case where interactables can be both models or a basepart you would need to add a check for that using :IsA(), but its probably better to standardise interactables as models just to keep code simple

for example, move any basepart interactable into a tagged empty model and set the primary part of the model to the basepart