Better way to run AI actions (and a few other things).

Hey guys. This is a bit of a "check my work" post for you regulars here on UnityAnswers. :)

Except more accurately, I need someone to check my logic.

My AI routines are a little messy. If not for nothing, half of my mess is coming from the super long if statements that allow my AI to take actions, especially because they can only take one action at a time (so they have to check if I can move, if I'm not currently reacting to a hit, if I'm not airborne, if I'm not already punching, and if I'm not already blocking... then I can do action X). You get the idea. Mile-long if statements that only get longer the more actions I code for the AI.

I really wanted to show you guys code chunks from an AI I'm working on, but the script is massive (compared to what we usually share around here). So I'm writing up code from scratch in my question. This demonstrates how I would typically do something. As I said above, could someone correct my logic/habits if applicable? I feel like I'm not doing everything as cleanly as I could, especially how I'm bouncing everything around through multiple invocations.

Without further disruption, here is my programming style thus far for AI in one of my projects for an AI that needs to be able to move, punch, and block. Correct me as necessary, and you could be rewarded with cake. Thanks guys.

void Start()
//Nothing here, just added for posterity I suppose.
void Update()
  //if decision-making cooldown integer is at or below zero...
  //if enemy is in range, if not punching, blocking, or jumping...
  //else if random decision leads me to want to block instead, and same checks as above...
  //else if player isn't even in range...
  //Move me closer to the player through series of commands inside of Update
void Punch()
  //Play my punch frame
  //Do damage to target, can't move
  //Invoke PunchCooldown in 1 second
  //Set a "CurrentlyPunching" boolean to true
void PunchCooldown()
  //Set "CurrentlyPunching" boolean to false
  //Resume my idle animation
void Block()
  //Do blocking animation frame
  //I can't take damage, I can't move
  //Invoke BlockCooldown in 1 second
void BlockCooldown()
  //Can take damage again
  //Resume my idle animation
void TakeDamage()
  //Hit react frame, take damage, can't take any more damage, etc.
  //Invoke Recover in 1 second
void Recover()
  //Can take damage again, can move, can act, etc.

What's worse is that every time I want to create a timed sequence of events, the longer the sequence, the more Invoke bounces. It gets to be cluttery. For instance, if I want to add a wind-up frame before an attack (keep in mind, I'm doing a 2D sprite brawler so I'm often referring to frames rather than animations, so I have to break this stuff up into frames), it'd be like:

  1. PunchStart();
  2. In PunchStart, Play wind-up animation, Invoke("PunchAnimate", 0.25F)
  3. In PunchAnimate, Play punch animation, Invoke("PunchCooldown", 1.0F)
  4. When PunchCooldown occurs, we set some boolean called "AmIPunching" to false so we can take another action later

And I end up with 4 different things I have to call. Then for each action being taken (attack, punch, jump/airborne, block, hit reaction) I have a different boolean I'm flipping on and off, and a new boolean to add to my if statement in every other action to check for to make sure it's off. Not to mention the clutter at the top of my script full of booleans. AmIPunching, AmIBlocking, AmIReactingToHit, AmIAirborne.

Can I streamline this? That is the question. :) I feel like there must be some way to programmatically have some list of events that can be checked very simply as one condition to see if my AI isn't doing anything so I don't have to go and edit every if condition and add a new condition every time I include a new action. Like:

ACTIONS Idle, Attack, Block, Walk, Jump, HitReact

And an if statement can simply go ahead by checking to see if idle (or rather, that nothing else) is his current action without needing to set a list of booleans in every action taken.

Feel free to throw out any good ideas even somewhat related to what I'm discussing here, I'm very open to gutting and redoing parts of my AI script anyways at this point so I'll take any nuggets of wisdom.

(I'm also thinking this would have been better suited to a forum post. Whew!)

If actions are exclusive, condense all your `isJumping` into a single var. Turns checking they are all false into "`if the action is idle`":

int curAction = 0; // 0=idle, 1=jumping, 2=blocking

If you are really cool, use an enum:

enum Action { idle, jump, block };
Action curAction = Action.idle;

if(curAction == Action.idle) // pick new action

I use "action functions" something like yours, but each action is one function with several waits:

bool canAct=true; // global "one action at a time" lock
bool canPunch; // for some reason, I can't continuously punch
float incomingDamageMult; // some var I want to adjust based on action phase

IEnumerator Punch() {
  incomingDamageMult=1.5f; // I take more damage for 1/2 sec of windup
  yield return new WaitForSeconds(0.5f);
  incomingDamageMult=1.0f; // vulnerable phase is over
  Instantiate(poop, poopMountPoint, .... );
  yield return new WaitForSeconds(2.0f); // throwing for 2 seconds
  canAct = true;
  yield return new WaitForSeconds(2.5f); // rest of special 5 sec punching rule

void AIstep() {
   while(true) {
    if(!canAct) continue;
    if(canPunch && .... ) StartCoroutine(Punch());
    else if( <I feel like jumping?> ) ...