From 93b495b1b97a8898bdd0fb80b926a2b1107fd15a Mon Sep 17 00:00:00 2001 From: Alice Cecile Date: Mon, 14 Mar 2022 14:27:38 -0400 Subject: [PATCH 01/12] Initial content port --- .../book/ecs/entities-components/_index.md | 288 +++++++++++++++++- 1 file changed, 286 insertions(+), 2 deletions(-) diff --git a/content/learn/book/ecs/entities-components/_index.md b/content/learn/book/ecs/entities-components/_index.md index ea2b028669..6dd4127f84 100644 --- a/content/learn/book/ecs/entities-components/_index.md +++ b/content/learn/book/ecs/entities-components/_index.md @@ -6,6 +6,290 @@ page_template = "book-section.html" insert_anchor_links = "right" +++ -TODO: explain the basic data model +As we discussed in the introduction to this chapter, **entities** represent objects in your game world, whose data is stored in the form of components. -TODO: show how to create an entity, add components to it, and query for it in pure `bevy_ecs` +## Spawning and despawning entities + +Before you can do much of anything in Bevy, you'll need to **spawn** your first entity, adding it to the app's [`World`]. +Once entities exist, they can likewise be despawned, deleting all of the data stored in their components and removing it from the world. + +There are two APIs to do so. The first is more direct, allowing you to add and remove entities directly on the world. + +```rust +use bevy::prelude::*; + +// Creates a new world +let mut world = World::new(); +// Spawns an entity with no components +world.spawn(); +// Spawns a second entity, keeping track of its unique identifier +let my_entity = world.spawn().id(); +// Uses the second entity's unique identifier to despawn it +world.despawn(my_entity); +``` + +If you're using Bevy as a whole (rather than just [`bevy_ecs`]), you'll tend to find that working with the world directly is rare: +often reserved for [writing tests](https://github.com/bevyengine/bevy/blob/main/tests/how_to_test_systems.rs). +Instead, almost all of your logic will be contained within systems, +which don't have the permissions to immediately spawn or despawn new entities (what if someone else was using that?!). +To work around this, we use **commands**, which have a delayed effect. +For now, let's take a look at how we can use them to work with entities in simple ways (you can read about all the details [later in this chapter](../commands/_index.md)). + +```rust +// This system needs to have a mutable argument with the `Commands` type +// allowing it to queue up commands to be processed at the end of the stage +fn spawning_system(mut commands: Commands){ + // These commands perform the exact same operations + // as the previous code snippet, + // but at the end of the stage, rather than immediately + commands.spawn(); + let my_entity = commands.spawn().id(); + commands.despawn(my_entity); +} +``` + +[`World`]: https://docs.rs/bevy/latest/bevy/ecs/world/struct.World.html +[`bevy_ecs`]: https://crates.io/crates/bevy_ecs + +## Working with components + +Entities are entirely bare when they're spawned: they contain no data other than their unique [`Entity`] identifier. +This of course is not very useful, so let's discuss how we can add and remove components to them which store data and enable behavior through systems. + +[`Entity`]: https://docs.rs/bevy/latest/bevy/ecs/entity/struct.Entity.html + +### Defining components + +To define a component type, we simply implement the [`Component`] trait for a Rust type of our choice. +You will almost always want to use the `#[derive(Component)]` macro to do this for you; which quickly and reliably generates the correct trait implementation. +Any underlying component data must be `Send + Sync + 'static` (enforced by the [trait bounds](https://doc.rust-lang.org/book/ch10-02-traits.html#trait-bound-syntax) on [`Component`]). +This ensures that the data can be sent across the threads safely and allows our [type reflection tools](https://github.com/bevyengine/bevy/tree/main/crates/bevy_reflect) to work correctly. + +With the theory out of the way, let's define some components! + +```rust +// This is a dataless "unit struct", which holds no data of its own. +// In Bevy, these are useful for distinguishing similar entities or toggling behavior +// and are called "marker components" +#[derive(Component)] +struct Combatant; + +// This simple components wrap a u8 in a tuple struct +#[derive(Component)] +struct Life(u8); + +// We can store arbitrary data in our components, as long as it has a 'static lifetime +// Types without lifetimes are always 'static, +// allowing us to safely hold a String, but not a &str +#[derive(Component)] +struct Name(String); + +// Naming your components' fields, +// makes them easier and safer to refer to +#[derive(Component)] +struct Stats { + strength: u8, + dexterity: u8, + intelligence: u8, +} + +// Enum components are great for storing mutually exclusive states +#[derive(Component)] +enum Allegiance { + Friendly, + Neutral, + Hostile +} +``` + +[`Component`]: https://docs.rs/bevy/latest/bevy/ecs/component/trait.Component.html + +### Spawning entities with components + +Now that we have some components defined, let's try adding them to our entities using [`Commands`]. + +```rust +fn spawn_combatants_system(mut commands: Commands) { + commands + .spawn() + // This inserts a data-less `Combatant` component into the entity we're spawning + .insert(Combatant) + // We configure starting component values by passing in concrete instances of our types + .insert(Life(10)) + // By chaining .insert method calls like this, we continue to add more components to our entity + .insert(Name("Gallant".to_string())) + // Instances of named structs are constructed with {field_name: value} + .insert(Stats { + strength: 15, + dexterity: 10, + intelligence: 8, + }) + // Instances of enums are created by picking one of their variants + .insert(Allegiance::Friendly); + + // We've ended our Commands method chain using a ;, + // and so now we can create a second entity + // by calling .spawn() again + commands + .spawn() + .insert(Combatant) + .insert(Life(10)) + .insert(Name("Goofus".to_string())) + .insert(Stats { + strength: 17, + dexterity: 8, + intelligence: 6, + }) + .insert(Allegiance::Hostile); +} +``` + +[`Commands`]: https://docs.rs/bevy/latest/bevy/ecs/system/struct.Commands.html + +### Adding and removing components + +Once an entity is spawned, you can use [`Commands`] to add and remove components from them dynamically. + +```rust + +#[derive(Component)] +struct InCombat; + +// This query returns the `Entity` identifier of all entities +// that have the `Combatant` component but do not yet have the `InCombat` component +fn start_combat_system(query: Query, Without>, mut commands: Commands){ + for entity in query.iter(){ + // The component will be inserted at the end of the current stage + commands.entity(entity).insert(InCombat); + } +} + +// Now to undo our hard work +fn end_combat_system(query: Query, With>, mut commands: Commands){ + for entity in query.iter(){ + // The component will be removed at the end of the current stage + commands.entity(entity).remove(InCombat); + } +} +``` + +## Bundles + +As you might guess, the one-at-a-time component insertion syntax can be both tedious and error-prone as your project grows. +To get around this, Bevy abstracts these patterns using **component bundles**. +These are implemented by implementing the [`Bundle`] trait for a struct; turning each of its fields into a distinct component on your entity when they are inserted. + +Let's try rewriting that code from above. + +```rust +#[derive(Bundle)] +struct CombatantBundle { + combatant: Combatant + life: Life, + stats: Stats, + allegiance: Allegiance, +} + +// We can add new methods to our bundle type that return Self +// to create principled APIs for entity creation. +// The Default trait is the standard tool for creating +// new struct instances without configuration +impl Default for CombatantBundle { + fn default() -> Self { + CombatantBundle { + combatant: Combatant, + life: Life(10), + stats: Stats { + strength: 10, + dexterity: 10, + intelligence: 10, + } + allegiance: Allegiance::Neutral, + } + } +} + +fn spawn_combatants_system(mut commands: Commands) { + commands + .spawn() + // We're using struct-update syntax to modify + // the instance of `CombatantBundle` returned by its default() method + // See the page on Rust Tips and Tricks at the end of this chapter for more info! + .insert_bundle(CombatantBundle{ + stats: Stats { + strength: 15, + dexterity: 10, + intelligence: 8, + } + allegiance: Allegiance::Friendly, + ..Default::default() + }) + // We can continue to chain more .insert or .insert_bundle methods + // to add more components and extend behavior + .insert(Name("Gallant".to_string())); + + commands + // .spawn_bundle is just syntactic sugar for .spawn().insert_bundle + .spawn_bundle(CombatantBundle{ + stats: Stats { + strength: 17, + dexterity: 8, + intelligence: 6, + } + allegiance: Allegiance::Hostile, + ..Default::default() + }) + .insert(Name("Goofus".to_string()));} +``` + +[`Bundle`]: https://docs.rs/bevy/latest/bevy/ecs/bundle/trait.Bundle.html + +### Nested bundles + +As your game grows further in complexity, you may find that you want to reuse various bundles across entities that share some but not all behavior. +One of the tools you can use to do so is **nested bundles**; embedding one bundle of components within another. +Be mindful; this can lead to overwrought, deeply nested code if overused and bundles are [not currently checked](https://github.com/bevyengine/bevy/issues/2387) for duplicate component types. +Later instances of the same type will overwrite earlier ones. + +With those caveats out of the way, let's take a look at the syntax by converting the bundle above to a nested one by creating a bundle of components that deal with related functionality. + +```rust +#[derive(Bundle)] +struct AttackableBundle{ + life: Life, + attack: Attack, + defense: Defense, +} + +#[derive(Bundle)] +struct CombatantBundle { + combatant: Combatant + // This attribute macro marks our attackable_bundle field as a bundle, + // allowing Bevy to properly flatten it out when building the final entity + #[bundle] + attackable_bundle: AttackableBundle, + position: Position, + stats: Stats, + allegiance: Allegiance, +} + +impl Default for CombatantBundle { + fn default() -> Self { + CombatantBundle { + combatant: Combatant, + attackable_bundle: AttackableBundle { + life: Life(10), + attack: Attack(5), + defense: Defense(1), + } + position: Position(0, 0), + stats: Stats { + strength: 10, + dexterity: 10, + intelligence: 10, + } + allegiance: Allegiance::Neutral, + } + } +} +``` From 611368c75fe342daf00eae991821b12bf2c5e39b Mon Sep 17 00:00:00 2001 From: Alice Cecile Date: Tue, 10 May 2022 12:49:12 -0400 Subject: [PATCH 02/12] Minor edits --- .../book/ecs/entities-components/_index.md | 24 +++++++++---------- 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/content/learn/book/ecs/entities-components/_index.md b/content/learn/book/ecs/entities-components/_index.md index 6dd4127f84..7de05a328a 100644 --- a/content/learn/book/ecs/entities-components/_index.md +++ b/content/learn/book/ecs/entities-components/_index.md @@ -6,7 +6,7 @@ page_template = "book-section.html" insert_anchor_links = "right" +++ -As we discussed in the introduction to this chapter, **entities** represent objects in your game world, whose data is stored in the form of components. +As we discussed in the [introduction](../_index.md) to this chapter, **entities** represent objects in your game world, whose data is stored in the form of components. ## Spawning and despawning entities @@ -60,15 +60,15 @@ This of course is not very useful, so let's discuss how we can add and remove co ### Defining components -To define a component type, we simply implement the [`Component`] trait for a Rust type of our choice. -You will almost always want to use the `#[derive(Component)]` macro to do this for you; which quickly and reliably generates the correct trait implementation. +To define a component type, we simply implement the [`Component`] [trait](https://doc.rust-lang.org/book/ch10-02-traits.html) for a Rust type of our choice. +You will almost always want to use the `#[derive(Component)]` [macro](https://doc.rust-lang.org/reference/attributes/derive.html) to do this for you; which quickly and reliably generates the correct trait implementation. Any underlying component data must be `Send + Sync + 'static` (enforced by the [trait bounds](https://doc.rust-lang.org/book/ch10-02-traits.html#trait-bound-syntax) on [`Component`]). -This ensures that the data can be sent across the threads safely and allows our [type reflection tools](https://github.com/bevyengine/bevy/tree/main/crates/bevy_reflect) to work correctly. +This ensures that the data can be [sent across threads safely](https://doc.rust-lang.org/book/ch16-04-extensible-concurrency-sync-and-send.html). With the theory out of the way, let's define some components! ```rust -// This is a dataless "unit struct", which holds no data of its own. +// This is a "unit struct", which holds no data of its own. // In Bevy, these are useful for distinguishing similar entities or toggling behavior // and are called "marker components" #[derive(Component)] @@ -84,7 +84,7 @@ struct Life(u8); #[derive(Component)] struct Name(String); -// Naming your components' fields, +// Naming your components' fields // makes them easier and safer to refer to #[derive(Component)] struct Stats { @@ -157,7 +157,7 @@ struct InCombat; // This query returns the `Entity` identifier of all entities // that have the `Combatant` component but do not yet have the `InCombat` component -fn start_combat_system(query: Query, Without>, mut commands: Commands){ +fn start_combat_system(query: Query, Without>), mut commands: Commands){ for entity in query.iter(){ // The component will be inserted at the end of the current stage commands.entity(entity).insert(InCombat); @@ -165,7 +165,7 @@ fn start_combat_system(query: Query, Without> } // Now to undo our hard work -fn end_combat_system(query: Query, With>, mut commands: Commands){ +fn end_combat_system(query: Query, With>), mut commands: Commands){ for entity in query.iter(){ // The component will be removed at the end of the current stage commands.entity(entity).remove(InCombat); @@ -176,8 +176,8 @@ fn end_combat_system(query: Query, With>, mut ## Bundles As you might guess, the one-at-a-time component insertion syntax can be both tedious and error-prone as your project grows. -To get around this, Bevy abstracts these patterns using **component bundles**. -These are implemented by implementing the [`Bundle`] trait for a struct; turning each of its fields into a distinct component on your entity when they are inserted. +To get around this, Bevy allows you to group components into **component bundles**. +These are defined by deriving the [`Bundle`] trait for a struct; turning each of its fields into a distinct component on your entity when the bundle is inserted. Let's try rewriting that code from above. @@ -248,7 +248,7 @@ fn spawn_combatants_system(mut commands: Commands) { As your game grows further in complexity, you may find that you want to reuse various bundles across entities that share some but not all behavior. One of the tools you can use to do so is **nested bundles**; embedding one bundle of components within another. -Be mindful; this can lead to overwrought, deeply nested code if overused and bundles are [not currently checked](https://github.com/bevyengine/bevy/issues/2387) for duplicate component types. +Try to stick to a single layer of nesting at most; multiple layers of nesting can get quite confusing. Later instances of the same type will overwrite earlier ones. With those caveats out of the way, let's take a look at the syntax by converting the bundle above to a nested one by creating a bundle of components that deal with related functionality. @@ -264,7 +264,7 @@ struct AttackableBundle{ #[derive(Bundle)] struct CombatantBundle { combatant: Combatant - // This attribute macro marks our attackable_bundle field as a bundle, + // This attribute macro marks our attackable_bundle field as a bundle (rather than a component), // allowing Bevy to properly flatten it out when building the final entity #[bundle] attackable_bundle: AttackableBundle, From e3bf2b0dcd182ff6814d7c29a6c3109fe1cdfd28 Mon Sep 17 00:00:00 2001 From: Alice Cecile Date: Tue, 10 May 2022 12:50:26 -0400 Subject: [PATCH 03/12] Update advice about duplicate components in bundles --- content/learn/book/ecs/entities-components/_index.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/content/learn/book/ecs/entities-components/_index.md b/content/learn/book/ecs/entities-components/_index.md index 7de05a328a..e2b5ef2d05 100644 --- a/content/learn/book/ecs/entities-components/_index.md +++ b/content/learn/book/ecs/entities-components/_index.md @@ -249,7 +249,7 @@ fn spawn_combatants_system(mut commands: Commands) { As your game grows further in complexity, you may find that you want to reuse various bundles across entities that share some but not all behavior. One of the tools you can use to do so is **nested bundles**; embedding one bundle of components within another. Try to stick to a single layer of nesting at most; multiple layers of nesting can get quite confusing. -Later instances of the same type will overwrite earlier ones. +Including duplicate components in your bundles in this way will cause a panic. With those caveats out of the way, let's take a look at the syntax by converting the bundle above to a nested one by creating a bundle of components that deal with related functionality. From 2a7fee300ff25da9798edfde6571d8f938b69868 Mon Sep 17 00:00:00 2001 From: Alice Cecile Date: Tue, 10 May 2022 12:56:47 -0400 Subject: [PATCH 04/12] Remove section of exclusive world access --- .../book/ecs/entities-components/_index.md | 37 +++++-------------- 1 file changed, 9 insertions(+), 28 deletions(-) diff --git a/content/learn/book/ecs/entities-components/_index.md b/content/learn/book/ecs/entities-components/_index.md index e2b5ef2d05..4448663f37 100644 --- a/content/learn/book/ecs/entities-components/_index.md +++ b/content/learn/book/ecs/entities-components/_index.md @@ -13,43 +13,24 @@ As we discussed in the [introduction](../_index.md) to this chapter, **entities* Before you can do much of anything in Bevy, you'll need to **spawn** your first entity, adding it to the app's [`World`]. Once entities exist, they can likewise be despawned, deleting all of the data stored in their components and removing it from the world. -There are two APIs to do so. The first is more direct, allowing you to add and remove entities directly on the world. +Spawning and despawning entities can have far-reaching effects, and so cannot be done immediately (unless you are using an [exclusive system](../exclusive-world-access/_index.md)). +As a result, we must use [`Commands`], which queue up work to do later. ```rust -use bevy::prelude::*; - -// Creates a new world -let mut world = World::new(); -// Spawns an entity with no components -world.spawn(); -// Spawns a second entity, keeping track of its unique identifier -let my_entity = world.spawn().id(); -// Uses the second entity's unique identifier to despawn it -world.despawn(my_entity); -``` - -If you're using Bevy as a whole (rather than just [`bevy_ecs`]), you'll tend to find that working with the world directly is rare: -often reserved for [writing tests](https://github.com/bevyengine/bevy/blob/main/tests/how_to_test_systems.rs). -Instead, almost all of your logic will be contained within systems, -which don't have the permissions to immediately spawn or despawn new entities (what if someone else was using that?!). -To work around this, we use **commands**, which have a delayed effect. -For now, let's take a look at how we can use them to work with entities in simple ways (you can read about all the details [later in this chapter](../commands/_index.md)). - -```rust -// This system needs to have a mutable argument with the `Commands` type -// allowing it to queue up commands to be processed at the end of the stage +// The `Commands` system parameter allows us to generate commands +// which operate on the `World` once all of the current systems have finished running fn spawning_system(mut commands: Commands){ - // These commands perform the exact same operations - // as the previous code snippet, - // but at the end of the stage, rather than immediately + // Spawning a single entity with no components commands.spawn(); + // Getting the `Entity` identifier of a new entity let my_entity = commands.spawn().id(); - commands.despawn(my_entity); + // Selecting and then despawning the just-spawned second entity + commands.entity(my_entity).despawn(); } ``` [`World`]: https://docs.rs/bevy/latest/bevy/ecs/world/struct.World.html -[`bevy_ecs`]: https://crates.io/crates/bevy_ecs +[`Commands`]: https://docs.rs/bevy/latest/bevy/ecs/system/struct.Commands.html ## Working with components From f0e85e6690d1e63be4203d69510113ceef04bd42 Mon Sep 17 00:00:00 2001 From: Alice Cecile Date: Tue, 10 May 2022 13:09:26 -0400 Subject: [PATCH 05/12] Revise Introduction --- content/learn/book/ecs/entities-components/_index.md | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/content/learn/book/ecs/entities-components/_index.md b/content/learn/book/ecs/entities-components/_index.md index 4448663f37..f1d7e2d5dd 100644 --- a/content/learn/book/ecs/entities-components/_index.md +++ b/content/learn/book/ecs/entities-components/_index.md @@ -6,7 +6,16 @@ page_template = "book-section.html" insert_anchor_links = "right" +++ -As we discussed in the [introduction](../_index.md) to this chapter, **entities** represent objects in your game world, whose data is stored in the form of components. +**Entities** are the fundamental objects of your game world, whizzing around, storing cameras, being controlled by the player or tracking the state of a button. +On its own, the [`Entity`] type is a simple identifer: it has neither behavior nor data. +Components store this data, and define the overlapping categories that the entity belongs to. + +Informally, we use the term "entity" to refer to the conceptual entry in our [`World`]: all of the component data with the correct identifier, although it's very rare to use all of the data for a single entity at once. +If you're an experienced programmer, you can reason about the [`World`] as something like a (very fast) [`HashMap`] from [`Entity`] to a collection of components. + +[`Entity`]: https://docs.rs/bevy/latest/bevy/ecs/entity/struct.Entity.html +[`HashMap`]: https://doc.rust-lang.org/std/collections/struct.HashMap.html +[`World`]: https://docs.rs/bevy/latest/bevy/ecs/world/struct.World.html ## Spawning and despawning entities @@ -29,7 +38,6 @@ fn spawning_system(mut commands: Commands){ } ``` -[`World`]: https://docs.rs/bevy/latest/bevy/ecs/world/struct.World.html [`Commands`]: https://docs.rs/bevy/latest/bevy/ecs/system/struct.Commands.html ## Working with components From 2d6f972f25843a6cdf8ed7d6e40e5a28a2c7b048 Mon Sep 17 00:00:00 2001 From: Alice Cecile Date: Tue, 10 May 2022 13:20:12 -0400 Subject: [PATCH 06/12] Shorten code blocks --- .../book/ecs/entities-components/_index.md | 25 ++++--------------- 1 file changed, 5 insertions(+), 20 deletions(-) diff --git a/content/learn/book/ecs/entities-components/_index.md b/content/learn/book/ecs/entities-components/_index.md index f1d7e2d5dd..546a5425ed 100644 --- a/content/learn/book/ecs/entities-components/_index.md +++ b/content/learn/book/ecs/entities-components/_index.md @@ -58,23 +58,14 @@ With the theory out of the way, let's define some components! ```rust // This is a "unit struct", which holds no data of its own. -// In Bevy, these are useful for distinguishing similar entities or toggling behavior -// and are called "marker components" #[derive(Component)] struct Combatant; -// This simple components wrap a u8 in a tuple struct +// This simple component wraps a u8 in a tuple struct #[derive(Component)] struct Life(u8); -// We can store arbitrary data in our components, as long as it has a 'static lifetime -// Types without lifetimes are always 'static, -// allowing us to safely hold a String, but not a &str -#[derive(Component)] -struct Name(String); - -// Naming your components' fields -// makes them easier and safer to refer to +// Naming your components' fields makes them easier to refer to #[derive(Component)] struct Stats { strength: u8, @@ -86,7 +77,6 @@ struct Stats { #[derive(Component)] enum Allegiance { Friendly, - Neutral, Hostile } ``` @@ -106,7 +96,6 @@ fn spawn_combatants_system(mut commands: Commands) { // We configure starting component values by passing in concrete instances of our types .insert(Life(10)) // By chaining .insert method calls like this, we continue to add more components to our entity - .insert(Name("Gallant".to_string())) // Instances of named structs are constructed with {field_name: value} .insert(Stats { strength: 15, @@ -123,7 +112,6 @@ fn spawn_combatants_system(mut commands: Commands) { .spawn() .insert(Combatant) .insert(Life(10)) - .insert(Name("Goofus".to_string())) .insert(Stats { strength: 17, dexterity: 8, @@ -212,10 +200,7 @@ fn spawn_combatants_system(mut commands: Commands) { } allegiance: Allegiance::Friendly, ..Default::default() - }) - // We can continue to chain more .insert or .insert_bundle methods - // to add more components and extend behavior - .insert(Name("Gallant".to_string())); + }); commands // .spawn_bundle is just syntactic sugar for .spawn().insert_bundle @@ -227,8 +212,8 @@ fn spawn_combatants_system(mut commands: Commands) { } allegiance: Allegiance::Hostile, ..Default::default() - }) - .insert(Name("Goofus".to_string()));} + }); +} ``` [`Bundle`]: https://docs.rs/bevy/latest/bevy/ecs/bundle/trait.Bundle.html From 17da5daabb19a2459615c83289b7816076ba134c Mon Sep 17 00:00:00 2001 From: Alice Cecile Date: Tue, 10 May 2022 13:54:08 -0400 Subject: [PATCH 07/12] Add section on component design --- .../book/ecs/entities-components/_index.md | 43 +++++++++++++++++++ 1 file changed, 43 insertions(+) diff --git a/content/learn/book/ecs/entities-components/_index.md b/content/learn/book/ecs/entities-components/_index.md index 546a5425ed..02f1d798e3 100644 --- a/content/learn/book/ecs/entities-components/_index.md +++ b/content/learn/book/ecs/entities-components/_index.md @@ -267,3 +267,46 @@ impl Default for CombatantBundle { } } ``` + +## Component design + +Over time, the Bevy community has converged on a few standard pieces of advice for how to structure and define component data: + +- try to keep your components relatively small + - combine common functionality into bundles, not large components + - small modular systems based on common behavior work well + - reducing the amount of data stored improves cache performance and system-parallelism + - keep it as a single component if you need to maintain invariants (such as current life is always less than or equal to max life) + - keep it as a single component if you need methods that operate across several pieces of data (e.g. computing the distance between two points) +- simple methods on components are a good tool for clean, testable code + - logic that is inherent to how the component works (like rolling dice or healing life points) is a great fit + - logic that will only be repeated once generally belongs in systems + - methods make it easier to understand the actual gameplay logic in your systems, and fix bugs in a single place +- marker components are incredibly valuable for extending your design + - it is very common to want to quickly look for "all entities that are a `Tower`", or "all entities that are `Chilled` + - filtering by component presence/absence is (generally) faster and clearer than looping through a list of boolean values + - try to model meaningful groups at several levels of abstraction / along multiple axes: e.g. `Unit`, `Ant`, `Combatant` +- enum components are very expressive, and help reduce bugs + - enums can hold different data in each variant, allowing you to capture information effectively + - if you have a fixed number of options for a value, store it as an enum +- implementing traits like [`Add`] or [`Display`] can provide useful behavior in an idiomatic way +- use [`Deref`] and [`DerefMut`] for tuple structs with a single item ([newtypes]) + - this allows you to access the internal data with `*my_component` instead of `my_component.0` + - more importantly, this allows you to call methods that belong to the wrapped type directly on your component +- define builder methods for your [`Bundle`] types that return [`Self`] + - this is useful to define a friendly interface for how entities of this sort tend to vary + - not as useful as you might hope for upholding invariants; components will be able to be accidentally modified independently later +- use [struct update syntax] to modify component bundles + - [`..default()`] is a particularly common idiom, to modify a struct from its default values +- consider definining traits for related components + - this allows you to ensure a consistent interface + - this can be very powerful in combination with generic systems that use trait bounds + +[`Add`]: https://doc.rust-lang.org/std/ops/trait.Add.html +[`Display`]: https://doc.rust-lang.org/std/path/struct.Display.html +[`Deref`]: https://doc.rust-lang.org/std/ops/trait.Deref.html +[`DerefMut`]: https://doc.rust-lang.org/std/ops/trait.DerefMut.html +[`Self`]: https://doc.rust-lang.org/reference/paths.html#self-1 +[`..default()`]: https://docs.rs/bevy/latest/bevy/prelude/fn.default.html +[newtypes]: https://doc.rust-lang.org/rust-by-example/generics/new_types.html +[struct update syntax]: https://doc.rust-lang.org/book/ch05-01-defining-structs.html#creating-instances-from-other-instances-with-struct-update-syntax From 1835a9f8bcea27beb44276a2224339be57edf7d0 Mon Sep 17 00:00:00 2001 From: Alice Cecile Date: Tue, 10 May 2022 14:17:56 -0400 Subject: [PATCH 08/12] Code typos Co-authored-by: Asier Illarramendi --- content/learn/book/ecs/entities-components/_index.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/content/learn/book/ecs/entities-components/_index.md b/content/learn/book/ecs/entities-components/_index.md index 02f1d798e3..3d73021bf0 100644 --- a/content/learn/book/ecs/entities-components/_index.md +++ b/content/learn/book/ecs/entities-components/_index.md @@ -134,7 +134,7 @@ struct InCombat; // This query returns the `Entity` identifier of all entities // that have the `Combatant` component but do not yet have the `InCombat` component -fn start_combat_system(query: Query, Without>), mut commands: Commands){ +fn start_combat_system(query: Query, Without)>, mut commands: Commands){ for entity in query.iter(){ // The component will be inserted at the end of the current stage commands.entity(entity).insert(InCombat); @@ -142,7 +142,7 @@ fn start_combat_system(query: Query, Without> } // Now to undo our hard work -fn end_combat_system(query: Query, With>), mut commands: Commands){ +fn end_combat_system(query: Query, With)>, mut commands: Commands){ for entity in query.iter(){ // The component will be removed at the end of the current stage commands.entity(entity).remove(InCombat); From d2bff345b1722c3976e7cdf707ae1126a771d73e Mon Sep 17 00:00:00 2001 From: "Alice I. Cecile" Date: Sat, 14 May 2022 11:05:26 -0400 Subject: [PATCH 09/12] Remove complex talk of Component trait bounds --- content/learn/book/ecs/entities-components/_index.md | 2 -- 1 file changed, 2 deletions(-) diff --git a/content/learn/book/ecs/entities-components/_index.md b/content/learn/book/ecs/entities-components/_index.md index 02f1d798e3..858d97a904 100644 --- a/content/learn/book/ecs/entities-components/_index.md +++ b/content/learn/book/ecs/entities-components/_index.md @@ -51,8 +51,6 @@ This of course is not very useful, so let's discuss how we can add and remove co To define a component type, we simply implement the [`Component`] [trait](https://doc.rust-lang.org/book/ch10-02-traits.html) for a Rust type of our choice. You will almost always want to use the `#[derive(Component)]` [macro](https://doc.rust-lang.org/reference/attributes/derive.html) to do this for you; which quickly and reliably generates the correct trait implementation. -Any underlying component data must be `Send + Sync + 'static` (enforced by the [trait bounds](https://doc.rust-lang.org/book/ch10-02-traits.html#trait-bound-syntax) on [`Component`]). -This ensures that the data can be [sent across threads safely](https://doc.rust-lang.org/book/ch16-04-extensible-concurrency-sync-and-send.html). With the theory out of the way, let's define some components! From 77a1b270f1faaebede71115e5f7bb917c8404b89 Mon Sep 17 00:00:00 2001 From: "Alice I. Cecile" Date: Sat, 14 May 2022 11:10:09 -0400 Subject: [PATCH 10/12] Revised Working with Components intro --- content/learn/book/ecs/entities-components/_index.md | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/content/learn/book/ecs/entities-components/_index.md b/content/learn/book/ecs/entities-components/_index.md index 4ee7eb8cba..a5fe88a288 100644 --- a/content/learn/book/ecs/entities-components/_index.md +++ b/content/learn/book/ecs/entities-components/_index.md @@ -42,8 +42,10 @@ fn spawning_system(mut commands: Commands){ ## Working with components -Entities are entirely bare when they're spawned: they contain no data other than their unique [`Entity`] identifier. -This of course is not very useful, so let's discuss how we can add and remove components to them which store data and enable behavior through systems. +Spawning an entity doesn't add any behavior or create a "physical object" in our game like it might in other engines. +Instead, all it does is provide us an [`Entity`] identifer for a collection of component data. + +In order to make this useful, we need to be able to add, remove and modify component data for each entity. [`Entity`]: https://docs.rs/bevy/latest/bevy/ecs/entity/struct.Entity.html From 70f84ecee44ebe3169ec83c929adbd6cfa20c97c Mon Sep 17 00:00:00 2001 From: "Alice I. Cecile" Date: Sat, 14 May 2022 11:12:31 -0400 Subject: [PATCH 11/12] Overwriting behavior of component insertion --- content/learn/book/ecs/entities-components/_index.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/content/learn/book/ecs/entities-components/_index.md b/content/learn/book/ecs/entities-components/_index.md index a5fe88a288..c5f8580e5e 100644 --- a/content/learn/book/ecs/entities-components/_index.md +++ b/content/learn/book/ecs/entities-components/_index.md @@ -150,6 +150,8 @@ fn end_combat_system(query: Query, With)>, mu } ``` +Entities can only ever store one component of each type: inserting another component of the same type will instead overwrite the existing data. + ## Bundles As you might guess, the one-at-a-time component insertion syntax can be both tedious and error-prone as your project grows. From 6b91edef3556674f1768cd5fc3e12b117fd30f7c Mon Sep 17 00:00:00 2001 From: "Alice I. Cecile" Date: Sat, 14 May 2022 11:24:39 -0400 Subject: [PATCH 12/12] Make example code compile --- .../book/ecs/entities-components/_index.md | 104 ++++++++++++++---- 1 file changed, 84 insertions(+), 20 deletions(-) diff --git a/content/learn/book/ecs/entities-components/_index.md b/content/learn/book/ecs/entities-components/_index.md index c5f8580e5e..b0d3909edb 100644 --- a/content/learn/book/ecs/entities-components/_index.md +++ b/content/learn/book/ecs/entities-components/_index.md @@ -26,6 +26,8 @@ Spawning and despawning entities can have far-reaching effects, and so cannot be As a result, we must use [`Commands`], which queue up work to do later. ```rust +# use bevy::ecs::system::Commands; + // The `Commands` system parameter allows us to generate commands // which operate on the `World` once all of the current systems have finished running fn spawning_system(mut commands: Commands){ @@ -57,6 +59,8 @@ You will almost always want to use the `#[derive(Component)]` [macro](https://do With the theory out of the way, let's define some components! ```rust +# use bevy::ecs::component::Component; + // This is a "unit struct", which holds no data of its own. #[derive(Component)] struct Combatant; @@ -88,6 +92,27 @@ enum Allegiance { Now that we have some components defined, let's try adding them to our entities using [`Commands`]. ```rust +# use bevy::ecs::prelude::*; +# +# #[derive(Component)] +# struct Combatant; +# +# #[derive(Component)] +# struct Life(u8); +# +# #[derive(Component)] +# struct Stats { +# strength: u8, +# dexterity: u8, +# intelligence: u8, +# } +# +# #[derive(Component)] +# enum Allegiance { +# Friendly, +# Hostile +# } + fn spawn_combatants_system(mut commands: Commands) { commands .spawn() @@ -128,6 +153,10 @@ fn spawn_combatants_system(mut commands: Commands) { Once an entity is spawned, you can use [`Commands`] to add and remove components from them dynamically. ```rust +# use bevy::ecs::prelude::*; +# +# #[derive(Component)] +# struct Combatant; #[derive(Component)] struct InCombat; @@ -145,7 +174,9 @@ fn start_combat_system(query: Query, Without) fn end_combat_system(query: Query, With)>, mut commands: Commands){ for entity in query.iter(){ // The component will be removed at the end of the current stage - commands.entity(entity).remove(InCombat); + // It is provided as a type parameter, + // as we do not need to know a specific value in order to remove a component of the correct type + commands.entity(entity).remove::(); } } ``` @@ -161,9 +192,30 @@ These are defined by deriving the [`Bundle`] trait for a struct; turning each of Let's try rewriting that code from above. ```rust +# use bevy::prelude::*; +# +# #[derive(Component)] +# struct Combatant; +# +# #[derive(Component)] +# struct Life(u8); +# +# #[derive(Component)] +# struct Stats { +# strength: u8, +# dexterity: u8, +# intelligence: u8, +# } +# +# #[derive(Component)] +# enum Allegiance { +# Friendly, +# Hostile +# } + #[derive(Bundle)] struct CombatantBundle { - combatant: Combatant + combatant: Combatant, life: Life, stats: Stats, allegiance: Allegiance, @@ -182,8 +234,8 @@ impl Default for CombatantBundle { strength: 10, dexterity: 10, intelligence: 10, - } - allegiance: Allegiance::Neutral, + }, + allegiance: Allegiance::Hostile, } } } @@ -199,9 +251,9 @@ fn spawn_combatants_system(mut commands: Commands) { strength: 15, dexterity: 10, intelligence: 8, - } + }, allegiance: Allegiance::Friendly, - ..Default::default() + ..default() }); commands @@ -211,9 +263,9 @@ fn spawn_combatants_system(mut commands: Commands) { strength: 17, dexterity: 8, intelligence: 6, - } + }, allegiance: Allegiance::Hostile, - ..Default::default() + ..default() }); } ``` @@ -230,6 +282,26 @@ Including duplicate components in your bundles in this way will cause a panic. With those caveats out of the way, let's take a look at the syntax by converting the bundle above to a nested one by creating a bundle of components that deal with related functionality. ```rust +# use bevy::prelude::*; +# +# #[derive(Component)] +# struct Combatant; +# +# #[derive(Component)] +# struct Life(u8); +# +# #[derive(Component)] +# struct Attack(u8); +# +# #[derive(Component)] +# struct Defense(u8); +# +# #[derive(Component)] +# enum Allegiance { +# Friendly, +# Hostile +# } + #[derive(Bundle)] struct AttackableBundle{ life: Life, @@ -239,13 +311,11 @@ struct AttackableBundle{ #[derive(Bundle)] struct CombatantBundle { - combatant: Combatant - // This attribute macro marks our attackable_bundle field as a bundle (rather than a component), + combatant: Combatant, + // The #[bundle] attribute marks our attackable_bundle field as a bundle (rather than a component), // allowing Bevy to properly flatten it out when building the final entity #[bundle] attackable_bundle: AttackableBundle, - position: Position, - stats: Stats, allegiance: Allegiance, } @@ -257,14 +327,8 @@ impl Default for CombatantBundle { life: Life(10), attack: Attack(5), defense: Defense(1), - } - position: Position(0, 0), - stats: Stats { - strength: 10, - dexterity: 10, - intelligence: 10, - } - allegiance: Allegiance::Neutral, + }, + allegiance: Allegiance::Hostile, } } }