CrossPlatform Design Roblox Creator Documentation

Roblox is inherently cross-platform as users can discover and join experiences on a PC, then later pick up their phone and continue where they left off. You should design your Roblox experiences to be accessible and enjoyable on all platforms that you choose to support, instead of optimizing for one platform and neglecting others.

UI Layout
Just because a UI fits perfectly on a PC screen doesn’t mean it’s as functional on smaller mobile screens. For example, the color customization tiles in this UI become small and cramped on a phone:

Color customization tiles positioned in two rows on bottom of UI modal

Tiles small and difficult to tap on phones and mini tablets

In contrast, a slight redesign of the UI menu offers a better user experience on both PC and phone:

Color customization tiles positioned on right side of UI modal

Tiles larger and easier to tap on phones and mini tablets

Reserved Zones
On mobile devices, the default controls occupy a portion of the bottom-left and bottom-right corners of the screen. When you design an experience’s UI, avoid placing important info or virtual buttons in these zones.

If your experience uses the default control setting of DevTouchMovementMode.UserChoice, users on mobile devices will be able to choose their input from Dynamic Thumbstick, Classic Thumbstick, or Tap to Move, causing the the on-screen controls and reserved zones to vary slightly. Remember to design your UI around this possibility.

Thumb Zones
Most mobile users use two thumbs — one on the virtual thumbstick and one on the jump button. Depending on the physical size of the device and the user’s hands, “reaching” too far from the bottom corners becomes uncomfortable or impossible, so you should avoid placing frequently-used buttons outside of easy-to-reach zones.

Button comfortably within reach of user’s right thumb

Button difficult to reach unless user stretches hand or thumb

Remember that comfortable thumb zones differ between phones and tablets because tablets have a larger screen. A button placed 30% below the screen’s top edge is reachable on a phone but almost unreachable on a tablet, so you should consider relational position over percentage-based.

Button 30% from top edge of phone, within reach of user’s thumb

Button 30% from top edge of tablet, difficult to reach without stretching

Context-Based UI
Screen space is limited on mobile devices, so you should show only the most vital information during active gameplay. For example, if your experience includes a special input action to open doors and treasure chests, it doesn’t make sense to constantly show an “Open” button on the screen. Instead, use a proximity prompt or similar method to accept input only when the character approaches a door or chest.

Custom button that you display only when character is near chest

Adaptable UI
A cross-platform UI layout should be adaptable across all devices, not just between PC, mobile, and console but also between phones and tablets. You can achieve this through relational positioning and input type detection.

Positioning in Relation
A reliable approach for adaptable UI on both phones and tablets is to position custom buttons near frequently-used controls like the default jump button, placing them within easy reach of the user’s right thumb.

The following code, placed in a LocalScript within StarterPlayerScripts, fetches the position of the jump button and creates a placeholder ImageButton above it.

1local Players = game:GetService(“Players”) Get reference to player’s jump button
4local player = Players.LocalPlayer
5local PlayerGui = player:WaitForChild(“PlayerGui”)
6local ScreenGui = PlayerGui:WaitForChild(“ScreenGui”)
7local TouchGui = PlayerGui:WaitForChild(“TouchGui”)
8local TouchControlFrame = TouchGui:WaitForChild(“TouchControlFrame”)
9local JumpButton = TouchControlFrame:WaitForChild(“JumpButton”) Get absolute size and position of button
12local absSizeX, absSizeY = JumpButton.AbsoluteSize.X, JumpButton.AbsoluteSize.Y
13local absPositionX, absPositionY = JumpButton.AbsolutePosition.X, JumpButton.AbsolutePosition.Y Create new button above jump button
16local customButton = Instance.new(“ImageButton”)
17customButton.Parent = ScreenGui
18customButton.AnchorPoint = Vector2.new(0.5, 1)
19customButton.Size = UDim2.new(0, absSizeX * 0.8, 0, absSizeY * 0.8)
20customButton.Position = UDim2.new(0, absPositionX + (absSizeX / 2), 0, absPositionY – 20)
Adjusting by Input Type
Another approach for adaptable UI is to adjust your layout based on which input type the user is using, for example keyboard/mouse, touch, or gamepad.

The following ModuleScript determines the user’s input type on join and detects changes to input type during gameplay. From a LocalScript that requires the module, you can detect the user’s input type at any time and/or connect to the module’s BindableEvent to detect input type changes. Upon detection, you can reposition UI elements to better accomodate the current input.

1local UserInputService = game:GetService(“UserInputService”)
5PlayerInput.InputTypes = {
6 KEYBOARD_MOUSE = “keyboard/mouse”,
11PlayerInput.CurrentInput = nil
12PlayerInput.InputTypeChanged = Instance.new(“BindableEvent”)
14local function getInputType(userInputType)
15 if userInputType == Enum.UserInputType.MouseButton1 or userInputType == Enum.UserInputType.MouseButton2 or userInputType == Enum.UserInputType.MouseButton3 or userInputType == Enum.UserInputType.MouseWheel or userInputType == Enum.UserInputType.MouseMovement or userInputType == Enum.UserInputType.Keyboard then return PlayerInput.InputTypes.KEYBOARD_MOUSE
24 elseif userInputType == Enum.UserInputType.Touch or userInputType == Enum.UserInputType.Accelerometer or userInputType == Enum.UserInputType.Gyro then return PlayerInput.InputTypes.TOUCH
30 elseif userInputType == Enum.UserInputType.Gamepad1 or userInputType == Enum.UserInputType.Gamepad2 or userInputType == Enum.UserInputType.Gamepad3 or userInputType == Enum.UserInputType.Gamepad4 or userInputType == Enum.UserInputType.Gamepad5 or userInputType == Enum.UserInputType.Gamepad6 or userInputType == Enum.UserInputType.Gamepad7 or userInputType == Enum.UserInputType.Gamepad8 then return PlayerInput.InputTypes.GAMEPAD
45local function setPlayerInputType(userInputType)
46 local playerInputType = getInputType(userInputType)
47 if playerInputType and playerInputType ~= PlayerInput.CurrentInput then PlayerInput.CurrentInput = playerInputType PlayerInput.InputTypeChanged:Fire(playerInputType) Initially set the player’s input type
54setPlayerInputType(UserInputService:GetLastInputType()) Update current input type based on last input type received
57UserInputService.LastInputTypeChanged:Connect(setPlayerInputType)
1local ReplicatedStorage = game:GetService(“ReplicatedStorage”)
2local PlayerInput = require(ReplicatedStorage:WaitForChild(“PlayerInput”)) Check the player’s input type at any time by reading PlayerInput.CurrentInput
5print(“Player is using”, PlayerInput.CurrentInput) Listen for input type changes with PlayerInput.InputTypeChanged event
8PlayerInput.InputTypeChanged.Event:Connect(function(newInputType)
9 print(“Input changed to”, newInputType)}