DEV Community

Ata Kuyumcu
Ata Kuyumcu

Posted on • Originally published at ata.gitlab.io

Custom commands in Bevy with extension traits

Bevy is an ECS-based game engine built in Rust. Extension traits are a pattern in rust that allows you to add methods to an existing type defined outside of your crate. You can probably guess where I'm going with this.

In bevy, any system can access the Commands structure to issue commands manipulate the World. The most common one would probably be the
Commands#spawn method which lets you spawn an entity with the components you specify. You can pass a structure implementing the Bundle trait to this method. Luckily, tuples of none to many components implement this trait thanks to macro magic, so you can just call the method like:

commands.spawn((Component1 {x: 3.0, y: 4.0}, Component2 {value: true}))
Enter fullscreen mode Exit fullscreen mode

Or, you can easily define your own bundles and use them:

use bevy::ecs::*;

#[derive(Bundle)]
struct HumbleBundle {
    component1: Component1,
    component2: Component2,
}

// ... lines later, somewhere in a system
commands.spawn(HumbleBundle {
    component1: Component1 {x: 3.0, y: 4.0},
    component2: Component2 {value: true}
});
Enter fullscreen mode Exit fullscreen mode

But maybe you need to spawn multiple entities that refer to each other or something. Then, you need to implement a new
Command yourself.

use bevy::ecs::*;

struct ReferringComponent {
    refers_to: Entity
}

struct ComponentFoo {
    bar: i32
}

struct SpawnReferringPair {
    first_bar: i32,
    second_bar: i32,
}

// create two entities, the second one referring to the first one
impl Command for SpawnReferringPair {
    fn write(self: Box<Self>, world: &mut World, resources: &mut Resources) {
        let first_entity = world.spawn((ComponentFoo {bar: self.first_bar}, ));
        world.spawn((ComponentFoo {bar: self.second_bar}, ReferringComponent {refers_to: first_entity}));
    }
}
Enter fullscreen mode Exit fullscreen mode

And we can use this Command with Commands, like:

commands.add_command(SpawnReferringPair {first_bar: 5, second_bar: 10});
Enter fullscreen mode Exit fullscreen mode

Now, this is completely fine and functional. But I think we can make it prettier, so we can use it like:

commands.spawn_referring_pair(5, 10);
Enter fullscreen mode Exit fullscreen mode

We just have to add a method to Bevy's already defined Commands structure with an extension trait.

// imagine that the code defining SpawnReferringPair is here as well :)

trait CommandsExt {
    fn spawn_referring_pair(&mut self, first_bar: i32, second_bar: i32) -> &mut Self;
}

impl CommandsExt for Commands {
    fn spawn_referring_pair(&mut self, first_bar: i32, second_bar: i32) -> &mut Self {
        self.add_command(SpawnReferringPair {
            first_bar, second_bar // field init shorthand
        });
        self
    }
}
Enter fullscreen mode Exit fullscreen mode

And voila, we can use this method just like the spawn method:

commands
    .spawn_referring_pair(5, 10)
    .spawn((SomeOtherComponent,))
    .spawn_referring_pair(1024, 1);
Enter fullscreen mode Exit fullscreen mode

In conclusion, don't let the computer tell you what to do, make it do what you want, however arbitrary it might be. Also, Bevy is pre-1.0 and as much as they try to keep things backwards-compatible, this article might not be correct beyond 0.4, or it could be, check the source code, nerd.

Oldest comments (1)

Collapse
 
spookythghost profile image
Spooky Th Ghost

How are you able to use self: Box and &mut Resources as params for write() in the first example?