How do I get the objects within the camera viewport every frame?

Hi, I’m working on an interaction system similar to the one in Doors and Pressure. Basically, I want to:

  1. Be able to check every frame all the objects that are visible to the player every single frame,
  2. Check whether they’re within range, and if they are
  3. Check if they have the “Interactable” tag, and if they do then
  4. Highlight the part (if its part of a model I highlight the entire model),
    And if there are multiple interactables I get the closest one to the middle of the screen (camera is first-person) through some mathematical operations.

What I need help with is the first step: Being able to get all objects inside of the cams viewport. Any help is appreciated.

1 Like

Why does it have to be inside the viewport? Wouldn’t the effect just work if you highlight any regardless?
Anyway, I found this with a quick Google search:

1 Like

Because I want the highlight to go away whenever the object isnt on the screen anymore, its too far away or another object has been highlighted

1 Like

Hm, so what you’re saying is i have to do CollectionService:GetTagged every frame to get all the objects that are tagged with “Interactable”? Wouldn’t that affect performance?

I mean, I didn’t say that, the post I linked to did.
It’d be better to cache the result of GetTagged(). If you were doing a similar level generation system to Doors and Pressure, you’d cache the results every time you generate a new room. Make sure to also check that the object in the cache still exists, as it might have been already collected before you refresh the cache.

So basically, i do

local interactableObjects

RunService.Stepped:Connect(function()
interactableObjects = CollectionService:GetTagged(“Interactable”)
for _, object in interactableObjects do
local vector, onScreen = cam:WorldToViewportFrame(object)
– rest of my code
end
interactableObjects = nil
end)
would this achieve what im looking for? and would it affect performance?

No, you only update interactableObjects when you generate a new room.
Whatever code you use to handle new rooms spawning in, place

interactableObject = CollectionService:GetTagged("Interactable")

in there. That way, you don’t have to run that every frame. Then simply do:

RunService.Stepped:Connect(function()
    for _, object in interactableObjects do
        if not object then continue end
        – rest of my code
    end
end)

To see if the object is in the viewport, you can either use the function linked here, which is more precise but slower, or just simply see if the pivot of the model is in the viewport, which is less precise but faster:

local function OnScreen(camera, part)
    local screenPoint = camera:WorldToViewportPoint(part.CFrame.Position)
    local x, y = screenPoint.X, screenPoint.Y
    local w, h = camera.ViewportSize.X, camera.ViewportSize.Y
    
    return x < w and x > 0 and y < h and y > 0
end

I still don’t understand why you want this. I understand the other 2 conditions, but why if it’s not in view? What benefit does this have?

Sorry if i wasnt clear enough, but I’m not making a doors type of game, I just liked the way their interaction system worked.

Couldnt i just use vector, OnScreen = camera:WorldToViewPort(obj) and then get OnScreen, because :WorldToViewPort returns these 2 values? (instead of the OnScreen function you gave me)

And to answer your last question, i thought that it wouldnt work otherwise, but on second thought if im still gonna check whether i reach the object then i probably dont need to disable the highlight if its not visible

1 Like

Doors just uses proximity prompts. You can trigger the highlight by listening to ProximityPromptService.PromptShown.

1 Like

Oh, well you could still update the variable at intervals.

That’s my bad, I saw you used the wrong method name so I thought you were making up the tuple return. I should’ve read the whole section in the reference.

That would… also work and do everything you described. I completely forgot about ProximityPrompts.

Im not just making this for doors, im making an all-encompassing interaction system that will do different things based on the tags of the instance.
The thing im doing right now is to highlight the closest interactable object, if it is within reach and if its on the screen.
And I would prefer to make a custom interact button instead of using proximityprompts to allow for more customization.

I thought about that, but I would prefer making my own custom interaction buttons.
Is it possible to use proximitypropts but still have them customized (as in how it looks)?

Yes, I’m sure that’s what Doors uses. You’d still have to manually program the custom interface, though.

1 Like

So I’d have to use this code How to make a custom proximity prompt? - #3 by PersonifiedPizza
for it to work, would I just put this script inside of starterplayerscripts, and customize it to my liking?

ProximityPrompts already allow for custom UI if that is what you want. Check this out: Proximity Prompt Customizer

All you need to do is give every tagged instance a proximity prompt and this post will handle the rest for you.

2 Likes

Alright thanks, I will try that. A ton of thanks for helping me out, you two.

1 Like

An update lol, managed to do what I was trying to, basically

I connect an event to the Cameras cframe changing, which then gets runs a function to additionally check for interactables.
If it’s within reach and on the screen, I get the magnitude between middle of the screen and the :WorldToViewportPoint() position of the object.
Do some comparing and find out which one is the closest if there are multiple

What I’m thinking is i have different proximityPrompts inside of this script, that get cloned to the highlighted object based on which tag it has (for example, doors/collectibles/buttons will have different tags), and inside of it theres a script that does what its supposed to do, and get deleted when it gets dehighlighted.

Thanks again for helping me out lol

And if anyone is opening this topic for a solution, you can get the ALL objects within your camera viewport, every frame, by doing:

local RunService = game:GetService("RunService")
local cam = workspace.CurrentCamera

-- you could use RenderStepped/Stepped/Heartbeat 
RunService.RenderStepped:Connect(function()
for _, object in workspace:GetDescendants() do -- to get all descendants of the workspace
    if object.ClassName ~= "Part" then -- to check if the object is a part, 
        continue  --otherwise it might not have a position property
	end
	local vector, OnScreen = cam:WorldToViewportPoint(object.Position) 
		-- vector returns a Vector3 value vector.X is X position on screen, vector.Y is Y position on screen,
		-- vector.Z is how far away from the screen it is
		-- OnScreen is a bool value that returns true if the object is on screen and false if it is off screen
	if OnScreen then
		print(object, " is on the screen!")
	end
end
end)

Does not work on models btw, you can create a dedicated function to get the position of the thing you supply it with, although if you supply it something wrong it will result in an error (for example, an empty folder)
heres the function:

local function getPosition(thing)
	if not thing then
		print("didn't get thing position")
		return
	end
	if thing:IsA("Model") then
	    local pivot = thing:GetPivot()
		return Vector3.new(pivot.X, pivot.Y, pivot.Z)
    else
    	return thing.CFrame.Position
	end
end

and you would replace :WorldToViewportPoint(obj) with :WorldToViewportPoint(getPosition(object))

Please note that I did not use this, as it is pretty hard on performance and my use case was different.
You’re better off using a while true do loop instead of runservice, and pausing like 0.1 seconds, but if you want it for every frame this script works.
Doesn’t work with unions apparently, and invisible parts will still get counted aslong as theyre within the bounds of the screen

Read more about RunService here
Read more about the :WorldToViewportPoint() here and to find out the difference between it and :WorldToScreenpoint()

This topic was automatically closed 14 days after the last reply. New replies are no longer allowed.