I’ve been working on an AI pattern for the last couple of days. It’s been challenging for me to come up with a clean solution that doesn’t get super complicated
I want enemy actions/abilities to be decoupled from the decision-making logic that triggers them. I want to drop behaviors onto an enemy and then attach the conditions that trigger those behaviors. So far I haven’t found a process I’m really happy with, but I think I at least have a “good enough” system in place.
Here’s the solution I landed on. It’s your basic state machine but with an added event layer on top. The players here are conducts, transitions, and reporters.
A conduct is an ability that gets attached to an enemy. It’s the finite state of the state machine. It’s what the enemy is currently doing - it’s behavior or goal. The granularity of these behaviors can be whatever. They can be as small as “jump” or as large as “chase the player while opening doors and shooting bottle rockets.” Currently I have a wander conduct, a pursue conduct, a shoot conduct, and a dodge conduct.
Transitions are attached to a conduct and determine when and how a new conduct is triggered (when the state machine changes state). It’s what needs to happen for an enemy to decide it should stop wandering and start shooting. Conducts can have multiple transitions. Again, transitions can be simple or complex.
Reporters are components attached to the enemy that send events to the state machine. As I’m writing this I’m wondering if I really need them. Reporters observe stuff happening in the world and report events to the state machine of the enemy they’re attached to. The rationale here is I wanted a reusable way to add “senses” to enemies. For example one monster would have a reporter that would check the player’s position every quarter second and if within a certain range would cast a ray to check for line of sight. If the player is “sighted”, the reporter would send a PlayerSighted event to the state machine, which might affect a transition object. A transition might be waiting to activate on this event. Then for a more difficult enemy, we could attach a different reporter that has a greater detection range and doesn’t bother with the line of sight test. It would send the PlayerSighted event even if the player was on the other side of a wall.
The reason I’m not sure if the reporter is needed now that I think more on it is it’s really just a decoupling of the trigger. A reporter decides when to send an event. A trigger decides what event to listen to and what conduct to switch to. I could nix the reporter and just have different transition for the two PlayerSighted cases mentioned previously. One would do the ray cast and one wouldn’t. However, having the detection logic in the reporter is pretty nice. Oh ok - I remember now why I added the reporters. Sorry guys this is just a stream of consciousness at this point. Like I said there are different kinds of transitions. Some will transition to the target conduct as soon as they get an event of a certain type, others will transition if they don’t get an event of a certain type within a time limit. The use case here is I want enemies to stop chasing Glace if they lose sight of him for a while. So the enemy has a reporter that sends player sightings to the state machine. The wander/idle conduct has a transition that listens for this event and upon receiving moves to the pursue player conduct. The pursue player conduct has a transition that will go back to wander/idle if it doesn’t get a player sighted event for like three seconds.
I’m excited to get some enemies fully coded in! Thanks for reading.