@Ortu Basically everything I do for game programming comes down to state management.
For example, the first enemy is nearly complete as far as their general behavior and combat are concerned.
This first enemy has 9 different states:
Idle (just the normal standing still idle animation for the enemy)
Advance (to within attacking range of player)
PauseBeforeAttack (for playability the enemy delays a slight amount before actually attacking)
Attack (this enemy has only one straightforward attack)
Retreat (this is so there is some back-and-forth interaction to the combat which is one of the key things I am going for... after the enemy attacks it then immediately retreats in an attempt to avoid an anticipated counter-attack)
Injured (this is just the injured animation for the enemy)
KnockedBack (when the player has hit an enemy with enough force it is pushed back a ways)
KnockedDown (when an enemy's health is below a certain amount and it is hit... OR when the player performs the special Ground Slam attack.... enemy is knocked to the ground... it may also be pushed away as well)
Dead (this is just the death animation for the enemy)
The actual delay in the PauseBeforeAttack state is based on the health of the enemy. Although it probably seems backwards from a playability aspect it worked out better to have the enemy pause delay decrease as its health drops from being injured.
Of course, in any case, if the player dilly dallies too long before attacking or moving out of the way they will be struck by the enemy.
But yeah basically I have always broken down complex behaviors into a series of different states. I never knew I was using a State Machine until I came across the term many years later.
To me it was just a natural evolution in my game programming. I knew that trying to see everything as a whole and implement it that way was either unfeasible or greatly over-complicating things.
So I came up with the idea of breaking the complex behavior down into a series of distinct actions. And by chaining each of these simple actions together into one or more sequences (Action Chaining is what I called it) I could produce as complex behavior as I wanted to.
There are a couple of other benefits that comes as a result from this approach:
1. It greatly simplifies the code making it easier to debug & enhance because I can concentrate on any one state at a time and in some cases may decide to break a state down into two states. That was the case with the Attack which became PauseBeforeAttack and Attack. Each dedicated to performing only that specific action in the chain.
2. Performance. Each frame the only code that needs to be executed is the code within the current state. Obviously, some code to sort out the overhead of which state routine to call but that should be super fast. Generally SELECT CASE structures are turned into Jump Tables. Not sure if AppGameKit treats them them way but anyway sorry got lost in a ramble. Oh yeah, each frame the only things that need to happen are processing to handle the requirements of this current state such as incrementing a timer, performing an animation and checking for only those things that are possible to do from this point in time from this specific state.
So... together this is extremely flexible and powerful.
So all of that just to say I simply use states to manage the AI. I decide how I want an enemy to behave and then I model that behavior through a series of specific states each handling one tiny piece of the overall behavior.
I also use States for the player control and basically everything else as well such as a GAME_STATE which may have Title state, GamePlay state, GameWon and GameLost States, etc.
EDIT: That all may not have really addressed how I approach the AI I guess. lol It tells how I implement it by breaking it down into a number of tiny action components.
From the high level view... basically, the way I approach games is I design an enemy. And I define certain abilities and limitations for what this AI character can do. Then
I put myself into the role of being that enemy and
using what I have to work with (the abilities I decided on earlier) and
keeping the limitations of my abilities (decided on earlier) in mind I know the behavior I want to model. So when an enemy is in my game
it is basically me... lol...
it is what I would do if I was that enemy and had their abilities and limitations to work with.
So, for an enemy in a dungeon walking across a platform for example... that is me... and in real life I would walk to the edge and look around for intruders (perhaps in the context of the role for this enemy) so that enemy will walk to the edge, stop & look around... then I might turn around and walk back to the other side of the platform OR maybe (assuming the enemy has that ability) I decide to jump down from the platform and patrol the area below... so in this case unless I have some other data to base the decision on it might end up being like an 85% chance that the enemy will turn around and walk back to the other side of the platform and a 15% chance the enemy will jump down and check out the area below. If the enemy decides to walk to the other side of the platform this repeats again... stop... look around... decide what to do next. Of course, if the enemy can hear sounds they might drop down to investigate, if they see something below they might drop down to investigate.
I don't use waypoints and pathfinding systems. Instead the enemies navigate their environment the same as I would. They look and see where they can walk. They know when they are at the edge of a platform. Basically they have all of the information I would have if I was them (again within the limitations I set for them).
It might be an odd way but this is how I have always approached it (unless I am making something super simple with canned enemy patrols this area walk back and forth and that it is, enemy flies across the screen and that is it). Each enemy is actually me. Basically inside that creature and using the abilities it has I decide what my options are within each action step and I program it accordingly.
But of course, it may be
a very simplified approach because again I place limitations on these enemies. An enemy doesn't need to actually be like a real-life humanoid.
TI/994a (BASIC) -> C64 (BASIC/PASCAL/ASM/Others) -> Amiga (AMOS/BLITZ/ASM/C/Gamesmith) -> DOS (C/C++/Allegro) -> Windows (C++/C#/Monkey X/GL Basic/Unity/Others)