1 unstable release
0.1.0 | Mar 13, 2024 |
---|
#1318 in Game dev
28 downloads per month
Used in moonshine-view
19KB
395 lines
🌴 Moonshine Objects
An extension to Bevy entities to make complex ECS hierarchies more ergonomic.
Entities are nice. Objects are better! 😎
Overview
Object
provides an ergonomic interface for traversing and querying an entity hierarchy.
It is a wrapper around 3 common queries in Bevy:
Query<&Name>
Query<&Parent>
Query<&Children>
Any Entity
can be expressed as an Object
.
Additionally, an Object<T: Kind>
provides type safety through Kinds.
An Object
is not a query item itself, rather accessible via the Objects<T>
system parameter:
#[derive(Component)]
struct Bird;
#[derive(Bundle)]
struct BirdBundle {
bird: Bird,
name: Name, // Optional!
}
fn bird_system(birds: Objects<Bird>, mut commands: Commands) {
for bird in birds.iter() {
flap_wings(bird, &mut commands);
}
}
Because an Object
has access to hierarchy and name information, it can provide a set of useful functions, such as:
impl<T: Kind> Object<T> {
fn is_root(&self) -> bool;
fn is_parent(&self) -> bool;
fn is_child(&self) -> bool;
fn is_descendant_of(&self, entity: impl Into<Entity>) -> bool;
fn find_by_path(&self, path: &str) -> Option<Object>;
fn root(&self) -> Object;
fn parent(&self) -> Option<Object>;
fn children(&self) -> impl Iterator<Item = Object>;
fn ancestors(&self) -> impl Iterator<Item = Object>;
// ...
}
This makes it very easy to pass this information between your systems and functions:
fn flap_wings(bird: Object<Bird>, commands: &mut Commands) {
if let Some(wings) = bird.find_by_path("body/wings") {
// TODO: Update state of wings
for wing in wings.children() {
// ...
}
} else {
error!("Bird has no wings! :(");
}
}
Casting
Like Instance<T>
, an Object<T>
maybe be cast into an Object<U>
if T
implements CastObjectInto<U>
. You may implement this trait for your own kinds using the safe_object_cast
macro:
// We expect every Bird to have a Creature component.
#[derive(Bundle)]
struct BirdBundle {
bird: Bird,
creature: Creature,
name: Name,
// ...
}
// Therefore, all birds may safely be assumed to be creatures:
safe_object_cast!(Bird => Creature);
// Birds can chirp.
fn chirp(bird: Object<Bird>) {
// ...
}
// Creatures can find food.
fn find_food(creature: Object<Creature>) {
// ...
}
// Birds chirp when they get hungry.
fn handle_hunger(bird: Object<Bird>) {
chirp(bird);
find_food(bird.cast_into()); // Safe!
}
Any Object<T>
is safely convertible to Object
.
You can define as many casts as you want. Any object kind may be cast into any other object kind as long as a safe_object_cast
is defined for it.
Filters
Like a standard Query
, you can filter Objects
by passing a QueryFilter
to the iter
method:
#[derive(Component)]
struct Flying;
fn update_flying_birds(birds: Objects<Bird, With<Flying>>) {
for object in birds.iter() {
// This system only iterates birds with `Flying` component.
}
}
Dependencies
~14MB
~284K SLoC