Game designers regularly face the challenge of creating a credible AI. This process can be either relatively simple or extremely complex, depending on the requirements of the project and the goals that you pursue. Banzai Games’ Senior Game Designer Mikhail Dragovalovskiy wrote about his experiences in creating the AI for the popular mobile fighting game Shadow Fight 3.
Differences between SF3 and Most Other Fighting Games
The Shadow Fight series was designed with a number of unique features that distinguish it from many other fighting games:
- The use of autoblocks: If the player’s character simply moves but does not attack, he automatically blocks any regular attack of the enemy;
- A slower pace and smooth animations as a tactical component: The player must have enough time to recognize what kind of blow the enemy strikes and which technique he should choose for a successful counterattack. This intentional slowdown of combat speed also allowed us to make the animations as beautiful and physically realistic as possible;
- Real collisions: This leads to very cool scenes in which one character dodges the attack of the opponent and immediately strikes back in response. Thanks to the smooth animations the player has the time to notice these details very well.
- Shadow form: When a special energy bar gets completely filled, the player can enter the shadow form and perform several spectacular tricks that penetrate his opponent’s block
AI in Previous Games in the Series
We already used AI logic to control the opponents in our previous fighting game Shadow Fight 2. But due to the transition to 3D graphics, the use of real character collisions and the introduction of the shadow form, we could only reuse a part of the existing AI logic. The rest had to be reinvented from scratch.
Where to Start Working on AI?
If you don’t want to use a bot that is powered by machine learning algorithms, there are two main other approaches:
- Use a weak bot, then strengthen it to the desired level with new features;
- Use an invincible bot and then weaken it.
The second option sounds very attractive: you create a cool AI that beats the players first, and then you mercifully reduce the difficulty of the bot. From the developers' point of view, this may be more convenient, because you can pre-design all the features of the bot’s behavior and have a full idea of its final performance in the fights.
Of course, we chose this option first, although we later realized that it wasn’t so simple: with our balance of animations, automatic blocks and the variety of special skills, a highly skilled player can almost always find out the necessary tactics and tricks that allow him to defeat the AI. We did not understand this right away, but after measuring the winrate of the players at different stages of the game it became obvious. This, by the way, contradicted the fact that for many people, judging by their feedback on the game, the bot was almost invincible.
How Does Our AI Work?
Triggers or rules of conduct
The system of rules under which the bot performs random actions is called triggers.
Here is an example of this set of rules:
- The bot evaluates the distance between itself and the player and generates a list of those attacks that could reach its opponent. Then it chooses a random attack from this list;
- The bot does not spam the player with throwing weapons;
- After being hit by a throwing weapon, the bot has a guaranteed chance to dodge the attack for a certain amount of time.
- and a bunch of other things that a bot should or should not do.
If you release such a bot against a beginner of an equal level, the result will depend on the armed weapons of the opponents. The one with the faster animation will win. Because, in fact, both just hectically push all buttons, except that the bot does it in a more meaningful way.
The logical step to improve the AI is to reduce this randomness and add a decision-making system that will guide the bot to victory. To do this, you need to understand which decisions are right in which situation. For fighting games there are two situations that happen all the time:
- The enemy is waiting;
- The enemy is attacking.
There are two solutions, too:
- You need to attack when there is a high probability to hit;
- You need to block when you’re attacked and can’t counterattack.
It turns out that for victory, the AI must interrupt enemy attacks with its counterattacks, or block if it cannot interrupt them.
We distinguish two groups of decisions that the bot has to make: first - when the player is standing, and second - when the player is attacking. In order for the AI to know exactly how to counter, we came up with collision tables.
Remember how Doctor Strange looked through all the possible outcomes of the battle with Thanos? The collision tables work similarly: a computer calculates the outcomes for each attacking animation at every possible distance in advance.
In more detail: we simulate a strike with a sword and at the same time a strike with a katana, and then we look who will hit whom. We do this procedure for all attacks in the game, at all distances and with delays of a different number of frames.
(Example: “I launch a blow with a sword, and my opponent starts a katana strike after N frames”).
All results are recorded in tables. We repeat this elaborate procedure before each game update and store the resulting tables in compressed format inside the game build, so the calculations don’t eat up the device’s resources in runtime. This way the player’s client already has a complete list of all the outcomes for any possible attack combination. The AI can see in these tables which action can counter any attack of the player at a specific distance, or understand that it’s best to block the attack.
As a result, the AI effectively responds to the player’s actions with the optimal counterattacks. Next, we need to teach the bot to attack in the optimal way, rather than using only random strikes.
The choice of distance and the right strike
An attentive and intelligent reader could say: “Stop! Why even attack by yourself if the enemy is in the autoblock mode most of the time? We simply have to wait for the player’s action and punish him with a quick blow that will interrupt his attack! ”
Yes, this is true, but a bot that does not move and does not attack without the player taking the initiative looks at least strange. A credible AI must also be able to choose effective attacks, even if the optimal strategy is to play just with counterattacks. To do this, we have developed a system for choosing the best distance and strikes.
We know that most players have a repertoire of several favorite punches. For us, this means that our bot should specifically choose such attacks that cannot be interrupted by these typical player punches (at a given distance). To do this, we...
- ... gather statistics of all strikes of the player;
- ... calculate the optimal distance for the most effective counterattacks against his favorite strikes;
- ... feed this value to the bot and make it stay on this distance throughout the battle.
In theory, it sounds cool, but in practice, it has proved to be unsuitable: players rarely stand idle - they almost always press buttons and attack the bot. Too often, the bot was busy only reacting to the player's actions, instead of taking the initiative. So, the choice of the right strike only works against passive players. But at least the choice of the right distance improved the effectivity of our counterattacks - they’ve become more diverse because the bot has more choices.
At this stage, the bot is already quite effective: it recognizes the best moment for an attack, it knows when to counterattack and when to block. Next, we will talk about the systems that allowed us to set the required level of complexity and weaken or strengthen the bot.
Resentment and forgiveness
This is an interesting concept in our balance system. Through this, we create a sufficient level of complexity for beginners and professionals, as well as motivate the player to use different strikes, which increases the interest and fun of the game.
The essence of this concept already lies in the name: the bot takes offense at the player’s attacks if the player uses the same ones too often. Resentment is an internal variable in the game that reduces the AI’s chance of making a bad decision and increases the chance that the AI will conduct a perfect counterattack.
On the other side of the scale lies forgiveness. This variable increases the bot’s chance of choosing non-optimal solutions and reduces the chance of a counterattack, when the player stops spamming and switches to using different attacks more often.
If we want to have a challenging bot, we reduce the speed of forgiveness and also set a high starting level of resentment. If we want to make a weak bot, the opposite is true.
Every bot in SF3 has a delay in making a decision. This parameter causes the bot to pause N frames before choosing an action option. It’s useful because it creates an illusion that the bot is thinking. The simpler the bot we want to make, the higher the delay we set for it.
In order to make some parts of the game more vivid, more emotional, we are constantly monitoring the states and relations of various game variables.
For example, we can force the bot not to use throwing weapons at certain distances. We can reduce the speed of forgiveness if a player has more hit points than a bot, or make the AI more aggressive if the player has fewer hit points.
In general, this is an add-on that we use to generate situations in which the bot dramatically changes its behavior and effectiveness. If necessary, we restrict some actions of the bot in certain situations. For example, we made it more difficult for a bot to make a throw than for a player. Otherwise, the player would never be able to throw a bot. Since the distances for a throw are the same for both fighters, the bot would always be the first to start the throwing animation.
Based on all of the mechanisms described above, we have created 9 basic AI presets that we use in different situations and sections of the game. This approach allows us to change the balance step by step and track the effecting changes.
The process of developing our bot tactics was a long one. We introduced these features gradually, so sometimes our players suffered, and then we suffered reading their complaints and curses. When we released the latest iterations of AI modifications, the flow of negative reviews regarding the behavior of opponents stopped, which made us proud of our work.
Our latest experience shows that the current approach to bot improvements will not give a noticeable further increase in quality. Therefore, we started working on a neural network that will simulate the actions of a player.
There are several reasons for this decision:
- We are developing a new PvP fighting game - Shadow Fight Arena. Its special feature is fights between heroes with very different tactics, different abilities, and mechanics. Creating a realistic bot under these conditions is many times more difficult than in the case of SF3;
- We already have a positive experience using neural networks to solve animation tasks in our new product Cascadeur. This is a software for creating realistic animations that we use for all our games;
- We want to assemble a team of competent ML specialists in order to further increase our expertise in this area and use it to improve Cascadeur and our games.