For the past few days, I've been working on the Bevy rewrite of Godot Dash (which I renamed to Prism Dash so it wouldn't mention the engine in its name). Bevy is a lot of fun to work with, and the modularity of systems feels so refreshing when coming from monolithic scripts!
Here's what we have so far:
- Collisions are set up using
avian2d - Rendering of the SVG sprites uses
bevy_vello - The camera can switch between following the player and being panned and zoomed around like in the editor
- The player can jump, and input is set up using
bevy_enhanced_input
Let's take the code to handle jumps as an example of the differences between the engines.
Godot
In the Godot version, everything was in the player script (Player.gd). This made the code a bit harder to work on, since you needed to find the right line handling the action.
# src/Player.gd
func _compute_velocity(
delta: float,
previous_velocity: Vector2,
direction: int,
jump_state: int,
was_sliding_on_slope: bool,
) -> Vector2:
var local_velocity: Vector2 = \
previous_velocity.rotated(-gameplay_rotation)
# ...
if (
internal_gamemode in [Gamemode.SWING, Gamemode.BALL]
and jump_state == 1
and orb_queue.is_empty()
):
gravity_flip *= -1
# ...
#region Handle jump.
var is_instant_jump: bool = internal_gamemode in [
Gamemode.SPIDER,
Gamemode.BALL,
Gamemode.UFO,
Gamemode.CUBE
]
if (
is_instant_jump
and jump_state == 1
and not colliding_pad
and orb_queue.is_empty()
):
match internal_gamemode:
# ...
Gamemode.BALL:
local_velocity.y = speed.y * gravity_flip * 0.5
Gamemode.CUBE:
local_velocity.y = -speed.y * gravity_flip
#endregion
# ...
return local_velocity.rotated(gameplay_rotation)Bevy
In Bevy, you're strongly recommended to write small systems, that only read and write the data they need. Since systems are just functions, they can be imported and added to the game easily, while staying in small modules that are easy to find.
Another advantage of systems is that they only run if their queries succeed.
Thanks to this, we can represent gamemode using components, and have systems that only run if the player is in a specific gamemode. This way, we avoid the need for a function that handles all gamemodes at once!
// src/player/gamemode/cube.rs
pub fn handle_jump(
_: On<Fire<Jump>>, // The system only runs if the input is detected.
mut commands: Commands,
players: Query<
(Entity, &mut LinearVelocity, &Gravity),
// Only runs for entities that are players, are in the cube gamemode,
// and are on the ground.
(With<Player>, With<Cube>, With<Grounded>),
>,
) {
for (entity, mut velocity, gravity) in players {
gravity.transform_local(&mut velocity, |local_velocity| {
local_velocity.y = gravity.flip.apply_to(JUMP_VELOCITY);
});
commands.entity(entity).remove::<Grounded>();
}
}// src/player/gamemode/ball.rs
pub fn handle_jump(
_: On<Start<Jump>>,
mut commands: Commands,
players: Query<
// The ball gamemode inverts the gravity when jumping,
// so it asks for mutable access to the Gravity component
// of the entity.
(Entity, &mut LinearVelocity, &mut Gravity),
(With<Player>, With<Ball>, With<Grounded>),
>,
) {
for (entity, mut velocity, mut gravity) in players {
gravity.transform_local(&mut velocity, |local_velocity| {
local_velocity.y = gravity.flip.apply_to(JUMP_VELOCITY);
});
gravity.flip.invert();
commands.entity(entity).remove::<Grounded>();
}
}