I’ve been working really hard on a turn based RPG project, and we needed a simple AI solution to allow us to easily create different behaviours for different monsters.
Eventually I settled on creating a sort of swappable AI brain that we could attach to any AI character.
The way the AI brain works is pretty simple! What I’m most proud of is the extremely modular and extensible nature of the AI system I created.
The way the AI works is very simple at a basic level. The GetMove function does all the hard work. I pass in the current BattleSystem, which is the encapsulation of a single battle—it holds all the information necessary to hold a battle. That is, all the party members in the fight, the special modifiers on the party members, where they’re fighting, and more.
With this simple structure, I was able to create basic AI very easily. An AI that uses a skill randomly against a random opponent was trivial.
It feels so good to be able to create such a thing so quickly! I created a bunch of different ones for basic use in the game. A attacker that always attacks the lowest HP enemy, an attacker that attacked a specific class more frequently, a healer that supports its teammates… it was all very simple to create! I could add as difficult or simple logic into the GetCommand function as I wanted!
The fact that my AI brain architecture was so easily extended made more likely to play around with it. Eventually we wanted a sort of AI that could do multiple things. We wanted an AI to heal friendlies if they were low, revive friendlies if they were dead, and then dispel friendlies and enemies based on the context.
I could have written a huge AI class just like I created the SingleAttackAIRandom class, but I realized that I already had all the little parts to this semi-complex AI already! So then after much pondering I started creating a new AvAI class… that holds AvAIs!
This ConditionalAI is very simple! This AI merely holds a bunch of AvAIModules, which are wrappers for AvAIs that also hold a conditional. The module merely nullifies the wrapped AI’s output if the requisite conditionals aren’t met. Whenever the ConditionalAI is asked to return a move, it starts asking at each AI module in order. If the module returns null, then the move has been nullified by the conditional, meaning the condition wasn’t satisfied. If one of the modules is satisfied, then it will return a legitimate move, and the ConditionalAI will just spit that back out!
The result of this isn’t crazy cool or amazingly groundbreaking, and might be very obvious to some people, but it was very neat to me to be able to write AI by snapping AI blocks together. It makes it easy to program a huge variety of different behaviours, each getting more and more complex! I’m pretty proud of myself for making such a cool, extensible, and easy to program AI system for this game.