Unity UI and Godot UI look similar enough that most people try a direct translation, then spend a week fighting layouts that will not sit still. The two systems disagree about one thing: who owns a widget's position. Get that straight and the rest of the port is mechanical.
The One Difference That Causes All the Others
In Unity, a RectTransform always owns its own rect. Layout Groups nudge it, but the component is still the thing holding the numbers. In Godot the ownership flips the moment you put a Control inside a Container: the docs are blunt about it — "all children Control nodes give up their own positioning ability."
So a Godot Control is in exactly one of two modes. Either it is free, and you position it with anchors and offsets, or it is inside a Container, and the Container positions it while you only get to influence the result through size flags. Dragging a Container's child in the editor and watching it snap back is not a bug; it is the Container doing its job. If the idea of a parent owning its children is still new, the nodes and scenes guide covers the same ownership model everywhere else in the engine.
Canvas Becomes CanvasLayer, or Nothing At All
Unity requires a Canvas as the root of any UI. Godot does not. A Control node works anywhere in the tree, and for a menu scene that fills the screen you often need no wrapper at all.
CanvasLayer is what you actually want for a HUD. It draws its children on a separate layer that ignores the world camera, which is the behaviour Unity gives you with a Screen Space - Overlay Canvas. Without it, a HUD parented under a moving camera will drift with the game world.
Canvas (Screen Space - Overlay)
CanvasScaler
GraphicRaycaster
HUDPanel (RectTransform)
HealthBar (Slider)
ScoreText (TextMeshProUGUI)CanvasLayer
MarginContainer
VBoxContainer
HealthBar (ProgressBar)
ScoreLabel (Label)There is no GraphicRaycaster to add. Input picking is built into Control via mouse_filter, which defaults to letting a Control consume the events it sits under.
RectTransform Becomes Anchors Plus Offsets
Godot splits what RectTransform does into two properties, and they are easier to reason about once you stop looking for a single rect.
- Anchors are ratios from 0.0 to 1.0 describing a point in the parent. 0.0 is the left or top edge, 1.0 is the right or bottom, 0.5 is centre. They answer "relative to what?"
- Offsets are pixel distances from those anchor points. They answer "how far from it?"
- Equal anchors on an axis (both 0, or both 1) pin the control at a fixed size. Different anchors (0 and 1) make it stretch with the parent.
That last rule is the whole system. A panel with left anchor 0, right anchor 1, and offsets of 24 on both sides is a panel that keeps a 24px margin at every window width — the Unity equivalent of stretching the anchors and setting left/right to 24.
Layout Groups Become Containers
This is the most direct mapping in the whole port. Every Unity Layout Group has a Godot Container that does the same job, and a few that Unity has no answer for:
VerticalLayoutGroup→VBoxContainer,HorizontalLayoutGroup→HBoxContainerGridLayoutGroup→GridContainer(set the column count, rows follow)- Padding on a Layout Group → a
MarginContainerwrapped around the thing you want padded ScrollRect→ScrollContainer, which takes one child and adds bars when that child overflows- A background Image behind a panel →
PanelContainer, which draws a StyleBox and sizes itself to its child - Centring a thing →
CenterContainer, which keeps children centred at their minimum size - No Unity equivalent:
AspectRatioContainer,HFlowContainer(wraps to the next line when it runs out of room), andTabContainer
Containers nest, and nesting is the intended style. A pause menu is usually CenterContainer → PanelContainer → MarginContainer → VBoxContainer → buttons. That looks like more nodes than the Unity version, and it is — but none of them carry hand-tuned numbers, so none of them break at a different resolution.
Size Flags Replace Layout Element
Unity's Layout Element component lets a child override what a Layout Group wants to do with it. Godot's equivalent lives on every Control as size_flags_horizontal and size_flags_vertical:
- Fill — occupy the space the container assigned. On by default.
- Expand — claim any spare space in the container. This is the flag people forget, and it is why one button refuses to grow.
- Shrink Begin / Center / End — where the control sits inside the space it was given, when it is not filling it.
- Stretch Ratio — when several siblings all Expand, this decides how the spare space splits between them. Two children at ratio 1 and 3 divide it one quarter to three quarters.
# A row where the label takes what it needs and the bar eats the rest.
# Set in the inspector normally; shown in code to make the mapping obvious.
func _ready() -> void:
$Row/NameLabel.size_flags_horizontal = Control.SIZE_SHRINK_BEGIN
$Row/HealthBar.size_flags_horizontal = Control.SIZE_EXPAND_FILLCanvasScaler Becomes Two Project Settings
Unity solves multi-resolution per Canvas, with a CanvasScaler set to Scale With Screen Size and a reference resolution. Godot solves it once for the whole project, in Project Settings → Display → Window.
Set the base window width and height there — that is your design size, and it is the blue rectangle you see in the 2D editor. Then set Stretch Mode:
- Disabled (the default) — one unit is one pixel, nothing scales. Rarely what you want once you ship.
- Canvas Items — the base size is stretched to the screen and everything renders at the real target resolution. This is the closest match to Scale With Screen Size, and the right default for most UI-heavy games.
- Viewport — the scene renders at exactly the base size and that image is then scaled up. Correct for pixel art, wrong for crisp text.
Stretch Aspect then decides what happens when the window's shape does not match your base size: keep letterboxes, expand shows more of the scene, and ignore distorts. For a game with a HUD, expand plus anchored corners gives you the behaviour Unity users expect from anchored UI.
UI Prefabs Become Scenes You Instance
A Unity UI prefab — an inventory slot, a dialogue line, a settings row — becomes a Godot scene with a Control root, saved as its own .tscn. You instance it the same way you instance anything else, and add it to a Container rather than positioning it.
const SLOT := preload("res://ui/inventory_slot.tscn")
func rebuild_inventory(items: Array) -> void:
for child in %SlotGrid.get_children():
child.queue_free()
for item in items:
var slot := SLOT.instantiate()
slot.setup(item)
%SlotGrid.add_child(slot) # GridContainer lays it out; you set no positionNote the missing line: nothing sets a position or a size. The GridContainer handles both. If you find yourself writing slot.position = ... inside a Container, that is the earlier ownership rule reasserting itself.
Styling: One Theme Instead of Per-Component
In Unity, each Button, Image, and Text carries its own colours and sprites, and consistency is a discipline problem. Godot uses a Theme resource: one file that defines fonts, colours, and StyleBoxes per control type, assigned once near the top of the tree and inherited by everything under it.
The practical consequence for a port is that you should not translate Unity's per-widget styling at all. Build the Theme first, from the styles your Unity UI already uses, then let the ported widgets inherit it. Individual overrides exist for the genuine exceptions, but reaching for them constantly means the Theme is missing something.
A Migration Order That Works
- Set base resolution and Stretch Mode first. Doing UI work before this means measuring against a size that will change.
- Build the Theme from your existing styles, before porting a single screen.
- Port the smallest real screen end to end — a pause menu is ideal. It exercises containers, buttons, and input without touching gameplay.
- Rebuild layouts with Containers rather than translating RectTransform numbers. Reach for free-floating anchors only where a Container genuinely cannot express the layout.
- Port the HUD next, under a CanvasLayer, and test it at three window shapes before moving on.
- Leave the complicated screens — inventory grids, skill trees, anything with dynamic children — until last, when you know the container system.
If you have not mapped the rest of the project yet, do that before the UI: the Unity to Godot path covers the node and lifecycle model, and the equivalents dictionary has the lookups for everything outside the UI layer.
The Short Version
Containers own their children. Anchors are ratios, offsets are pixels, and equal anchors pin while different anchors stretch. CanvasScaler is now two project settings, and per-widget styling is now one Theme. Port a pause menu, and the rest of the interface stops being an argument.
