Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ Gum addresses these challenges with ready-made solutions, allowing us to focus o
>
> Keep in mind that while it is possible to build a full UI system without any external dependencies, creating a layout engine is complicated and beyond the scope of this tutorial. Instead, we will be taking advantage of the Gum NuGet package.
>
> Gum is a powerful system enabling the creation of virtually any game UI, and we will be covering some of the basics of its use in this tutorial. The full Gum documentation can be found here: [https://docs.flatredball.com/gum/code/monogame](https://docs.flatredball.com/gum/code/monogame)
> Gum is a powerful system enabling the creation of virtually any game UI, and we will be covering some of the basics of its use in this tutorial. The full Gum documentation can be found here: [https://docs.flatredball.com/gum/code/about](https://docs.flatredball.com/gum/code/about)

## Gum Concepts

Expand Down Expand Up @@ -218,8 +218,8 @@ startButton.Anchor(Gum.Wireframe.Anchor.BottomLeft);

// Set the X and Y position so it is 20px from the left edge
// and 20px from the bottom edge.
startButton.Visual.X = 20;
startButton.Visual.Y = -20;
startButton.X = 20;
startButton.Y = -20;
```

The `Click` event is raised whenever the button is activated and provides a standard way to respond regardless of input device:
Expand Down Expand Up @@ -291,7 +291,7 @@ Gum allows you to customize visuals in two ways:
With simple property changes, you can directly assign values in code. For example, the following code example changes the width of a button:

```cs
startButton.Visual.Width = 100;
startButton.Width = 100;
```

Direct property assignment works well for initial setup, such as positioning elements or setting their dimensions when first creating your UI. However, when you need visual elements to respond to user interactions (like highlighting a button when it is focused), a different approach is required.
Expand Down Expand Up @@ -321,7 +321,7 @@ To add the Gum NuGet package in Visual Studio Code:
2. Choose `Add NuGet Package` from the context menu.
3. Enter `Gum.MonoGame` in the `Add NuGet Package` search prompt and press Enter.
4. When the search finishes, select the `Gum.MonoGame` package in the results
5. When prompted for a version choose version `2025.8.3.3`.
5. When prompted for a version choose version `2025.12.9.1`.

#### [Visual Studio 2022](#tab/vs2022)

Expand All @@ -332,7 +332,7 @@ To Add the Gum NuGet package in Visual Studio 2022:
3. In the NuGet Package Manager window, select the `Browse` tab if it is not already selected.
4. In the search box, enter `Gum.MonoGame`.
5. Select the "Gum.MonoGame" package from the search results.
6. On the right, in the version dropdown, select version `2025.8.3.3` and click the "Install" button.
6. On the right, in the version dropdown, select version `2025.12.9.1` and click the "Install" button.

#### [dotnet CLI](#tab/dotnetcli)

Expand All @@ -342,7 +342,7 @@ To add the Gum NuGet package using the dotnet CLI:
2. Enter the following command:

```sh
dotnet add DungeonSlime.csproj package Gum.MonoGame --version 2025.8.3.3
dotnet add DungeonSlime.csproj package Gum.MonoGame --version 2025.12.9.1
```

---
Expand All @@ -351,11 +351,11 @@ To add the Gum NuGet package using the dotnet CLI:
> You can verify the package was successfully added by examining your `DungeonSlime.csproj` file, which should now contain a reference like:
>
> ```xml
> <PackageReference Include="Gum.MonoGame" Version="2025.8.3.3" />
> <PackageReference Include="Gum.MonoGame" Version="2025.12.9.1" />
> ```

> [!IMPORTANT]
> This tutorial uses version `2025.8.3.3` of Gum, which is the latest version of Gum as of this writing. That exact version is specified to use in the section above when installing the NuGet package to ensure compatibility throughout this tutorial. If there are newer versions of Gum available, please consult the [Gum documentation](https://docs.flatredball.com/gum/gum-tool/upgrading) before updating in case there are any breaking changes from the code that is presented in this tutorial.
> This tutorial uses version `2025.12.9.1` of Gum, which is the latest version of Gum as of this writing. That exact version is specified to use in the section above when installing the NuGet package to ensure compatibility throughout this tutorial. If there are newer versions of Gum available, please consult the [Gum documentation](https://docs.flatredball.com/gum/gum-tool/upgrading) before updating in case there are any breaking changes from the code that is presented in this tutorial.

### Adding UI Sound Effect

Expand Down Expand Up @@ -397,7 +397,7 @@ Finally, update the [**Initialize**](xref:Microsoft.Xna.Framework.Game.Initializ

The following is a breakdown of this initialization process:

1. **Basic Initialization**: `GumService.Default.Initialize(this, DefaultVisualsVersion.V2)` sets up the Gum system with our game instance. This is required for any gum project. The second parameter specifies the default visual styling. V2 is the latest version which makes it easy to style the default controls.
1. **Basic Initialization**: `GumService.Default.Initialize(this, DefaultVisualsVersion.V3)` sets up the Gum system with our game instance. This is required for any gum project. The second parameter specifies the default visual styling. V3 is the latest version which makes it easy to style the default controls.

> [!NOTE]
> We only need to pass our [**Game**](xref:Microsoft.Xna.Framework.Game) instance and the visuals version since we are using Gum as a code-first approach. Gum also offers a visual editor that creates Gum project files. When using the editor, you will need to also pass the Gum Project file to `Initialize`. For more information on how to use the Gum visual editor, see the [Gum Project Forms Tutorial](https://docs.flatredball.com/gum/code/monogame/tutorials/gum-project-forms-tutorial).
Expand Down Expand Up @@ -668,7 +668,7 @@ While this UI is now functional, you may have noticed that it uses Gum's default
:::question-answer
The two ways to customize Gum UI elements are:

1. **Direct property assignment**: Setting properties directly in code (like `MyButton.Visual.Width = 100`). This works well for initial setup and static properties.
1. **Direct property assignment**: Setting properties directly in code (like `MyButton.Width = 100`). This works well for initial setup and static properties.
2. **States**: Using Gum's state system (`StateSave` objects) to define different visual states that can be applied in response to specific conditions or events. States are automatically applied by Forms controls in response to user interactions (like focus or highlighting).

States are useful for dynamic changes that occur during gameplay, as they separate visual response logic from game logic.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ private void InitializeGum()
// Initialize the Gum service. The second parameter specifies
// the version of the default visuals to use. V2 is the latest
// version.
GumService.Default.Initialize(this, DefaultVisualsVersion.V2);
GumService.Default.Initialize(this, DefaultVisualsVersion.V3);

// Tell the Gum service which content manager to use. We will tell it to
// use the global content manager from our Core.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,10 @@ private void CreatePausePanel()
{
_pausePanel = new Panel();
_pausePanel.Anchor(Anchor.Center);
_pausePanel.Visual.WidthUnits = DimensionUnitType.Absolute;
_pausePanel.Visual.HeightUnits = DimensionUnitType.Absolute;
_pausePanel.Visual.Height = 70;
_pausePanel.Visual.Width = 264;
_pausePanel.WidthUnits = DimensionUnitType.Absolute;
_pausePanel.HeightUnits = DimensionUnitType.Absolute;
_pausePanel.Height = 70;
_pausePanel.Width = 264;
_pausePanel.IsVisible = false;
_pausePanel.AddToRoot();

Expand All @@ -23,17 +23,17 @@ private void CreatePausePanel()
_resumeButton = new Button();
_resumeButton.Text = "RESUME";
_resumeButton.Anchor(Anchor.BottomLeft);
_resumeButton.Visual.X = 9f;
_resumeButton.Visual.Y = -9f;
_resumeButton.Visual.Width = 80;
_resumeButton.X = 9f;
_resumeButton.Y = -9f;
_resumeButton.Width = 80;
_resumeButton.Click += HandleResumeButtonClicked;
_pausePanel.AddChild(_resumeButton);

var quitButton = new Button();
quitButton.Text = "QUIT";
quitButton.Anchor(Anchor.BottomRight);
quitButton.Visual.X = -9f;
quitButton.Visual.Y = -9f;
quitButton.X = -9f;
quitButton.Y = -9f;
quitButton.Width = 80;
quitButton.Click += HandleQuitButtonClicked;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ private void CreateOptionsPanel()

var musicSlider = new Slider();
musicSlider.Anchor(Gum.Wireframe.Anchor.Top);
musicSlider.Visual.Y = 30f;
musicSlider.Y = 30f;
musicSlider.Minimum = 0;
musicSlider.Maximum = 1;
musicSlider.Value = Core.Audio.SongVolume;
Expand All @@ -25,7 +25,7 @@ private void CreateOptionsPanel()

var sfxSlider = new Slider();
sfxSlider.Anchor(Gum.Wireframe.Anchor.Top);
sfxSlider.Visual.Y = 93;
sfxSlider.Y = 93;
sfxSlider.Minimum = 0;
sfxSlider.Maximum = 1;
sfxSlider.Value = Core.Audio.SoundEffectVolume;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,18 +7,18 @@ private void CreateTitlePanel()

var startButton = new Button();
startButton.Anchor(Gum.Wireframe.Anchor.BottomLeft);
startButton.Visual.X = 50;
startButton.Visual.Y = -12;
startButton.Visual.Width = 70;
startButton.X = 50;
startButton.Y = -12;
startButton.Width = 70;
startButton.Text = "Start";
startButton.Click += HandleStartClicked;
_titleScreenButtonsPanel.AddChild(startButton);

_optionsButton = new Button();
_optionsButton.Anchor(Gum.Wireframe.Anchor.BottomRight);
_optionsButton.Visual.X = -50;
_optionsButton.Visual.Y = -12;
_optionsButton.Visual.Width = 70;
_optionsButton.X = -50;
_optionsButton.Y = -12;
_optionsButton.Width = 70;
_optionsButton.Text = "Options";
_optionsButton.Click += HandleOptionsClicked;
_titleScreenButtonsPanel.AddChild(_optionsButton);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -84,18 +84,12 @@ To convert from pixel coordinates to normalized values, you divide the pixel pos

#### Visual States

Rather than directly modifying properties when UI elements change state (like when a button is focused), Gum uses a state-based system. Each control type has a specific category name that identifies its collection of states:
When UI elements change state (like when a button is focused), Gum uses a state-based system. A control's visual can be casted to a type specific to that control to access its states. Each control-specific visual has a type name matching its control, with the word Visual appended. For example, a button's `Visual` can be casted to type `ButtonVisual` to access button-specific properties and states. Once casted, the visual provides a `States` property containing all available states for that control type. Most control visuals, including `ButtonVisual`, provide the following States properties:

- Buttons use `Button.ButtonCategoryName`.
- Sliders use `Slider.SliderCategoryName`.
- Other control types have their own category names.

Within each category, you define named states that correspond to the control's possible conditions:

- "Enabled" (the normal, unfocused state).
- "Focused" (when the control has focus).
- "Highlighted" (when the mouse hovers over the control).
- "Disabled" (when the control cannot be interacted with).
- States.Enabled (the normal, unfocused state).
- States.Focused (when the control has focus).
- States.Highlighted (when the mouse hovers over the control).
- States.Disabled (when the control cannot be interacted with).

Each state contains an `Apply` action that defines what visual changes occur when that state becomes active. For example, when a button becomes focused, its state might change the background color or switch to an animated version.

Expand Down Expand Up @@ -301,6 +295,8 @@ The slider uses color changes to provide visual feedback:

When the slider is focused, all its elements change from gray to white, making it clear to the player which UI element currently has focus.

Notice that unlike the button which used existing states, we create the slider's states from scratch. These states must use specific names so that Gum knows to use them when the slider gains or loses focus. These names can be obtained through the `FrameworkElement` class. Our slider only needs to handle focused and unfocused (which returns to the `enabled` state), bug Gum provides additional states if needed.

#### Fill Visualization

One of the most important aspects of a slider is the visual representation of its value. We achieve this by updating the width of the `_fillRectangle` element:
Expand Down Expand Up @@ -397,7 +393,7 @@ The principles you have learned in this chapter extend beyond the specific compo
:::question-answer
The two main approaches are:

- **Direct property assignment**: Setting properties directly in code (like `button.Visual.Width = 100`). This approach is best for initial setup of UI elements and static properties that do not change during gameplay.
- **Direct property assignment**: Setting properties directly in code (like `button.Width = 100`). This approach is best for initial setup of UI elements and static properties that do not change during gameplay.
- **States (StateSave objects)**: Defining different visual states that are applied automatically in response to interactions. This approach is best for dynamic changes that happen during gameplay, like highlighting a button when it is focused or changing colors when a slider is adjusted.

:::
Expand Down Expand Up @@ -434,8 +430,7 @@ The principles you have learned in this chapter extend beyond the specific compo
:::question-answer
Gum's state system links with Forms controls through specifically named categories and states:

- Each Forms control type has a reserved category name (e.g., Button.ButtonCategoryName)
- Within that category, the control looks for states with specific names (Enabled, Focused, Highlighted, etc.)
- Each Forms control type has states specific to that type which are accessed through a casted visual (e.g., `buttonVisual.States.Enabled`)
- When the control's state changes (like gaining focus), it automatically applies the corresponding visual state

This relationship is important because it:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
using Gum.DataTypes;
using Gum.DataTypes.Variables;
using Gum.Forms.Controls;
using Gum.Forms.DefaultVisuals;
using Gum.Forms.DefaultVisuals.V3;
using Gum.Graphics.Animation;
using Gum.Managers;
using Microsoft.Xna.Framework.Input;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,10 @@ private void CreatePausePanel()
{
_pausePanel = new Panel();
_pausePanel.Anchor(Anchor.Center);
_pausePanel.Visual.WidthUnits = DimensionUnitType.Absolute;
_pausePanel.Visual.HeightUnits = DimensionUnitType.Absolute;
_pausePanel.Visual.Height = 70;
_pausePanel.Visual.Width = 264;
_pausePanel.WidthUnits = DimensionUnitType.Absolute;
_pausePanel.HeightUnits = DimensionUnitType.Absolute;
_pausePanel.Height = 70;
_pausePanel.Width = 264;
_pausePanel.IsVisible = false;
_pausePanel.AddToRoot();

Expand Down Expand Up @@ -33,16 +33,16 @@ private void CreatePausePanel()
_resumeButton = new AnimatedButton(_atlas);
_resumeButton.Text = "RESUME";
_resumeButton.Anchor(Anchor.BottomLeft);
_resumeButton.Visual.X = 9f;
_resumeButton.Visual.Y = -9f;
_resumeButton.X = 9f;
_resumeButton.Y = -9f;
_resumeButton.Click += HandleResumeButtonClicked;
_pausePanel.AddChild(_resumeButton);

AnimatedButton quitButton = new AnimatedButton(_atlas);
quitButton.Text = "QUIT";
quitButton.Anchor(Anchor.BottomRight);
quitButton.Visual.X = -9f;
quitButton.Visual.Y = -9f;
quitButton.X = -9f;
quitButton.Y = -9f;
quitButton.Click += HandleQuitButtonClicked;

_pausePanel.AddChild(quitButton);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ private void CreateOptionsPanel()
musicSlider.Name = "MusicSlider";
musicSlider.Text = "MUSIC";
musicSlider.Anchor(Gum.Wireframe.Anchor.Top);
musicSlider.Visual.Y = 30f;
musicSlider.Y = 30f;
musicSlider.Minimum = 0;
musicSlider.Maximum = 1;
musicSlider.Value = Core.Audio.SongVolume;
Expand All @@ -32,7 +32,7 @@ private void CreateOptionsPanel()
sfxSlider.Name = "SfxSlider";
sfxSlider.Text = "SFX";
sfxSlider.Anchor(Gum.Wireframe.Anchor.Top);
sfxSlider.Visual.Y = 93;
sfxSlider.Y = 93;
sfxSlider.Minimum = 0;
sfxSlider.Maximum = 1;
sfxSlider.Value = Core.Audio.SoundEffectVolume;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,16 +7,16 @@ private void CreateTitlePanel()

AnimatedButton startButton = new AnimatedButton(_atlas);
startButton.Anchor(Gum.Wireframe.Anchor.BottomLeft);
startButton.Visual.X = 50;
startButton.Visual.Y = -12;
startButton.X = 50;
startButton.Y = -12;
startButton.Text = "Start";
startButton.Click += HandleStartClicked;
_titleScreenButtonsPanel.AddChild(startButton);

_optionsButton = new AnimatedButton(_atlas);
_optionsButton.Anchor(Gum.Wireframe.Anchor.BottomRight);
_optionsButton.Visual.X = -50;
_optionsButton.Visual.Y = -12;
_optionsButton.X = -50;
_optionsButton.Y = -12;
_optionsButton.Text = "Options";
_optionsButton.Click += HandleOptionsClicked;
_titleScreenButtonsPanel.AddChild(_optionsButton);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,10 @@ private Panel CreateGameOverPanel(TextureAtlas atlas)
{
Panel panel = new Panel();
panel.Anchor(Gum.Wireframe.Anchor.Center);
panel.Visual.WidthUnits = DimensionUnitType.Absolute;
panel.Visual.HeightUnits = DimensionUnitType.Absolute;
panel.Visual.Width = 264.0f;
panel.Visual.Height = 70.0f;
panel.WidthUnits = DimensionUnitType.Absolute;
panel.HeightUnits = DimensionUnitType.Absolute;
panel.Width = 264.0f;
panel.Height = 70.0f;
panel.IsVisible = false;

TextureRegion backgroundRegion = atlas.GetRegion("panel-background");
Expand Down Expand Up @@ -33,8 +33,8 @@ private Panel CreateGameOverPanel(TextureAtlas atlas)
_retryButton = new AnimatedButton(atlas);
_retryButton.Text = "RETRY";
_retryButton.Anchor(Gum.Wireframe.Anchor.BottomLeft);
_retryButton.Visual.X = 9.0f;
_retryButton.Visual.Y = -9.0f;
_retryButton.X = 9.0f;
_retryButton.Y = -9.0f;

_retryButton.Click += OnRetryButtonClicked;
_retryButton.GotFocus += OnElementGotFocus;
Expand All @@ -44,8 +44,8 @@ private Panel CreateGameOverPanel(TextureAtlas atlas)
AnimatedButton quitButton = new AnimatedButton(atlas);
quitButton.Text = "QUIT";
quitButton.Anchor(Gum.Wireframe.Anchor.BottomRight);
quitButton.Visual.X = -9.0f;
quitButton.Visual.Y = -9.0f;
quitButton.X = -9.0f;
quitButton.Y = -9.0f;

quitButton.Click += OnQuitButtonClicked;
quitButton.GotFocus += OnElementGotFocus;
Expand Down
Loading