Game Dev Artisan Logo
Using Control and Container Nodes, we can layout our User Interface to meet the needs of our games, by presenting information clearly to our users. We can use these nodes to give us ultimate control over how we structure our views, and compose scenes together.
Tutorials

Godot Container Nodes - Deep Dive

Author Avatar
2024-04-17
23 min read

Table of Contents

Introduction 

In our last post we discussed Control Nodes and took a look at the ability to position and size them using the Anchor system. We'll be looking at the Container Nodes and the various types that arrange the children nodes and dictate their sizing and position based on a set of rules.

Using Control and Container Nodes, we can layout our User Interface to meet the needs of our games, by presenting information clearly to our users. We can use these nodes to give us ultimate control over how we structure our views, and compose scenes together.

For example, we may want to present our characters stats; a hot bar for our active items, alerts for things such as leveling up, and prompts for dialogue and other information.

control-nodes-scene

These elements can be broken up into components that are composed of Control and Container Nodes, which do most of the heavy lifting.

Video 

Containers 

Containers on the Godot's Official Documentation. 

Container extend the Control class, which means they also have the ability to position and size using the Anchors system, which we covered previously.

When using a Container node you have more capability to layout your children nodes based on a set of rules, based on which Container node you choose.

All of the child Control nodes within a Container will have their size and positions automatically updated, which means manually setting the position and size of those children may cause them to be changed when the parent Container is updated. This behavior allows children to rely on their sizing and positions updating whenever the parent resizes.

This is where the use of a Control's Sizing Options comes into play. Whenever a Control is a child of a Container node, it will have the ability to define it's own Container Sizing Options. These are defined per axis (horizontal and vertical), and their behaviors are implemented per Container type.

  • FILL
    • Forces the Control to fill the entire area within the container. This is on by default.
  • EXPAND
    • Expand along the axis to take up as much space is available by the parent node. This will push non expanding siblings away. Expanding Control nodes will determine the amount of space they consume based on the Stretch Ratio. This option is only available with certain Container Types
  • SHRINK_BEGIN
    • Positions itself along the start of the axis, while taking as little space in that direction.
  • SHRINK_CENTER
    • Positions itself along the center of the axis, while taking as little space in that direction.
  • SHRINK_END
    • Positions itself along the end of the axis, while taking as little space in that direction.

The Stretch Ratio determines how much expanded Control nodes will take up relative to one another. The percentage this node will stretch is a ratio of ratio / sum of expanded ratios. For example a HboxContainer with 3 children each with a ratio of 1 will take up equal amount of space 1 / 3, but if the first element has a ratio of 2 it will take up 2x the space as the others, or 2 / 4. If we instead set the first element to a ratio of .5 it would take up half of the space as the others, or .5 / 2.5.

Let's discuss each container type and the way they arrange the children within them.

You can also check out Godot's tutorial on UI and Containers over on their manual. 

Panel Container 

Panel Containers on the Godot's Official Documentation. 

The PanelContainer keeps of the children Control nodes within the area of the defined StyleBox. The Control's mouse_filter property is also automatically set to MouseFilter.MOUSE_FILTER_STOP by default. This allows the PanelContainer to receive input events and respond to them accordingly.

To modify the style of the PanelContainer you can override the Theme's panel property to a custom StyleBox of your own design.

Margin Container 

Margin Containers on the Godot's Official Documentation. 

The MarginContainer keeps all children within the bounds of the Container and allows you to customize the additional margin along each edge.

These margins are controlled via the margin_top, margin_left, margin_bottom, and margin_right properties from the Theme. You can override these individually to determine how much of a margin to add for that specific direction. These are each set to 0 by default.

Aspect Ratio Container 

Aspect Ratio Containers on the Godot's Official Documentation. 

The Aspect Ratio Container controls the child Control nodes to preserve the proportions whenever it is resized and ensures the aspect ratio is retained.

It can have it's alignment set per axis (alignment_horizontal, alignment_vertical) with a corresponding AlignmentMode:

  • ALIGNMENT_BEGIN
    • Aligns children to the start of the axis.
  • ALIGNMENT_CENTER
    • Aligns children to the center of the axis.
  • ALIGNMENT_END
    • Aligns children to the end of the axis.

You can control the aspect ratio with the ratio property. This ratio works with the stretch_mode and is width / height.

The various stretch modes control how to size the children using the stretch_mode property.

  • STRETCH_WIDTH_CONTROLS_HEIGHT
    • Children height is adjusted based on the container's width.
  • STRETCH_WIDTH_CONTROLS_WIDTH
    • Children width is adjusted based on the container's height.
  • STRETCH_FIT
    • Children bounds are adjusted to fit inside the container while retaining the aspect ratio.
  • STRETCH_COVER
    • Children width and height are adjusted to cover the container while retaining the aspect ratio.

Box Container 

Box Containers on the Godot's Official Documentation. 

The Box Container types will arrange the children Control nodes along the axis they are based on. These are popular for creating a stack or list of elements that you can control the spacing and direction of.

You can set the alignment mode for the given axis with AlignmentMode:

  • ALIGNMENT_BEGIN
    • Aligns children to the start of the axis.
  • ALIGNMENT_CENTER
    • Aligns children to the center of the axis.
  • ALIGNMENT_END
    • Aligns children to the end of the axis.`

The vertical bool property of a box container determines if the children should flow along the vertical axis.

The add_spacer(begin: bool) method adds a spacer Control child element and accepts a boolean parameter to determine if it should add the spacer before all the children nodes.

For Theme Properties you can override the separation which determines the space between in elements, in pixels. This is 4 by default.

HBox Container 

HBox Containers on the Godot's Official Documentation. 

The HBoxContainer node extends the BoxContainer and will arrange the children along the horizontal axis, and expands the children to fill the vertical space of the Container. This set's the vertical property to false and cannot be modified.

VBoxContainer 

VBox Containers on the Godot's Official Documentation. 

The VBoxContainer node extends the BoxContainer and will arrange the children along the vertical axis, and expands the children to fill the horizontal space of the Container. This set's the vertical property to true and cannot be modified.

Center Container 

Center Containers on the Godot's Official Documentation. 

The CenterContainer will keep all of it's children nodes at it's center using their minimum size. This ensures all children are always aligned to the center, without requiring manually setting their positions.

You can set the use_top_left property to center the children relative to the top left corner of this Container. This is false by default.

Flow Container 

Flow Containers on the Godot's Official Documentation. 

The FlowContainer will arrange all of the children along the respective axis. When it runs out of space along that axis, it will wrap the children to the next line or column (depending on the axis).

Similar to the BoxContainer you can set the alignment mode for the given axis with AlignmentMode:

  • ALIGNMENT_BEGIN
    • Aligns children to the start of the axis.
  • ALIGNMENT_CENTER
    • Aligns children to the center of the axis.
  • ALIGNMENT_END
    • Aligns children to the end of the axis

The vertical bool property of the Container determines if the children should flow along the vertical axis.

The get_line_count() method will return number of lines this Container has for the children based on how many times it's wrapped around the axis bounds.

The Theme Properties h_separation and v_separation can overridden to determine the amount of separation along the axis for the children nodes.

HFlow Container 

HFlow Containers on the Godot's Official Documentation. 

The HFlowContainer node extends the FlowContainer and will arrange children along the horizontal axis. This will wrap children to a new line if the width of the bounds is exceeded by a child element. This will set the vertical property to false and cannot be modified.

VFlow Container 

VFlow Containers on the Godot's Official Documentation. 

The VFlowContainer node extends the FlowContainer and will arrange children along the vertical axis. This will wrap children to a new column if the height of the bounds is exceeded by a child element. This will set the vertical property to true and cannot be modified.

Grid Container 

Grid Containers on the Godot's Official Documentation. 

A GridContainer node arranges it's children along a grid layout. The number of columns can be defined and has a default of 1.

The number of rows within the grid will be determined based on the number of children within the Container. This can be calculated as floor(count of children / columns).

The Theme Properties h_separation and v_separation can overridden to determine the amount of separation along the axis for the children nodes.

Scroll Container 

Scroll Containers on the Godot's Official Documentation. 

The ScrollContainer should only contain a single Control child. When the child is larger than the Container, it will add scroll bars around the node to allow scrolling along the desired axis. Scroll bars will be added based on the Control.custom_minimum_size for the child within the Container.

The Control.clip_contents property will clip the content of the child if it's size exceeds the Container bounds. This is set to true by default.

The follow_focus property will automatically scroll to focused children to make them fully visible. This is set to false by default.

The scroll mode can be defined for each axis using horizontal_scroll_mode and vertical_scroll_mode. ScrollMode

  • SCROLL_MODE_DISABLED
    • Scrolling will be disabled for this axis
  • SCROLL_MODE_AUTO
    • Scrolling is enabled, the scrollbar will only be shown if needed.
  • SCROLL_MODE_SHOW_ALWAYS
    • Scrolling is enabled, the scrollbar is always shown.
  • SCROLL_MODE_SHOW_NEVER
    • Scrolling is enabled, the scrollbar is always hidden.

The scroll_deadzone property controls the deadzone for touch scrolling. A lower deadzone results in more sensitive scrolling.

The scroll_horizontal and scroll_vertical properties allows you to control how far into scrolling you are.

NOTE: Setting this within _ready will require a deferred call as the scrollbar's Range.max_value has not been initialized yet.

func _ready():
    set_deferred("scroll_horizontal", 600)

The scroll_horizontal_custom_step and scroll_vertical_custom_step properties control the ScrollBar.custom_step property for when clicking the internal scrollbar's increment and decrement buttons, or when using arrow keys to control the ScrollBar.

The ensure_control_visible(Control: control) method allows you to ensure the given control is visible. The control must be a direct or indirect child of the ScrollContainer, as it is used by the follow_focus.

NOTE: If the node was just added during the same frame, you must wait until the next frame using the SceneTree.process_frame.

add_child(child_node)
await get_tree().process_frame
ensure_control_visible(child_node)

The get_h_scroll_bar() and get_v_scroll_bar() methods will return the respective HScrollBar and VScrollBar for this ScrollContainer.

WARNING: The ScrollBar are internal and required, if you attempt to remove or free them, it may cause a crash. If you want to disable or hide a scrollbar, you can use horizontal_scroll_mode.

The Theme Properties panel may be overridden to give the ScrollContainer's background some custom styling with a StyleBox.

Split Container 

Split Containers on the Godot's Official Documentation. 

The SplitContainer takes two child nodes and places them side by side with a dragger. This respects both horizontal and vertical flags, as well as ratio. You can drag the dragger to change the size ratio between each child.

The dragged signal is emitted when the dragger is dragged by the user.

The collapsed property controls if the first Control will be collapsed and the dragger disabled.

The dragger_visibility property determines the dragger's visibility based on the DraggerVisbility enumerator:

  • DRAGGER_VISIBLE
    • The split dragger is visible when the cursor hovers it.
  • DRAGGER_HIDDEN
    • The split dragger is never visible.
  • DRAGGER_HIDDEN_COLLAPSED
    • The split dragger is never visible and it's space is collapsed.

The split_offset property is the initial offset of the splitting between the two Control nodes. with 0 being at the end of the first Control.

The vertical property determines if the SplitContainer will arrange it's children vertically, rather than horizontally.

The clamp_split_offset() method will clamp the split_offset value to not go outside the currently possible minimal and maximal values.

The Theme Properties you can override are:

  • autohide
    • If true the grabber will hide automatically when it isn't under the cursor. If false, it's always visible.
  • minimum_grab_thickness
    • The minimum thickness of the areas users can click on to grab the splitting line. If separation or h_grabber/v_grabber thickness are too small, this ensures that the splitting line can still be dragged.
  • separation
    • The space between sides of the Container
  • grabber
    • Texture2D Icon used for the grabber drawn in the middle area.
  • h_grabber
    • Texture2D Icon used for the grabber drawn in the middle area when vertical is false
  • v_grabber
    • Texture2D Icon used for the grabber drawn in the middle area when vertical is true

HSplit Container 

HSplit Containers on the Godot's Official Documentation. 

The HSplitContainer node extends the SplitContainer and will split two child Control nodes along the horizontal axis, and provides a grabber for adjusting the split ratio. This set's the vertical property to false and cannot be modified.

VSplit Container 

VSplit Containers on the Godot's Official Documentation. 

The VSplitContainer node extends the SplitContainer and will split two child Control nodes along the vertical axis, and provides a grabber for adjusting the split ratio. This set's the vertical property to true and cannot be modified.

SubViewport Container 

SubViewport Containers on the Godot's Official Documentation. 

The SubViewportContainer is a special Control that will only accept a single SubViewport node as a child, and displays it as if it was an image. It's size is based on the SubViewport size unless it's stretch is enabled.

Note: Changing a SubViewportContainer's Control.scale will cause its contents to appear distorted. To change its visual size without causing distortion, adjust the node's margins instead (if it's not already in a container).

Note: The SubViewportContainer forwards mouse-enter and mouse-exit notifications to its sub-viewports.

The focus_mode property overrides the Control's focus mode and defaults it to 1.

The stretch property allows the sub-viewport to automatically resize to the Control's size.

The stretch_shrink property Dives the sub-viewport's effective resolution by this value while preserving its scale. This can be used to speed up rendering. stretch must be true for this property to work.

The _propagate_input_event(event: InputEvent) method is a virtual method that may implemented. If it returns true, the event is propagated to SubViewport children. Propagation doesn't happen if it returns false. If the function is not implemented, all events are propagated to SubViewports

Tab Container 

Tab Containers on the Godot's Official Documentation. 

The TabContainer allows multiple children Control nodes to be stacked on top of one another. Only a single Control will be visible at a time.

It will create a tab for each child at the top of the TabContainer, these tabs have their titles generated via the node's name, but can be modified using the set_tab_title(tab_idx: int, title: String) method.

The all_tabs_in_front property determines if the tabs should be drawn in front of the panel. Otherwise they are drawn behind the panel.

The clip_tabs property will take the overflowing tabs and hide them in favor of navigation buttons. Otherwise it will adjust the minimum size of the container to show all tabs.

The current_tab property returns the currently visible tab's index. Setting this property will set all other children Control nodes to visible of false and this index's Control node will have it's visible set to true.

The drag_to_rearrange_enabled property will enable the ability to drag the tabs with your mouse.

The tab_alignment property will determine the position that tabs will be placed, using the TabBar.AlignmentMode:

  • ALIGNMENT_LEFT
    • Places tabs to the left.
  • ALIGNMENT_CENTER
    • Places tabs in the middle.
  • ALIGNMENT_RIGHT
    • Places tabs to the right.
  • ALIGNMENT_MAX
    • Represents the size of the AlignmentMode enumerator.

The tab_focus_mode property controls the focus for the TabBar using the FocusMode:

  • FOCUS_NONE
    • The TabBar cannot grab focus.
  • FOCUS_CLICK
    • The TabBar can only grab focus on mouse clicks.
  • FOCUS_ALL
    • The TabBar can grab focus on mouse click, using the arrows and the Tab keys on the keyboard, or using the D-pad buttons on a gamepad.

The tabs_rearrange_group property determines which group id the TabContainer tabs can be moved within. Other TabContainer's with the same group id can freely drag tabs between them, though drag_to_rearrange_enabled must be true. The default of -1 means rearranging between TabContainers is disabled.

The tabs_visible property determines if a tab and content will be visible.

The use_hidden_tabs_for_min_size property enables hidden tabs to factor their minimum size affect the total and not only the currently visible tab.

The TabContainer has several methods:

  • get_current_tab_control()
    • Returns the child Control at the current active tab index.
  • get_popup()
    • Returns the Popup node instance if one has been set using the set_popup method.
  • get_previous_tab()
    • Returns the previously active tab index.
  • get_tab_bar()
    • Returns the TabBar contained in this Container.
  • get_tab_button_icon(tab_idx: int)
    • Returns the button icon from the tab at index.
  • get_tab_control(tab_idx: int)
    • Returns the Control node from the tab at index.
  • get_tab_count()
    • Returns the number of tabs.
  • get_tab_icon(tab_idx: int)
    • Return the Texture2D for the tab at index. Will return null if the tab has no Texture2D
  • get_tab_idx_at_point(point: Vector2)
    • Returns the index of the tab at local coordinates point. Returns -1 if the point is outside the Control boundaries or if there's no tab at the point.
  • get_tab_idx_from_control(control: Control)
    • Returns the index of the tab tied tot he given control. The Control must be a child of the TabContainer.
  • get_tab_metadata(tab_idx: int)
    • Returns the metadata value set to the tab at index using the set_tab_metadata method. If no metadata was previously set, will return null.
  • get_tab_title(tab_idx: int)
    • Returns the title of the tab at index. Tab titles default to the name of the indexed child node, but this can be overridden with the set_tab_title method.
  • is_tab_disabled(tab_idx: int)
    • Returns true if the tab at index is disabled.
  • is_tab_hidden(tab_idx: int)
    • Returns true if the tab at index is hidden.
  • select_next_available()
    • Selects the first available tab with greater index than the currently selected. Returns true if tab selection changed.
  • select_previous_available()
    • Selects the first available tab with lower index than the currently selected. Returns true if tab selection changed.
  • set_popup(popup: Node)
    • If set on a Popup node instance, a popup menu icon appears in the top-right corner of the TabContainer. If set to null it will make it go away. Clicking it will expand the Popup node.
  • set_tab_button_icon(tab_idx: int, icon: Texture2D)
    • Sets the button icon from the tab at index.
  • set_tab_disabled(tab_idx: int, disabled: bool)
    • If disabled is true, disables the tab at index, making it non-interactable.
  • set_tab_hidden(tab_idx: int, hidden: bool)
    • If hidden is true, hides the tab at index, making it disappear from the tab area.
  • set_tab_icon(tab_idx: int, icon: Texture2D) -Sets an icon for the tab at index.
  • set_tab_metadata(tab_idx: int, metadata: Variant)
    • Sets the metadata value for the tab at index, which can be retrieved later using the get_tab_metadata method.
  • set_tab_title(tab_idx: int, title: String)
    • Sets a custom title for the tab at index. Tab titles default to the name of the indexed child node. Set it back to the child's name to make the tab default to it again.

The Theme Properties you can define are:

  • drop_mark_color
    • Color for the drop_mark icon.
  • font_disabled_color
    • Font color for disabled tabs.
  • font_hovered_color
    • Font color for hovered tabs.
  • font_outline_color
    • Tint color for the outline on text of the tab name.
  • font_selected_color
    • Font color of the current tab.
  • font_unselected_color
    • Font color of the other, unselected tabs.
  • icon_max_width
    • The maximum allowed with of the tab's icon. This is applied on top of the default size, but before the value set with TabBar.set_tab_icon_max_width. The height is adjusted according tot he icon's ratio.
  • icon_separation
    • Space between tab's name and it's icon.
  • outline_size
    • The size of the tab text outline. If using the font with FontFile.multichannel_signed_distance_field enabled, it's FontFile.msdf_pixel_range must be set to at least twice the value of outline_size for outline rendering to look correct. Otherwise, the outline may appear to be cut off earlier than intended.
  • side_margin
    • The space at the left or right edges of the tab bar, accordingly with the current tab_alignment. The margin is ignored with TabBar.ALIGNMENT_RIGHT if the tabs are clipped or a popup has been set. The margin is always ignored with TabBar.ALIGNMENT_CENTER.
  • font
    • The font used to draw tab names.
  • font_size
    • The font size of the tab names.
  • decrement
    • Texture2D Icon for the left arrow button that appears when there are too many tabs to fit the container width. When the button is disabled, it appears semi-transparent.
  • decrement_highlight
    • Texture2D Icon for the left arrow button that appears when there are too may tabs to fit in the container width. Used when the button is being hovered with the cursor.
  • drop_mark
    • Texture2D Icon shown to indicate where a dragged tab is going to be dropped.
  • increment
    • Texture2D Icon for the right arrow button that appears when there are too many tabs to fit the container width. When the button is disabled, it appears semi-transparent.
  • increment_highlight
    • Texture2D Icon for the right arrow button that appears when there are too may tabs to fit in the container width. Used when the button is being hovered with the cursor.
  • menu
    • Texture2D Icon for the menu button.
  • menu_highlight
    • Texture2D Icon for the menu button when it's being hovered with the cursor.
  • panel
    • The StyleBox for the background fill.
  • tab_disabled
    • The StyleBox for disabled tabs.
  • tab_focus
    • The StyleBox for when the TabBar is focused. The tab_focus StyleBox is displayed over the base StyleBox of the selected tab, so a partially transparent StyleBox should be used to ensure the base StyleBox remains visible. A StyleBox that represents an outline or an underline works well for this purpose. To disable the focus visual effect, assign a StyleBoxEmpty resource. Noe that disabling the focus visual effect will harm keyboard/controller navigation usability, so this is not recommended for accessibility reasons.
  • tab_hovered
    • The StyleBox for hovered tabs. Note this style will be drawn with the same width as tab_unselected at minimum.
  • tab_selected
    • The StyleBox for currently selected tab.
  • tab_unselected
    • The StyleBox for other, unselected tabs.
  • tabbar_background
    • The StyleBox for the background fill of the TabBar area.

Recap 

We should now have a better understanding of how the Container nodes work and a good overview of all the functionalities of each based on the documentation. From here you can take what you've learned about container nodes and better create reusable elements for your games and designs.

Stay tuned for future releases of shorter breakdowns with specific examples for creating elements using all of these Nodes we've discussed!

If you want to discuss more about Godot Nodes, or just Game Development in general, be sure to join us over on our growing community Discord page !

I really appreciate all of the support  I've been receiving lately. All financial support goes towards helping me continue to produce articles and videos in the future. Thanks to everyone who chooses to do so!

Thanks for reading and I look forward to chatting with you over on discord !

Happy Coding.

Want to support our work?Support us via Ko-fi