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
+
// 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
+
fnspawning_system(mutcommands:Commands){
32
+
// Spawning a single entity with no components
33
+
commands.spawn();
34
+
// Getting the `Entity` identifier of a new entity
35
+
letmy_entity=commands.spawn().id();
36
+
// 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.
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
+
# usebevy::ecs::component::Component;
61
+
62
+
// This is a "unit struct", which holds no data of its own.
63
+
#[derive(Component)]
64
+
structCombatant;
65
+
66
+
// This simple component wraps a u8 in a tuple struct
67
+
#[derive(Component)]
68
+
structLife(u8);
69
+
70
+
// Naming your components' fields makes them easier to refer to
71
+
#[derive(Component)]
72
+
structStats {
73
+
strength:u8,
74
+
dexterity:u8,
75
+
intelligence:u8,
76
+
}
77
+
78
+
// Enum components are great for storing mutually exclusive states
// 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
+
# usebevy::prelude::*;
192
+
#
193
+
# #[derive(Component)]
194
+
# structCombatant;
195
+
#
196
+
# #[derive(Component)]
197
+
# structLife(u8);
198
+
#
199
+
# #[derive(Component)]
200
+
# structStats {
201
+
# strength:u8,
202
+
# dexterity:u8,
203
+
# intelligence:u8,
204
+
# }
205
+
#
206
+
# #[derive(Component)]
207
+
# enumAllegiance {
208
+
# Friendly,
209
+
# Hostile
210
+
# }
211
+
212
+
#[derive(Bundle)]
213
+
structCombatantBundle {
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
+
implDefaultforCombatantBundle {
225
+
fndefault() ->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
+
fnspawn_combatants_system(mutcommands: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
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
+
# usebevy::prelude::*;
282
+
#
283
+
# #[derive(Component)]
284
+
# structCombatant;
285
+
#
286
+
# #[derive(Component)]
287
+
# structLife(u8);
288
+
#
289
+
# # #[derive(Component)]
290
+
# structAttack(u8);
291
+
#
292
+
# # #[derive(Component)]
293
+
# structDefense(u8);
294
+
295
+
296
+
#[derive(Bundle)]
297
+
structAttackableBundle{
298
+
life:Life,
299
+
attack:Attack,
300
+
defense:Defense,
301
+
}
302
+
303
+
#[derive(Bundle)]
304
+
structCombatantBundle {
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
+
implDefaultforCombatantBundle {
314
+
fndefault() ->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
0 commit comments