What is a good method to properly scaling UI?

I’ve been putting off trying to implement UI scaling for all devices, but I thought I knew how to do it (I was just to lazy). Now that I’ve tried it, I realized that my idea / method is not a good idea. My idea was to detect the device they’re using and using a UIScale, to scale the entire UI to a certain value (depending on the type of device they’re using). I’m now realizing this method has a few flaws.

I literally cannot think of another way, there are many topics out there about UI scaling, yet they never say how to actually do it. They say to measure depending on screen size? or resolution? or something… but they never actually say how to.

I’ve been doing some research and I cannot find a concrete method to doing so. Any ideas?

2 Likes

I just use the scale option and it looks pretty good most of the time.

2 Likes

Might I ask what your flaws were with the other idea? This is what I intended on doing so I could scale to the device, and leave out some UI elements if necessary

If keeping your UI pixel-perfect to the design matters at all, you’ll likely need to use UIScale to avoid having to mess with an unholy amount of UIAspectRatioConstraints. Often, unless you specifically need to deal with text lengths, using relative sizing (UDim2 Scale) with a couple of aspect ratio constraints is ok.

If you wish to use UIScale, here’s a Roact component I wrote recently that scales UI fairly well:

local Workspace = game:GetService("Workspace")
local GuiService = game:GetService("GuiService")

local Camera = Workspace.CurrentCamera
local TopInset, BottomInset = GuiService:GetGuiInset()

local Roact = require("Roact")

local Scale = Roact.PureComponent:extend("Scale")

function Scale:init()
	self:update()

	self.Listener = Camera:GetPropertyChangedSignal("ViewportSize"):Connect(function()
		self:update()
	end)
end

function Scale:update()
	local currentSize = self.props.Size
	local viewportSize = Camera.ViewportSize - (TopInset + BottomInset)

	self:setState({
		Scale = 1 / math.max(
			currentSize.X / viewportSize.X,
			currentSize.Y / viewportSize.Y
		)
	})
end

function Scale:willUnmount()
	self.Listener:Disconnect()
end

function Scale:render()
	return Roact.createElement("UIScale", {
		Scale = self.state.Scale * self.props.Scale
	})
end

Scale.defaultProps = {
	Scale = 1
}

return Scale

The component tries to make element as big as possible while still fitting in the screen, then multiplies that by Scale. Here’s an example of the usage:

Roact.createElement(Scale, {
	Size = Vector2.new(150, 150),
	Scale = 0.2
})

The result is an element that will always fit within the boundaries of the screen, no matter what resolution.

31 Likes

How would I use this? (local script / script, location… etc)

Normally, you’d use Roact. However, if you’re not familiar with Roact, I’ve adapted it to work with regular UI too:

local Workspace = game:GetService("Workspace")
local GuiService = game:GetService("GuiService")

local Camera = Workspace.CurrentCamera
local TopInset, BottomInset = GuiService:GetGuiInset()

local Targets = {}

local function Update()
	local size = Camera.ViewportSize - (TopInset + BottomInset)

	for target, options in pairs(Targets) do
		target.Scale = 1 / math.max(
			options.Size.X / size.X,
			options.Size.Y / size.Y
		) * options.Scale
	end
end

for _, object in pairs(script.Parent:GetDescendants()) do
	if object.ClassName == "UIScale" then
		local size = object:FindFirstChild("Size")
		local scale = object:FindFirstChild("Scale")

		if size and scale then
			Targets[object] = {
				Size = Vector2.new(size.Value.X, size.Value.Y),
				Scale = scale.Value
			}
		end
	end
end

Update()
Camera:GetPropertyChangedSignal("ViewportSize"):Connect(Update)

Place the script inside of the ScreenGui, and whenever you want to scale something automatically, create a UIScale inside of it and give it 2 children:

  • NumberValue: Scale
  • Vector3Value: Size (because there isn’t a Vector2Value)

The settings correspond to the ones noted in the first post. Ignore the Z parameter of the Vector3Value.

6 Likes

This is how I set it up, yet the UI is scaled to nan

image

What are the values on Scale and Size?