🐣 Using Bevy Entity Component System outside of Bevy #
Not every game needs an Entity Component System (ECS), but for this Macroquad Rapier ECS post, I was keen to see how you might add an ECS to a Macroquad game with Rapier physics.
In a previous post, I used the Shipyard ECS with Macroquad. I have been working through a build-your-own physics engine tutorial, which used the Bevy game engine. I loved the ergonomics of the embedded ECS, so thought, I would take it for a spin here. Rapier the Rust physics engine used here has a Bevy plugin, but I wanted to try using both Rapier and the stand-alone Bevy ECS outside of Bevy, just for the challenge.
🧱 Macroquad Rapier ECS: What I Built #
I continued with the bubble simulation, used for a few recent posts. It had no ECS, dozens of entities and a few other features that made it interesting to try with an ECS data model. So that was my starting point.
The simulation releases bubbles which float to the top of the screen, where they are trapped by a Rapier height field. Once all existing bubbles are settled, the simulation spawns a fresh one.
The simulation uses a random number generator to decide on the initial velocity of spawned balls, and has running and paused states. The simulation triggers the paused state when the bubbles almost completely fill the window. Both the random number generator and the simulation state are singletons, and fit well into the ECS resource model. I also put the physics engine itself into an ECS resource. The C/C++ flecs ECS library, for example, calls resources singletons.
The Simulation within the ECS Model #
As you would expect, each bubble is an entity. If you are new to ECSs you might imagine the ECS entities as rows of a database table. In this database world, the table columns are components. In my case, the balls all have the same components:
- a circle static mesh (which encodes colour and size data needed for rendering);
- a circle collider (for handling physical collisions with Rapier); and
- position and velocity components (used for updating the simulation physics).
Systems mutate the component properties, for example, there is a ball update system, which uses Rapier, to work out what the current velocity of the bubble is, at each step, then update the velocity component.
In the following sections, we look at some code snippets to help illuminate the points here, and hopefully give you an idea of how I put the simulation together, adding an ECS to Macroquad. The full code is on GitHub, and there is a link to it further down.
🧩 Components #
I just mentioned that bubble entities each have a few components. Here is some example code for spawning a new ball using the simulation ECS:
The new entity gets a Position
, CircleMesh
, CircleCollider
and Velocity
.
The number of components here is arbitrary, and not fixed (as it might be when calling a
constructor using an object-oriented approach). This grants flexibility as you develop the
simulation or game.
Also note, I follow a Rust convention in naming the _ball_entity
identifier.
The initial underscore (_
) indicates the identifier is not used
anywhere else in the scope. We do not need it later, since the systems used to query and mutate
the component properties, operate on entities with specific sets of components (properties). We
will see this more clearly later.
In an ECS model, the entity is not much more than an integer, which Bevy ECS can use as an ID.
Units of Measurement #
In a previous post on adding units of measurement to a Rapier game, I introduced the length units used in the snippet above. This pattern of using units leveraging Rusts type system, sense-checking calculations and helping to avoid unit conversion errors.
🏷️ Macroquad Rapier ECS: Tags #
The Position
and Velocity
components,
mentioned before, are structs with associated data. You can also have ECS tags, which are akin to components
without data. Rapier has a type of collider that just detects a collision, and beyond that does not
interact with the physics system, these are sensors.
I used a Sensor
tag in the ECS for colliders that are sensors, which
helps to separate them out when running systems. The only sensor in the simulation runs along the bottom
of the window. Towards the end of the simulation, when the window is almost full, a newly spawned bubble
will inevitably bounce off an existing one and collide with the floor sensor. I added a system to pause
the simulation when this occurs, effectively ending the simulation.
This, above, code snippet shows a CuboidCollider
(used for the floor
sensor), which is a regular component and then the Sensor
tag. The
code snippet, below, initializes the floor sensor:
Note, the Sensor
tag is included. To spawn the wall colliders on either
side of the window, a very similar block is used, only omitting the Sensor
tag. We will see how this can be used with systems next.
🎺 Systems #
Systems are code blocks, which are only run on components belonging to a specified component set. As an example, here is the system for updating bubble position and velocity within the game loop:
This system runs on any entity that satisfies the query of having Position
, Velocity
and CircleCollider
components.
We can be more prescriptive, choosing entities that have a set of components, and also, either do,
or do not have some other component or tag. We use With
when creating
the floor sensor during the simulation initialization:
As you might expect, the equivalent code for creating the side wall colliders uses Without
in its query, and omits .sensor(true)
in its Rapier setup code.
📆 Schedules #
We use ECS schedules to determine when systems run. The simulation has:
- a setup schedule, run once during simulation setup,
- a running schedule, executed on every run through the game loop in simulate/running mode; and
- a paused schedule, runs when we have paused the simulation.
Bevy ECS organizes the systems above into these schedules, which can include constraints to ensure systems run in the right order.
Here is the paused schedule code:
This just needs to draw the balls (the screen is cleared in each game loop iteration, even while the simulation is paused). The running schedule features more systems.
That code above is triggered in the game loop, while the simulation state is set to paused:
That’s it! We covered all the major constituents of an ECS!
🗳 Poll #
🙌🏽 Macroquad Rapier ECS: Wrapping Up #
In this post on Macroquad Rapier ECS, we got an introduction to working with an ECS in Macroquad. In particular, we saw:
- the main constituents of an ECS including resources, schedules and tags, as well as entities, components, and systems;
-
why you might want to add tags, and how you can use them in Bevy ECS system queries along with
With
andWithout
; and - ECS schedules for different game or simulation states.
I hope you found this useful. As promised, you can get the full project code on the Rodney Lab GitHub repo . I would love to hear from you, if you are also new to Rust game development. Do you have alternative resources you found useful? How will you use this code in your own projects?
🙏🏽 Macroquad Rapier ECS: Feedback #
If you have found this post useful, see links below for further related content on this site. Let me know if there are any ways I can improve on it. I hope you will use the code or starter in your own projects. Be sure to share your work on X, giving me a mention, so I can see what you did. Finally, be sure to let me know ideas for other short videos you would like to see. Read on to find ways to get in touch, further below. If you have found this post useful, even though you can only afford even a tiny contribution, please consider supporting me through Buy me a Coffee.
Just dropped a new post on using Macroquad with Rapier physics and Bevy ECS.
— Rodney (@askRodney) May 15, 2024
We look at:
— main ECS parts,
— integrating Bevy ECS with Macroquad; and
— Bevy ECS features like queries and schedules.
Hope you find it useful!
#askRodney #rustlang #gamedevhttps://t.co/y72RoSb8Yf
Finally, feel free to share the post on your social media accounts for all your followers who will find it useful. As well as leaving a comment below, you can get in touch via @askRodney on X (previously Twitter) and also, join the #rodney Element Matrix room. Also, see further ways to get in touch with Rodney Lab. I post regularly on Game Dev as well as Rust and C++ (among other topics). Also, subscribe to the newsletter to keep up-to-date with our latest projects.