Okay, more AI, but I’ll stop soon. I will also focus on refactoring, cleaning and generally improving my enemy and AI code. (It’s really important that I like the code, otherwise I won’t want to work on it. And “wanting to work” is already really hard to come by.) I need to reach a comfortable place with AI where it’s prototype-showable, even if not “perfect”.
Firstly, I don’t like that my desires have both assumptions and beliefs as separate concepts. On retrospect, I was being too careful not to mess up my belief logic with adding assumptions. But I realize now that I should have just added them as beliefs, because all they really do is duplicate the same conditions:
So I can just converted them into this (with -100 essentially clearing the desire fully):
Now all desires are always “active”, some just have no chance of being desired at times. And this works pretty much the same as it did before, but with cleaner code and slightly uncleaner -100 scoring. I also had to do some heavy optimization for the desires, as every enemy has this system and they all keep checking everything every frame. Now they check things periodically over multiple frames and cache their results.
There are still a ton of various issues with enemies. Here’s three stuck trying to move into the same coordinate:
This happens because enemies don’t realize they can go to the next location on the path. So I added some logic to skip the coordinate if the enemy is already closer to the next one. And an additional check if the enemy doesn’t reach a coordinate in a certain time amount, then just stop the pathing altogether. This might cause enemies to do a half-loop and then move on, which is perfectly fine with my “enemies are kind of dumb” design.
I also need to work on my enemy player awareness logic. This will solve several other issues with enemies spotting the player too fast or too slow, not being able to get alerted about their buddies in fights, having any sort of debuffs or effects causing awareness loss (or gain), and just generally my code having poor flags instead of a reasonably designed approaches. My whole “can see player” logic would b supplemented by “aware of player”.
I have three awareness states: have not seen the player, have seen the player, and can see the player. For this, there are also four internal states that allow the enemies to gradually acquire sight and lose sight of the player:
I am parametrizing most of this as well:
I can’t realistically GIF all the scenarios I want, because it takes a really long time to set them up and successfully capture. But the end result is fairly nice. Enemies don’t notice the player instantly, but after a small delay. If the player comes really close, the enemies will notice them at once. The keep firing at the player location a brief time after the player leaves their sight. And they seek the player and all that goodness as well as these values simply plug into the belief/desire checks. In short, most of this just “slows” down enemy responses and makes them feel less robotic.
Here’s another issue I encounter: there are two enemies attacking the player and three enemies completely oblivious to where the player is:
I need my enemies to somehow notify nearby enemies about the player’s general location. I don’t want to literally add a telepathic rule where enemies learn such things instantly. What I want is an “investigate disturbance” desire which gets triggered when enemies learn about a nearby disturbance. Moving to a disturbance is easy, but I have to define what a “disturbance” is exactly and what happens when there are multiple ones or they have different significance. (Or if I even want to go that complex. Honestly, most of chasing the player logic is so they cannot cheese the AIs by quick-peeking corners or running through without fighting at all.)
So I’m going to keep a “disturbance list” and enemies can always look at it and “decide” which one they actually noticed. Then that becomes their “target disturbance” that they can investigate if they have nothing better to do. I just have to make sure not to create too many disturbances. For example, is every player shot an audible disturbance? I think I will start super-simple and have there be a single possible disturbance — player spotted. Then enemies in range can notice this. Here are AIs doing pretty much most of their logic:
A couple guys noticed the player peeking the doorway and went out to find them, then proceeded to attack them. Then other AIs sensed a disturbance and went to check that out, then also joined the fight. This seems to be enough for now, and I can extend the whole disturbances thing later.
The one messy thing here is that seek player and wander around is very very similar to the investigate disturbance. So much so that I pretty much cannot tell the difference in-game. Stuff like this usually implies there is a redundancy somewhere. And I think it’s my “wander around” logic that’s redundant here. In fact, I can pretty much get rid of it. And after some tweaks, I can get the enemies to pretty much act as before if not better.
There is also an implementation problem of having just a “disturbance list”. If each enemy has one, they cannot share it. If all enemies use the same one, they cannot make individual decisions. What I need is a 2 subsystems: a (world) disturbance monitor and (enemy) disturbance tracker. The monitor is a global system that observes disturbances enemies spot like “enemy sighted here”. And the tracker is each enemy’s own module that watches the monitor and picks out the disturbances that the enemy can actually detect (green circles as the player moves left):
It’s hard to show the difference, but the main thing is that enemies can only notice a fight going on if they are near enough. And if the player moves, they won’t go to the new location unless they actually notice the new fight too.
AI is great when they work. In contrast, here’s a bunch of enemies stuck in the door:
Enemies just don’t know how to queue behind each other and use doors effectively, especially once they’re through. My “doors” are really tiny and I haven’t yet fully considered room sizes and door sizes for navigation purposes. There, problem solved:
Well, I’ll get back to this at some point, but fatter doors do actually fix like 90% of the issues.
Oh boy, I sure feel like I’m over-scoping and over-complicating this whole AI framework. And this is just one enemy type — trash gunman. But I can hopefully reuse the system without too much trouble for different AI types and styles. But first I am cleaning up, consolidating and trimming whatever I can. For example, I can just reuse the same values for all my beliefs and intentions, because those tiny differences hardly matter:
After all, I will have to debug it all, and that’s an… interesting thing to do with so many moving parts:
Keeping my debug readable is a constant struggle:
Also, looking at my intentions and comparing them to my desires, they are the same exact thing. So I am just going to reuse the same value throughout and simplify the code somewhat (already part of the above debug output — only desire is visible).
Speaking of areas, my enemies need a way to limit their firing range. It’s fine if they occasionally shoot from far away, but they should realyl prefer to get in at least reasonable range to the player. Behold, a new desire is born:
And this works out nicely when enemies randomly stray away too far from the player or the player moves away. They won’t immediately rush back, but their desire to do so will increase and they will eventually approach the player (green lines are the directions enemies want to take towards the play):
And I’m glad I can have more than a dozen enemies and they still manage to successfully attack the player (eventually):
This is a probably an exaggerated example as I don’t think the player can realistically hope to fight this many enemies. The only “real” issue is when an enemy fails to find a location to go to. This is usually because the location is too congested. So I added a “failed to move to desired location” belief that briefly triggers the “idle around” desire:
With this, the enemies move around a bit instead of standing frozen (looks broken) whenever they can’t go to where they want.
By now I’ve “played” the game hundreds of time now, tweaked a lot of numbers along the way, and the enemies are behaving pretty much close to what I imagine dumb smart AI to be. But I think it’s time I stop expanding the AI stuff for now. No one cares how it’s made as long as the results are as expected. I’ll tweak parameters further as I play it. But I will likely not expand until I need different AI types.