The following blog post, unless otherwise noted, was written by a member of Gamasutras community.
The thoughts and opinions expressed are those of the writer and not Gamasutra or its parent company.
Disclaimer: This post will feature aspects from a design point of view, some information can be useful when coding a similar system, but no programming skills will be required.
As an established genre in the industry, turn-based strategy games follow some traditions we had in mind when we first started developing Conflict0: Revolution, which didn’t mean by any ways that we couldn’t explore new possibilities and improve some already existent mechanics while adding some new ones. The game acts most of the time as a traditional TBS, having many elements already found in similar games, such as movement by tiles, different units with different abilities – similar to a class or job system -, different types of attacks – such as melee and ranged – and other special abilities. There was also an Auto System, a kind of mecha which a human class could pilot – they were usually stronger, having different traits. Some could cover a lot of the level space, such as the Pumas, while others were extremely resistant, like the Jabutis.
Conflict0: Revolution was first developed for Tizen smartphones, and many things were different back then – the UI, many of the stages effects that we had to adapt to fit low-end smartphones and stuff not so noticeable at a first glance – such as enemy disposition in some stages and a different artificial intelligence.
The artificial intelligence as implemented for Tizen was working good enough, but due to time constraints, it wasn’t so consistent and diverse as it could be. Many behavior patterns were easily predictable and the stages could all be played in the same way once you learned how each class worked. Apart from that, many behaviors from some classes were weird – for example, soldiers running away while medics were attacking stronger and full-life autos which would just retaliate them in a counter-attack.
We also had the problem of the same unit having a lot of different decisions, making many units act similar to each other, removing the identity of how each one thinks and acts.
There was also no difference whom an enemy unit would opt to attack. The only thing took in consideration was the HP of the target and how close the target was.
The highlighted unit, for example, has story-wise the trait of prioritizing autos when attacking, but that didn’t happen in the game, making the Sniper choose the closest unit instead of autos inside its range (even if they are further, which makes sense as the Sniper is a ranged unit).
How the system works
Each unit has a set of decisions it can choose from. Those decisions take into account considerations when they are chosen. The decision that can be took in the game are Attack, Heal, Repair (heal but for autos), Join In (enter autos), Join Out (eject from autos), Move Far (move as far as possible from unit X), Move Near (move as near as possible to unit X), Move Near Range (move as near as possible while taking in consideration its attack range) and Move Far Range (move as far as possible, but keeping the target in attack range – specially useful to make ranged units attack outside from counterattack range).
Those decisions are the actions enemy units can perform on their turn, they are chosen from a consideration system, which works like a filter that returns a number from 0 to 1. Some of them act as a boolean, returning just 0 or 1 if false or true, while others act as a float, returning a number between 0 and 1.
In the example above, the consideration filter, in this order: isEnemy (return 1 or 0 if there is one of the enemies listed in the role filter); isInRange (return 1 for enemy units in range and 0 for enemy units out of range); isLowHP (returns an inversely proportional value according to the remaining HP of enemy units, so the lower the HP, the higher the returned value) and finally isLowDefense (returns an inversely proportional value according to the defense of enemy units, so the lower the HP, the higher the returned value). All those values are them multiplied to return a consideration weight.
This value would then be multiplied by the decision weight that has this consideration as a parameter. The consideration above is one used for an Attack (human) decision.
The returned value would then be multiplied by 3 in this example and return a float value. All other decisions would then run their consideration controller, and by the end, the unit would choose the action with the higher value to perform. Medics, for example, have higher HealDecision weight, while Snipers have higher AttackAutoDecision weight.
What we changed (Android Iteration)
Each unit had now a narrower range of decisions, making them more oriented to their focus and also making different units having different behaviors, giving an identity for each one of them.
There is a new ConsiderationController for Attacking autos, so now each unit will choose between attacking human enemy units and auto enemy units. Stronger units, such as Snipers and Rebels usually opt to attack Autos, while weaker units such as Medics would normally opt to attack human units with low health when healing is not an option.
Now this is not a system change exactly, but before we had a Considerations and a Decisions folder for each unit, but since the considerations just calculated a number to, just then, multiply them by the Decision weight of each unit, we had now only one folder for all considerations and then separated folders for decisions of each class. This kept the project organized and we could locate considerations easily, fastening our process.
Finally, as designers, we know sheets are our friends, so we made a sheet with all classes with all decisions and their respective weight. We colored the sheet according to the decision weight so we could track which decision each class would take more often, this helped us to, again, distinct units from one another. We also had a sheet below to track all parameters we took into consideration for the ConsiderationController of each decision.
This saved us a lot of time.
A lot of time.
When characters other than the playable ones are essential to the gameplay, a functional artificial intelligence is a basis to keep it consistent and fun. We worked to improve the AI from the original version of Conflict0: Revolution and make it better in every aspect.
Even if AI is, most of the time, a mathematical system, understanding what each character do and think in the game context makes your AI more realistic and give you a point from where to start.
As shown, our main focus was to keep each character unique, so their range of decisions became wider, giving each one of them their personal identity, so the player wouldn’t expect a fragile medic attacking an auto, while Snipers became a real threat – making the positioning of playable characters move strategically.
Last, but not least, having an easy to read document, such as the sheet we had to track the decision weight of each unit saves a lot of time, this alongside with having an organized project can make your life really easier.
Conflict0: Revolution will soon be available for free on Google Play.