Search Unity

Where To Start Learning Ai/CPU and Boss scripting?

Discussion in '2D' started by Dogg, Sep 4, 2014.

  1. Dogg

    Dogg

    Joined:
    Mar 5, 2014
    Posts:
    203
    Hello people of Unity forums, I'm sorta stuck/lost here. I've been looking into implementing Ai/CPU and bosses into my game, but I haven't found any tutorials that actually helped me, also most are in Java script which get's annoying to translate to C#. Can anyone point me somewhere or recommend something to me, maybe even give me some pointers from yourself? I know you guys will probably need to know what type of bosses and Ai I'm trying to make, but just think the Ai/CPU doesn't attack me instead they avoid certain obstacles that could kill them or me, and the bosses just pace back and forward sometimes trying to attack you directly, and other times doing some special move, also some could fly. Well that's it I hope you guys can help, thanks for reading.
     
  2. GarBenjamin

    GarBenjamin

    Joined:
    Dec 26, 2013
    Posts:
    7,441
    The way I've always handled this is through states.

    Basically I create an enumeration of different states. Based on your description a rough guess would be something like this:
    Code (CSharp):
    1. public enum BossActionType
    2. {
    3.     Idle,
    4.     Moving,
    5.     AvoidingObstacle,
    6.     Patrolling,
    7.     Attacking
    8. }
    The boss will then have a variable to determine its current state:
    Code (CSharp):
    1. private BossActionType eCurState = BossActionType.Idle;
    And in my Update method I use a switch to see which state is currently active.
    Code (CSharp):
    1. switch (eCurState)
    2. {
    3.     case BossActionType.Idle:
    4.         HandleIdleState();
    5.         break;
    6.  
    7.     case BossActionType.Moving:
    8.         HandleMovingState();
    9.         break;
    10.  
    11.     case BossActionType.AvoidingObstacle:
    12.         HandleAvoidingObstacleState();
    13.         break;
    14.  
    15.     case BossActionType.Patrolling:
    16.         HandlePatrollingState();
    17.         break;
    18.  
    19.     case BossActionType.Attacking:
    20.         HandleAttackingState();
    21.         break;
    22. }
    What brings the boss (or any other enemy) to life is the chaining together of many small states.

    By breaking down each state and handling them individually you can focus only on what the Boss needs to do while in that state.

    For instance, HandleMovingState() could have the enemy just moving around randomly until it encounters an obstacle. Then you switch to the AvoidingObstacle state. Now, the coding would be different because the problem is different. In this state the Boss needs to focus on getting around the obstacle. After the boss succeeds in avoiding the obstacle you switch back to the Moving state. Occasionally, you may put the boss into Idle state. Perhaps he would stop and look around. If the player moves within a certain proximity of the Boss you can put the Boss into Attacking state. And so forth.

    Basically each state you can think of as a little program of its own. This allows you to really nail it down as detailed as you like. You only need to code for the things that can and should happen while it is in that state. For example, if this was for your player, your HandleJumping() would not need to check for another jump because the player is already in the air. Unless, of course you want to support double jumps. In that case you'd end up in the HandleDoubleJump() state and you would not need to check for another jump because it can not be done while in this state.
    This helps to clean your code clean and manageable.
     
  3. Dogg

    Dogg

    Joined:
    Mar 5, 2014
    Posts:
    203
    Wow very informative thank you very much. By the way, are these states for animation or things like transform.position, rigidbody2D.AddForce (newVector2 (0, jump));, and other coding in order to make the boss/Ai move and do all kinds of things?
     
  4. orb

    orb

    Joined:
    Nov 24, 2010
    Posts:
    3,037
    It's just a state machine. It's a common construct for logic. What you do in the cases is up to you , so you could write code that triggers animations, moves the object, figures out where the player is (if applicable in that state) and so on.
     
    GarBenjamin likes this.
  5. GarBenjamin

    GarBenjamin

    Joined:
    Dec 26, 2013
    Posts:
    7,441
    Hopefully the mods don't boot me soon for posting the link to my WIP game.

    But I am going to do it because this will illustrate it. It uses the web player so you can play it in your browser:
    http://supergar.com/Unity3D/platformcollect/pgdemo.html

    The states I am talking about are the current state of an object. The action the object is currently performing.

    The states are just a reference. Basically just labels you come up with to describe the different types of behavior you want to have.

    You will have a block of code, ideally a method to handle each state.
    You can also create a new script for each state.

    Inside each of your state methods you will control movement, animation, performing any special things that should be done while in that state (such as checking for the player being within a certain range, checking for an obstacle, etc).

    Anyway, getting to the example game.

    Do you see how the snakes patrol the platforms?

    There is a sequence of actions (states) happening that control their behavior.

    Initially, the snakes all start out in the Idle state.
    In this state, they just wait (kind of looking out) for a period of time.

    Their state then changes to Patrolling. In this state they move, animate and check if they can see the player.
    If they can see the player and the player is not too far away their state changes to Chasing. In this state their movement and animation speed is greatly accelerated.

    If they never change to Chasing then they will continue patrolling. In either case, when they reach the end of the platform their state changes to TurnAround. Once TurnAround completes they reenter the Idle state. This causes them to look like they move across the platform. Stop and look out. Then turn and head back the other way.

    Basically, this is the only way I would recommend to make any game. Heck I even use this approach in my day job doing business programming. And it is definitely better than the code I have seen which is a monolithic Update trying to check umpteen variables and flags just to get something to move around and jump and so forth.

    It allows you to break down complex behaviors and make it all simple. You just string a bunch of different states (behaviors) together and it can end up looking pretty damn impressive. And it is not just a matter of changing their animation to look like they are doing something. With this approach, they really are doing it. The animation is just the visual aspect.

    I hope that all helps to make it clearer.
     
    Last edited: Sep 5, 2014
  6. Dogg

    Dogg

    Joined:
    Mar 5, 2014
    Posts:
    203
    Ah so that's the reason thank you so much for your help. I appreciate all of this. Thank you too orb. :)
     
  7. Dogg

    Dogg

    Joined:
    Mar 5, 2014
    Posts:
    203
    Sorry for coming back, but this is related. So I've been messing around with moving the Ai/CPU, so far I got it to move back and forward, but I have a question. How can I change states? I haven't figured out how to do it yet so I've been using coroutines as a temporary replacement. Here's how my script looks like:

    Code (CSharp):
    1. public class CPUScript : MonoBehaviour {
    2.  
    3.     private BossActionType eCurState = BossActionType.Idle;
    4.     private bool dirRight = true;
    5.     public float speed = 2.0f;
    6.    
    7.  
    8.     public enum BossActionType
    9.     {
    10.         Idle,
    11.         Moving,
    12.         AvoidingObstacle,
    13.         Patrolling,
    14.         Attacking
    15.     }
    16.  
    17.     IEnumerator MoveLeft()
    18.     {
    19.         yield return new WaitForSeconds (1.0f);
    20.         dirRight = false;
    21.         if (dirRight == false)
    22.                         transform.Translate (-Vector2.right * speed * Time.deltaTime);
    23.                         StartCoroutine (MoveRight ());
    24.                
    25.     }
    26.  
    27.     IEnumerator MoveRight()
    28.     {
    29.         yield return new WaitForSeconds (1.0f);
    30.         dirRight = true;
    31.         if (dirRight)
    32.             transform.Translate (Vector2.right * speed * Time.deltaTime);
    33.         StartCoroutine(MoveLeft());
    34.     }
    35.  
    36.     void Update(){
    37.  
    38.  
    39.     switch (eCurState)
    40.     {
    41.     case BossActionType.Idle:
    42.             if (dirRight)
    43.                 transform.Translate (Vector2.right * speed * Time.deltaTime);
    44.             StartCoroutine(MoveLeft());
    45.            
    46.         //HandleIdleState();
    47.         break;
    48.        
    49.     case BossActionType.Moving:
    50.             if(dirRight == false && transform.position.x >= 4.0f)
    51.             transform.Translate (-Vector2.right * speed * Time.deltaTime);
    52.             Debug.Log("Sweet Sweet");
    53.         //HandleMovingState();
    54.         break;
    55.        
    56.     case BossActionType.AvoidingObstacle:
    57.         //HandleAvoidingObstacleState();
    58.         break;
    59.        
    60.     case BossActionType.Patrolling:
    61.         //HandlePatrollingState();
    62.         break;
    63.        
    64.     case BossActionType.Attacking:
    65.         //HandleAttackingState();
    66.         break;
    67.     }
    68. }
    69. }
     
  8. Graph

    Graph

    Joined:
    Jun 8, 2014
    Posts:
    154
    Ok you may not like this suggestion very much but first you should learn the underlying principles.

    MITs 6.034 is a pretty good start

    I stumbled across it when I was playing with GAs for evolving pursuit target preferences

    After that you should ofc watch the AI related Unite 2014 talks and other Tutorials.. @AngryAnt gave a good one and also published the src on his repo. (The state machines in there are quite nice to get one going and exactly what you're looking for)
     
    emilianop likes this.
  9. GarBenjamin

    GarBenjamin

    Joined:
    Dec 26, 2013
    Posts:
    7,441
    You may want to check out the video and the Unite stuff. Not seen any of it so I can't comment on it.

    It's simple stuff though. Some people build fancy behavior systems complete with GUIs. But it all seems like such overkill to me. But then a lot of the things do. In my day job I have seen systems using threads to implement messaging systems that in reality only needed delegates to throw some events. People have a tendency to make their code overly complex, over engineering. So whatever you do keep it as simple as you can.

    Anyway, if you want I can knock out a little example project in Unity.
     
  10. Dogg

    Dogg

    Joined:
    Mar 5, 2014
    Posts:
    203
    That would be great if you can make an example project. :) I just finished the video that Graph posted and are currently viewing tutorials on the AngryAnt site. So I wouldn't mind it would help a lot.
     
  11. GarBenjamin

    GarBenjamin

    Joined:
    Dec 26, 2013
    Posts:
    7,441
    Okay, I will knock out something simple. Probably do that tomorrow.
     
  12. Dogg

    Dogg

    Joined:
    Mar 5, 2014
    Posts:
    203
    Okay thank you very much.
     
  13. Dogg

    Dogg

    Joined:
    Mar 5, 2014
    Posts:
    203
    Update: Well I've been messing around with the Ai now and I got something that's half smart. You see sometimes the Ai is smart and dodges the obstacles no problem, but other times it's a machine that only knows how to kill itself. Here's the script(it's a little messy):

    Code (CSharp):
    1. public class CPUScript : MonoBehaviour {
    2.  
    3.     private BossActionType eCurState = BossActionType.Idle;
    4.     private bool dirRight = true;
    5.     bool turning = false;
    6.     bool jump = false;
    7.     public float speed = 2.0f;
    8.     public float jumpForce = 700f;
    9.     int clickCount;
    10.     bool facingRight = true;
    11.      
    12.  
    13.     public enum BossActionType
    14.     {
    15.         Idle,
    16.         Moving,
    17.         AvoidingObstacle,
    18.         Patrolling,
    19.         Attacking
    20.     }
    21.  
    22.  
    23.     void OnTriggerEnter2D(Collider2D other)
    24.     {
    25.         if (other.tag == "Player") {
    26.             eCurState = BossActionType.Moving;
    27.          
    28.                 }
    29.         if (other.tag == "Bomb") {
    30.             jump = true;
    31.             eCurState = BossActionType.AvoidingObstacle;
    32.                 }
    33.         if (other.tag == "Dangerous") {
    34.                         Debug.Log ("HSK");
    35.  
    36.                 Destroy(gameObject);
    37.             Debug.Log("Dead");
    38.             }
    39.         }
    40.  
    41.     IEnumerator MoveLeft()
    42.     {
    43.         yield return new WaitForSeconds (3.0f);
    44.         dirRight = false;
    45.         if (dirRight == false)
    46.                         transform.Translate (-Vector2.right * speed * Time.deltaTime);
    47.         turning = false;
    48.                         StartCoroutine (MoveRight ());
    49.              
    50.     }
    51.  
    52.     IEnumerator MoveRight()
    53.     {
    54.         yield return new WaitForSeconds (3.0f);
    55.         dirRight = true;
    56.         if (dirRight)
    57.             transform.Translate (Vector2.right * speed * Time.deltaTime);
    58.         turning = true;
    59.         StartCoroutine(MoveLeft());
    60.     }
    61.  
    62.     void Update(){
    63.  
    64.  
    65.     switch (eCurState)
    66.     {
    67.     case BossActionType.Idle:
    68.             if (dirRight)
    69.                 transform.Translate (Vector2.right * speed * Time.deltaTime);
    70.             turning = true;
    71.             StartCoroutine(MoveLeft());
    72.  
    73.         //HandleIdleState();
    74.         break;
    75.      
    76.     case BossActionType.Moving:
    77.             if(dirRight == false && transform.position.x >= 4.0f)
    78.             transform.Translate (Vector2.right * speed * Time.deltaTime);
    79.             Debug.Log("Sweet Sweet");
    80.         //HandleMovingState();
    81.         break;
    82.      
    83.     case BossActionType.AvoidingObstacle:
    84.             if(jump == true){
    85.             transform.Translate (-Vector2.right * speed * Time.deltaTime);
    86.             rigidbody2D.AddForce (new Vector2 (0, jumpForce));
    87.             jump = false;
    88.             }
    89.         //HandleAvoidingObstacleState();
    90.         break;
    91.      
    92.     case BossActionType.Patrolling:
    93.         //HandlePatrollingState();
    94.         break;
    95.      
    96.     case BossActionType.Attacking:
    97.         //HandleAttackingState();
    98.         break;
    99.     }
    100. }
    101.  
    102.     void FixedUpdate()
    103.     {
    104.  
    105.         float move = Input.GetAxis ("Horizontal");
    106.         rigidbody2D.velocity = new Vector2 (move * speed, rigidbody2D.velocity.y);
    107.  
    108.         if (turning == true && !facingRight)
    109.             Flip ();
    110.         else if (turning == false && facingRight)
    111.             Flip ();
    112.     }
    113.  
    114.     void Flip()
    115.     {
    116.         facingRight = !facingRight;
    117.         Vector3 theScale = transform.localScale;
    118.         theScale.x *= -1;
    119.         transform.localScale = theScale;
    120.     }
    121. }
     
  14. GarBenjamin

    GarBenjamin

    Joined:
    Dec 26, 2013
    Posts:
    7,441
    Excellent! Glad to see you have made progress.

    I knocked out a simple example. Coming up with an idea to illustrate the states was the greatest challenge!

    What I decided to go with is a basic worker from an RTS game. Basically, something like the old Warcraft where lumberjacks head over to the woods, cut down trees then return the wood to store house.

    The lumberjack starts in the Idle state.
    Then he chooses a tree and enters the MovingToForest state.
    Once he arrives, he enters the GatheringWood state.
    Once finished, he enters the ReturningToStorehouse state.
    Once he arrives at the storehouse he enters the UnloadingWood state.
    If there are more trees left, he chooses another tree and enters the MovingToForest state.
    Otherwise, he enters the MovingToRestPoint state.
    Finally, when he reaches the rest point he enters WorkIsDone state.

    So basically, you will see the Lumberjack continually heading to the forest, chopping down trees, returning the wood to the storehouse... until finally he has cleared the entire forest. Then he will go to the rest point and that is the end.

    This is a very simple example. And the graphics are very basic. Just geometric shapes.

    You can check out the web player example here:
    http://supergar.com/Unity3D/StatesRTSexample/StatesRTSexample.html

    And download the project by right clicking on this link:
    http://supergar.com/Unity3D/StatesRTSexample/StatesExample.zip

    Of course, you can also use coroutines, lerp and so forth if you prefer.

    I am used to doing everything manually through code so I just did it the way I always do handling everything myself.

    Anyway, I hope this helps.
     
    Last edited: Sep 9, 2014
  15. Dogg

    Dogg

    Joined:
    Mar 5, 2014
    Posts:
    203
    Thank you very much. Wow a lot of stuff to look at. I better look at it carefully, thanks again.:)
     
    GarBenjamin likes this.
  16. GarBenjamin

    GarBenjamin

    Joined:
    Dec 26, 2013
    Posts:
    7,441
    You're welcome. Glad to help. :)

    I made a small update this morning just to refactor a bit of code and use two new functions. Decreased the LumberjackControl script by a few lines. Project files (all 150 KB of them) are available at the link I posted prevously.

    A lot can be done with the states. Their real power, in my opinion, is that in addition to being able to create some complex behavior quite easily... it also isolates the current behavior so you are only dealing with conditions and processing necessary for this current state. Which means each state method becomes quite lean and mean so it serves as an optimization method as well. Of course, keeping code clean is always nice too.

    Good luck with your project!
     
    Last edited: Sep 9, 2014
  17. Twinzeno21

    Twinzeno21

    Joined:
    Oct 30, 2018
    Posts:
    1

    Hi, I'm new in game development and want to learn how to make Boss AI. I found that the link you post before didn't work anymore. Can you please upload it again? Because it will help me a lot.

    Thank you
     
  18. GarBenjamin

    GarBenjamin

    Joined:
    Dec 26, 2013
    Posts:
    7,441
    I don't have time to do it right now because I am working hard on the launch of my first tiny paid game. I just decided to check the forum for a brain break for a few minutes.

    But... it's very easy stuff. Like everything a person can make state management as complex or easy as they want to. I simply use a switch / select case structure to sort it out.

    All you need to do (and I think I covered this above probably... not sure since I think this is something from years ago but I would think I had) is focus on breaking down complex behaviors in to a series of much simpler behaviors. Each of these are categorized into states or if more complex into states and tasks.

    State is your top level such as PATROL and task is MOVE (to next way point or move along with a pathfinding algorithm or move to a destination using a navmesh, etc) and anything else like perhaps the entity periodically stops to look around so task would become SCAN_ENVIRONMENT or SLL (Stop, Look around and Listen) and depending on the outcome of that task would return to MOVE to continue movement or control may be passed to a new state such as COMBAT with task being EngagePlayer or whatever.

    In this way you can implement complex / interesting behavior while keeping it all very simple because each task only handles a very specific piece of the overall behavior.
     
  19. NotSoLuckyDucky

    NotSoLuckyDucky

    Joined:
    Apr 16, 2018
    Posts:
    56
    That was very informative WOW...