Skip to content
This repository has been archived by the owner on Jul 8, 2024. It is now read-only.
/ spew Public archive

A simple helper for spawning objects in Bevy.

License

Apache-2.0, MIT licenses found

Licenses found

Apache-2.0
license-apache.txt
MIT
license-mit.txt
Notifications You must be signed in to change notification settings

janhohenheim/spew

Repository files navigation

Important

Starting from Bevy 0.14, we now have observers!. Because they can do everything spew set out to do, Bevy 0.14 will be the last version supported by the plugin. From now on, spew will no longer receive updates.

For an example of how I recommend to do spawning logic now, see this PR on the new semi-official Bevy jam template.

Spew

crates.io docs.rs

A simple helper for spawning objects in Bevy.

Usage

First, create an enum that holds objects you might want to spawn:

#[derive(Debug, Eq, PartialEq)]
enum Objects {
    Player,
    Monster,
    Coin,
}

Think about which data you want to pass to the spawning function. In this example, we will specify a Transform for the new object. Next, add the plugin to your app, noting the two types we just mentioned:

use spew::prelude::*;
use bevy::prelude::*;

fn main() {
    App::new()
    // ...
        .add_plugins(SpewPlugin::<Objects, Transform>::default()) // <--- Add the plugin
    // ...
        .run();
}

Now, we are ready to register our spawn functions. Each variant of the enum will be associated with its own spawn function that takes in a &mut World and the user provided data:

use spew::prelude::*;
use bevy::prelude::*;

fn main() {
    App::new()
    // ...
        .add_spawners( // <--- Register the spawn functions
            (Objects::Player, spawn_player),
            (Objects::Monster, spawn_monster),
            (Objects::Coin, spawn_coin),
        )
    // ...
        .run();
}

fn spawn_player(In(transform): In<Transform>, mut commands: Commands) {
    commands.spawn((
        Name::new("Spiffy the Adventurer"),
        TransformBundle::from_transform(transform),
    ));
}

fn spawn_monster(In(transform): In<Transform>, mut commands: Commands) {
    commands.spawn((
        Name::new("Grumblor the Grumpy"),
        TransformBundle::from_transform(transform),
    ));
}

fn spawn_coin(In(transform): In<Transform>, mut commands: Commands) {
    commands.spawn((
        Name::new("$1000"),
        TransformBundle::from_transform(transform),
    ));
}

Finally, we can set our spawn functions to work by sending a SpawnEvent:

use spew::prelude::*;
use bevy::prelude::*;

fn main() {
    App::new()
    // ...
        .add_systems(Startup, setup_map)
    // ...
        .run();
}

fn setup_map(mut spawn_events: EventWriter<SpawnEvent<Object, Transform>>) {
    spawn_events.send(SpawnEvent::with_data(
        Objects::Player,
        Transform::from_xyz(0.0, 0.0, 0.0),
    ));
    spawn_events.send(SpawnEvent::with_data(
        Objects::Monster,
        Transform::from_xyz(5.0, 0.0, 0.0),
    ));
    spawn_events.send(SpawnEvent::with_data(
        Objects::Coin,
        Transform::from_xyz(10.0, 0.0, 0.0),
    ));
}

You can read through the docs or peruse the examples for more use cases. Other cool stuff you can do is delay the spawning by a certain amount of frames or time or organize your spawn lists into multiple enums.

Compatibility

bevy spew
0.14 0.6
0.13 0.5
0.12 0.4
0.11 0.3
0.10 0.2

Motivation

Bevy's Commands API allows you to spawn new entities with arbitrary components:

use bevy::prelude::*;

fn spawn_player(commands: &mut Commands) {
    commands.spawn((
        Name::new("Adventurer"),
        TransformBundle::from_transform(Transform::from_xyz(0.0, 0.0, 0.0)),
    ));
}

This works great! We can spawn more complex objects by just adding more components like assets:

use std::f32::consts::TAU;
use bevy::prelude::*;

fn spawn_bullet(commands: &mut Commands, asset_server: Res<AssetServer>) {
    commands.spawn((
        Name::new("Bullet"),
        SceneBundle {
            scene: asset_server.load("models/bullet.gltf#Scene0"),
            transform: Transform {
                translation: Vec3::new(5.0, 4.0, 12.0),
                scale: Vec3::splat(0.012),
                rotation: Quat::from_rotation_y(TAU / 2.),
            },
            ..default()
        },
    ));
}

but, in a real project, we would not spawn a bullet like that. The bullet would be spawned by a weapon at a certain translation. We might thus encapsulate the bullet spawning like this:

use bevy::prelude::*;
fn handle_input(...) {
    // ...
    if should_fire_bullet {
        let position = player_transform.translation;
        spawn_bullet(&mut commands, &asset_server, position);
    }
}

fn spawn_bullet(commands: &mut Commands, asset_server: &AssetServer, position: Vec3) {
    commands.spawn((
        Name::new("Bullet"),
        SceneBundle {
            scene: asset_server.load("models/bullet.gltf#Scene0"),
            transform: Transform {
                translation: position,
                scale: Vec3::splat(0.012),
                rotation: Quat::from_rotation_y(TAU / 2.),
            },
            ..default()
        },
    ));
}

As you can see, this works but is quite ugly. handle_input has to pass around an asset server we might otherwise not even need in the system, and spawn_bullet has a jumble of seemingly unrelated parameters that will grow and grow over time. Growing parameter lists are not a problem when writing a system, but notice how here spawn_bullet is no longer a system but a helper function. Thus, its call will get longer and uglier over time, with all its parameters leaking into handle_input.

The solution to this is to move the spawning of the bullet into an own system that is accessed indirectly by handle_input via events, which is just what this crate helps you with! :)