Welcome back, Dungeon Masters! Today, we’re finally going to look at the Dungeon Master object (…natch) and its role in processing the game’s command queue.
As I explained last time, the DungeonMaster object maintains a queue of all the commands collected from the player.
Pretty straightforward. The DungeonMaster maintains a List object of MobCommands, which we saw the structure of last time. There’s an addCommand() method, which adds a new command to the end of the queue. We keep a constant to make sure the queue doesn’t get too large (which normally won’t happen, but if the game’s running slow and the user starts monkeybashing the UI, commands might get backed up in the queue).
Next up is the processCommandQueue() method. This method simply checks to see if the queue is empty. If it isn’t, it calls the dungeon’s advanceTime() method – which, as the name suggests, advances the time passed in the dungeon by one turn – and passes it the first command in the queue as the player’s input for that turn. It then removes the command. Simple enough, right?
Where the actual magic happens is in the Dungeon class. It didn’t do much the last time we looked at it, so let’s revisit it now.
We’ve moved the map generation and player-spawning code out of the Start() method and into its own init() method, which will become relevant next time when we start doing some more sophisticated setup code in the Game object. Stay tuned for that! Moving on, you’ll notice we now have a DungeonMaster object and a turnQueue, which is a list of all of the commands collected from both the player and the monsters’ AI for the turn.
Since the Dungeon is a Unity Monobehavior, it has an Update() method which gets called repeatedly as the game runs. This is our update loop. Every pass through the loop, the dungeon tells the dungeonMaster to collect input from the player (…which we’ll see shortly). Any input collected that frame is converted into a MobCommand and added to the DungeonMaster’s commandQueue. And, after the input has been collected, that command queue is processed, as we just saw.
Now, you may be wondering why, if we’re only collecting input once a frame, why we don’t just pass that input directly to the dungeon. Why the extra queue? Well, the answer is because eventually we want to be able to collect input from the game’s UI, as the result of button presses and event handlers. Those happen outside of this loop, so we need a way to collect all of the command input in one place and handle it in the order it was generated.
So, how do we do that? Here’s the DungeonMaster’s collectInput() method, which is textbook Unity keyEvents.
See how we’re instantiating new commands of the types that we defined last time? The MoveCommands take a reference to the player as their source (I added a getter to the dungeon to access its player object), as well as a constant denoting the direction, and the PassCommand merely takes the player (also as the source; remember, the source object is the one that initiated the command) and nothing else.
Okay, once we have commands to process, what do we do with them? I’m glad you asked. Remember, the dungeon calls advanceTime() to advance the game one turn. Here’s that method.
Not exactly rocket science, is it? Every turn, the dungeon clears its turnQueue, then adds the passed-in player’s command object to it. (Eventually, it will loop through all the other Mobs and ask them for their commands, but we haven’t gotten that far yet!) We then sort the queue, using the CompareTo() method we defined to sort on the turnOrder of the Command. We pass in a lambda function calling that method with two MobCommands (denoted by the parameters mc1 and mc2) as our sort comparator (check the C# documentation for more on that – it’s kinda tricky).
Once the queue has been sorted, all of the commands will now be in turn order – so, we merely loop through each command in the queue and process it by calling the process() method we looked at in the previous blog. Finally, we loop through all of the entities in the map (mobs, the player, items, and so forth) and call their postCommandUpdate() methods. This method is currently empty, but will eventually do things that update the Entity’s state, like check if its health dropped below zero as the result of any attacks carried out in the queue so that it can mark it as dead.
…and that’s it! This simple queue processor runs the entire game, and is stupidly flexible. We simply define new commands for any new action we want a mob to be able to take, and as long as they inherit from MobCommand and define their own process() method, the turnQueue will handle them.
Tune in next time when I’ll show you how I finally decided to represent Entity spawning, including the use of JSON-based default data to avoid hard-coding values. Things are starting to get interesting!