Scripting a Turn-Based Tactical Command System with C# in Unity

A Brute Force Solution to a Complicated System

Micha Davis
Geek Culture
Published in
4 min readAug 6, 2021

--

Disclaimer: I know there are standards and conventions for this sort of diagram, but I didn’t need to share this idea with anyone but me when I drew it, I just needed to see how things connect so I could plan.

Here’s the objective for today: create a system for recording and replaying multiple commands from multiple actors, where all actors first declare their moves in ascending initiative order, and afterward all moves are played in descending initiative order.

There is a more elegant solution than the one I’m presenting here. This is the initial brute force implementation to get the mechanics working. We’ll be shaping this all up into something more polished in the next article.

We’re gonna take this one in two parts. Today’s article will focus on actors declaring their actions. Tomorrow I’ll cover executing the action queue.

Actors Declare Actions

We’ll start just after we calculate initiative, with the DeclareActions() method:

Stepping through this, we start with a for loop based on the number actors in the scene. We collect the last person in initiative order from the SelectionManager, and we create a new instance of the Turn Class (below). Then we check to see if the actor is a hero. If so, we turn on the UI panel for player commands, wait until their action is selected (with a bool comparing the number of actions the player gets with the number they’ve queued), and then turn the panel off. If the actor is not a hero, for now it creates a Panic feat with a range of 0 and passes that along.

For selecting the hero’s action, we have the following method:

That’s a slight refactor of our earlier SelectTarget() method. Here it is only enabled during the player’s turn, and now each possible outcome leads to a LogFeat() method call.

There are three overrides, one for each combination of feats and targets our actions will allow. Every action could potentially have a feat and a target, a feat alone, or a target alone. You’ll notice I’m using the object type for the target variable. This type can hold any data — specifically in this case, either a GameObject target, or a Vector3 movement target. We load the data received into the AddAction() method of the Turn instance we created earlier.

Let’s look at that Turn class:

IMPORTANT: A ‘new object[2]’ is required each time AddAction is called to avoid overwriting the references in the action list.

All actions potentially have one or both of a Feat and a target. As I mentioned above, we have two potential data types in the target variable. The Feat is a third. I want to store all of these in one data structure to make it easy to package up, so I’ll use an object array to hold all three kinds of data in one variable.

In order to get the action[] information, I’ll use two methods, one that accepts a target with optional feat, and one that accepts a feat with optional target. That should handle all three possible combinations.

Now I finally have a single object that represents all the data for any action any player could have done, and I add it to the end of the list. Once all the actions are collected, we resume the DeclareActions() method at the end, where the instance of Turn is loaded into the Round list and we push the last person in initiative to the front of the line.

The Round list is a linked list of Turns in the Round.

After this loop runs through all the actors in the scene there will be a list of Turns each with its own list of actions.

Whew. That’s the big part. Tomorrow I’ll go over the ProcessRound() method, which will playback our recorded actions by traversing the list of lists (of lists) we’ve generated. And THANKFULLY because we’re using linked lists we won’t have to use any for or foreach loops to do it.

Until then!

--

--

Micha Davis
Micha Davis

Written by Micha Davis

Unity Developer / Game Developer / Artist / Problem Solver

Responses (1)