Happy Holidays and welcome back to the dev log for our upcoming game We Got Dungeons!
Today we'll talk a little about the AI of the NPCs.
We talked about many different ways of approaching the AI in our game. Through our discussions, we realized that what we wanted is a couple of things from our enemies.
First and foremost they should be consistent. This might run counter to intuition but ultimately, what good AI should do is not "outsmart" the player, it should instead give the player a sense of accomplishment and strategic mastery. This cannot be achieved if the enemies act randomly. On top of that random AI generates unpredictability which is a tactical game that can lead to frustration as all your master strategies are thwarted not by something that's "smart" but by something random.
The next thing we wanted to be was for the enemies to be complimentary and varied in their behavior. This creates the potential for emergent game-play and keeps every encounter interesting and enjoyable. Chess wouldn't be much fun if all the pieces moved like the peons.
Ultimately the AI should give character to the enemies. Furtive hit and run AI conveys a much different character than one that actively hunts you down.
These principles work together to create a combat system that is fun and engaging and also acts as a puzzle of sorts. The player can predict the enemy's behavior, plan accordingly, and formulate a winning strategy. And that always feels awesome.
Let's look at an example AI
They [VHS enemies] will attempt to attack in melee and will use their constrict attack once every three turns. They will attack if a player is adjacent to them, but they will always try to target the player with the least HP.
You can see how this simple and predictable behavior instantly gives character and strategic depth to the opponents in combat.
Now let's talk about the implementation. Our good friends, the function pointers, make an appearance once again here.
In our general-purpose creature struct, we have a pointer to a void function that takes a game object (itself) as a parameter.
Next, we declare the enemy data as a constant which means it is stored in the ROM and we just set the pointer of the function to the according to AI module function.
This approach allows us the easy making of AI modules that are completely independent of the rest of the code base, which in turn speeds up the debugging and tweaking one of the most fickle aspects of game development. One can easily see how bad AI can be just switched off (Stop Dave...) and quickly repaired.That concludes our chat on enemy AI. Join us next time when we'll be revealing even more details about We Got Dungeons.