Stage 3 - Monsters
In this section, we're going to make a
class just like we did with the
Earlier, we used just two variables (
) to represent position, but
already has that. So when we want to move a monster around, we'll simply pass it a tile.
has its own
. In addition, the monster will immediately move to its starting
OK, let's start working on that movement code. Before moving we first check a
function. Why is this needed?
Because in a broughlike the player and other monsters may often "try" to move into tiles where they can't fit! This might mean bouncing off a wall or it might turn into bump combat, a staple of classic roguelikes.
For now, we'll check the neighboring tile (the one we're trying to move into) and only allow a
if the tile is passable and has no monster in it. We'll
to indicate the move was successful - either we could move or attack (we'll do that part later).
method has to do is update a bunch of references: which
is on which
is holding which
OK then. Which monster are we going to make first? The player!
"Beware that, when fighting monsters, you yourself do not become a monster... "
When I first started writing roguelikes I naturally coded the player as a separate thing from the monsters. It seemed counterintuitive to make them the same kind of thing, but actually they share so much behavior. And also it's a common roguelike mechanic that monsters and players behave in similar ways
This should look similar to how we implemented our
classes. We're passing a tile that we live on, a sprite index of
and a maximum HP of
I'm setting an extra flag called
to dintinguish from other monsters.
Some people might say this is a little kludgy, but I find this kind of flag very easy to use.
Ok, now let's put that player class to use and rip out all our
Test out your game. The player moves around but can't go through walls. Awesome.
Now let's switch gears and do some art.
I'd like you to draw the 5 monsters used in the game. As a reminder, the strategy I used was to create a basic shape and then in the next two steps draw shading and highlights.
The tiny resolution and small color palette of each sprite makes this process fairly easy.
Importantly, I didn't worry too much about how great these looked or if they made sense (they definitely don't). Rather my goal was simple sprites that all felt distinct from one another.
First, the lowly Bird.
Here, I made a lizardy dude that I'm calling Snake for some reason.
Some blobby thing that's going to have lot of health thus called Tank.
Sort of a big dinosaur head called Eater.
The last monster is the Jester.
While we're here, let's draw an HP pip sprite.
With the hard part out of the way, let's code up the monsters. For now, each one will only differ by sprite and starting HP. More detail to follow.
We need to think about how we're going to spawn
the monsters into the game. Right off the bat, I know we're going to want to scale the number of monsters based on the current map level, so first let's add a variable to keep track of that.
Then two new functions: one to spawn a single monster and another to create an bunch of monsters by repeatedly calling the first.
In this code, we're going to make an array of
and spawn some monsters into it. How many do we want to spawn? One more than the current level (2 on the first floor, 3 on the second floor, etc.)
, we start with an array of
classes that we just coded. To grab a random one, we'll
the array and grab the first element. Here again you see the use of the
keyword but this time combined with a variable instead of the literal name of a class. We start them each on a
like we did with the player. Then we add them to our
Only two more things to get monsters into the game: actually triggering
and then drawing them.
Check it out. We've got monsters on the map. They aren't doing much besides blocking our path. We'll need some monster AI to make this more interesting.
I often use a pathfinding algorithm called A*
to handle monster movement. But take a look at the pseudocode
. It's hard to get right, even if you've written it before. If you did
want to use A*
, I would strongly recommend a library like rot.js to handle it.
Instead we're going to take a shortcut and use "greedy" movement, which simply means trying to get closer on every
turn even if it's not the ideal path in the long term. Monsters will try to move closer even when that gets them trapped. Trust me, this will still lead to interesting (but unique) gameplay.
Our movement code will go in a method called
. Why not just do it all in
? We very often want to separate updates (things like status effect counters or health regeneration) from monster actions.
We start by getting a monster's passable, adjacent neighbors that are either empty and can be moved to or contain the player and can be attacked. Now we need to pick the closest
tile to the player. We do that with a
that is going to sort our
by their distance to the player, picking the first one (which will be closest), and trying to move to it.
Here's the method for calculating distance, specifically Manhattan distance
When should we call
In game dev, the code to update the world and the monsters in it is commonly called a "tick". So we need one of those.
We iterate over
(importantly in reverse
so they can be safely deleted), call
if each monster is alive, and if not delete them with
The last piece of the puzzle is when to call
and I bet you can guess. In a turn based roguelike, monsters move immediately after the player takes an action.
We override the
method in the
class. The overriden method that we wrote earlier in
is still available for us to use by calling
. If that method returns true
(meaning the player action was a success instead of, say, bumping into a wall), we can trigger
and all the monsters will then move.
Try out your game and that's exactly what you should see.
In the next section
, we're going to get the monsters attacking and fill out the details in our 5 monster implementations.