Skip to content

Commit 78f67b8

Browse files
Entities and components section for ECS chapter (#294)
Ported from #182. # Status - [x] revisit and revise - [x] remove all use of direct world APIs - [x] distinguish between `Entity` type and entity concept
1 parent 7958aff commit 78f67b8

File tree

1 file changed

+369
-2
lines changed
  • content/learn/book/ecs/entities-components

1 file changed

+369
-2
lines changed

content/learn/book/ecs/entities-components/_index.md

Lines changed: 369 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,373 @@ page_template = "book-section.html"
66
insert_anchor_links = "right"
77
+++
88

9-
TODO: explain the basic data model
9+
**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.
10+
On its own, the [`Entity`] type is a simple identifer: it has neither behavior nor data.
11+
Components store this data, and define the overlapping categories that the entity belongs to.
1012

11-
TODO: show how to create an entity, add components to it, and query for it in pure `bevy_ecs`
13+
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.
14+
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.
15+
16+
[`Entity`]: https://docs.rs/bevy/latest/bevy/ecs/entity/struct.Entity.html
17+
[`HashMap`]: https://doc.rust-lang.org/std/collections/struct.HashMap.html
18+
[`World`]: https://docs.rs/bevy/latest/bevy/ecs/world/struct.World.html
19+
20+
## Spawning and despawning entities
21+
22+
Before you can do much of anything in Bevy, you'll need to **spawn** your first entity, adding it to the app's [`World`].
23+
Once entities exist, they can likewise be despawned, deleting all of the data stored in their components and removing it from the world.
24+
25+
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)).
26+
As a result, we must use [`Commands`], which queue up work to do later.
27+
28+
```rust
29+
# use bevy::ecs::system::Commands;
30+
31+
// The `Commands` system parameter allows us to generate commands
32+
// which operate on the `World` once all of the current systems have finished running
33+
fn spawning_system(mut commands: Commands){
34+
// Spawning a single entity with no components
35+
commands.spawn();
36+
// Getting the `Entity` identifier of a new entity
37+
let my_entity = commands.spawn().id();
38+
// Selecting and then despawning the just-spawned second entity
39+
commands.entity(my_entity).despawn();
40+
}
41+
```
42+
43+
[`Commands`]: https://docs.rs/bevy/latest/bevy/ecs/system/struct.Commands.html
44+
45+
## Working with components
46+
47+
Spawning an entity doesn't add any behavior or create a "physical object" in our game like it might in other engines.
48+
Instead, all it does is provide us an [`Entity`] identifer for a collection of component data.
49+
50+
In order to make this useful, we need to be able to add, remove and modify component data for each entity.
51+
52+
[`Entity`]: https://docs.rs/bevy/latest/bevy/ecs/entity/struct.Entity.html
53+
54+
### Defining components
55+
56+
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.
57+
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.
58+
59+
With the theory out of the way, let's define some components!
60+
61+
```rust
62+
# use bevy::ecs::component::Component;
63+
64+
// This is a "unit struct", which holds no data of its own.
65+
#[derive(Component)]
66+
struct Combatant;
67+
68+
// This simple component wraps a u8 in a tuple struct
69+
#[derive(Component)]
70+
struct Life(u8);
71+
72+
// Naming your components' fields makes them easier to refer to
73+
#[derive(Component)]
74+
struct Stats {
75+
strength: u8,
76+
dexterity: u8,
77+
intelligence: u8,
78+
}
79+
80+
// Enum components are great for storing mutually exclusive states
81+
#[derive(Component)]
82+
enum Allegiance {
83+
Friendly,
84+
Hostile
85+
}
86+
```
87+
88+
[`Component`]: https://docs.rs/bevy/latest/bevy/ecs/component/trait.Component.html
89+
90+
### Spawning entities with components
91+
92+
Now that we have some components defined, let's try adding them to our entities using [`Commands`].
93+
94+
```rust
95+
# use bevy::ecs::prelude::*;
96+
#
97+
# #[derive(Component)]
98+
# struct Combatant;
99+
#
100+
# #[derive(Component)]
101+
# struct Life(u8);
102+
#
103+
# #[derive(Component)]
104+
# struct Stats {
105+
# strength: u8,
106+
# dexterity: u8,
107+
# intelligence: u8,
108+
# }
109+
#
110+
# #[derive(Component)]
111+
# enum Allegiance {
112+
# Friendly,
113+
# Hostile
114+
# }
115+
116+
fn spawn_combatants_system(mut commands: Commands) {
117+
commands
118+
.spawn()
119+
// This inserts a data-less `Combatant` component into the entity we're spawning
120+
.insert(Combatant)
121+
// We configure starting component values by passing in concrete instances of our types
122+
.insert(Life(10))
123+
// By chaining .insert method calls like this, we continue to add more components to our entity
124+
// Instances of named structs are constructed with {field_name: value}
125+
.insert(Stats {
126+
strength: 15,
127+
dexterity: 10,
128+
intelligence: 8,
129+
})
130+
// Instances of enums are created by picking one of their variants
131+
.insert(Allegiance::Friendly);
132+
133+
// We've ended our Commands method chain using a ;,
134+
// and so now we can create a second entity
135+
// by calling .spawn() again
136+
commands
137+
.spawn()
138+
.insert(Combatant)
139+
.insert(Life(10))
140+
.insert(Stats {
141+
strength: 17,
142+
dexterity: 8,
143+
intelligence: 6,
144+
})
145+
.insert(Allegiance::Hostile);
146+
}
147+
```
148+
149+
[`Commands`]: https://docs.rs/bevy/latest/bevy/ecs/system/struct.Commands.html
150+
151+
### Adding and removing components
152+
153+
Once an entity is spawned, you can use [`Commands`] to add and remove components from them dynamically.
154+
155+
```rust
156+
# use bevy::ecs::prelude::*;
157+
#
158+
# #[derive(Component)]
159+
# struct Combatant;
160+
161+
#[derive(Component)]
162+
struct InCombat;
163+
164+
// This query returns the `Entity` identifier of all entities
165+
// that have the `Combatant` component but do not yet have the `InCombat` component
166+
fn start_combat_system(query: Query<Entity, (With<Combatant>, Without<InCombat>)>, mut commands: Commands){
167+
for entity in query.iter(){
168+
// The component will be inserted at the end of the current stage
169+
commands.entity(entity).insert(InCombat);
170+
}
171+
}
172+
173+
// Now to undo our hard work
174+
fn end_combat_system(query: Query<Entity, (With<Combatant>, With<InCombat>)>, mut commands: Commands){
175+
for entity in query.iter(){
176+
// The component will be removed at the end of the current stage
177+
// It is provided as a type parameter,
178+
// as we do not need to know a specific value in order to remove a component of the correct type
179+
commands.entity(entity).remove::<InCombat>();
180+
}
181+
}
182+
```
183+
184+
Entities can only ever store one component of each type: inserting another component of the same type will instead overwrite the existing data.
185+
186+
## Bundles
187+
188+
As you might guess, the one-at-a-time component insertion syntax can be both tedious and error-prone as your project grows.
189+
To get around this, Bevy allows you to group components into **component bundles**.
190+
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.
191+
192+
Let's try rewriting that code from above.
193+
194+
```rust
195+
# use bevy::prelude::*;
196+
#
197+
# #[derive(Component)]
198+
# struct Combatant;
199+
#
200+
# #[derive(Component)]
201+
# struct Life(u8);
202+
#
203+
# #[derive(Component)]
204+
# struct Stats {
205+
# strength: u8,
206+
# dexterity: u8,
207+
# intelligence: u8,
208+
# }
209+
#
210+
# #[derive(Component)]
211+
# enum Allegiance {
212+
# Friendly,
213+
# Hostile
214+
# }
215+
216+
#[derive(Bundle)]
217+
struct CombatantBundle {
218+
combatant: Combatant,
219+
life: Life,
220+
stats: Stats,
221+
allegiance: Allegiance,
222+
}
223+
224+
// We can add new methods to our bundle type that return Self
225+
// to create principled APIs for entity creation.
226+
// The Default trait is the standard tool for creating
227+
// new struct instances without configuration
228+
impl Default for CombatantBundle {
229+
fn default() -> Self {
230+
CombatantBundle {
231+
combatant: Combatant,
232+
life: Life(10),
233+
stats: Stats {
234+
strength: 10,
235+
dexterity: 10,
236+
intelligence: 10,
237+
},
238+
allegiance: Allegiance::Hostile,
239+
}
240+
}
241+
}
242+
243+
fn spawn_combatants_system(mut commands: Commands) {
244+
commands
245+
.spawn()
246+
// We're using struct-update syntax to modify
247+
// the instance of `CombatantBundle` returned by its default() method
248+
// See the page on Rust Tips and Tricks at the end of this chapter for more info!
249+
.insert_bundle(CombatantBundle{
250+
stats: Stats {
251+
strength: 15,
252+
dexterity: 10,
253+
intelligence: 8,
254+
},
255+
allegiance: Allegiance::Friendly,
256+
..default()
257+
});
258+
259+
commands
260+
// .spawn_bundle is just syntactic sugar for .spawn().insert_bundle
261+
.spawn_bundle(CombatantBundle{
262+
stats: Stats {
263+
strength: 17,
264+
dexterity: 8,
265+
intelligence: 6,
266+
},
267+
allegiance: Allegiance::Hostile,
268+
..default()
269+
});
270+
}
271+
```
272+
273+
[`Bundle`]: https://docs.rs/bevy/latest/bevy/ecs/bundle/trait.Bundle.html
274+
275+
### Nested bundles
276+
277+
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.
278+
One of the tools you can use to do so is **nested bundles**; embedding one bundle of components within another.
279+
Try to stick to a single layer of nesting at most; multiple layers of nesting can get quite confusing.
280+
Including duplicate components in your bundles in this way will cause a panic.
281+
282+
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.
283+
284+
```rust
285+
# use bevy::prelude::*;
286+
#
287+
# #[derive(Component)]
288+
# struct Combatant;
289+
#
290+
# #[derive(Component)]
291+
# struct Life(u8);
292+
#
293+
# #[derive(Component)]
294+
# struct Attack(u8);
295+
#
296+
# #[derive(Component)]
297+
# struct Defense(u8);
298+
#
299+
# #[derive(Component)]
300+
# enum Allegiance {
301+
# Friendly,
302+
# Hostile
303+
# }
304+
305+
#[derive(Bundle)]
306+
struct AttackableBundle{
307+
life: Life,
308+
attack: Attack,
309+
defense: Defense,
310+
}
311+
312+
#[derive(Bundle)]
313+
struct CombatantBundle {
314+
combatant: Combatant,
315+
// The #[bundle] attribute marks our attackable_bundle field as a bundle (rather than a component),
316+
// allowing Bevy to properly flatten it out when building the final entity
317+
#[bundle]
318+
attackable_bundle: AttackableBundle,
319+
allegiance: Allegiance,
320+
}
321+
322+
impl Default for CombatantBundle {
323+
fn default() -> Self {
324+
CombatantBundle {
325+
combatant: Combatant,
326+
attackable_bundle: AttackableBundle {
327+
life: Life(10),
328+
attack: Attack(5),
329+
defense: Defense(1),
330+
},
331+
allegiance: Allegiance::Hostile,
332+
}
333+
}
334+
}
335+
```
336+
337+
## Component design
338+
339+
Over time, the Bevy community has converged on a few standard pieces of advice for how to structure and define component data:
340+
341+
- try to keep your components relatively small
342+
- combine common functionality into bundles, not large components
343+
- small modular systems based on common behavior work well
344+
- reducing the amount of data stored improves cache performance and system-parallelism
345+
- 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)
346+
- 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)
347+
- simple methods on components are a good tool for clean, testable code
348+
- logic that is inherent to how the component works (like rolling dice or healing life points) is a great fit
349+
- logic that will only be repeated once generally belongs in systems
350+
- methods make it easier to understand the actual gameplay logic in your systems, and fix bugs in a single place
351+
- marker components are incredibly valuable for extending your design
352+
- it is very common to want to quickly look for "all entities that are a `Tower`", or "all entities that are `Chilled`
353+
- filtering by component presence/absence is (generally) faster and clearer than looping through a list of boolean values
354+
- try to model meaningful groups at several levels of abstraction / along multiple axes: e.g. `Unit`, `Ant`, `Combatant`
355+
- enum components are very expressive, and help reduce bugs
356+
- enums can hold different data in each variant, allowing you to capture information effectively
357+
- if you have a fixed number of options for a value, store it as an enum
358+
- implementing traits like [`Add`] or [`Display`] can provide useful behavior in an idiomatic way
359+
- use [`Deref`] and [`DerefMut`] for tuple structs with a single item ([newtypes])
360+
- this allows you to access the internal data with `*my_component` instead of `my_component.0`
361+
- more importantly, this allows you to call methods that belong to the wrapped type directly on your component
362+
- define builder methods for your [`Bundle`] types that return [`Self`]
363+
- this is useful to define a friendly interface for how entities of this sort tend to vary
364+
- not as useful as you might hope for upholding invariants; components will be able to be accidentally modified independently later
365+
- use [struct update syntax] to modify component bundles
366+
- [`..default()`] is a particularly common idiom, to modify a struct from its default values
367+
- consider definining traits for related components
368+
- this allows you to ensure a consistent interface
369+
- this can be very powerful in combination with generic systems that use trait bounds
370+
371+
[`Add`]: https://doc.rust-lang.org/std/ops/trait.Add.html
372+
[`Display`]: https://doc.rust-lang.org/std/path/struct.Display.html
373+
[`Deref`]: https://doc.rust-lang.org/std/ops/trait.Deref.html
374+
[`DerefMut`]: https://doc.rust-lang.org/std/ops/trait.DerefMut.html
375+
[`Self`]: https://doc.rust-lang.org/reference/paths.html#self-1
376+
[`..default()`]: https://docs.rs/bevy/latest/bevy/prelude/fn.default.html
377+
[newtypes]: https://doc.rust-lang.org/rust-by-example/generics/new_types.html
378+
[struct update syntax]: https://doc.rust-lang.org/book/ch05-01-defining-structs.html#creating-instances-from-other-instances-with-struct-update-syntax

0 commit comments

Comments
 (0)