
๐งฑ Section 1: Rust GUI Game Tutorial โ Project Setup with Bevy
๐ฏ Goal: Prepare a clean Rust game project using Bevy that opens a basic GUI window. This section is the foundation of your Rust GUI game development journey.
๐ Prerequisite
If you havenโt set up Rust yet, follow this step-by-step guide:
๐ How to Set Up Rust Development Environment on Windows
This Rust GUI game tutorial assumes you have Rust and cargo
installed and properly configured.
๐๏ธ Step 1: Create a New Rust Project
Letโs start by creating a fresh Rust project for our mug game:
cargo new mug_game
cd mug_game
This command sets up a basic project with the following structure:
mug_game/
โโโ Cargo.toml
โโโ src/
โโโ main.rs
๐ฆ Step 2: Add Bevy to Your Project
Weโll use the Bevy engine, a modern game engine built in Rust. Add it to your Cargo.toml
file:
# Cargo.toml
[dependencies]
bevy = “0.13”
โ Bevy handles the game loop, 2D rendering, assets, input, and more โ making it perfect for this Rust 2D game tutorial.
After saving the file, fetch the dependencies:
cargo build
This may take a few minutes on the first build, as Bevy is a large framework.
๐งช Step 3: Launch Your First GUI Window
Edit src/main.rs
to show a blank window using Bevy:
use bevy::prelude::*;
/// Entry point of the mug game
fn main() {
App::new()
.add_plugins(DefaultPlugins) // Adds default rendering, input, windowing, etc.
.add_startup_system(setup_camera) // Runs once when the game starts
.run();
}
/// Sets up a 2D camera for rendering our game world
fn setup_camera(mut commands: Commands) {
commands.spawn(Camera2dBundle::default());
}
This code:
- Initializes the game app
- Loads default plugins (window, input, render loop)
- Spawns a 2D camera so we can render sprites later
Now run your project:
cargo run
You should see a blank GUI window โ success! ๐
Your first 2D Rust game window is live.
โ Summary of This Section
Step | Description |
---|---|
Project Init | cargo new mug_game |
Bevy Setup | Add bevy = "0.13" in Cargo.toml |
GUI Window | Bevy’s default plugins + 2D camera |
Shall we move on to Section 2: Loading Graphics & Displaying a Mug Icon?
In the next part, weโll load a PNG image and render a mug sprite in the center of the screen.
Table of Contents
๐ผ๏ธ Section 2: Load a Mug Icon and Display it on Screen
๐ฏ Goal: In this part of the Rust GUI game tutorial, youโll load a PNG image of a mug and display it in the game window using the Bevy engine.
๐ Step 1: Prepare an Asset Folder
Create a folder named assets/
in your project root:
mug_game/
โโโ assets/
โ โโโ mug.png
โโโ Cargo.toml
โโโ src/
โโโ main.rs
Place a simple PNG image of a mug in that folder (name it mug.png
).
You can download free assets from sites like:
๐ Tip: Keep the image size small (e.g. 64×64 or 128×128 pixels) for now.
โ๏ธ Step 2: Update main.rs
to Load and Display the Mug
Replace src/main.rs
with this updated code:
use bevy::prelude::*;
/// Entry point for the Rust GUI game tutorial project
fn main() {
App::new()
.add_plugins(DefaultPlugins) // Core Bevy plugins
.add_startup_system(setup_camera)
.add_startup_system(spawn_mug)
.run();
}
/// Adds a 2D camera so the world is visible
fn setup_camera(mut commands: Commands) {
commands.spawn(Camera2dBundle::default());
}
/// Loads a mug sprite and displays it at the center of the screen
fn spawn_mug(
mut commands: Commands,
asset_server: Res<AssetServer>,
mut textures: ResMut<Assets<Image>>,
) {
// Load the mug image from the assets folder
let texture_handle = asset_server.load("mug.png");
// Spawn the sprite at the center
commands.spawn(SpriteBundle {
texture: texture_handle,
transform: Transform::from_xyz(0.0, 0.0, 0.0), // center of the screen
..default()
});
}
โ๏ธ Step 3: Run the Game
Now run your game:
cargo run
If everything is correct, a window opens with your mug icon rendered in the center.
Congratulations! ๐ You’ve completed a core milestone in your Rust 2D game development journey.
๐ง How It Works (Algorithm Explanation)
Part | What It Does |
---|---|
AssetServer::load | Loads image from assets/ folder as texture handle |
SpriteBundle | A Bevy struct that displays an image |
Transform | Controls position (x, y, z) on screen |
Camera2dBundle | Required to display anything in a 2D space |
๐ง Note: Asset loading in Bevy is asynchronous, but Bevy handles that behind the scenes.
โ Summary
Task | Result |
---|---|
Created assets/ | Organized sprite resources |
Loaded mug.png | Displayed it on window |
Used SpriteBundle | Positioned sprite at center |
Shall we continue to Section 3: Add Mouse Interaction to the Mug Icon?
Next, weโll make the mug clickable and maybe even respond with a simple animation or log output.
๐ฑ๏ธ Section 3: Add Mouse Interaction to the Mug Icon
๐ฏ Goal: In this part of the Rust GUI game tutorial, you’ll learn how to detect mouse clicks on a sprite (the mug icon) and respond with movement or printed feedback. This introduces input handling in Bevy.
๐ Step 1: Tag the Mug Sprite
Bevy uses components to identify entities. We’ll create a custom component called Mug
so we can detect which sprite was clicked.
// Define a marker component to identify the mug
#[derive(Component)]
struct Mug;
โ๏ธ Step 2: Update Code to Include the Mug
Tag
Modify the spawn_mug
function to add the Mug
component:
commands.spawn((
SpriteBundle {
texture: texture_handle,
transform: Transform::from_xyz(0.0, 0.0, 0.0),
..default()
},
Mug, // Add our custom component to this sprite
));
๐ฑ๏ธ Step 3: Detect Mouse Click on the Mug
Now we add a system that checks if the user clicked on the mug. This uses Input<MouseButton>
, the camera, and sprite positions.
fn click_mug(
buttons: Res<Input<MouseButton>>,
windows: Query<&Window>,
camera_q: Query<(&Camera, &GlobalTransform)>,
mug_q: Query<(&GlobalTransform, &Handle<Image>), With<Mug>>,
images: Res<Assets<Image>>,
) {
// Only run if left mouse button was just pressed
if buttons.just_pressed(MouseButton::Left) {
let window = windows.single();
let (camera, camera_transform) = camera_q.single();
if let Some(cursor_pos) = window.cursor_position() {
// Convert cursor position to world coordinates
if let Some(world_pos) = camera.viewport_to_world_2d(camera_transform, cursor_pos) {
for (mug_transform, image_handle) in mug_q.iter() {
if let Some(image) = images.get(image_handle) {
let sprite_size = Vec2::new(image.width() as f32, image.height() as f32);
let mug_pos = mug_transform.translation.truncate();
let half = sprite_size / 2.0;
let min = mug_pos - half;
let max = mug_pos + half;
if (min.x..=max.x).contains(&world_pos.x) &&
(min.y..=max.y).contains(&world_pos.y) {
println!("โ You clicked the mug!");
}
}
}
}
}
}
}
๐ก This system:
- Converts screen coordinates to Bevy world coordinates
- Checks if the click was within the mugโs bounding box
๐งฉ Step 4: Register the Click System
Finally, add click_mug
to your App
setup:
.add_systems(Update, click_mug)
Update
ensures this system runs every frame, checking for click input.
โ Summary
Task | Description |
---|---|
Defined Mug component | Marks the mug sprite |
Checked mouse input | Responds only on left click |
Calculated hitbox area | Compares cursor to sprite bounds |
Printed interaction log | Click feedback printed to console |
๐ Result
Now, when you click the mug icon in your game window, the terminal will display:
โ You clicked the mug!
Next up in Section 4: Animate the Mug or Move It After Click, weโll use Bevy’s system scheduler to animate or move the mug when clicked โ opening the door to interactive gameplay.
๐ฌ Section 4: Animate or Move the Mug When Clicked
๐ฏ Goal: Extend your Rust GUI game tutorial by adding basic interactivity. When the user clicks the mug, it moves or animates in response โ making the game feel alive.
๐ง Concept: Responding to Input in Bevy
In Bevy, gameplay behavior is composed of systems. We’ll:
- Detect the mug click (already done in Section 3)
- Update its position or apply a small animation
๐ Step 1: Add a Movement Event
We’ll use Bevyโs event system to separate input from actions. This makes the architecture cleaner.
// Define a custom event type
struct MugClickedEvent;
Register the event in main()
:
.add_event::<MugClickedEvent>()
In the click_mug
system, emit the event instead of printing:
event_writer.send(MugClickedEvent);
Update the function signature to include EventWriter
:
fn click_mug(
...
mut event_writer: EventWriter<MugClickedEvent>,
)
๐ Step 2: Move the Mug After Click
Now create a new system to respond to the event:
fn move_mug_on_click(
mut events: EventReader<MugClickedEvent>,
mut query: Query<&mut Transform, With<Mug>>,
) {
for _ in events.read() {
for mut transform in &mut query {
transform.translation.y += 50.0; // Move mug up by 50 units
println!("๐ Mug moved!");
}
}
}
This system reads the event and moves the mug upward when clicked.
๐ฆ Step 3: Register the System
In your main()
function, add:
.add_systems(Update, (click_mug, move_mug_on_click))
Bevy will run both systems in order, per frame.
โ Summary
Task | Result |
---|---|
Defined Event | MugClickedEvent to decouple logic |
Detected Click | Emit event from click_mug() |
Responded to Event | Move mug up using Transform component |
๐ Bonus: Add a Sound or Animation?
In later sections, you could:
- Add a sound using
bevy_kira_audio
- Use time-based movement or fade-in/fade-out effects with
Timer
๐ Debug Output Example
โ You clicked the mug!
๐ Mug moved!
Next up in Section 5: Add a Score Counter or Game State System, weโll begin managing game logic: when the player clicks mugs, theyโll gain points โ your first step toward full gameplay!
๐งฎ Section 5: Add a Score Counter and Game State System
๐ฏ Goal: In this step of the Rust GUI game tutorial, weโll introduce a simple game state and implement a score counter that increases every time the player clicks the mug icon.
๐ง Why Use a Game State?
Tracking game data like score, health, or inventory requires a resource or component that lives in the ECS world.
In Bevy, we use a Resource
to store and globally access the score.
๐ฆ Step 1: Define a Score Resource
Add this struct at the top of main.rs
:
#[derive(Resource)]
struct Score(u32);
Register it as a startup resource in your app:
.insert_resource(Score(0))
โ๏ธ Step 2: Update the Mug Click Logic to Add Points
In move_mug_on_click
, modify it like this:
fn move_mug_on_click(
mut events: EventReader<MugClickedEvent>,
mut query: Query<&mut Transform, With<Mug>>,
mut score: ResMut<Score>,
) {
for _ in events.read() {
for mut transform in &mut query {
transform.translation.y += 50.0;
}
score.0 += 1;
println!("๐ฏ Score: {}", score.0);
}
}
This:
- Moves the mug upward
- Increments the score each time it’s clicked
- Logs the current score
๐บ Step 3: (Optional) Display the Score On Screen
We’ll later add UI rendering (Section 6), but for now, print to console:
๐ฏ Score: 1
๐ฏ Score: 2
โ Summary
Feature | Result |
---|---|
Score Resource | Tracks player progress |
Game State Logic | Updates state with every interaction |
Click Feedback | Visually moves mug and logs score |
๐ง Concept Recap
Resource<T>
holds global game-wide data- You access it using
ResMut<T>
to update it in systems - This separates data from entities โ a key ECS pattern
๐ฅ๏ธ Section 6: Displaying the Score Using Bevy UI โ Creating HUD in Rust
๐ฏ Goal: In this stage of our Rust GUI game tutorial, weโll learn how to create and update a simple on-screen score counter using Bevy UI. This heads-up display (HUD) will provide real-time feedback to the player whenever the mug is clicked.
๐ง Why Use Bevy UI?
In many beginner Rust game tutorials, developers focus on logic but neglect visual feedback. However, building a 2D GUI in Rust using the Bevy engineโs built-in UI system is straightforward and essential for a better game experience. Whether itโs displaying health, score, or inventory, the HUD (heads-up display) is the playerโs primary feedback channel.
๐ In our mug game, we’ll render the score as a floating text in the top-left corner of the game window.
๐ฆ Step 1: Create a Score Text Component
We need a way to update the text dynamically, so weโll tag the score text with a ScoreText
component.
#[derive(Component)]
struct ScoreText;
๐งฉ Step 2: Spawn a Text UI Element in the Startup System
Bevy provides TextBundle
for rendering text on the screen. Add a new system that sets up the UI:
fn setup_score_ui(mut commands: Commands, asset_server: Res<AssetServer>) {
commands.spawn((
TextBundle::from_section(
"Score: 0",
TextStyle {
font: asset_server.load("fonts/FiraSans-Bold.ttf"), // add this font
font_size: 30.0,
color: Color::WHITE,
},
)
.with_style(Style {
position_type: PositionType::Absolute,
position: UiRect {
left: Val::Px(15.0),
top: Val::Px(15.0),
..default()
},
..default()
}),
ScoreText,
));
}
๐๏ธ Note: You need a font file!
Download FiraSans or use any .ttf
and place it under assets/fonts/
.
Your directory should look like this:
assets/
โโโ mug.png
โโโ fonts/
โโโ FiraSans-Bold.ttf
๐ Step 3: Update the Score Text When the Mug is Clicked
Weโll now modify our existing move_mug_on_click
system to update the text on screen.
fn move_mug_on_click(
mut events: EventReader<MugClickedEvent>,
mut query: Query<&mut Transform, With<Mug>>,
mut score: ResMut<Score>,
mut text_query: Query<&mut Text, With<ScoreText>>,
) {
for _ in events.read() {
for mut transform in &mut query {
transform.translation.y += 50.0;
}
score.0 += 1;
for mut text in &mut text_query {
text.sections[0].value = format!("Score: {}", score.0);
}
println!("๐ฏ Score updated: {}", score.0);
}
}
๐ง This is a perfect example of how to update UI in Bevy: just query for your tagged UI entity, and change the text’s
.sections[0].value
.
๐ง Step 4: Register All Systems
Make sure your main()
includes:
.insert_resource(Score(0))
.add_event::<MugClickedEvent>()
.add_startup_systems((setup_camera, spawn_mug, setup_score_ui))
.add_systems(Update, (click_mug, move_mug_on_click))
โ What Youโve Learned
Feature | Explanation |
---|---|
TextBundle | Bevy’s way to show text on screen |
ScoreText component | Helps us target only the score text entity |
Dynamic UI update | Updates score with every click |
TextStyle & Style | Controls font, size, color, and screen position |
Using resources in UI | Combines ECS data with GUI rendering |
๐ธ Example Output
When you run your game, the top-left corner should now show:
Score: 0
Each time you click the mug, it will increase:
Score: 1
Score: 2
Score: 3
All live, on-screen, in true Rust game development style.
๐ง Deep Dive: Why Use Text.sections[0].value
?
Bevy UI text is composed of sections for multiple font styles or colors in one line. Even though we use only one section here, this design supports flexibility for multi-style text (like colored numbers or bold labels).
๐ Whatโs Next?
Your 2D GUI in Rust now includes:
- Clickable game objects
- Moving animations
- A real-time HUD using Bevy UI
Next in Section 7: Add a Coffee-Making Mechanic (Click + Timer), weโll introduce a progress bar and simulate brewing a coffee when the mug is clicked repeatedly โ moving toward a real gameplay loop.
โฑ๏ธ Section 7: Add a Coffee-Making Mechanic with a Timer and Progress Bar
๐ฏ Goal: In this advanced stage of the Rust GUI game tutorial, weโll simulate a coffee-making process. When the user clicks the mug a certain number of times, a progress bar fills up. Once complete, the player โbrewsโ a cup of coffee, incrementing a separate count.
โ Gameplay Concept
- Each time the mug is clicked, it adds +1 to a “brew progress”
- Once progress hits a threshold (e.g. 5), it resets and increases a
coffee_count
- A visual progress bar (UI rectangle) reflects brewing status
๐ฆ Step 1: Define Resources and Components
We need to track progress and coffee count:
#[derive(Resource)]
struct BrewProgress {
current: u8,
goal: u8,
}
#[derive(Resource)]
struct CoffeeCount(u32);
#[derive(Component)]
struct ProgressBarFill;
Initialize them in main()
:
.insert_resource(BrewProgress { current: 0, goal: 5 })
.insert_resource(CoffeeCount(0))
๐งฉ Step 2: Create the Progress Bar UI
Weโll display a progress bar below the score. It consists of:
- A background node (gray)
- A fill node (colored, scaled based on progress)
fn setup_progress_bar(mut commands: Commands) {
commands
.spawn(NodeBundle {
style: Style {
size: Size::new(Val::Px(200.0), Val::Px(20.0)),
position_type: PositionType::Absolute,
position: UiRect {
left: Val::Px(15.0),
top: Val::Px(60.0),
..default()
},
..default()
},
background_color: Color::DARK_GRAY.into(),
..default()
})
.with_children(|parent| {
parent.spawn((
NodeBundle {
style: Style {
size: Size::new(Val::Percent(0.0), Val::Percent(100.0)),
..default()
},
background_color: Color::ORANGE_RED.into(),
..default()
},
ProgressBarFill,
));
});
}
โ๏ธ Step 3: Update Progress on Mug Click
Modify move_mug_on_click
:
fn move_mug_on_click(
mut events: EventReader<MugClickedEvent>,
mut mug_query: Query<&mut Transform, With<Mug>>,
mut score: ResMut<Score>,
mut progress: ResMut<BrewProgress>,
mut coffee_count: ResMut<CoffeeCount>,
mut bar_query: Query<&mut Style, With<ProgressBarFill>>,
) {
for _ in events.read() {
for mut transform in &mut mug_query {
transform.translation.y += 10.0;
}
// Add progress
if progress.current < progress.goal {
progress.current += 1;
}
// If finished, make coffee
if progress.current >= progress.goal {
coffee_count.0 += 1;
progress.current = 0;
println!("โ Coffee brewed! Total: {}", coffee_count.0);
}
// Update progress bar width
for mut style in &mut bar_query {
style.size.width = Val::Percent((progress.current as f32 / progress.goal as f32) * 100.0);
}
score.0 += 1;
println!("๐ฏ Score: {} | Progress: {}", score.0, progress.current);
}
}
โ Whatโs Happening Here?
Step | Behavior |
---|---|
Click mug | Moves icon, increments brew progress |
Progress goal met | Adds 1 coffee, resets progress |
UI updates | Fills progress bar dynamically |
๐ฎ Example Gameplay Loop
- Click mug 5 times โฉ โโ Coffee brewed!โ appears in console
- Progress bar resets
- Coffee count increments
- Score continues to increase
๐ Bonus Ideas
To enhance the mechanic:
- Add coffee icons to screen (1 per brew)
- Add a cooldown (
Timer
) so clicks must wait - Play a sound when brewing completes
๐ง ECS Recap
This mechanic uses:
- Resources for global mutable state (
BrewProgress
,CoffeeCount
) - Component-based UI (
ProgressBarFill
) - System logic for real-time input โ state โ visual update
โ Summary Table
Feature | Implementation Detail |
---|---|
Brew Logic | BrewProgress resource, goal check |
Visual Feedback | UI bar with ProgressBarFill component |
Score Integration | Updates both score and coffee count in sync |
๐งบ Section 8: Add Coffee Inventory UI (Icons or Counters)
๐ฏ Goal: In this part of the Rust GUI game tutorial, weโll expand your GUI system to visually display the number of coffees brewed. Youโll learn how to use Bevy UI to show either coffee icons or a dynamic number counter โ essential for real-time inventory systems in Rust game development.
๐ฆ Concept Overview
So far, your game tracks brewed coffee internally. Now weโll make that visible to the player in two possible ways:
- ๐ Number Counter โ โCoffees: 3โ
- ๐งฑ Icon-Based Inventory โ e.g. 3 coffee cup images side by side
This simple inventory visualization is foundational to building resource systems in Rust GUI games โ from shop mechanics to item crafting menus.
If you find this kind of coffee system intriguing, you might also enjoy
๐ Rust Coffee Vending Machine Simulator,
a fun Rust game dev post where coffee recipes are simulated with real-time logic. Definitely worth a read!
๐ข Option 1: Number-Based Coffee Counter
Letโs start with the simplest version: showing the coffee count as text on the screen.
๐งฉ Step 1: Add a New Component
#[derive(Component)]
struct CoffeeText;
๐งฉ Step 2: Spawn the UI Text
Place this inside a startup system (setup_coffee_counter_ui
):
fn setup_coffee_counter_ui(mut commands: Commands, asset_server: Res<AssetServer>) {
commands.spawn((
TextBundle::from_section(
"Coffees: 0",
TextStyle {
font: asset_server.load("fonts/FiraSans-Bold.ttf"),
font_size: 30.0,
color: Color::GOLD,
},
)
.with_style(Style {
position_type: PositionType::Absolute,
position: UiRect {
right: Val::Px(15.0),
top: Val::Px(15.0),
..default()
},
..default()
}),
CoffeeText,
));
}
๐งฉ Step 3: Update When Coffee is Brewed
Extend your move_mug_on_click
system to include:
mut coffee_text_q: Query<&mut Text, With<CoffeeText>>,
And inside the system:
for mut coffee_text in &mut coffee_text_q {
coffee_text.sections[0].value = format!("Coffees: {}", coffee_count.0);
}
๐งฑ Option 2: Icon-Based Inventory (Advanced)
If you want a graphical display (one coffee cup image per brewed unit):
- Create a
coffee_icon.png
image and place it inassets/
- When a coffee is brewed, spawn a new Sprite UI element as a child node
- Limit maximum visible icons (e.g. 10)
We’ll expand this part later in Section 9: Grid-Based Coffee Inventory UI
โ Summary Table
Feature | Method Used |
---|---|
Count Display (text) | TextBundle , CoffeeText component |
Inventory Tracking | CoffeeCount resource |
Real-Time Updates | ECS system responding to brewing events |
Bonus Learning | UI layout and dynamic label management |
๐ง Rust GUI Game Tutorial Deep Dive
By integrating these UI components, your game now resembles a real clicker/tycoon experience. This is a common pattern in Rust 2D game development โ reacting to game state changes with on-screen feedback using Bevy UI.
๐งฑ Section 9: Grid-Based Coffee Inventory with Icons and Limits
๐ฏ Goal: In this part of the Rust GUI game tutorial, weโll implement a coffee inventory that visually grows as you brew more coffee. Instead of just showing a number, each coffee brewed will add a small mug icon to a dynamic UI grid.
This is a major upgrade in user experience and prepares your Rust GUI game for more advanced features like inventory management, crafting, or item slots.
๐ฆ Design Goals
- ๐งฑ Display one coffee icon per brew
- โ Add new icons dynamically each time a mug is brewed
- ๐ฏ Limit the total number of visible icons (e.g., 10 max on screen)
- ๐ Reset or cycle inventory (optional advanced step)
โ Combined with the score and progress bar from earlier sections, this creates a full clicker loop โ something you’d see in real-world Rust game development using Bevy engine.
๐งฉ Step 1: Setup Inventory Container Node
Create a UI container at the bottom of the screen using a horizontal layout:
#[derive(Component)]
struct InventoryRoot;
fn setup_inventory_ui(mut commands: Commands) {
commands.spawn((
NodeBundle {
style: Style {
size: Size::new(Val::Px(400.0), Val::Px(64.0)),
position_type: PositionType::Absolute,
position: UiRect {
left: Val::Px(15.0),
bottom: Val::Px(15.0),
..default()
},
flex_direction: FlexDirection::Row,
align_items: AlignItems::Center,
..default()
},
background_color: Color::NONE.into(),
..default()
},
InventoryRoot,
));
}
๐ก
flex_direction: Row
means children are laid out horizontally.
๐ผ๏ธ Step 2: Load and Spawn Coffee Icon
Weโll dynamically spawn child nodes under InventoryRoot
every time a coffee is brewed.
Assume coffee_icon.png
is placed inside assets/
.
Modify your move_mug_on_click
system like this:
fn move_mug_on_click(
mut events: EventReader<MugClickedEvent>,
mut mug_q: Query<&mut Transform, With<Mug>>,
mut score: ResMut<Score>,
mut progress: ResMut<BrewProgress>,
mut coffee_count: ResMut<CoffeeCount>,
mut bar_q: Query<&mut Style, With<ProgressBarFill>>,
mut text_q: Query<&mut Text, With<CoffeeText>>,
inventory_q: Query<Entity, With<InventoryRoot>>,
mut commands: Commands,
asset_server: Res<AssetServer>,
) {
for _ in events.read() {
for mut mug in &mut mug_q {
mug.translation.y += 10.0;
}
if progress.current < progress.goal {
progress.current += 1;
}
if progress.current >= progress.goal {
coffee_count.0 += 1;
progress.current = 0;
println!("โ Coffee brewed! Total: {}", coffee_count.0);
// Spawn coffee icon
if let Ok(inventory_entity) = inventory_q.get_single() {
if coffee_count.0 <= 10 {
commands.entity(inventory_entity).with_children(|parent| {
parent.spawn(ImageBundle {
style: Style {
size: Size::new(Val::Px(48.0), Val::Px(48.0)),
margin: UiRect::all(Val::Px(4.0)),
..default()
},
image: UiImage::new(asset_server.load("coffee_icon.png")),
..default()
});
});
}
}
}
for mut style in &mut bar_q {
style.size.width = Val::Percent((progress.current as f32 / progress.goal as f32) * 100.0);
}
for mut text in &mut text_q {
text.sections[0].value = format!("Coffees: {}", coffee_count.0);
}
score.0 += 1;
}
}
๐ง Step 3: Asset Preparation
Your assets/
directory should now contain:
assets/
โโโ mug.png
โโโ fonts/
โ โโโ FiraSans-Bold.ttf
โโโ coffee_icon.png
You can download a simple coffee cup PNG from:
๐ Summary of Features
Feature | Implementation |
---|---|
Grid UI | Flex-based horizontal node layout |
Coffee tracking | Updates per brewed unit |
Coffee icons | Dynamically added as children |
UI state synchronization | Real-time GUI reflects game logic |
๐ Advanced Options (Future)
- ๐ Add a limit (e.g., 10 icons max)
- โฑ๏ธ Add fade-in animations (with
bevy_tweening
) - ๐งบ Use scroll containers or page-based inventories
๐ง ECS Insight
This system shows how powerful Entity-Component-System (ECS) design is in Rust game development:
- Inventory is just another
Node
- Coffee icons are
Entity
children - The system modifies world state and UI in sync
โ Final Results (after 5โ10 clicks)
- The mug moves
- The score updates
- The progress bar fills
- Text displays โCoffees: 3โ
- Inventory bar fills with coffee icons
๐ Section 10: Add Sound Effects and Polish the UX
๐ฏ Goal: In this final stage of the Rust GUI game tutorial, weโll enrich the gameplay experience by adding sound effects. Each click and coffee brew will now have an audible response โ completing the feel of an interactive, polished 2D Rust GUI game.
๐ก Why UX Polish Matters in Rust GUI Games
Game mechanics alone donโt create immersion. The combination of visual feedback, real-time response, and sound design makes your Rust GUI game feel alive.
This section ties everything together โ score system, coffee brewing logic, UI inventory, and now sound effects.
๐ Whether youโre making a simple clicker or a full 2D RPG, understanding how to add game audio in Rust is a crucial step in becoming a complete game developer.
๐ต Step 1: Add the Audio Plugin Dependency
Update your Cargo.toml
to include:
[dependencies]
bevy = "0.13"
bevy_kira_audio = "0.18"
This plugin provides high-level support for playing sound in Bevy games. It’s widely used in Rust game development for beginners who want a simple and effective audio API.
๐ฆ Step 2: Load and Insert Audio Files
Place your .ogg
or .mp3
files in the assets/audio/
folder:
assets/
โโโ audio/
โ โโโ click.ogg
โ โโโ brew.ogg
Then register the plugin in main()
:
use bevy_kira_audio::prelude::*;
fn main() {
App::new()
.add_plugins((DefaultPlugins, AudioPlugin))
...
๐งฉ Step 3: Load Sounds into the ECS World
In your startup system:
#[derive(Resource)]
struct GameAudio {
click: Handle<AudioSource>,
brew: Handle<AudioSource>,
}
fn load_sounds(mut commands: Commands, asset_server: Res<AssetServer>) {
commands.insert_resource(GameAudio {
click: asset_server.load("audio/click.ogg"),
brew: asset_server.load("audio/brew.ogg"),
});
}
๐งช Step 4: Play Sound on Mug Click or Coffee Brew
Update move_mug_on_click
to use the audio system:
fn move_mug_on_click(
...
audio: Res<Audio>,
game_audio: Res<GameAudio>,
) {
for _ in events.read() {
...
audio.play(game_audio.click.clone());
if progress.current >= progress.goal {
coffee_count.0 += 1;
audio.play(game_audio.brew.clone());
...
}
}
}
Now every mug click will play a satisfying click sound, and a brew sound will play when coffee is made.
โ Final Game Loop (Fully Polished)
- ๐ฏ Rust GUI game tutorial journey complete
- ๐ Mug clicks move icon + update progress
- ๐ง Logic triggers score and brew state
- ๐งบ Coffee inventory fills with icons
- ๐ผ๏ธ GUI text and bar update in real-time
- ๐ Audio feedback brings it all to life
๐ Tips for Audio UX Polish
Tip | Description |
---|---|
Normalize volume | Avoid too loud/quiet sounds |
Add variation | Use 2โ3 alternate click sounds randomly |
Add delay or cooldown logic | Prevent overlapping spam sounds |
Consider spatial audio | For large games with map-based positioning |
๐ Wrap-Up: Your First Rust GUI Game Tutorial Completed
You’ve built a fully functional 2D game UI system with Rust and Bevy:
- โ Clickable gameplay loop
- โ Real-time progress & state
- โ Dynamic UI (score, inventory, bar)
- โ Audio effects for deeper interaction
- โ Expandable ECS structure for future levels
This concludes your beginner-to-intermediate level Rust GUI game tutorial. You’ve laid the foundation for more complex mechanics like drag-and-drop UI, inventory merging, shop systems, or even crafting logic.
Want to dive deeper? Try adding:
- Save/load with
serde
- Shop system using gold
- Coffee upgrades that boost brew speed
๐งฉ Final Thoughts โ Where to Go Next?
Congratulations! ๐ Youโve completed a full Rust GUI game tutorial using the Bevy engine in Rust โ one of the most beginner-friendly and powerful frameworks for making 2D games in Rust today.
Over the course of this project, you didnโt just build a toy app โ you built a foundational clicker-style game with:
- โ Interactive GUI components
- โ Real-time state management
- โ Sound integration
- โ Dynamic UI layout
- โ Icon-based inventory system
If you’re looking for a beginner Rust game tutorial that teaches you actual game design and ECS structure, this is it โ a complete, scalable, and expandable base.
๐ What Youโve Learned
Skill | Description |
---|---|
Bevy engine Rust | Game loop, ECS structure, UI system |
Rust 2D game | Created using sprites, 2D camera, UI overlays |
Rust game development for beginners | A step-by-step path from logic to polish |
GUI game Rust example | Fully playable with interaction, UI, and audio |
Learn Rust with game | Hands-on with Bevy ECS, components, resources, event systems |
How to make a game in Rust | From setup to complete GUI gameplay |
๐ Want to Learn More?
If you’re feeling ambitious or curious about the backend logic of multiplayer-style games, or how to implement more complex simulations using text-based systems in Rust, check out these follow-up guides:
- ๐งญ Rust MUD Game Tutorial โ Map & World System
โ Learn how to generate and manage structured world data using structs and enums. - ๐ง Rust MUD Game Tutorial โ Monsters, Turn-Based Combat & Event Handling
โ Go deeper into enemy logic, combat simulations, and state transitions. - ๐งฑ Rust MUD Game Tutorial Part 3 โ Full Game Systems
โ Discover how to structure a large-scale Rust terminal game using modular ECS design.
These articles are great companions to this GUI-based project โ they tackle different game types, but share the same core Rust foundations.
You didnโt just learn Rust with a game โ youโve experienced firsthand how to make a game in Rust from concept to execution.
Happy coding! โ๐ฆ
If youโd like help exporting your game to web (WASM), adding animations, or even publishing it, let me know โ we can build on this momentum together.
โ FAQ โ Rust GUI Game Tutorial
1. Do I need prior game development experience to follow this Rust GUI game tutorial?
No. This tutorial is designed for Rust beginners and also serves as a solid beginner Rust game tutorial with step-by-step guidance.
2. What is Bevy and why is it used here?
Bevy is a modern game engine built in Rust. It offers a powerful ECS (Entity Component System), real-time rendering, input handling, and a flexible UI system โ making it perfect for Rust 2D game development.
3. Can I build this GUI game in Rust on macOS or Linux?
Yes! Rust and Bevy are fully cross-platform. This GUI game Rust example works on Windows, macOS, and Linux.
4. Whatโs the difference between a GUI-based Rust game and a MUD game?
A GUI game uses visual sprites, text, and graphics. A MUD (Multi-User Dungeon) is typically text-based. For MUDs, check out this terminal-based Rust MUD tutorial.
5. Can I replace the mug icon and use different graphics?
Absolutely. Just place your .png
image into the assets/
folder and update the asset path in your code. Bevy makes asset swapping easy.
6. Can I add sound effects even if Iโve never used audio in programming before?
Yes. The tutorial uses bevy_kira_audio
, which makes it beginner-friendly to add click and brew sounds with one line of code.
7. How do I deploy or share my Rust GUI game with others?
You can compile it to a native .exe
, .app
, or .elf
file. Or, you can export it to WebAssembly (WASM) for browser-based play using wasm-pack
.
8. What should I learn next after completing this Rust GUI game tutorial?
You could explore:
- Crafting systems
- Shop menus
- Save/load with
serde
- Or try the MUD game series:
World System
Combat & Monsters
9. Is Bevy the only way to make GUI games in Rust?
No. Other options include ggez
, macroquad
, and SDL2
. However, Bevy is the most modern and scalable option for serious Rust game development for beginners.
10. Can I use Bevy to make a full commercial game?
Yes. Bevy is open-source and MIT licensed. Many indie devs are using it to prototype or even release full games.
11. How do I customize the UI font and colors?
Just replace the .ttf
file in your assets folder and change the TextStyle
color/font in the code. Bevy supports complete UI theming.
12. How large can my game get before performance is affected?
Bevy is very performant and written in native Rust. Even hundreds of entities (like icons, enemies, bullets) are handled efficiently due to ECS.
13. Can I build this tutorial into a full clicker/idle game?
Yes! This is a perfect base for building a clicker, idle tycoon, or simulation game. Add timers, offline income, upgrades, and you’re there.
๐ Official External Links โ Rust GUI Game Tutorial
๐ฆ Rust Programming Language
- ๐ Official site: https://www.rust-lang.org
โ Learn more about Rust, install the toolchain, and explore the language guide.
๐ฎ Bevy Game Engine
- ๐ Bevy official site: https://bevyengine.org
โ The official homepage for the Bevy engine โ documentation, roadmap, and examples. - ๐ Bevy API Docs: https://docs.rs/bevy/latest/bevy
โ Complete API documentation for all Bevy features, including UI, ECS, and rendering.
๐ bevy_kira_audio (Sound Plugin)
- ๐ฆ Crate page: https://crates.io/crates/bevy_kira_audio
โ Official plugin for playing audio in Bevy with simple commands. - ๐ Docs: https://docs.rs/bevy_kira_audio/latest/bevy_kira_audio/
โ Detailed usage instructions and examples for adding sound to Bevy games.
๐จ Free Game Assets
- ๐ผ๏ธ Kenney Game Assets: https://kenney.nl/assets
โ High-quality free assets including UI elements, icons, and game sprites. - ๐งฐ OpenGameArt.org: https://opengameart.org
โ A vast repository of open-licensed audio, textures, icons, and more. - ๐ฎ Itch.io Game Assets: https://itch.io/game-assets/free
โ Community-driven, free and premium asset packs useful for 2D games.