- Home /
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...
Punch();
//else if random decision leads me to want to block instead, and same checks as above...
Block();
//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:
- PunchStart();
- In PunchStart, Play wind-up animation, Invoke("PunchAnimate", 0.25F)
- In PunchAnimate, Play punch animation, Invoke("PunchCooldown", 1.0F)
- 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!)
Answer by Owen-Reynolds · Mar 24, 2011 at 04:50 AM
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() { canAct=false; incomingDamageMult=1.5f; // I take more damage for 1/2 sec of windup animation.Play("windup"); yield return new WaitForSeconds(0.5f); incomingDamageMult=1.0f; // vulnerable phase is over animation.Play("hurlFeces"); 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 canPunch=true; }
void AIstep() { while(true) { if(!canAct) continue; if(canPunch && .... ) StartCoroutine(Punch()); else if( <I feel like jumping?> ) ...
Ah... that all makes it easier. $$anonymous$$y current downfall is I have a background in VB and only learned C# once I picked up Unity, so I still have a lot to learn about the language itself. Had no idea what enums were or how to use them. I also embarrassingly had no idea I could use simple wait commands. But I suppose that's because of the coroutine, which I also am very poorly versed in (and they're not well-documented in the manual in regards to practical applications). I still don't quite grasp how a coroutine varies from a regular function (except being able to pause it).
Just as a last quick question before I mark your answer, what is the significance of making your coroutine function an IEnumerator? I'm sure it's something simple, but again, I know jack about enumerators (except that I'm gathering they are useful as shorthand).
Enums are just a fancy way to give names to ints, so you don't have to remember that 2=attack. Anything in Unity C# that has a Wait
in it needs an IEnumerator
return type, just because. $$anonymous$$y AIstep
should have had one in front, since it needs to Wait between checks. Functions with Waits need to be called using StartCoroutine(thingWithWaitInIt());
. StartCoroutine says to split it off into another $$anonymous$$i-program (a thread) running along beside you, sort of a "fire-and-forget."
Your answer
Follow this Question
Related Questions
The name 'Joystick' does not denote a valid type ('not found') 2 Answers
Shooting Damage Help 1 Answer
Enemies Moving Through Terrain Help 1 Answer
AI enemy scripting help 1 Answer
Stay Back! 2 Answers