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?
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:
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.