Make UIStroke Responsive on All Screen Size

Hi
UIStroke is now live but the Thickness property accepts a number instead of UDim and with that it’s rather difficult to make it responsive.
If you apply a UIStroke to a button and set the Thickness to 5 this is how it will look on a 1920x1080 resolution screen:
image
while it looks like this on the 667x375 screen
image
finally, this is how it looks like in studio
image

pretty big difference, we can make all of them look like the one in studio with some code though.


I’ve decided to include 2 methods - the first one is slightly less performant but automated and doesn’t need anything after you add the script, while the second one requires adding a tag to each UIStroke instance you ever use

Method 1

for this method you just need to get the resolution of your current screen in studio, so we can calculate the exact thickness needed for other resolutions and make them look like the one in studio.

execute this in the command bar and check the output to get the resolution:

print(workspace.CurrentCamera.ViewportSize)

now add a LocalScript inside StarterPlayerScripts, paste the code, and make sure to change the STUDIO_SCREEN_SIZE variable on the first line to the resolution that you got from the output.

Code
local STUDIO_SCREEN_SIZE = Vector2.new(0, 0)  -- change 0, 0 to your studio resolution
local camera = workspace.CurrentCamera

local player = game:GetService('Players').LocalPlayer
local playerGui = player.PlayerGui


local function GetAverage(vector: Vector2): number
	return (vector.X + vector.Y) / 2
end


local studioAverage = GetAverage(STUDIO_SCREEN_SIZE)
local currentScreenAverage = GetAverage(camera.ViewportSize)


local function AdjustThickness(ui: UIStroke)
	local ratio = ui.Thickness / studioAverage
	ui.Thickness = currentScreenAverage * ratio
end


local function ModifyUiStrokes()
	local function ChangeGuiObject(instance: Instance)
		if instance:IsA("UIStroke") then
			AdjustThickness(instance)
		end
	end
	
	for _, v in ipairs(playerGui:GetDescendants()) do
		ChangeGuiObject(v)
	end
	playerGui.DescendantAdded:Connect(ChangeGuiObject)
end


ModifyUiStrokes()

Method 2

firstly, we’ll need to tag all UIStroke instances so we won’t have to loop through the descendants later every time a UIStroke is added or the window gets resized.

execute this in the command bar to add a tag to each UIStroke instance in your StarterGui

for _, v in ipairs(game.StarterGui:GetDescendants()) do if v:IsA("UIStroke") then game:GetService('CollectionService'):AddTag(v, 'UIStroke') end end

make sure you tag any other UIStroke instances you add, you can use the Tag Editor plugin as well.


after that, you need to get the resolution of your current screen in studio, so we can calculate the exact thickness needed for other resolutions and make them look like the one in studio.

execute this in the command bar and check the output to get the resolution:

print(workspace.CurrentCamera.ViewportSize)

finally, add a LocalScript inside StarterPlayerScripts, paste the code, and make sure to change the STUDIO_SCREEN_SIZE variable on line 3 to the resolution that you got from the output.

Code
local CollectionService = game:GetService('CollectionService')

local STUDIO_SCREEN_SIZE = Vector2.new(0, 0)  -- change 0, 0 to your studio resolution
local camera = workspace.CurrentCamera
local instanceAddedSignal = nil  -- stores a connection


local function GetAverage(vector: Vector2): number
	return (vector.X + vector.Y) / 2
end


local studioAverage = GetAverage(STUDIO_SCREEN_SIZE)
local currentScreenAverage = GetAverage(camera.ViewportSize)


local function AdjustThickness(ui: UIStroke)
	local ratio = ui.Thickness / studioAverage
	ui.Thickness = currentScreenAverage * ratio
end


local function ModifyUiStrokes()
	currentScreenAverage = GetAverage(camera.ViewportSize)  -- re-calculate the screen average as it could've changed
	
	for _, ui: UIStroke in ipairs(CollectionService:GetTagged('UIStroke')) do
		AdjustThickness(ui)
	end
	
	if instanceAddedSignal then
		instanceAddedSignal:Disconnect()
	end
	instanceAddedSignal = CollectionService:GetInstanceAddedSignal('UIStroke'):Connect(AdjustThickness)
end


ModifyUiStrokes()
camera:GetPropertyChangedSignal('ViewportSize'):Connect(ModifyUiStrokes)  -- this will fire every time a window size changes

after that, you can use the device emulator in the studio to confirm that they all look the same

mobile

xbox

19 Likes

A reminder that the thickness property of UI stroke is pretty performance heavy and that you probably shouldn’t be changing it everytime the ViewPort size changes.

Oh did they mention that somewhere?

They mentioned it in the original post about UIStroke, they say not to tween the property as it is incredibly performance heavy to edit the property.

(Look at animation and performance)

2 Likes

On the dev hub page

https://developer.roblox.com/en-us/articles/applying-strokes

1 Like

Thanks for the heads-up, tweening is definitely not a good idea since it changes the value many times in a short amount of time, I only adjust it once after the window size changes though (which shouldn’t even happen that often). I’ll still check the performance and comment out the last line if I see some impact.

1 Like

Dragging the window size updates the property multiple hundred times.

yes I’ll defer the call if necessary. performance issue only applies to UIStrokes used on texts though which is a bit better.

1 Like

updated the code and added another method.

report any bugs in my DMs or in this topic’s replies

Hello, I just came across this thread while playing around with the new UIStroke feature and I was able to notice a flaw in your code, specifically in Method 2.

If you check line 18, it can be seen that the ratio is taken from the CURRENT UIStroke’s thickness divided by the studioAverage. Because of making use of the CURRENT UIStroke’s thickness, when you change the window size multiple times, the ratio will not be constant (which is supposed to be) for each UIStroke Instance. This results in the border getting too thick or too thin in the event of changing window sizes multiple times.

SOLUTION
After completing the steps in method 2, I added an attribute for every UIStroke called “origThickness” and set its value to the original thickness of the UIStroke. You can do this by entering this line of code in the command bar:

for _, uiStroke in pairs(game:GetService("CollectionService"):GetTagged("UIStroke")) do
	uiStroke:SetAttribute("origThickness", uiStroke.Thickness)
end

Then, I modified line 18 of the code in method 2:

local ratio = ui:GetAttribute("origThickness") / studioAverage

Hope this helps!

1 Like