From 662dd938c858cd9e55b12b5936f6534fcf7462c1 Mon Sep 17 00:00:00 2001 From: Phil Ellison Date: Sun, 9 Jun 2024 12:07:46 +0100 Subject: [PATCH 1/3] Add example enum Component usage to ecs_guide --- examples/ecs/ecs_guide.rs | 68 +++++++++++++++++++++++++++++++-------- 1 file changed, 55 insertions(+), 13 deletions(-) diff --git a/examples/ecs/ecs_guide.rs b/examples/ecs/ecs_guide.rs index be77af1cc864e..e3b0d5ceb0023 100644 --- a/examples/ecs/ecs_guide.rs +++ b/examples/ecs/ecs_guide.rs @@ -30,6 +30,7 @@ use bevy::{ utils::Duration, }; use rand::random; +use std::fmt; // COMPONENTS: Pieces of functionality we add to entities. These are just normal Rust data types // @@ -46,6 +47,25 @@ struct Score { value: usize, } +// This component tracks how many consecutive rounds a player has/hasn't +// scored in - enums can also be used as components. +#[derive(Component)] +enum PlayerStreak { + Hot(usize), + None, + Cold(usize), +} + +impl fmt::Display for PlayerStreak { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + PlayerStreak::Hot(n) => write!(f, "{n} round hot streak"), + PlayerStreak::None => write!(f, "0 round streak"), + PlayerStreak::Cold(n) => write!(f, "{n} round cold streak"), + } + } +} + // RESOURCES: "Global" state accessible by systems. These are also just normal Rust data types! // @@ -85,20 +105,38 @@ fn new_round_system(game_rules: Res, mut game_state: ResMut) { - for (player, mut score) in &mut query { +// This system updates the score for each entity with the `Player`, `Score` and `PlayerStreak` components. +fn score_system(mut query: Query<(&Player, &mut Score, &mut PlayerStreak)>) { + for (player, mut score, mut streak) in &mut query { let scored_a_point = random::(); if scored_a_point { + // Accessing components immutably is done via a regular reference - `player` + // has type `&Player`. + // + // Accessing components mutably is performed via type `Mut` - `score` + // has type `Mut` and `streak` has type `Mut`. + // + // `Mut` implements `Deref`, so struct fields can be updated using + // standard field update syntax ... score.value += 1; + // ... and matching against enums requires deferencing them + *streak = match *streak { + PlayerStreak::Hot(n) => PlayerStreak::Hot(n + 1), + PlayerStreak::Cold(_) | PlayerStreak::None => PlayerStreak::Hot(1), + }; println!( - "{} scored a point! Their score is: {}", - player.name, score.value + "{} scored a point! Their score is: {} ({})", + player.name, score.value, *streak ); } else { + *streak = match *streak { + PlayerStreak::Hot(_) | PlayerStreak::None => PlayerStreak::Cold(1), + PlayerStreak::Cold(n) => PlayerStreak::Cold(n + 1), + }; + println!( - "{} did not score a point! Their score is: {}", - player.name, score.value + "{} did not score a point! Their score is: {} ({})", + player.name, score.value, *streak ); } } @@ -106,8 +144,8 @@ fn score_system(mut query: Query<(&Player, &mut Score)>) { // this game isn't very fun is it :) } -// This system runs on all entities with the "Player" and "Score" components, but it also -// accesses the "GameRules" resource to determine if a player has won. +// This system runs on all entities with the `Player` and `Score` components, but it also +// accesses the `GameRules` resource to determine if a player has won. fn score_check_system( game_rules: Res, mut game_state: ResMut, @@ -139,8 +177,9 @@ fn game_over_system( // This is a "startup" system that runs exactly once when the app starts up. Startup systems are // generally used to create the initial "state" of our game. The only thing that distinguishes a -// "startup" system from a "normal" system is how it is registered: Startup: -// app.add_systems(Startup, startup_system) Normal: app.add_systems(Update, normal_system) +// "startup" system from a "normal" system is how it is registered: +// Startup: app.add_systems(Startup, startup_system) +// Normal: app.add_systems(Update, normal_system) fn startup_system(mut commands: Commands, mut game_state: ResMut) { // Create our game rules resource commands.insert_resource(GameRules { @@ -157,12 +196,14 @@ fn startup_system(mut commands: Commands, mut game_state: ResMut) { name: "Alice".to_string(), }, Score { value: 0 }, + PlayerStreak::None, ), ( Player { name: "Bob".to_string(), }, Score { value: 0 }, + PlayerStreak::None, ), ]); @@ -189,6 +230,7 @@ fn new_player_system( name: format!("Player {}", game_state.total_players), }, Score { value: 0 }, + PlayerStreak::None, )); println!("Player {} joined the game!", game_state.total_players); @@ -199,7 +241,6 @@ fn new_player_system( // "exclusive system". // WARNING: These will block all parallel execution of other systems until they finish, so they // should generally be avoided if you want to maximize parallelism. -#[allow(dead_code)] fn exclusive_player_system(world: &mut World) { // this does the same thing as "new_player_system" let total_players = world.resource_mut::().total_players; @@ -216,6 +257,7 @@ fn exclusive_player_system(world: &mut World) { name: format!("Player {}", total_players + 1), }, Score { value: 0 }, + PlayerStreak::None, )); let mut game_state = world.resource_mut::(); @@ -225,7 +267,7 @@ fn exclusive_player_system(world: &mut World) { // Sometimes systems need to be stateful. Bevy's ECS provides the `Local` system parameter // for this case. A `Local` refers to a value of type `T` that is owned by the system. -// This value is automatically initialized using `T`'s `FromWorld`* implementation upon the system's initialization upon the system's initialization. +// This value is automatically initialized using `T`'s `FromWorld`* implementation upon the system's initialization. // In this system's `Local` (`counter`), `T` is `u32`. // Therefore, on the first turn, `counter` has a value of 0. // From 5e14a21fdc3b2bd4ea9f040c34d9d165a280c1b0 Mon Sep 17 00:00:00 2001 From: Phil Ellison Date: Sun, 9 Jun 2024 20:11:20 +0100 Subject: [PATCH 2/3] Tweak wording --- examples/ecs/ecs_guide.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/examples/ecs/ecs_guide.rs b/examples/ecs/ecs_guide.rs index e3b0d5ceb0023..5ecc902637f45 100644 --- a/examples/ecs/ecs_guide.rs +++ b/examples/ecs/ecs_guide.rs @@ -47,8 +47,8 @@ struct Score { value: usize, } -// This component tracks how many consecutive rounds a player has/hasn't -// scored in - enums can also be used as components. +// Enums can also be used as components. +// This component tracks how many consecutive rounds a player has/hasn't scored in. #[derive(Component)] enum PlayerStreak { Hot(usize), From b76346c797ebc11f4000b8614151eca6babef1e3 Mon Sep 17 00:00:00 2001 From: Phil Ellison Date: Sun, 9 Jun 2024 20:22:18 +0100 Subject: [PATCH 3/3] Fix typo --- examples/ecs/ecs_guide.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/ecs/ecs_guide.rs b/examples/ecs/ecs_guide.rs index 5ecc902637f45..76fc719141cc9 100644 --- a/examples/ecs/ecs_guide.rs +++ b/examples/ecs/ecs_guide.rs @@ -119,7 +119,7 @@ fn score_system(mut query: Query<(&Player, &mut Score, &mut PlayerStreak)>) { // `Mut` implements `Deref`, so struct fields can be updated using // standard field update syntax ... score.value += 1; - // ... and matching against enums requires deferencing them + // ... and matching against enums requires dereferencing them *streak = match *streak { PlayerStreak::Hot(n) => PlayerStreak::Hot(n + 1), PlayerStreak::Cold(_) | PlayerStreak::None => PlayerStreak::Hot(1),