Limit the visible angle of BillboardGui


Hello everyone.
High-beam glare on trains was created using BillboardGui.
However, because BillboardGui is displayed from all angles, the glare is visible even from angles where the light source is not visible.(See the video.)
I know the best way to solve this is to use SurfaceGui, but SurfaceGui does not have autoscaling. (I need to make the glare appear greater the further away from the light source.)
Any tips on how to solve this?

Just raycast those lightbulbs from the client side and check if they actually they hit those from the position of camera.

Here is a little script I made.

local RunService = game:GetService("RunService")

local currentCamera = workspace.CurrentCamera -- Current camera
local billboardPart = workspace:WaitForChild("Part") -- The part which contains the BillboardGui
local billboardGui = billboardPart:WaitForChild("BillboardGui") -- The BillboardGui itself.

local visibleThreshold = 0.25 -- Higher this number, higher the visibility angle.

RunService.RenderStepped:Connect(function()
	local partLookVector = billboardPart.CFrame.LookVector -- Get the direction the part containing the BillboardGui is facing
	local camLookVector = currentCamera.CFrame.LookVector -- Get the direction the player's camera is facing
	local dotProduct = partLookVector:Dot(camLookVector) -- Get the dot product of the two look vectors. Visit this for more info: https://devforum.roblox.com/t/the-ultimate-guide-to-vector3cross-and-vector3dot/953984
	
	if dotProduct <= visibleThreshold then -- Check if the dot product is less than threshold
		billboardGui.Enabled = true -- Set enabled to true
	else
		billboardGui.Enabled = false -- Else set enabled to false
	end
end)

Here is the place file if you wanna play around:
limitVisibility.rbxl (40.4 KB)

(Make sure the light part is facing forward)

Hope this helps.

1 Like

Is the “AlwaysOnTop” property enabled on your BillboardGui? This would cause your BillboardGui to be visible through other objects

Thanks for the tip!
I’ve never used Raycast before, so I’ll start by looking up examples.

Thanks for the great samples!
I will try to see if I can control all the gui’s using the for loop.

The AlwaysOnTop property is set to “false”.
Therefore, as shown in the video, only a portion of the GUI is visible, hidden by the model.

I think I misunderstood your question. You should use TheBrainy06’s script, if it works. Raycasting is fairly simple, I’ll give a quick example:

local MyParams = RaycastParams.new()
MyParams.FilterType = Enum.RaycastingFilterType.Blacklist -- just an example, but raycast params have a lot of stuff you can play around with

local MyRay = workspace:Raycast(startPosition, (EndPosition - StartPosition), RaycastParams)

inside of the parenthesis of workspace:Raycast(), you have 3 things to fill in: the position where the ray starts, the direction the ray travels, and raycast parameters (basically like settings for your raycast). Please note the direction that the ray travels can also be a lookVector.

You can access the part which the ray hits by using MyRay.Instance, or the position where the ray hits a part by using MyRay.Position

Hopefully this provides a basic understanding of what TheBrainy06 sent

Sorry. There was an error in the text, which has been corrected.
AlwaysOnTop is “false”.
I read in another post that you can use raycast to know if a part is on camera or not, so I will try that.

local RunService = game:GetService("RunService")

local currentCamera = workspace.CurrentCamera -- Current camera

local visibleThreshold = 0.25 -- Higher this number, higher the visibility angle.

RunService.RenderStepped:Connect(function()
	
	for _, v in ipairs(workspace:GetDescendants(""))do
		local camLookVector = currentCamera.CFrame.LookVector -- Get the direction the player's camera is facing
		
		if v:IsA("BillboardGui") then continue end
		if v.Name == "Gui" then
			print("a")
			local partLookVector = v.Parent.CFrame.LookVector -- Get the direction the part containing the BillboardGui is facing
			local dotProduct = partLookVector:Dot(camLookVector) -- Get the dot product of the two look vectors.
			if dotProduct <= visibleThreshold then -- Check if the dot product is less than threshold
				v.Enabled = true -- Set enabled to true
				print("b")
			else
				v.Enabled = false -- Else set enabled to false
				print("c")
			end
		end
	end
end)

スクリーンショット 2022-09-05 09.29.23
I modified the script to control all BillboardGui named “Gui” in the Workspace, but it seems that the for loop does not work.
Anyway, thanks for all your tips!


I tried various approaches, but they did not work with my technical skills.
Finally, I dealt with this by changing the size of the SurfaceGui depending on the distance from the camera.

--StarterPlayerScript
while task.wait(0.1) do	
for _, v in ipairs(workspace:GetDescendants("")) do
	if not v:IsA("ImageLabel") then continue end
	if v.Name == "GlareTex" then
			local a = v.Parent.Parent.Position
			local cameraPos = game:GetService("Workspace").CurrentCamera.CFrame.Position
			local mag = (a - cameraPos).Magnitude
			local size = mag / 50
			if size < 5 then
				v.Size = UDim2.new(size,0,size,0)
			else
				v.Size = UDim2.new(5,0,5,0)
			end
			if size < 0.3 then
				v.ImageTransparency = 1
			else
				v.ImageTransparency = 0
			end
		end
	end
end

This is probably a poor performance approach, so I will continue to research raycasting and other methods.

1 Like

Edit this to if not v:IsA("BillboardGui") then continue end

What your code does is, it will make it so it does not run the code below if the object is a BillboardGui

Oops, I made a small mistake. :sweat_smile:
Thank you very much.

1 Like