How To Properly Use BillboardGUI?

Hi! I’m going to be needing a lot of BillboardGui elements in my game and I was hoping I could have some help with it.

To start, I’m having trouble getting the things to work how I want them to. I have a part in my game that I’ve set up in the workspace that acts exactly how I’d like it to, but when I attempt to create and place it on a part in the game it behaves differently than anticipated. Essentially, I want it to shrink as it moves away from the camera, however attempting to edit the scale of the BillboardGui doesn’t seem to be working properly, and even things like attempting to use UIScale don’t seem to be working. Here’s what I’ve tried so far.

	local BillboardGui = Instance.new("BillboardGui")
				BillboardGui.Size = UDim2.new(1,0,1,0)
				BillboardGui.MaxDistance = 32
				local Text = Instance.new("TextLabel")
				Text.BackgroundTransparency = 1
				Text.AnchorPoint = Vector2.new(0.5,0.5)
				Text.Position = UDim2.new(0.5,0,0.5,0)
				ext.FontFace = Font.new(
					"rbxasset://fonts/families/FredokaOne.json",
					Enum.FontWeight.Bold
				)
				Text.Text = <text>
				Text.TextColor3 = Color3.new(1,1,1)
				Text.TextSize = 100
				Text.TextStrokeTransparency = 0
				-- Text.TextScaled = true
				-- local uiScale = Instance.new("UIScale")
				-- uiScale.Scale = .5
				-- uiScale.Parent = Text
                -- commented because i dont think its 100% needed
				Text.Parent = BillboardGui
				BillboardGui.Parent = part

Additionally I have a few more problems I’m going to need to resolve. For one, I’m wanting these BillboardGui’s to be parented to a part that has size Vector3.new(0,0,0), and I’m going to need to create heck-of-a-lot of them in a really short amount of time; potentially thousands at once. Any idea of how to optimize this would be greatly appreciated as well.

Finally I’ve run into one more problem with these, this one being a hardware issue. I’m not sure how laggy it is on other computers, but mine is pretty old, so I’m going to need a way to focus only on ones specifically in front of the player in order to not catch my PC alight. I’m thinking of achieving this by attaching a cylinder part to the player’s head and only enabling the ones whose parent parts are clipping with the cylinder. However, being a bit new to the engine, I’m not sure what operation I should be using for this, most importantly which one would be the most efficient.
Additionally, any ideas on how to increase transparency the further away the BillboardGui is from the cylinder would be appreciated. Essentially, it should start phasing in from the side of the player’s vision and only be fully corporeal when close to the center of the player’s vision. It would also be great to have this effect apply when increasing the distance between the player and the BillboardGui. Thank you!

1 Like

Add these to the script and see if it helps:

Text.TextScaled = true
Text.Size = UDim2.fromScale(1, 1)
1 Like

You’re correctly only using the scale components of the size property of the BillboardGui, so it is indeed scaling, it’s just that the TextLabel you’ve parented to the BillboardGui has a default size of UDim2.new(0, 200, 0, 50), which is in pixels, and you’re not changing the size of the TextLabel. That’s why the TextLabel maintains its size of 200 pixels by 50 pixels regardless of distance to the camera. Do this:

Text.Size = UDim2.new(1, 0, 1, 0)

The scale components of the Size property of any UI object is based on the size of the object it’s parented to. If you use the above code, the TextLabel will maintain a scale of 100% of the BillboardGui’s size. You’ve left it at the default size of UDim2.new(0, 200, 0, 50), so it’s maintaining an on-screen size of 200 pixels by 50 pixels.

There’s a read-only property of the BillboardGui called CurrentDistance which obviously tells you the distance the Billboard is from the player’s camera. You can create a loop that runs every frame to adjust the transparency of the TextLabel based on the distance the billboard is from your camera:

local FADE_LOWER_THRESHOLD = 500
local FADE_UPPER_THRESHOLD = 750

while (BillboardGui.Parent) do
    task.wait()
    local distance = BillboardGui.CurrentDistance
    local transparency = math.clamp((distance - FADE_LOWER_THRESHOLD) / (FADE_UPPER_THRESHOLD - FADE_LOWER_THRESHOLD), 0, 1)
    Text.Transparency = transparency
end 

Essentially, the moment the camera reaches FADE_LOWER_THRESHOLD, or 500 studs away, the TextLabel will start to gain Transparency and will reach 1 at FADE_UPPER_THRESHOLD, or 750. Of course you can adjust these values.

Doing the proper scaling as well as setting TextScaled and TextWrapped to true seemed to do the trick for my first question. However, as I’ve said, I’ll potentially be having literal thousands of BillboardGuis at once, so would this script be able to run without causing much lag?

It’d probably cause a good amount of lag, I mean performing a math operation even as simple as this one thousands of times per frame is bound to be performance intensive. I’d go about finding another way to achieve what you’re trying to achieve without using thousands of BillboardGuis.

Gah, figured as much. Unfortunate since this is literally perfect for what I need, which would be to display numbers in the air and change each of their values as needed. Oh well, got any alternatives in mind?

Alright, so CurrentDistance doesn’t actually have any functionality; for some reason or another they disabled its functionality altogether, so you’d have to manually determine the BillboardGui’s position every frame to measure its distance to the camera which is fairly straightforward.

I ran some tests, and unsurprisingly, having several thousand BillboardGuis(I had 3000+) rendering all at once causes quite a lot of lag, especially when the camera moves or the parts the BillboardGuis are parented to move. Even when they all are fully transparent, they still cause some lag, so I added a line of code that disables a BillboardGui if it is fully transparent, but still, running thousands of these calculations every single frame causes quite a bit of lag. Here’s the code I used:

local FADE_LOWER_THRESHOLD = 50
local FADE_UPPER_THRESHOLD = 200

local camera = workspace.CurrentCamera
local billboardParts = workspace.BillboardParts:GetChildren()

game:GetService("RunService").Stepped:Connect(function()
	for _, part: Part in billboardParts do
		local billboard: BillboardGui = part.BillboardGui
		local cameraCf = camera.CFrame
		local guiPosition: Vector3 = CFrame.new(part.Position + billboard.StudsOffsetWorldSpace) * cameraCf.Rotation * CFrame.new(billboard.StudsOffset).Position
		local distance = (guiPosition - cameraCf.Position).Magnitude
		local transparency = math.clamp((distance - FADE_LOWER_THRESHOLD) / (FADE_UPPER_THRESHOLD - FADE_LOWER_THRESHOLD), 0, 1)
		billboard.Enabled = transparency ~= 1
		
		local label = billboard.TextLabel
		label.TextTransparency = transparency
		label.BackgroundTransparency = transparency
	end
end)

Even without the code, rendering all of the BillboardGuis causes a lot of lag, so this is really not a viable option for you. I can’t really think of any alternative that would be more efficient, at least not off the top of my head. If you dig for long enough, you’ll probably find someone who’s had a similar problem before and will be able to figure something out.

Hmm… maybe instead of facing the player the entire time, the text instead could be rotating in place? Might be less resource intensive, although I’m not sure how I’d go about setting it up.

I ran some more tests and it seems using physical objects over BillboardGuis or SurfaceGuis is better, but it’s still pretty laggy. It also is actually faster to unanchor the parts and use AlignPosition and AngularVelocity objects to keep the parts in place and rotate them than it is to anchor them and use a script to update their orientation every frame. I used 3x3 cubes to test this out, but there’s only 6 polygons on a cube, and if you want physically represent each number, you’re going to have multiple meshes with way more than just 6 polygons. And I didn’t even test this with the fading behavior you wanted, so I honestly don’t really think something like this is that feasible, but then again, I haven’t delved too much into this problem.

I think I’ve got something figured out, but as a supporting yet seemingly unrelated question, is it possible to have a SurfaceGui render from both sides instead of just the front? My immediate thought is no (in fact this may be the first time that anyone, sane or otherwise, has ever had a need to ask this question, or have ever even thought to ask this question) but I’m hoping there’s the small chance that it’s possible before I resort to having to use 2 of them at once.

Ignore the last question, here’s something I quickly scraped together and it actually works as a fairly good alternative to using BillboardGui. Instead of doing an update each frame to make the cube rotate I’ve opted for using TweenService to make it rotate for an arbitrary amount of time, which I’ll just make incredibly long. Of course, there’s still a problem of efficiency, in that even though I’m not going to be using a custom script to update the rotation every frame, it will still be laggy as I’ll have to spawn in potential thousands of these at a single time. Is there any way to reduce the size of the script to make one of these in any way? Every possible optimization would go a long way.

Here’s the file that spawns it in:
floatingnumbertest.rbxl (55.1 KB)

Actually, surprisingly enough, spawning all these in is actually pretty okay, as I’ve just ran that block about 4k times and there wasn’t much of a noticeable increase in freeze time. Of course, rendering 8k at once unsurprisingly causes ridiculous lag, but that I’ve got a solution for I think. Only optimization I can think of that would be of great help would be whether or not I can make these double sided so that I don’t have to have double the amount I’d prefer at any given time.

Unfortunately I don’t think there’s a way to double-side SurfaceGuis but it does actually give you a noticeable boost in performance if you unanchor the parts and use physics constraints to hold them in place and rotate them in place as opposed to anchoring them and using tweens or updating their CFrame every frame.

Unanchored and using AlignPosition and AngularVelocity constraints:

Anchored and using tweens:

Anchored and using frame-by-frame CFrame updates:

I noticed having the part at the smallest possible size made the AngularVelocity constraint behave strangely so I had to resize it to 6, 6, 0.001 and adjust some of the properties of the SurfaceGui and TextLabel.

Here’s the placefile if you want to mess with it:
floatingnumbertest.rbxl (62.4 KB)

Woah, cool! Unfortunate that you can’t double-side a SurfaceGui though, not sure how much of an improvement it would be but it would still help. Anyway, your solution of unanchoring the parts seems very promising, and I can hope I can use it in my game, although I’m not completely sure how feasible it would be to do so. Regardless, thanks for the assistance!

1 Like