Crate moonshine_object

Source
Expand description

ยง๐ŸŒด Moonshine Object

crates.io downloads docs.rs license stars

An extension to Bevy which provides an ergonomic interface for managing complex Entity hierarchies.

Entities are nice. Objects are better! ๐Ÿ˜Ž

ยงOverview

This crate is designed to provide a wrapper for some commonly used operations when working with entities in Bevy.

It is often required for various systems to be able to traverse complex entity hierarchies. This is especially true for initialization code when various components need to reference various entities within a hierarchy of entities.

For example, consider a system which reacts to a flying bird by flapping its wings:

use bevy::prelude::*;

#[derive(Component)]
struct Bird;

#[derive(Component)]
struct Flying;

fn setup_bird(
    query: Query<Entity, (With<Bird>, Added<Flying>)>,
    children_query: Query<&Children>,
    name: Query<&Name>,
    mut commands: Commands
) {
    for entity in query.iter() {
        if let Ok(children) = children_query.get(entity) {
            for child in children.iter() {
                if let Ok(name) = name.get(child) {
                    if name.as_str() == "Wings" {
                        if let Ok(wings) = children_query.get(child) {
                            for wing in wings.iter() {
                                // TODO: Flap! Flap!
                            }
                        }
                    }
                }
            }
        }
    }
}

Although, this code is intentionally verbose to show the hierarchy complexity, this crate tries to make these situations more ergonomic by introducing Object<T>.

It behaves like an Entity or Instance<T> with some extra features:

use bevy::prelude::*;
use moonshine_object::prelude::*;

#[derive(Component)]
struct Bird;

#[derive(Component)]
struct Flying;

fn setup_bird(birds: Objects<Bird, Added<Flying>>, mut commands: Commands) {
    for bird in birds.iter() {
        if let Some(wings) = bird.find_by_path("./Wings") {
            for wing in wings.children() {
                // TODO: Flap! Flap!
            }
        }
    }
}

ยงFeatures

  • Less boilerplate when dealing with complex entity hierarchies
  • Full type safety enforced through Kind semantics
  • No macros! No registration!

ยงUsage

ยงObjects<T>

Use Objects<T> as a system parameter to access all Object<T> instances.

This SystemParam is designed to be used like a Query:

use bevy::prelude::*;
use moonshine_object::prelude::*;

#[derive(Component)]
struct Bird;

fn update_birds(birds: Objects<Bird>) {
    for bird in birds.iter() {
        // ...
    }
}

Like a Query, you may also use a QueryFilter:

use bevy::prelude::*;
use moonshine_object::prelude::*;

#[derive(Component)]
struct Bird;

#[derive(Component)]
struct Flying;

fn update_flying_birds(birds: Objects<Bird, With<Flying>>) {
    for bird in birds.iter() {
        // ...
    }
}

Internally, Objects<T> is just a thin wrapper around some common queries:

  • Query<Instance<T>>
  • Query<&ChildOf> / Query<&Children>
  • Query<&Name>

ยงObject<T>

Each Object<T> is a reference to an Entity with type, name, and hierarchy information. This provides a convenient way to pass this data between functions:

use bevy::prelude::*;
use moonshine_object::prelude::*;

#[derive(Component)]
struct Bird;

#[derive(Component)]
struct Flying;

fn update_flying_birds(birds: Objects<Bird, With<Flying>>) {
    for bird in birds.iter() {
        flap_wings(bird);
    }
}

fn flap_wings(bird: Object<Bird>) {
    if let Some(wings) = bird.find_by_path("./Wings") {
        for wing in wings.children() {
            // TODO: Flap! Flap!
        }
    }
}

โš ๏ธ Unlike an Entity or Instance<T>, Object<T> has a non-static lifetime and may not be used as a Query term.

ยงCasting

Like Instance<T>, any Object<T> may be be cast into an Object<U> if T implements CastInto<U>.

You may implement this trait for your own kinds using the kind macro:

use bevy::prelude::*;
use moonshine_kind::prelude::*;
use moonshine_object::prelude::*;

#[derive(Component)]
struct Bird;

struct Creature;

// Every Bird is a Creature by definition:
impl Kind for Creature {
    type Filter = (With<Bird>, /* ... */);
}

// Therefore, all birds may safely be cast into creatures:
kind!(Bird is Creature);

// Birds can chirp.
fn chirp(bird: Object<Bird>) {
    // TODO: Chirp!
}

// Creatures can find food.
fn find_food(creature: Object<Creature>) {
    // TODO: Find food!
}

// 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<Any>.

ยงInstallation

Add the following to your Cargo.toml:

[dependencies]
moonshine-object = "0.2.2"

This crate is also included as part of ๐Ÿธ Moonshine Core.

ยงSupport

Please post an issue for any bugs, questions, or suggestions.

You may also contact me on the official Bevy Discord server as @Zeenobit.

Modulesยง

prelude
Prelude module to import all necessary traits and types for working with objects.

Structsยง

Any
Represents the kind of any Entity.
Object
Represents an Entity of Kind T with hierarchy and name information.
ObjectRef
Similar to EntityRef with the benefits of Object<T>.
Objects
A SystemParam similar to Query which provides Object<T> access for its items.

Traitsยง

CastInto
A trait which allows safe casting from one Kind to another.
Kind
A type which represents the kind of an Entity.
ObjectHierarchy
Object methods related to hierarchy traversal.
ObjectName
Object methods related to naming.
ObjectRebind
Object methods related to rebinding and casting.

Type Aliasesยง

RootObjects
Ergonomic type alias for all Objects of Kind T without a parent.