In the Build Your First Game With Rust chapter of Hands-On Rust I finish by suggesting some improvements to the game - and provide the source code for an improved version in the flappy-bonus
directory of the book’s source code. As you have have guessed, I originally planned to include some of these enhancements in the book itself, but page count concerns led to it being cut.
In this article, I’ll walk you through the changes involved in creating the bonus content.
Just Add Graphics
I started by creating a tileset. Original graphics were taken from Bevoulin.com’s OpenGameArt page, and then modified to fit a spritesheet. The completed spritesheet looks like this:
The tileset is mostly a 32x32 font, with a bit of shading applied. There are several new characters:
- Several animation frames of Mr. Dragon flapping his wings.
- A shaded wall texture for collision elements.
- A ground icon.
Initializing Bracket-Lib
Following the instructions from later in the book, I replaced the initialization code:
fn main() -> BError {
let context = BTermBuilder::new()
.with_font("../resources/flappy32.png", 32, 32)
.with_simple_console(SCREEN_WIDTH, SCREEN_HEIGHT, "../resources/flappy32.png")
.with_fancy_console(SCREEN_WIDTH, SCREEN_HEIGHT, "../resources/flappy32.png")
.with_title("Flappy Dragon Enhanced")
.with_tile_dimensions(16, 16)
.build()?;
main_loop(context, State::new())
}
There’s one new trick here: it uses a “fancy console”. Fancy consoles provide for fractional positioning on the terminal. You don’t have to settle for a glyph being at (5,3)
- it can now be at (5.2, 3.1)
.
Extending the Player
The Player
structure needs to be expanded a bit to support storing the y
position as a floating point number, and an annotation for the current frame:
struct Player {
x: i32,
y: f32,
velocity: f32,
frame: usize // Usize to index arrays
}
impl Player {
fn new(x: i32, y: i32) -> Self {
Player {
x,
y: y as f32,
velocity: 0.0,
frame: 0
}
}
...
Changing y
to an f32
also necessitates a lot of small changes. Basically, wherever an integer was applied to the player’s position you instead need to use an f32
. Let’s work through these changes.
Applying Gravity
The gravity_and_move
function needs to be updated to use fractional y
values. It also needs to oscilate between frames: increase the frame number, and if it is 6 or higher (there are 5 animation states) revert to the first one. You can accomplish this as follows:
fn gravity_and_move(&mut self) {
if self.velocity < 2.0 {
self.velocity += 0.1;
}
self.y += self.velocity;
if self.y < 0.0 {
self.y = 0.0;
}
self.x += 1;
self.frame += 1;
self.frame = self.frame % 6; // % is modulus - remainder
}
Flapping Your Wings
Play-testing showed that the flap was a little too weak for my liking in this version, so I adjusted flap
to apply a greater shift:
fn flap(&mut self) {
self.velocity = -2.0;
}
Rendering with Animation
The “fancy console” from bracket-lib
supports a much more complicated version of the set
function. When you call set_fancy
, you specify:
- A
PointF
containing the sprite location in floating-point coordinates. - A scale.
- An optional rotation.
- Color tinting.
- The character to render.
Replace the render
function (in the Player
implementation) as follows:
fn render(&mut self, ctx: &mut BTerm) {
ctx.set_active_console(1);
ctx.cls();
ctx.set_fancy(
PointF::new(0.0, self.y),
1,
Degrees::new(0.0),
PointF::new(2.0, 2.0),
WHITE,
NAVY,
DRAGON_FRAMES[self.frame]
);
ctx.set_active_console(0);
}
You may notice that we haven’t defined DRAGON_FRAMES
. This is an array constant that maps the animation frames to the font file. It is defined with the other constants towards the top of the file:
const DRAGON_FRAMES : [u16; 6] = [ 64, 1, 2, 3, 2, 1 ];
Notice that it contains a cycle - the dragon flaps through frames 1, 2, 3 and then 2, 1 again. It’s only using 4 different frames, but it looks more natural for wings to up and then gradually down again, rather than simply repeating itself.
Flap, Dragon, Flap!
With these changes, you are ready to play Flappy Dragon: Bonus Edition. The changes are largely cosmetic: the game has gained some nicer graphics and movement is a little smoother.