Skip to content

Commit

Permalink
Entities and components section for ECS chapter (#294)
Browse files Browse the repository at this point in the history
Ported from #182.

# Status

- [x] revisit and revise
- [x] remove all use of direct world APIs
- [x] distinguish between `Entity` type and entity concept
  • Loading branch information
alice-i-cecile committed May 14, 2022
1 parent 7958aff commit 78f67b8
Showing 1 changed file with 369 additions and 2 deletions.
371 changes: 369 additions & 2 deletions content/learn/book/ecs/entities-components/_index.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,373 @@ page_template = "book-section.html"
insert_anchor_links = "right"
+++

TODO: explain the basic data model
**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.

TODO: show how to create an entity, add components to it, and query for it in pure `bevy_ecs`
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

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.

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::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){
// Spawning a single entity with no components
commands.spawn();
// Getting the `Entity` identifier of a new entity
let my_entity = commands.spawn().id();
// Selecting and then despawning the just-spawned second entity
commands.entity(my_entity).despawn();
}
```

[`Commands`]: https://docs.rs/bevy/latest/bevy/ecs/system/struct.Commands.html

## Working with components

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

### Defining components

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.

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;

// This simple component wraps a u8 in a tuple struct
#[derive(Component)]
struct Life(u8);

// Naming your components' fields makes them easier 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,
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
# 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()
// 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
// 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(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
# use bevy::ecs::prelude::*;
#
# #[derive(Component)]
# struct Combatant;

#[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<Entity, (With<Combatant>, Without<InCombat>)>, 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<Entity, (With<Combatant>, With<InCombat>)>, mut commands: Commands){
for entity in query.iter(){
// The component will be removed at the end of the current stage
// 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::<InCombat>();
}
}
```

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.
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.

```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,
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::Hostile,
}
}
}

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()
});

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()
});
}
```

[`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.
Try to stick to a single layer of nesting at most; multiple layers of nesting can get quite confusing.
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,
attack: Attack,
defense: Defense,
}

#[derive(Bundle)]
struct CombatantBundle {
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,
allegiance: Allegiance,
}

impl Default for CombatantBundle {
fn default() -> Self {
CombatantBundle {
combatant: Combatant,
attackable_bundle: AttackableBundle {
life: Life(10),
attack: Attack(5),
defense: Defense(1),
},
allegiance: Allegiance::Hostile,
}
}
}
```

## 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

0 comments on commit 78f67b8

Please sign in to comment.