Stage 4 - Monsters Part 2
Since the monsters will be attacking the player and vice versa, let's first draw the HP for each.
monster.js
Monster HP now displays nicely.
The idea here is to draw one HP pip sprite for each unit of HP the monster has. We can't just draw each pip in the same spot; you would only see the top one in that case. We've got a bit of funky math to layout all the pips:
-
- Since
operates on a sprite index we normally pass in whole numbers representing 16 pixel sprites. However, we can instead work in individual pixels by using fractions. So
means 5 pixels within a 16 pixel sprite.
-
- this resets to 0 every 3 pips.
-
- this increases by one every 3 pips.
The result means pips are drawn first left to right offset by 5 pixels each and then stacked vertically offset by 5 pixels for each row.
Attacking
Now onto attacking. We can let the monsters attack the player and vice versa with a small addition to
.
monster.js
We're comparing
flags to make sure monsters don't attack each other.
When an attack is successful, that triggers
,
applying damage to the target monster's HP and if they run out of HP, they
. When dying, the monster sprite is set to index
(our player corpse). This will only apply to the player since we earlier wrote code to delete monsters as soon as they are
.
Cool beans! Attacking is in the game. If you let the monsters kill you, you'll notice that you're still able to move around the map as a corpse. We'll tackle that later.
Monsters are working as expected, but with identical behavior. Here's the plan for making each one unique:
- Bird: our basic monster with no special behavior
- Snake: moves twice (yes, basically copied from 868-HACK's Virus)
- Tank: moves every other turn
- Eater: destroys walls and heals by doing so
- Jester: moves randomly
Snake
Since
is already done, let's start with
. Make sure to test out each monster after updating their code. While testing this code, it may be easier to temporarily modify the
code to only generate a specific kind of monster (left as an exercise for the reader). Or you can just refresh a bunch of times.
monster.js
Rather simple. The Snake can move twice, move and attack, but not attack twice (that's overpowered!).
We need one tie-in within tryMove to set
to true upon attacking.
monster.js
Tank
While working on the Tank, we'll introduce a
flag. When a monster is stunned, they'll be unable to react until the next turn.
We'll be able to use this flag in multiple ways: to stun monsters whenever they are hit by the player or hit by certain spells and to pause the action of monsters like the Tank.
monster.js
When monsters are attacked, they get
,
making it easier for the player to take on tough monsters.
monster.js
If the
flag is true, we reset it to false and do a
which exits the function and prevents the monster from doing anything until next turn.
monster.js
Here, the
monster stuns itself if it wasn't already
at the beginning of the turn. Effectively, this results in action only every other turn.
Eater
Then comes the
.
Before doing normal monster behavior, this guy is going to check for any nearby walls and eat them for health! Each wall will grant half a health point (our
method only draws whole points though).
monster.js
First, we need to get all the nearby walls using
and only include tiles that are not
(indicating a wall)
and are also
(so the outer wall doesn't get destroyed).
If walls are found, we're going to call two new methods. The
method is replacing a
tile with a
tile.
The
method adds half a hitpoint to the monster. If no walls are found, we'll simply do the normal monster behavior.
Now let's implement those methods.
monster.js
This method
is a one-liner. Add some amount of healing "damage" without going over some global
, which we'll need to define next. We don't want our monsters to gain infinite health!
index.html
Next up is
.
tile.js
You can use
any time one tile type changes into another type. Here it's a wall replacing a floor, but imagine if a water tile replaced a floor!
One thing that's not coded here is to copy over monsters and items present on the old tile to the new tile. Keep that in mind for future additions.
Jester
The last monster is the
and it's able to move randomly simply by trying to move to the first neighbor returned by the (pre-shuffled)
getAdjacentPassableNeighbors
.
tile.js
With those enemy behaviors in place, our little broughlike is starting to feel like... a game. 😍
In the
next section, we'll turn this thing into a proper game with a title screen, multiple levels, and victory and failure conditions.