Skip to content

Commit 3457f12

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 3457f12

File tree

1 file changed

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

1 file changed

+360
-2
lines changed

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

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