I can see people wonder …. why would you bother ? Don’t we have enough engines already? Well, yes, but how many have you written so far ? Have you tried writing one? It is really really fun to work on something like this. If you like RPG games that is!
This article was born due to Eric Lippert. I follow what he writes, because let’s face it, the guy knows what he’s talking about. A few days ago he started to write Wizards and Warriors, part one.
That was such an awesome subject that I had to contribute and I remembered I actually had written such an engine a while back. It was just for fun and because I saw a mobile game which will go unnamed simply because I forgot what it was called! The game was not very complicated, you had up to three guys on the left side of the screen, up to 3 on the right, they would fight each other, do damage and whichever had guys still standing up at the end was the winner. You could have weapons and armors, knights, mages etc. it was not very complicated, but it was fun.
Fancying myself a programmer and a massive RPG lover I started to think how hard would it be to code something like this? So, I thought to myself .. why not? I had time and I was just getting into mobile stuff, so I wanted everything to run on a mobile device so there you go, another decision was born : to come up with a pcl version I could then run through Xamarin Android and deploy to my Nexus 7 tablet.
The thought process behind all this was quite interesting and it makes for the bulk of this article, it is in fact the purpose of this article. The code is available on github so please have a look so you can follow up with what comes next.
The solution is made up of two projects. One is the engine itself built as a PCL library. The second is a Console app I used to test some of the functionality. There is no graphical interface, I wouldn’t even dare to build one up myself!
First things first, what were my requirements ?
- I wanted to have the concept of two sides fighting each other with an unlimited and unequal number of combatants. So I could have 1 guy fighting 5 if I wanted.
- Next, I wanted to have the concept of inventory slots, different one for everything such as chest, left hand, right hand etc. This would allow me to add cool stuff like armour and weapons and shields. You can’t have an RPG game without those things!
- Next, I wanted the concept of loot, the guy you would kill would drop something, it could be like 300 gold, or a cool item.
- Finally I wanted to be able to change the combat system, the way damage was dealt in an easy manner, should i ever want to.
Now it was time to code something and see how it starts to shape up.
First I needed something to represent the players. I didn’t have a clear image of what this would require so I decided to start simple and have a character name and some base stats. I was pretty confident I wanted all my characters and AI controlled monsters to inherit from this base class so I can do some basic initialization for everything in one place.
So this was settled, the base class would be an abstract class and the first thing was to create and attach some basic stats. I decided to keep it simple, so I would have three stats to begin with Damage, Defense and HitPoints.
These stats were needed so I can build some items which meant something and changed something visible. I could have a chest piece with 20 defense and 30 hit points which meant the hit points pool of the character would get bigger with each item added. This is al nice and cool but it’s obvious I needed to recalculate the stats the moment a gear item was equipped. Let’s not forget that equipping an item could mean another item was unequipped and then the new one equipped so I had to recalculate the stats by adding in the base stats and then the stats of each item in each slot.
This is how the Gear abstract class was born. It has a list of GearSlot items. What’s a GearSlot you ask? A GearSlot knows what slot it belongs to and what item is in it. It also has the concept of equipping an item. In order to equip an item in that slot, I needed to be sure that the item can be equipped in that slow. So an Item would need to include this information in its definition. This is all nice and dandy but what exactly is an Item ? Should it be another abstract class ? I decided not to make it a class because there was no functionality I needed to add to it. All I needed was the name, the slot it belongs to, plus the stats it has. Remember the three base stats? I needed values for each of those in the definition of each item. Now, hang on a sec, characters also have stats so clearly I needed something I can reuse everywhere, something without any functionality. Well, an interface matches this perfectly. I needed an IStats interface, very simple, like in the code below:
As you can see, nothing fancy there, just the basic stats I wanted.
Now that I had the stats, the IItem interface started to shape up:
We can have an interface extend another interface so that’s what I did here. This way duplication was avoided, the stats remained in their own interface which would then be used in other places.
So, now let’s see how this is used in the Gear class. This class is an abstract one, it actually has various methods which is why it can’t be an interface. The whole point for this class was to hold data on all the slots, all the gear a character has equipped and provide a way to equip an item in a specific slots. It also recalculates its final stats. This statement is important because this means one thing : remember how we said each character has a set of base stats they start with? Think about racial differences. Well, we cannot recalculate the stats without knowing the base stats so I decided to add a reference to the owner of the gear.
This owner is the one who tries to equip the gear item in a specific slot, which is fine, I can do that, but let’s not forget that each slot has a method to equip an item so all I needed really was to call that base method and then recalculate the stats. This is only done in the ActorBase class since this is where we know the base stats. All we talked about here indicates a certain design. We will have a constructor which takes the owner, this allows us to calculate the stats and then offers a way to equip an item. The code could look like this :
Why did I make the Slots list read-only ? Simply because that list is not meant to be changed once it’s initialized. Each character is supposed to have one slot of each type and they never change so to indicate this design intent I made the list read-only.
Up to this point we talked about slots, items, gear but not about the main ActorBase class. Let’s have a look and see what’s going on in there. Why the name ? Well actor seemed suitable and base because it’s meant to be an abstract class.
It will hold data on the stats among other things. Ok I said stats, but which stats ? We have base stats to start with, but the stats values are different based on the gear equipped in the gear slots. So clearly we need to keep not only the base stats, but also the current ones with the latest values. Every time an item is changed we will clear the current stats, start from the base ones and add the stats of each equipped items. Once we’re done that’s what we store in the current stats. This method also frees us up to add spell effects which modify these stats further if we want to.
Cool, with this settled, we moved on to Gear. Well this is easy, we’ll simply add an instance of the GearAbstract class and be done with it. I chose this design so I can inject whatever implementation I choose and for that I chose a factory pattern. If things need to change, only a change in the factory needs to happen and nothing else changes. I could have added the implementation through dependency injection in the constructor, but this meant you could potentially have actors with different gear implementations and I did not want that. It would become way too complicated for this exercise and more difficult to follow.
An interesting point here, you’ll notice that the stats are actually private set. Why would I do that? the answer is simple. I chose this way of doing things because I only want the stats to be calculated not set directly everywhere. if no item changes or no spell is cast, there is no reason to alter a stat be it a base one or a current one and we know what the difference is.
Another interesting aspect here is that you will notice another EquipItem method. You could wonder why, since we already have that in other places. This was done so that I could call it on the main actor. The code would be actor.EquipItem instead of actor.gear.slot.EquipItem. It makes it a lot easier to work with. The implementation calls the sub method anyway, but that’s hidden from the programmer. It’s just a little thing to make our own life a bit easier instead of having to think where something should live and keep going through a number of class levels to get there.
Finally the RecalculateStats method. This is triggered every time an item is equipped.
We start from the base stats values and then keep adding stats for each item equipped. Very simple.
How do we alter the stats? Let’s take a look at the ActorStats class which is used in ActorBase. We can see it implements the IStats interface, which forces us to keep the same stats names and thus remain consistent across the system. The most important method here is AddCharacterStatValue which tells us which stat we want to change and by what value. The values could be negative, imagine a curse spell effect or a drain.
This method is the one used by items to update the current stats.
Ok, so at this point in time we have characters, we have gear, items and everything fits nicely into place. How could we test this ? Well, this is what the Test console app is for!
Now, we want to see how the stats change when stuff is equipped. So, we create a player, we create a few items with fancy names and some random stats, equip them and output the result to the console. I could have written some unit tests here and I probably will but for now this will do. As you can see the code is easy to read and in my eyes it makes sense and it feels natural. I don’t have to call anything else myself as everything is called automatically when it should and my stats reflect the correct values without me having to worry about recalculating them. Awesome stuff for a programmer. I don’t need to remember anything basically! This is no way means that I am lazy! Or does it?
This concludes part 1. In part 2 we will look at the combat system. See you then!