Hi, everyone! Welcome back to Learning to Fly.
In this episode, we'll go over a bunch of changes and improvements
that we've made to Doodle Dash since the previous episode.
Then we'll show off the newest features of the game.
Spoiler alert: we added some menus and improved the gameplay
by tweaking things like Dash's speed
and the distance between platforms.
Of course, we have to talk about what we struggled with
because that's what Learning to Fly is all about.
We'll highlight a few of our struggles
and talk about how we solved them.
Does that sound good to you?
Sounds good to me.
Great, then let's hop in.
♪ [music] ♪
As a refresher, in Episode 1,
we talked about our goals for this season of Learning to Fly
and the game that we're building, Doodle Dash.
We're building the game with Flame,
a 2D game engine, written on top of Flutter.
Quick side note:
in this episode, we'll be discussing Flame-specific terms and concepts.
If you don't have any experience using Flame,
we do encourage you to check out Episode 1.
The link is up there, or up here,
and down in the show notes down below.
We also talked about how we implemented
the core game loop for Doodle Dash,
and this functionality allows Dash to jump up on the platform forever,
and allows players to try and jump as high as possible
without falling off the screen.
The game was in a state that is technically functional,
but it left a lot to be desired.
So for this sprint,
we made a lot of improvements to mechanics
to try and make it a bit more challenging
and a bit more interesting.
Eric, how did we make things more challenging and interesting?
What did we improve on?
Sure. At first, the game was optimized to be less boring.
The most noticeable difference is that Dash's speed has increased,
so the pace is a bit more exciting.
And second, the game was a bit too easy,
so we added code needed to change the difficulty.
There are five difficulty levels,
and the game always starts in Level 1,
and then the difficulty increases as the game goes on.
Spoiler alert:
in the future, we plan on allowing the player
to select their starting difficulty,
so you can jump right into whatever point
you find fun and challenging.
And then, we also tweaked the collision detection
because it was a little janky.
Previously, Dash would collide with the platform a bit too early
before she was actually touching the platform,
or a bit too late,
after she was already overlapping with the platform a bit,
and it just didn't look great.
But Flame really brought the heat here.
I changed the
to another method that Flame provides out of the box
called
I don't really know why, but I'll take it.
Wait, actually, I can talk a little bit to that, Eric.
I had the same question when I was working with Flame
on my Dash for Flappy Dash.
The
when two objects are colliding.
Another way to say it
is
whenever there is a collision actively happening.
On the flip side,
the
are only called for one tick each in a collision.
So, at the very beginning,
first tick when two objects first start colliding,
and then the very last tick at the end when a collision has stopped.
And actually, on the topic of collisions,
we also updated the PlatformManager class
to get rid of some unwanted behavior.
Previously, platforms would be placed partially off screen
or overlap with each other.
This didn't break gameplay,
but fixing it adds some much-needed polish to the game.
Ultimately, we needed to account
for the dimensions of the platforms in the code
where we were generating the platform position.
So, that's it, as far as improvements go.
Do you want to talk about some of the new features
that we've added to the game?
Sure. The most obvious addition we added is the overlays.
These overlays make up the Main menu,
the GameOver menu, and the Pause screen.
And then there's also an overlay that's shown during gameplay
that shows the current score.
Of course, we also added logic to actually do the scorekeeping.
But these overlays are all just plain Flutter widgets,
and I loved how Flame includes a way to integrate
plain widgets into our game.
To add overlays in Flame,
you start with in the game class
where Flame exposes an argument called
This map is similar to the
used to add routes in your Flutter app.
To use it, you just pass in a
in which the keys are
that return widgets.
And these callbacks are passed to the game object,
which you can then pass into your widgets,
which means you have access to actions like pausing the engine
from your Flutter widgets,
and we were also able to keep score state in the Flame components,
but display it from the Flutter widgets, which is pretty neat.
And there's also new features in the gameplay.
First, we added new platforms.
They're all visually different,
but for now, they mostly behave the same.
Most of them extend
on the pre-existing platform class that we added previously.
They just have different sprites at this point.
Spoiler alert number three:
these different platforms will behave differently by the next episode.
And, actually, one of the platforms, however,
is the exception here,
and it behaves, well, a tiny bit differently.
It's a springboard, so this is a power-up for Dash.
When she jumps on a springboard platform,
she'll be launched upwards, zooming past several platforms.
This one was a lot of fun to implement
using a sprite group component, like Dash's sprite.
Just like we did for Dash,
we gave the component a map of the sprite states and sprite assets.
Then from there,
we used the
to change the platform state.
Whenever the state changed,
the springboard would either compress or expand automatically.
Because of the springboard,
we also added an optional parameter called
to Dash's Jump method.
If a
If not, Dash jumps at her regular speed.
We did this to allow for easier integration
with more power-ups in the future, similar to the springboard.
This way, we don't have to think about what Dash's regular speed is.
Just tell her to jump at a different speed
when she actively has a power-up.
That covers all the changes and additions
that you'll notice in the second version of the game.
Now let's talk about what we struggled with.
Want to start us off, Eric?
Sure. My biggest challenge with sprint was resetting the game state
when a player lost and wanted to play again.
This is something that exists in every run-based game
and, probably, every game ever.
I knew it was possible, and it might even be straightforward,
but alas, I couldn't figure it out for the longest time.
Notably, this was basically a state management problem.
The first piece of state to manage
is whether the game engine is running or not;
and the second is what overlays are shown on the screen
at any given points.
In my first approach, I was managing the game engine state
via Flames built-in methods called
For example,
if a player lost the game by falling off the screen,
I would pause the engine and then show the Game Over screen.
Then, when the player pressed
I would remove the overlay, start the engine,
and then reset Dash's position to the starting position,
but not necessarily in that order.
The problem with this approach
was that as soon as the engine unpauses
before the game has a chance to move Dash,
the game
determine that Dash was below the visible screen,
and immediately show the Game Over screen again.
And there's probably a way I could've made this work,
but because I was calling
in different places,
and also calling the methods to show and hide overlays
in so many different places,
it was hard to manage,
and the game was always getting into a weird state.
So, I just poked around the example apps in Flame's GitHub
and found a better solution.
By using an enum called
that has values for
I could detect at the top of the
if the state was
If it was, I could immediately return from that method
before doing any of the other logic that method normally does,
including checking if Dash was below the visible screen.
That means I never had to technically pause the engine,
so I could just reset the entity's locations
and the camera as normal,
while the overlays were shown,
and then give power back to the
The takeaway I learned, in this case,
is that I never wanted to actually pause the engine at all.
I just wanted it to appear to the player
that the engine was paused.
Eric, that's such a cool way to handle resetting Game State.
Funny enough, I had the same problem, building Flappy Dash
and opted for a Boolean
The Game State enum is so much better for future extensibility.
And on my end,
I think the biggest challenge for me
was deciding how I wanted to add on the springboard
as a special platform.
At the beginning, I thought, well,
I could just add the springboard
and extend it from the original platform,
until I realized that the springboard
would need to have the changing Sprite State.
Then I thought, okay, I can refactor the platform
to be a Sprite Component Group,
but that didn't quite make sense either
because the normal platforms didn't have changing Sprite State.
So, it seemed like overkill and overcomplicating things.
At the end of the day,
I landed on making the normal platforms its own component
built on top of the Sprite component.
And then, the springboard and an entirely different component
built on top of the Sprite Group component.
In the future, any additional power-ups
will most likely use the Sprite Group component as well.
And on the same point,
another potential challenge that seems to be lurking
is how sprawling our code base is.
I know we chatted about this a bit,
and you felt kind of the same way,
but while we've been writing this game code,
I feel like it's been harder to maintain a separation of concerns.
All of the logic seems, well, more intertwined
than a regular app.
Right. I agree.
The resetting the Game State problem I had
was basically a consequence of this:
because the state is changing all over the place,
it's hard to follow,
and it's really easy to get into a bad state.
Unfortunately, I don't know what the best practices
for managing state are in games.
So, if any of y'all have experience with this,
let us know in the comments.
But for now, that's all we have for this episode of Learning to Fly.
Thanks for joining us.
In the next episode, we'll be doing something
that we are very excited about: making the game pretty.
We'll be adding on new assets and styles
to make the game have its own personality.
And we'll be adding the final core features
that make the game fun and challenging,
like more power-ups, platforms that are trickier,
and even some enemies that Dash has to avoid.
Speaking of what cool power would you add to Doodle Dash,
if you were building it,
let us know in the comments below,
and then hang on to that thought
because we might have a little challenge for you
coming up in the next episode.
As a reminder,
if you want to see the entire finished game now,
go check out the code lab at the link below in the show notes,
and you can build the entire game for yourself.
Thanks for joining us on this episode of Learning to Fly,
and we'll see you tomorrow.
-Bye. -See you.
♪ [music] ♪