325e278af1
Add plugin skeleton with versioned skill directories, TODO SKILL.md scaffolds with goals for skill-creator, and copy over bevy-skill-research reports from spacecraft into docs/workpad/research/.
71 lines
2.9 KiB
Markdown
71 lines
2.9 KiB
Markdown
# Bevy Skill Research: Hierarchy & Relationships
|
|
|
|
> **Bevy version**: 0.18, with 0.19 notes
|
|
> **Date**: 2026-07-07
|
|
|
|
## ChildOf Replaces Parent (Bevy 0.16+)
|
|
|
|
- `Parent` renamed to **`ChildOf(pub Entity)`** — "entities with ChildOf are children, not parents"
|
|
- `Deref` removed; use **`child_of.parent()`** to get the parent Entity
|
|
- `ChildOf` implements `Relationship`; `Children` implements `RelationshipTarget` — they auto-sync via hooks
|
|
- Adding `ChildOf(parent)` to child auto-inserts `Children` on parent; removing it auto-updates parent
|
|
- Despawning a parent **auto-despawns all descendants** (was `despawn_recursive`, now just `despawn()`)
|
|
|
|
## Spawning Children
|
|
|
|
```rust
|
|
// Insert ChildOf directly
|
|
commands.spawn(ChildOf(parent));
|
|
|
|
// Inline child on parent
|
|
commands.entity(parent).with_child((SpriteBundle::default(), ChildOf(parent)));
|
|
|
|
// Multiple children via closure
|
|
commands.spawn_empty().with_children(|spawner: &mut ChildSpawnerCommands| {
|
|
spawner.spawn(MyComponent(255));
|
|
let parent = spawner.target_entity(); // was parent_entity()
|
|
});
|
|
|
|
// Add existing entities as children
|
|
commands.entity(parent).add_child(child);
|
|
commands.entity(parent).add_children(&[child1, child2]);
|
|
commands.entity(parent).insert_children(index, &[child1, child2]);
|
|
```
|
|
|
|
## Querying Hierarchy
|
|
|
|
- **`Children`** derefs to `[Entity]`, implements `IntoIterator`
|
|
- Correct iteration: **`for child in &children`** or **`children.into_iter()`**
|
|
- Wrong: ~~`children.iter().copied()`~~ — unnecessary, `IntoIterator` yields `Entity` directly
|
|
- Methods: `len()`, `is_empty()`, `contains()`, `first()`, `last()`, `sort_by()`
|
|
- Query extensions (0.16+): `query.related::<ChildOf>(entity)`, `query.relationship_sources::<Children>(entity)`
|
|
|
|
## Detaching vs Despawning (0.18 Renames)
|
|
|
|
| Old name (≤0.17) | New name (0.18+) | Effect |
|
|
|-------------------|------------------|--------|
|
|
| `remove_children` | **`detach_children`** | Detach only, do NOT despawn |
|
|
| `clear_children` | **`detach_all_children`** | Detach all, do NOT despawn |
|
|
| `remove_child` | **`detach_child`** | Detach one, do NOT despawn |
|
|
|
|
To actually despawn children: `despawn_related::<Children>()`
|
|
|
|
## Transform Propagation
|
|
|
|
- `GlobalTransform` is auto-propagated through `ChildOf` hierarchy
|
|
- Child `Transform` is relative to parent; `GlobalTransform` is computed world-space
|
|
- Propagation runs in **`PostUpdate`** — reading `GlobalTransform` in `Update` gives stale (last-frame) values
|
|
|
|
## Common Mistakes
|
|
|
|
| Mistake | Correct |
|
|
|---------|---------|
|
|
| `Parent` component | `ChildOf(entity)` |
|
|
| `children.iter().copied()` | `children.into_iter()` or `for child in &children` |
|
|
| `*child_of` (deref) | `child_of.parent()` |
|
|
| `despawn_recursive()` | `despawn()` (auto-recursive now) |
|
|
| `parent_entity()` in spawner | `target_entity()` |
|
|
| `clear_children()` | `detach_all_children()` (0.18+) |
|
|
| `remove_children()` | `detach_children()` (0.18+) |
|
|
| Reading GlobalTransform in Update | Read in PostUpdate, or accept one-frame lag |
|