Since the beginning I was aware that one of the most critical and difficult part of the development of Wizards’ Duel would have been the Event Manager. I wrote a bit about it in an earlier post, but now that I am focusing on it exclusively I thought it was time to go a bit deeper and also help myself give shape to my thoughts. Please be patient if this post may seem more akin to a brainstorming.
As you may know the basic Design Pattern in game development is the Game Loop, a continuous loop where 1) inputs from the user are collected, 2) the game world is updated and 3) the scene is rendered.
In a real-time game, where everything happens at once, the update step just cycles over all the game objects and run their update methods. How the updates are managed is a whole another story, however you can imagine that you don’t strictly have to control the flow of every action: the updates naturally runs in a sequential and ordered way.
Turn-based games are different. Only in their most basic form you can reduce the game logic to a simple sequence of actions, only pausing when it is the player turn. But as soon as we introduce animations or initiative (actors may have different speeds and the order in which they take action changes from turn to turn) things become complex. In the case of Wizards’ Duel the “logic” step looks like this:
For Wizards’ Duel I have wrapped this kind of logic in an Event Manager class, or Event Dispatcher. The dispatcher keeps a list of Objects sorted by their initiative and only gives them the right to act when it is their turn. These objects can be of several types, they may be Player objects, Computer Controlled objects (Non-Player Characters) but also “logical” ones like the Level. Levels have their own initiative and Artificial Intelligence so they can dynamically react to the player or just do their business like overflowing with lava or freezing things to death.
Animations are the main problem here. When an object moves around, for example, the dispatcher must understand that something is happening and stop execution until later. If you do not, the logical and visual representations of the game world would go “out of sync”. Since the world is grid-based objects are either on a cell or another, they cannot happen to be “in-between”, movement is then instantaneous. However animating the same movement takes time: when this happens other objects may react as if the actor has already finished moving, thus starting their animation. For example attacks will appear to be performed too soon: you will see the attack animation when the defender hasn’t even started to move yet!
Now consider multi-part animations, like an attack causing a death animation to trigger, and you will see that it is not so simple to pause and restart the execution at the right time. Even worse, multiple actions from the same object at the same time that may result in multiple animations! (for this I implemented a condition for skipping the “Remove Object from queue” step after an action is taken)
The Event Manager also stores the user Inputs and only executes them at the right time. This store-and-run method, instead of just ignoring user events when out of turn, permits the game to move freely from a sort of real-time feeling when nothing is happening on screen except for the player’s character moving around to the turn-based tactical combat. Of course having these asynchronous events does nothing but complicate further the animation problem, especially because a lot of events may happen at the same time.
Then there are scripted events, like boss entrance scenes, something that I have not really factored in yet. A way to solve this problem is to interrupt the normal game logic and implement a different pipeline of actions, more akin to animations, but I find exception based design ugly and chaotic (while developing… we will talk about mechanics another time). I think it would be best to just create specific kind of events that implement time-based conditions, like “stop queuing for N milliseconds” while a script updates the world on the background, linked to a Script object in the queue.
The logic seems sound, but I still have not completely nailed the implementation. What are your thought? Do you have any experiences to share?
Thanks for reading.