You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
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
**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.
10
12
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.
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
+
# usebevy::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
+
fnspawning_system(mutcommands:Commands){
34
+
// Spawning a single entity with no components
35
+
commands.spawn();
36
+
// Getting the `Entity` identifier of a new entity
37
+
letmy_entity=commands.spawn().id();
38
+
// Selecting and then despawning the just-spawned second entity
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
+
# usebevy::ecs::component::Component;
63
+
64
+
// This is a "unit struct", which holds no data of its own.
65
+
#[derive(Component)]
66
+
structCombatant;
67
+
68
+
// This simple component wraps a u8 in a tuple struct
69
+
#[derive(Component)]
70
+
structLife(u8);
71
+
72
+
// Naming your components' fields makes them easier to refer to
73
+
#[derive(Component)]
74
+
structStats {
75
+
strength:u8,
76
+
dexterity:u8,
77
+
intelligence:u8,
78
+
}
79
+
80
+
// Enum components are great for storing mutually exclusive states
// 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
+
# usebevy::prelude::*;
196
+
#
197
+
# #[derive(Component)]
198
+
# structCombatant;
199
+
#
200
+
# #[derive(Component)]
201
+
# structLife(u8);
202
+
#
203
+
# #[derive(Component)]
204
+
# structStats {
205
+
# strength:u8,
206
+
# dexterity:u8,
207
+
# intelligence:u8,
208
+
# }
209
+
#
210
+
# #[derive(Component)]
211
+
# enumAllegiance {
212
+
# Friendly,
213
+
# Hostile
214
+
# }
215
+
216
+
#[derive(Bundle)]
217
+
structCombatantBundle {
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
+
implDefaultforCombatantBundle {
229
+
fndefault() ->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
+
fnspawn_combatants_system(mutcommands: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
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
+
# usebevy::prelude::*;
286
+
#
287
+
# #[derive(Component)]
288
+
# structCombatant;
289
+
#
290
+
# #[derive(Component)]
291
+
# structLife(u8);
292
+
#
293
+
# #[derive(Component)]
294
+
# structAttack(u8);
295
+
#
296
+
# #[derive(Component)]
297
+
# structDefense(u8);
298
+
#
299
+
# #[derive(Component)]
300
+
# enumAllegiance {
301
+
# Friendly,
302
+
# Hostile
303
+
# }
304
+
305
+
#[derive(Bundle)]
306
+
structAttackableBundle{
307
+
life:Life,
308
+
attack:Attack,
309
+
defense:Defense,
310
+
}
311
+
312
+
#[derive(Bundle)]
313
+
structCombatantBundle {
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
+
implDefaultforCombatantBundle {
323
+
fndefault() ->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
0 commit comments