Separating the Core Game Loop


Most of my work the last week or so has been focused on a bit of a rewrite.  I know it's a bit early in the project for a rewrite, but since I'm a bit of a noob when it comes to unity development I somewhat wrote myself into a corner with the project so far.  I wanted to take the time to separate out things properly before the game got too large to tease apart easily.

Separating Logic from View

My goal with the rewrite was to separate the "game" from the "view".  The idea is the rules and execution of the game would be done in its own separate code project independent of any sort of game engine, and then the "view" (in this case, a Unity 6 scene) would just plug into the game project and show things on the screen when stuff happened.

core game engine loop

The main benefit of this approach is a "separation of concerns"; the game code doesn't care who or what or how the game is being displayed to the player, it just does stuff like making sure units moves are valid and calculating damage for attacks.  Likewise, the view code doesn't know what actions a player can do or where they can move, it just gets that information from the game and displays it to the player.

Because of this, we can do cool things like battle simulations where we can put together crazy combinations of units and actions and objectives, run them 1000 times in a few seconds, and spit out a report on win ratios and what units used what actions.  It's easy to create test scenarios with a few lines of code to see how many Sharp-Hisses (geese) are needed to take down one Great-Antler (elk).

Conversely, the downside of this approach is that actions in the game do execute as quickly as the CPU can execute them, which doesn't lend well to animation.  To counter this, we use a combination of events and an animation queue.

animation queue subscribing to core events

For each thing that we want to visualize, we emit an event.  That's centralized on the View side through an "animation event listener", where events result in "animation actions" being placed on a queue.  The queue is used to ensure the animations are played in order one after the other, instead of all at once.  This way, even though a "CPU Turn" consists of "move, attack, end turn" in about 2 milliseconds, that still gets queued up as several animation events that take however long we want to resolve, one after the other.

Benefits

Because of this, it's quite easy to iterate on the game logic without having to worry about the animation, and vice versa.  If I wanted to try out a new style of attack where a unit charges towards its target, attacks it, and knocks the target back a space - I've already written the "move" and "attack" animation steps!  I can just make a new Action in the "Core" project that fires off "move", "attack", "move" events and it will just work from an animation perspective, and I can focus on the movement and attack restrictions and calculation.

Even then, if I decide that the first movement has to be in a straight line, the view already knows how to ask the core logic what movement squares are valid; we can just return "three squares in front of the active unit" and the view will highlight them and restrict the selection, because we've already coded that.

Hopefully this illustrates the benefits of this approach, and I'm hoping it'll speed up development of new features going forward :)

Leave a comment

Log in with itch.io to leave a comment.