• 2D shoot 'em up
SDL2 Rogue tutorial
SDL2 Gunner tutorial
SDL2 Shooter 2 tutorial
SDL2 Widget tutorial
SDL2 Adventure tutorial
— Creating a simple roguelike —
Now that we can traverse floors, it's about time that we introduced some more Monsters (just variations on the Micro Mouse, to be honest) and equipment into the game. This part will focus on introducing those things, and also offering more powerful monsters and equipment as we rise through the floors.
Extract the archive, run make, and then use ./rogue13 to run the code. You will see a window open displaying the player character in a small room, with just a stair case (leading up). Play the game as normal and battle the new monsters as you encounter them. New weapons, armour, and microchips can be equipped as before. Once you're finished, close the window to exit.
Inspecting the code
Starting out, we've made an update to defs.h:
We've set the maximum number of floors to 13. This will be the goal of the our little game and where we must reach to defeat the Mouse King (who's incharge of this whole operation!).
Returning next to structs.h, we've introduced a new struct:
MonsterTypeChance will be used to determine the chance that a monster will appear on a floor. `name` is the name of the Monster (as used with the entity factory), while `chance` will be the percent chance of the Monster being added to the floor.
Moving over to monsters.c now, we've updated addMonsters:
We've more or less completely rewritten this function. To start with, we're setting a variable called `adj` to 10, multiplied by the value of dungeon's `floor` less 1. So, if we're on floor 1, adj will be 0; floor 2, and adj will be 10, etc. We're then setting up our MonsterTypeChances. monsterTypeChance is a static array in monsters.c, of MAX_MONSTER_TYPES in length (defined as 5 in monsters.h). We set each index in the array to a different monster type, starting with the easier monsters (Micro Mouse) and ending with the hardest (Meta Mouse). Note how the `chance` value of the MonsterTypeChance increases as we go along, moving beyond 100. These are our default values. We're subtracting `adj` from each value, however. This means that as we go higher up, the more the value of `chance` will decrease. We'll see what this does in a moment.
With our monsterTypeChance array setup, we then assign a variable called `n` a random number between 8 and 15. We're next using a for-loop from 0 to `n`, and calling addEntityToDungeon, using the result of initEntity, which is using the result of a new function called getRandomMonster.
getRandomMonster is where we're making use of our monsterTypeChance array:
The idea behind this function is to grab a random monster that is appropriate to the dungeon floor we're on. We start by assigning a variable called `r` a random value between 0 and 99. We're then looping through our monsterTypeChance array and testing whether the `chance` is greater than 0 and also whether `r` is less than `chance`. If so, we'll return the name of the monster.
What that means is that we'll be looking for a monsterTypeChance whose `chance` fits our floor. So, for floor 1, the monsterTypeChance array would look like this:
This is just one of many ways in which we could've handled our random monster creation.
Moving on, we'll quickly look at the new Monsters we've added. Starting with NeoMouse (note - these functions largely speak for themselves, so we won't linger):
A Neo Mouse is tougher than a standard Micro Mouse and is worth 35 xp.
initTouchMouse is next:
Tougher still, and with a higher sight range (visRange). A Tough Mouse is worth 50 xp.
initRabidMouse comes next:
In some aspects more difficult, in others, not so. We have plans for Rapid Mouse in a future part. This guy is worth 65 xp.
105 xp! The Meta Mouse is worth quite a lot! This is, again, because we have plans for him in a future part.
Now, over to armour.c, where we've introduced some new equipment. Starting with addArmour:
For now, we'll always add armour to our dungeon. There's a 50/50 chance that we'll either add a Biker Jacket or a Bulletproof Vest.
The Bulletproof Vest is a new item, that is created via initBulletproofVest:
The function is largely like initBikerJacket. Notice that the vest reduces our maxAttack by 1. This is just to demonstrate that armour doesn't have to always have a positive impact. Notice, too, that we're calling a new function at the end of initBulletproofVest named applyBonus, and passing in the entity (`e`) and the equipment (`eq`):
applyBonus is a simple function to understand:
The idea behind this function is to apply a bonus to our armour, by increasing the level of `defence` on offer. We want armour with bonuses to be found at higher dungeon levels, and so we subtract the dungeon's `floor` value from MAX_FLOORS (13), and assign it to `chance`. We then use the value of `chance` against rand and check if the result is 0. This means that it will be rare to find armour with a bonus at level 1, but more common at level 9.
After we've decided that we want to add a bonus to our armoud, we'll determine the bonus value to add. We take a further random of the dungeon `floor`, plus 1, and assign it to a variable called `bonus`. This means that the higher dungeon floor we're at, the greater the chance of getting a larger bonus. It does not mean, however, that finding armour with a +10 bonus is a sure thing, however. We take the value of `bonus` and add it to the armour's (`eq`) defence. We then use sprintf and a variable called `text`, to create a string reflecting the bonus (for example, +1, +5, +8, etc). Finally, we use strcat to add the bonus text to the entity's `name`.
This means that our Biker Jacket with a +2 bonus will now be called "Biker Jacket +2". Note that this applyBonus function has also been added to initCrowbar. It's not exclusive to initStunBaton.
Moving over the weapons.c now, we've made quite similar updates. Starting with addWeapons:
There's now a 50/50 chance we'll add a Crowbar or Stun Baton.
We're created our stun batons in initStunBaton:
Much large initCrowbar, we're setting all the weapon's attributes. Notice how the stun baton reduces our `defence` by 1 point, again just to demonstrate that weapons can have negative impacts on stats. We're also calling applyBonus at the end of the function. This is not quite the same function as was in armour.c, however, as we're altering different stats:
Rather than update the weapon's defence value, we're increasing the minAttack and maxAttack values. Otherwise, the two functions are the same.
Now, let's turn to microchips.c, where we've made some updates to initMicrochip:
We've changed now the microchip's random attribute assignment works. We're now assigning a variable called `n` the value of dungeon's `floor` multiplied by 2. This means that the higher the floor, the higher the value of `n`. We're then using a for-loop from 0 to `n` to randomly affect the microchip's variable stats. At each step of the loop, we're assigning a variable called `amount` a value of either 1 or -1. We're then using a switch statement against a random value of 4. Depending on the outcome, we'll add amount to the microchip's `hp`, minAttack, maxAttack, or `defence`. Overall, this means that as we move higher up the dungeon, the microchip's effects (both positive and negative!) will become more extreme. It will be possible, for example, to find a microchip that offers +24 `defence`, or even -24 maxAttack.
That's all our monster and equipment changes done. We can now look through all the other misc. updates we've added.
Starting with dungeon.c, we've updated createDungeon:
We're now only adding monsters and items to floors 1 through 12. Floors 0 and 13 will be free of those.
Turning to map.c, we've made a similar update in generateMap:
Again both floors 0 and 13 will be empty maps, with only 1-12 featuring mazes.
Over to items.c, where we've tweaked addItems:
There is now a 50/50 chance a Key will be added to the floor, while there may be up to 2 health packs to be found (or perhaps none at all!).
We've also updated the `touch` function, renaming it from `touch` to touchItem:
The function is now public (no longer static), and is used in armour.c, weapons.c, and microchips.c. Since these function were all doing the same thing, making them call a global function centralizes the logic and stops us from repeating code.
Finally, we've updated inventory.c, to fix a bug when we equip an item:
When using an ET_WEAPON, ET_ARMOUR, or ET_MICROCHIP, we're now testing to see if an entity has already been set at game's equipment array at index selectedEquipmentSlot. If so, we'll call addToInventory, passing over the entity at the slot index to put it back into the inventory, and then put the new one in its place. Not doing so means that the existing item would be lost for good (and also cause a memory leak).
The last thing we need to do is turn to entityFactory.c, and update initEntityFactory:
Adding in all the init functions for our new armour, weapons, and monsters means that they will be available to our dungeon.
And that's another part finished! Our game looks to almost be done. There are several more features that we want to put in, however. For example, what are the Keys for that we've been collecting? Keys open things, such as doors. In the next part, we'll be adding in doors and also placing the weapons, armour, and microchips behind them and in alcoves, rather than scatter them about the dungeon floor, for the player to trip over ...
The source code for all parts of this tutorial (including assets) is available here:
It is also available as part of the SDL2 tutorial bundle (with on-going updates):
If you do not wish to create an itch.io account, you can also purchase the tutorial bundle using PayPal. This method will be slower, however, as it will require manual verification of the transaction.
Share your comments and thoughts below. All comments are anonymous and cannot be edited.