Welcome back, Roguelikers! Today we’re going to be looking at the basic structure of the Command classes… which, collectively, are the heart of the entire game.
As many Roguelikes before it, Dungeon Ho! is turn-based. Whenever the player inputs a command, the game puts that command into a queue, then queries all of the other Mobs to see what they want to do. Each Mob also selects a Command (…even if it’s just doing nothing), then adds those to the queue as well. The game then sorts all of the commands into an “initiative order” to represent the fact that some Commands should take priority (IE, “go faster”) than others. For example, moving is faster than casting a spell (usually!), so if a Mob submitted a MoveCommand and another Mob submitted a CastSpellCommand, the first Mob would act before the second. Since Mobs attack one another by moving into another Mob’s space, this means the first Mob may very well kill the second Mob before it gets to act! Once all of the commands have been sorted, the game then runs down the list and processes each command in order, carrying out whatever tasks need to be done in order to fully execute the command. This may involve updating the mob’s position in the world, running an attack routine, picking up an item and adding it to the mob’s inventory, or a host of other things.
For now, though, let’s look at the basic structure of a command. It’s represented by the MobCommand class, and each different type of command will get its own class that inherits from MobCommand. For example, moving is a MoveCommand. We’ll get to that in a moment.
Don’t worry about the ToString() method being cut off on the bottom. You’re not missing much. All it does is replace the standard ToString() method with one that outputs a formatted string including the Command’s turnOrder, name, and data.
Anyway, let’s look at this class. Note the two member variables of type Entity; we defined our Entity class in a previous blog entry, and it’s the building block of all… well, entities in our game. A MobCommand needs to know its source (the entity that initiated the command) and its target (the entity the command is invoked upon). For example, if one Mob was attacking another, we’d create a MobCommand with a source equal to the attacker, and the target equal to the victim.
We provide two constructors for our command class; note that one takes a parameter of type object, which gets assigned to a member variable called data. Don’t confuse an object (little ‘o’) with an Object (big ‘O’)! The lower-case object type is the base object class that everything in the C# language inherits from… that includes basic datatypes like integers! The Object class is the base Unity object class, and the two of them are not equivalent. We want our data variable to theoretically hold anything we can stuff in there, so it needs to be the most base of objects the language allows. We could easily just have a single constructor that can take null for the data value, but this is more intuitive when you see it in action, IMO. The variables are assigned in the init() method, to keep from duplicating code across constructors.
The process() method is the heart of a MobCommand. This is the method that the game will call in order for the command to do whatever it needs to do. We give it a reference to our Dungeon, because although most commands will simply act on the source and target, some of them will most likely need to gather additional data from the dungeon… like when a Mob tries to move. (We’ll look at the MoveCommand shortly, so hang on!).
The compareTo() method allows us to compare two MobCommands and evaluate whether one is “higher” than another based on their turnOrder. This method, as you will see later on, gets called in the Dungeon’s update loop when there is a turn to process. It simply calls the built-in C# method CompareTo(), which is implemented in all basic datatypes… including ints, which is what our turnOrder is defined as.
The getTurnOrder() method should require no explanation. Since turnOrder is private, it’s just a getter that allows other objects to access the variable. We might not even need it, as turnOrder is basically calculated once and then forgotten about.
…and it’s calculated in determineTurnOrder(), which is a virtual method that can either default to the base initiative as calculated by the source entity (currently, it’s merely the maximum value an ability score can be, which is 100. We’ll talk more about ability scores and how they determine initiative in a future blog.), or get overridden by a child class that provides its own way to calculate the turnOrder. For example, we may want MoveCommands to use the mob’s current Agility score plus some kind of action modifier.
So, how do we make new commands? Glad you asked. Let’s do it. Here’s the PassCommand, which is the command a mob would invoke when they didn’t want to do anything at all.
Fascinating, right? The most interesting thing about the PassCommand class is that it inherits from MobCommand and invokes that base class’ constructor, passing it the values it is given by the code (defaulting the data parameter to null, as the PassCommand never provides data. Hm, could probably invoke the other constructor instead, then… better fix that!). We have to do this because otherwise Unity would complain that there is no “default” constructor, which is a constructor that takes no parameters… and it’s right!
We also override the process() method, because eventually the PassComand is going to put a message into the game’s log when a Mob passes its turn. For now, though, we can just fart out a message to the log so that we know our queue has processed the command.
Now, let’s look at one with more beef to it; the MoveCommand.
The MoveCommand currently defines four constants to indicate the compass directions. Note the TODO tag that implies that we’ll be expanding this in the future to handle diagonal movement. (It’ll be fascinating, I assure you.) Anyway, again the command provides its own constructor, and this one uses the data parameter because the game needs to pass one of those compass directions in so the command knows which way the source plans to go. (Once again, I note that there’s an unused parameter – targ – so I modified the constructor definition after this screenshot was taken to remove it and simply pass null to the parent class.)
When the game processes a MoveCommand, it looks to see which direction is stored in the data, then constructs a Vector3 representing the modification to the source Mob’s position. It queries said position (making a copy of it in the process, as the getter for that property provides a copy of the value, not the actual property itself), modifies that copy, and then checks if the Mob can move into the new space. If they can, then the command updates the source Mob’s position with the new value… not using the setLocation() method that I defined for precisely that purpose! Argh! Y’know, it’s a good thing I’m doing these blogs. They’re helping me catch a lot of mistakes that I’m overlooking while coding.
Anyway, we’ll fix that later. You’re probably wondering what magic the canMoveTo() method is doing to check if the Mob’s path is clear. Wonder no longer.
Remember how I said a command might need access to the Dungeon? I’ve added a getter method to access the DungeonMap object, and passed that to the canMoveTo method, which calls the map’s getTile() method to return the Tile object on the WALLS layer at that position. If there is no tile there, then the space is clear and we can move there. Easy! That’s the whole reason we have a WALLS layer in the first place – all collidable/solid tiles that block a Mob’s movement exist on that layer, and that layer alone. You’re not shrooming if you don’t remember my defining a getTile() method, though; I added it as part of implementing collision.
Well, that’s enough code for today! Tune in next time and I’ll explain how the game polls the turn queue and processes command input. We’ll have our adventurer trekking about in no time, I promise.