๐Ÿ“ฆ Zeenobit / moonshine_util

A collection of utilities for Bevy game engine

โ˜… 12 stars โ‘‚ 1 forks ๐Ÿ‘ 12 watching โš–๏ธ MIT License
๐Ÿ“ฅ Clone https://github.com/Zeenobit/moonshine_util.git
HTTPS git clone https://github.com/Zeenobit/moonshine_util.git
SSH git clone git@github.com:Zeenobit/moonshine_util.git
CLI gh repo clone Zeenobit/moonshine_util
Zeenobit Zeenobit Update README.md 9f192ae 3 months ago ๐Ÿ“ History
๐Ÿ“‚ main View all commits โ†’
๐Ÿ“ .github
๐Ÿ“ src
๐Ÿ“„ .gitattributes
๐Ÿ“„ .gitignore
๐Ÿ“„ Cargo.toml
๐Ÿ“„ LICENSE
๐Ÿ“„ README.md
๐Ÿ“„ README.md

๐Ÿ› ๏ธ Moonshine Utilities

crates.io downloads docs.rs license stars

Collection of utilities for Bevy.

Features

[Expect<T>]

A decorator for QueryData which panics if it doesn't match.

This helps avoid silent failures in systems due to missing components:

use bevy::prelude::*;
use moonshine_util::prelude::*;

#[derive(Component)]
struct A;

#[derive(Component)]
struct B;

#[derive(Bundle)]
struct AB {
    a: A, // Every `A` is expected to have a `B`
    b: B,
}

fn bad_system(mut commands: Commands) {
    commands.spawn(A); // BUG: Spawn A witout B!
}

fn unsafe_system(mut query: Query<(&A, &B)>) {
    for (a, b) in query.iter() {
        // An instance of `A` does exist, but this system skips over it silently!
    }
}

fn safe_system(mut query: Query<(&A, Expect<&B>)>) {
    for (a, b) in query.iter() {
        // This system will panic if an `A` instance is missing a `B`!
    }
}

Normally, expected components would just be added as required components. However, in some cases it may not be possible to add the required components, such as when dealing with third party crates or generic code.

Note that [Expect<T>] may also be used as a required component:

use bevy::prelude::*;
use moonshine_util::prelude::*;

#[derive(Component)]
struct A;

#[derive(Component)]
#[require(Expect<A>)] // Expect `A` to be present
struct B;

In this context, [Expect<T>] will panic if B is ever inserted into an entity without A.

[Get<T>] and [MapQuery]

An ergonomic and generic way to process repetitive query patterns:

use bevy::prelude::*;
use moonshine_util::prelude::*;

struct Height(f32);

impl MapQuery for Height {
    type Query = &'static GlobalTransform;
    type Output = Self;

    fn map(data: &GlobalTransform) -> Self {
        Self(data.translation().y)
    }
}

fn average_height(query: Query<Get<Height>>) -> Height {
    // Transforms are so yesterday!
    let mut total_height = 0.0;
    let mut count = 0;
    for Height(h) in query.iter() {
        total_height += h;
        count += 1;
    }

    Height(total_height / count as f32)
}

[HierarchyQuery]

A convenient SystemParam for traversing and querying entity hierarchies:

use bevy::prelude::*;
use moonshine_util::hierarchy::HierarchyQuery;

#[derive(Component)]
struct Needle;

#[derive(Component)]
struct Haystack;

fn spawn_haystack(mut commands: Commands) {
    // A complex hierarchy ...
    commands.spawn(Haystack).with_children(|x| {
        x.spawn_empty().with_children(|y| {
            y.spawn_empty().with_children(|z| {
                z.spawn(Needle);
            });
        });
    });
}

fn find_needle(
    haystack: Query<Entity, With<Haystack>>,
    needle_query: Query<Entity, With<Needle>>,
    hierarchy: HierarchyQuery
) {
    let haystack = haystack.single().unwrap();
    for needle in hierarchy.descendants_deep(haystack) {
        // ...
    }
}

Some useful functions include:

  • fn parent(&self, Entity) -> Option<Entity>
  • fn children(&self, Entity) -> Iterator<Item = Entity>
  • fn ancestors(&self, Entity) -> Iterator<Item = Entity>
  • fn descendants_wide(&self, Entity) -> Iterator<Item = Entity>
  • fn descendants_deep(&self, Entity) -> Iterator<Item = Entity>
See [documentation] for details.

For even more convenient hierarchy traversal, check out ๐ŸŒด Moonshine Object.

[RunSystemLoop]

A trait similar to RunSystemOnce which allows you to run a system loop for testing purposes:

use bevy::prelude::*;
use moonshine_util::diagnostics::RunSystemLoop;

let mut world = World::new();
let results = world.run_system_loop(2, |mut commands: Commands| {
    commands.spawn_empty().id()
});


// Output is the result of each system run
let mut it = results.into_iter().map(Result::unwrap);

assert!(world.get_entity(it.next().unwrap()).is_ok());
assert!(world.get_entity(it.next().unwrap()).is_ok());
assert!(it.next().is_none());

[SingleEvent]

A trait designed to behave like standard Bevy events. Unlike standard events, a [SingleEvent] may only be handled by a single observer. This allows the single observer to consume and mutate the event data as needed.

use bevy::prelude::*;
use moonshine_util::prelude::*;

struct BigEvent(/* ... */);

impl SingleEvent for BigEvent {}

fn big_event_plugin(app: &mut App) {
    // Panics if there is another single observer registered for `BigEvent`:
    app.add_single_observer(on_big_event);
}

fn trigger_big_event(mut commands: Commands) {
    commands.trigger_single(BigEvent(/* ... */));
}

fn on_big_event(event: OnSingle<BigEvent>) {
    let event: BigEvent = event.consume().unwrap();
    /* ... */
}

Utility Systems

A growing collection of simple and generic systems useful for constructing larger system pipelines:

  • has_event<T: Event>
  • has_resource<T: Resource>
  • remove_resource<T: Resource>
  • remove_resource_immediate<T: Resource>
  • remove_all_components<T: Component>
See documentation for details and usage examples.

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.