The release of Bevy 0.6 is exciting. And with each release the ECS engine will become more powerful. Already I’ve become envious of several of key Bevy features1. I can’t bring myself to use the full engine, with the intolerable Windows compile times. Especially compared to a Macroquad / Legion setup.
Framework | cargo run Times |
---|---|
Bevy Sprite Example | 5.9 |
My Legion Game | 2.3 |
The release has increase my desire to move completely to the Bevy Game Engine, but not without a significant compile speed improvement I will hold off. However, with the bevy_ecs crate I can jump start my migration. Overall I have found that change to mildly tedious, but simply done.
Differences between Bevy Ecs and Legion
World
Both Bevy ECS and Legion have concepts of a “World”. Which is a container for the entities and components. In legion Resources (non Entity-Component data) are maintained outside the World. As of Bevy version 0.6 the World contains Resources, Entity-Components, and Events in one collection.
Legion Example
|
|
Bevy Example
|
|
One gotcha that I ran into what that I needed to recognize that scheduler.run
is part of a trait called Stage
.
Schedule
Both Legion and Bevy ECS includes a Schedule
struct manages the Systems Pipeline. However, there are two keys differences.
With default feature set of Legion will run stages in parallel between flushes.
Legion Example
|
|
In the above example, parallel_a
and parallel_b
could be run at the same time while single_c
system always runs after a
and b
.
However, Bevy ECS is built for a game engine suite, so has more complicated setup. In fact, there are multiple ways to do the same style of scheduling. However, we are trying to do a simple [[green to green refactor]] in this scenario, so we will pick a pattern that gives us the easier migration
Bevy Example
|
|
Components
in Legion and BevyESC before 0.6.0 components were simple Structs or Enums that were sized , send, and sync. However, as of Bevy ECS 0.6.0 components require the Component
trait which can be satisfied with a simple #[derive(Component)]
Legion Example
|
|
becomes
Bevy Example
|
|
The System Functions
Systems in general, are the major UX differences between Bevy ECS and Legion. Not only how the Systems are Scheduled, but how the systems are written. However, the changes are not so significant that you need to re-write your Systems from the ground up.
The first difference between Legion and Bevy ECS is that Legion requires you to notate a function is a System using the #[system]
macro. Bevy ECS relies on traits to decide if a function is a valid system.2 What this means is that every Bevy ECS parameter needs to be a valid implementation of the System Parameter trait.
The full System Parameter List can be found here
Resources in Systems
in Legion resources were annotated with an attribute macro
Legion Example
|
|
However, like we mention Bevy uses System Parameters to identifiy the types. In our case we can use Res
, MutRes
, Option<Res<>>
, and Option<ResMut<<>>
Bevy Example
|
|
The Res
container does rely on the deref trait to have easy access to wrapped resource, so one downside of this pattern is you cannot do destructuring in Legion. If you enjoy feeling clever, like me, by destructing only the parts you need from a Struct in a function parameter, this will obviously be one downside of Bevy for you. When converting from Legion to Bevy ECS I would suggest doing a refactor where you remove any Parameter destructing for you systems.
|
|
Queries in Systems
Queries for Bevy remarkably close to Legion queries, however you will notice they are in the System Parameter List which means you get to skip the two different steps from the legion example.
Legion use proc macros to identify which components to load into the sub world. Bevy derives similar behavior from the descriptors of the Query<>
params
Legion
|
|
I did come across this in the Game Engine examples, I had hoped that I could use that for a more 1:1 refactor. After some code spelunking, there was no obvious way to do that with just bevy_ecs
crate.
I feel this also breaks the spirit of refactoring to Bevy exercise for me.
Instead, with Bevy ECS the queries go in the parameter. The Legion example becomes something much simpler.
Bevy ECS
|
|
Personally, I like this better, and overall the Bevy ECS.
Commands in Systems
The Legion command pattern in systems was interesting. It was relatively easy to insert into the Entity-Component system, but for anything bigger you had to use an exec_mut. With Bevy ECS no callbacks are found.
Spawn new Entity
Both examples are straight forward
Legion
|
|
Bevy ECS
|
|
Insert Component into existing Entity
In both cases, the interface for inserting components are different, but also straight forward.
Legion
|
|
Bevy ECS
|
|
Insert a new Resources
Ths is the case where Bevy has a much simpler interface. Callbacks are not too difficult to manage, sometimes there can trouble with lifecycles.
Legion
|
|
Bevy ECS
|
|
Commands Outside of Systems
If you’re using the whole Bevy Engine you don’t really need to think about the Commands outside the context of systems. However, when you are migrating from macroquad
/ legion
you might have spawned entities directly. However, if you are like me and planned for needing to spawn the entities inside the Scheduler
at a later point you made your spawn functions take the CommandBuffer
and not world.
Legion
|
|
Bevy ECS
|
|
Closing Thoughts
My biggest hope that that Bevy ECS would give me a significant improvement on compile times. However, I will admit I have not noticed the improvement I desired.
The Scheduler for Bevy is significantly more powerful than Legion’s. I have barely scratched the surface, but I’m excited to get yak shave that for a minute.
It appears that Bevy has become the most popular game framework, and even as I’ve added a few more systems to my own game. There are so many great examples of doing awesome work in Bevy which has been helpful for me to learn how to work with the Bevy ECS system. Such as flock-rs, Pyrite Box, and the lovely Country Slice.
Bevy Labels Lightly goes over some of this, but mostly from the
Label
metaprogramming aspect. ↩︎