Greetings, all! First off, we'd like to thank the itch.io community for your amazing support, suggestions and feedback! We’re nearly 35% funded on Kickstarter and could use all the support we can muster! Consider helping us with even a $1 pledge—you’ll get your name in the credits, access to backer-only updates and even a digital background! Also keep in mind that we have a free demo as well, which can be picked up on itch.io as well!
For this update, I wanted to take a deeper dive into our enemy AI (artificial intelligence) system to give a glimpse into how enemies decide how best to dismantle your party! We’ve put a lot of stock in our enemy AI and often times we get asked, "Why are your enemies so mean?" So we spent a little bit of time working on some visualizations to answer that very question!
As you already know, Bevontule is a little different among tactical RPGs in that it doesn’t feature a grid. So the question of "What should the enemy do this turn?" becomes a little more difficult to answer, since there are a few more options!
Every enemy turn starts off the same way—we figure out which positions the enemy could move to. We do this by generating ‘rings’ around the enemy, where each colored dot represents a position the enemy could move to. In this GIF, the blue dots represent the ‘best’ positions and the red dots represent the ‘worst’ positions.
We account for the size of the enemy as well, so smaller enemies have way more potential movement options.
Now I know what you’re thinking: this bug needs to switch to decaf. Since this is a visualization, however, this doesn’t actually happen in the game itself, but is more an internal look at what the enemy is ‘thinking.’ So what is actually going on here?
We’re essentially running this particular enemy through every single action he can perform on every single target that he can reach—that’s really all there is to it! You’ll notice that the dots change colors as the enemy ‘shifts’ through each possible action and that’s because the enemy’s optimal action is being updated as it tries out different possibilities. Blue positions are ‘better’, red positions are ‘worse.’
You can also see this reflected in the list to the left, which is simply keeping track of what the top 20 best actions are. In this particular scenario, this little bugger has about 384 actions to process! Most of them are less-than-optimal moves, so you won’t see them in the list.
After all actions have been processed, the enemy chooses one from the list. Now, he could always pick the ‘best’ action, which is in the number one slot—but we want the enemy to mess up occasionally: it’s kinda similar to how an enemy in a first person shooter doesn’t always blow your brains out with every single shot (unless it’s PUBG, of course.)
We add in two parameters to adjust for this: a difficulty parameter and a variance parameter. The difficulty parameter can be thought of as a percentile. If it were set to 100%, he’d always choose the ‘best’ action. If it were set to 90%, he’d choose randomly from the top 10% of actions.
The variance parameter can be thought of as a percentage as well, but basically says, "randomly add or subtract x% to the difficulty", where x is the variance. So if we wanted a perfect enemy, we’d give a difficulty of 100% and a variance of 0%. If we wanted a relatively ‘dumb’ enemy with random flashes of genius, we might go with 50% for the difficulty and 30% for the variance.
Each enemy features its own configurable AI behavior, controlled mainly by the values shown above. When an enemy ‘processes’ an action, it essentially runs through these values, seeing whether the movement position, action and selected targets meet certain criteria.
The ‘scores’ shown in the previous GIFs are calculated based on these values! The value next to each piece of text could be thought of as a ‘weighting’ towards that particular action. So, in bugbro’s case, he likes to stay back and deal magical damage primarily—in fact, magical damage is weighted 25x heavier than other actions. When a value is 0, it means that the enemy doesn’t give any significance to the described action at all, and when it’s negative, the enemy prefers to avoid that action. That’s why, in the earlier GIF, the movement positions near the enemy were ‘better’, because he’s a lazy POS.
The nice thing is that we sum up these values for every target that an enemy could hit with a given skill from a given position. This means that our little friend will automatically calculate higher scores when hitting multiple targets—and in general, every enemy will, unless we were to set "Action Target Count" to a negative value.
Here’s a more involved example, featuring an enemy that can buff and heal his fellow enemies. Even though the visualization itself takes quite some time (this GIF has even been sped up by 500%!), the actual computation takes less than a second, mainly because we break up the processing among multiple frames. Without this technique, the game would completely freeze while the AI ‘deliberated.’
Finally, here’s what it looks like in-game, from calculation to execution!
That sums up today’s update! I want to also remind our supporters that our Thunderclap campaign is active—consider supporting it so that we can have the strongest possible final stretch of our campaign! We’ve also added a few more entries to the Bevontule challenge drawings! There are only a few days left, so make sure to get in there and whip some whelp!
We hope you found this update informative and like always, we’re more than willing to answer any questions, address any feedback and respond to any suggestions you might have! Thanks for reading!