//! This is a guided introduction to Bevy's "Entity Component System" (ECS)1//! All Bevy app logic is built using the ECS pattern, so definitely pay attention!2//!3//! Why ECS?4//! * Data oriented: Functionality is driven by data5//! * Clean Architecture: Loose coupling of functionality / prevents deeply nested inheritance6//! * High Performance: Massively parallel and cache friendly7//!8//! ECS Definitions:9//!10//! Component: just a normal Rust data type. generally scoped to a single piece of functionality11//! Examples: position, velocity, health, color, name12//!13//! Entity: a collection of components with a unique id14//! Examples: Entity1 { Name("Alice"), Position(0, 0) },15//! Entity2 { Name("Bill"), Position(10, 5) }16//!17//! Resource: a shared global piece of data18//! Examples: asset storage, events, system state19//!20//! System: runs logic on entities, components, and resources21//! Examples: move system, damage system22//!23//! Now that you know a little bit about ECS, lets look at some Bevy code!24//! We will now make a simple "game" to illustrate what Bevy's ECS looks like in practice.2526use bevy::{27app::{AppExit, ScheduleRunnerPlugin},28prelude::*,29};30use core::time::Duration;31use rand::random;32use std::fmt;3334// COMPONENTS: Pieces of functionality we add to entities. These are just normal Rust data types35//3637// Our game will have a number of "players". Each player has a name that identifies them38#[derive(Component)]39struct Player {40name: String,41}4243// Each player also has a score. This component holds on to that score44#[derive(Component)]45struct Score {46value: usize,47}4849// Enums can also be used as components.50// This component tracks how many consecutive rounds a player has/hasn't scored in.51#[derive(Component)]52enum PlayerStreak {53Hot(usize),54None,55Cold(usize),56}5758impl fmt::Display for PlayerStreak {59fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {60match self {61PlayerStreak::Hot(n) => write!(f, "{n} round hot streak"),62PlayerStreak::None => write!(f, "0 round streak"),63PlayerStreak::Cold(n) => write!(f, "{n} round cold streak"),64}65}66}6768// RESOURCES: "Global" state accessible by systems. These are also just normal Rust data types!69//7071// This resource holds information about the game:72#[derive(Resource, Default)]73struct GameState {74current_round: usize,75total_players: usize,76winning_player: Option<String>,77}7879// This resource provides rules for our "game".80#[derive(Resource)]81struct GameRules {82winning_score: usize,83max_rounds: usize,84max_players: usize,85}8687// SYSTEMS: Logic that runs on entities, components, and resources. These generally run once each88// time the app updates.89//9091// This is the simplest type of system. It just prints "This game is fun!" on each run:92fn print_message_system() {93println!("This game is fun!");94}9596// Systems can also read and modify resources. This system starts a new "round" on each update:97// NOTE: "mut" denotes that the resource is "mutable"98// Res<GameRules> is read-only. ResMut<GameState> can modify the resource99fn new_round_system(game_rules: Res<GameRules>, mut game_state: ResMut<GameState>) {100game_state.current_round += 1;101println!(102"Begin round {} of {}",103game_state.current_round, game_rules.max_rounds104);105}106107// This system updates the score for each entity with the `Player`, `Score` and `PlayerStreak` components.108fn score_system(mut query: Query<(&Player, &mut Score, &mut PlayerStreak)>) {109for (player, mut score, mut streak) in &mut query {110let scored_a_point = random::<bool>();111if scored_a_point {112// Accessing components immutably is done via a regular reference - `player`113// has type `&Player`.114//115// Accessing components mutably is performed via type `Mut<T>` - `score`116// has type `Mut<Score>` and `streak` has type `Mut<PlayerStreak>`.117//118// `Mut<T>` implements `Deref<T>`, so struct fields can be updated using119// standard field update syntax ...120score.value += 1;121// ... and matching against enums requires dereferencing them122*streak = match *streak {123PlayerStreak::Hot(n) => PlayerStreak::Hot(n + 1),124PlayerStreak::Cold(_) | PlayerStreak::None => PlayerStreak::Hot(1),125};126println!(127"{} scored a point! Their score is: {} ({})",128player.name, score.value, *streak129);130} else {131*streak = match *streak {132PlayerStreak::Hot(_) | PlayerStreak::None => PlayerStreak::Cold(1),133PlayerStreak::Cold(n) => PlayerStreak::Cold(n + 1),134};135136println!(137"{} did not score a point! Their score is: {} ({})",138player.name, score.value, *streak139);140}141}142143// this game isn't very fun is it :)144}145146// This system runs on all entities with the `Player` and `Score` components, but it also147// accesses the `GameRules` resource to determine if a player has won.148fn score_check_system(149game_rules: Res<GameRules>,150mut game_state: ResMut<GameState>,151query: Query<(&Player, &Score)>,152) {153for (player, score) in &query {154if score.value == game_rules.winning_score {155game_state.winning_player = Some(player.name.clone());156}157}158}159160// This system ends the game if we meet the right conditions. This fires an AppExit event, which161// tells our App to quit. Check out the "event.rs" example if you want to learn more about using162// events.163fn game_over_system(164game_rules: Res<GameRules>,165game_state: Res<GameState>,166mut app_exit_events: EventWriter<AppExit>,167) {168if let Some(ref player) = game_state.winning_player {169println!("{player} won the game!");170app_exit_events.write(AppExit::Success);171} else if game_state.current_round == game_rules.max_rounds {172println!("Ran out of rounds. Nobody wins!");173app_exit_events.write(AppExit::Success);174}175}176177// This is a "startup" system that runs exactly once when the app starts up. Startup systems are178// generally used to create the initial "state" of our game. The only thing that distinguishes a179// "startup" system from a "normal" system is how it is registered:180// Startup: app.add_systems(Startup, startup_system)181// Normal: app.add_systems(Update, normal_system)182fn startup_system(mut commands: Commands, mut game_state: ResMut<GameState>) {183// Create our game rules resource184commands.insert_resource(GameRules {185max_rounds: 10,186winning_score: 4,187max_players: 4,188});189190// Add some players to our world. Players start with a score of 0 ... we want our game to be191// fair!192commands.spawn_batch(vec![193(194Player {195name: "Alice".to_string(),196},197Score { value: 0 },198PlayerStreak::None,199),200(201Player {202name: "Bob".to_string(),203},204Score { value: 0 },205PlayerStreak::None,206),207]);208209// set the total players to "2"210game_state.total_players = 2;211}212213// This system uses a command buffer to (potentially) add a new player to our game on each214// iteration. Normal systems cannot safely access the World instance directly because they run in215// parallel. Our World contains all of our components, so mutating arbitrary parts of it in parallel216// is not thread safe. Command buffers give us the ability to queue up changes to our World without217// directly accessing it218fn new_player_system(219mut commands: Commands,220game_rules: Res<GameRules>,221mut game_state: ResMut<GameState>,222) {223// Randomly add a new player224let add_new_player = random::<bool>();225if add_new_player && game_state.total_players < game_rules.max_players {226game_state.total_players += 1;227commands.spawn((228Player {229name: format!("Player {}", game_state.total_players),230},231Score { value: 0 },232PlayerStreak::None,233));234235println!("Player {} joined the game!", game_state.total_players);236}237}238239// If you really need full, immediate read/write access to the world or resources, you can use an240// "exclusive system".241// WARNING: These will block all parallel execution of other systems until they finish, so they242// should generally be avoided if you want to maximize parallelism.243fn exclusive_player_system(world: &mut World) {244// this does the same thing as "new_player_system"245let total_players = world.resource_mut::<GameState>().total_players;246let should_add_player = {247let game_rules = world.resource::<GameRules>();248let add_new_player = random::<bool>();249add_new_player && total_players < game_rules.max_players250};251// Randomly add a new player252if should_add_player {253println!("Player {} has joined the game!", total_players + 1);254world.spawn((255Player {256name: format!("Player {}", total_players + 1),257},258Score { value: 0 },259PlayerStreak::None,260));261262let mut game_state = world.resource_mut::<GameState>();263game_state.total_players += 1;264}265}266267// Sometimes systems need to be stateful. Bevy's ECS provides the `Local` system parameter268// for this case. A `Local<T>` refers to a value of type `T` that is owned by the system.269// This value is automatically initialized using `T`'s `FromWorld`* implementation upon the system's initialization.270// In this system's `Local` (`counter`), `T` is `u32`.271// Therefore, on the first turn, `counter` has a value of 0.272//273// *: `FromWorld` is a trait which creates a value using the contents of the `World`.274// For any type which is `Default`, like `u32` in this example, `FromWorld` creates the default value.275fn print_at_end_round(mut counter: Local<u32>) {276*counter += 1;277println!("In set 'Last' for the {}th time", *counter);278// Print an empty line between rounds279println!();280}281282/// A group of related system sets, used for controlling the order of systems. Systems can be283/// added to any number of sets.284#[derive(SystemSet, Debug, Hash, PartialEq, Eq, Clone)]285enum MySystems {286BeforeRound,287Round,288AfterRound,289}290291// Our Bevy app's entry point292fn main() {293// Bevy apps are created using the builder pattern. We use the builder to add systems,294// resources, and plugins to our app295App::new()296// Resources that implement the Default or FromWorld trait can be added like this:297.init_resource::<GameState>()298// Plugins are just a grouped set of app builder calls (just like we're doing here).299// We could easily turn our game into a plugin, but you can check out the plugin example for300// that :) The plugin below runs our app's "system schedule" once every 5 seconds.301.add_plugins(ScheduleRunnerPlugin::run_loop(Duration::from_secs(5)))302// `Startup` systems run exactly once BEFORE all other systems. These are generally used for303// app initialization code (ex: adding entities and resources)304.add_systems(Startup, startup_system)305// `Update` systems run once every update. These are generally used for "real-time app logic"306.add_systems(Update, print_message_system)307// SYSTEM EXECUTION ORDER308//309// Each system belongs to a `Schedule`, which controls the execution strategy and broad order310// of the systems within each tick. The `Startup` schedule holds311// startup systems, which are run a single time before `Update` runs. `Update` runs once per app update,312// which is generally one "frame" or one "tick".313//314// By default, all systems in a `Schedule` run in parallel, except when they require mutable access to a315// piece of data. This is efficient, but sometimes order matters.316// For example, we want our "game over" system to execute after all other systems to ensure317// we don't accidentally run the game for an extra round.318//319// You can force an explicit ordering between systems using the `.before` or `.after` methods.320// Systems will not be scheduled until all of the systems that they have an "ordering dependency" on have321// completed.322// There are other schedules, such as `Last` which runs at the very end of each run.323.add_systems(Last, print_at_end_round)324// We can also create new system sets, and order them relative to other system sets.325// Here is what our games execution order will look like:326// "before_round": new_player_system, new_round_system327// "round": print_message_system, score_system328// "after_round": score_check_system, game_over_system329.configure_sets(330Update,331// chain() will ensure sets run in the order they are listed332(333MySystems::BeforeRound,334MySystems::Round,335MySystems::AfterRound,336)337.chain(),338)339// The add_systems function is powerful. You can define complex system configurations with ease!340.add_systems(341Update,342(343// These `BeforeRound` systems will run before `Round` systems, thanks to the chained set configuration344(345// You can also chain systems! new_round_system will run first, followed by new_player_system346(new_round_system, new_player_system).chain(),347exclusive_player_system,348)349// All of the systems in the tuple above will be added to this set350.in_set(MySystems::BeforeRound),351// This `Round` system will run after the `BeforeRound` systems thanks to the chained set configuration352score_system.in_set(MySystems::Round),353// These `AfterRound` systems will run after the `Round` systems thanks to the chained set configuration354(355score_check_system,356// In addition to chain(), you can also use `before(system)` and `after(system)`. This also works357// with sets!358game_over_system.after(score_check_system),359)360.in_set(MySystems::AfterRound),361),362)363// This call to run() starts the app we just built!364.run();365}366367368