Search Unity

Need Help With Enemy AI State Machine for 2D Beat 'Em Up (street of rage style)

Discussion in 'Scripting' started by gdossantos87, Oct 25, 2016.

  1. gdossantos87

    gdossantos87

    Joined:
    Mar 18, 2014
    Posts:
    43
    Hello people, i'm currently working on a 2D beat em up game for android (street of rage style) i have manage to build some basic behavior like Idle , chasing, attack ,fleeing etc.

    but i want to improve the enemy behavior a little bit more, i want to check the number of enemies attacking the player and in which side of the player are they (is the enemy in front of the player or in the back of the player?) and to keep the remaining enemies wandering around the player on a fixed range.

    here is a video of how my enemies current behavior.


    thanks for your time and knowledge .




    Code (csharp):
    1.  
    2. using UnityEngine;
    3. using System.Collections;
    4.  
    5. public abstract class EnemyBehaviour : MonoBehaviour
    6. {
    7.     Transform player;
    8.     Transform enemie;
    9.     PlayerHealth playerHealth;
    10.     EnemyHealth enemyHealth;
    11.     [Tooltip("Velocidad en la que el enemigo se mueve")]
    12.     public float speed;
    13.  
    14.     public bool isFacingThePlayer;
    15.     public bool isOnRange;
    16.     public bool isAttacking;
    17.     public bool isWalking;
    18.  
    19.     Animator enemyAnim;
    20.     SpriteRenderer enemySprite;
    21.     GameObject closestEnemy;
    22.     float SightRange = 5;
    23.     int myAction;
    24.  
    25.     public enum EnemyState
    26.     {
    27.         initializing,
    28.         idle,
    29.         sawPlayer,
    30.         chasing,
    31.         checkForEnemiesAndPlayer,
    32.         attacking,
    33.         damagedealth,
    34.         fleeing,
    35.         dead
    36.     }
    37.     /*This is the currentState of the Enemy, this is what you'll change in the child-Class*/
    38.     public EnemyState currentState;
    39.  
    40.     public GameObject playerReference;
    41.  
    42.     void Awake()
    43.     {
    44.         currentState = EnemyState.initializing;
    45.  
    46.         player = GameObject.FindGameObjectWithTag("Player").transform;
    47.         enemie = GameObject.FindGameObjectWithTag("Enemy").transform;
    48.         playerHealth = player.GetComponent<PlayerHealth>();
    49.         enemySprite = GetComponentInChildren<SpriteRenderer>();
    50.         enemyAnim = GetComponent<Animator>();
    51.     }
    52.  
    53.     public virtual void Update()
    54.     {
    55.  
    56.         ChangeEnemySrotingLayer();
    57.  
    58.         switch (currentState)
    59.         {
    60.             case EnemyState.initializing:
    61.                 /*filling in the player reference for easier access*/
    62.                 playerReference = GameObject.FindGameObjectWithTag("Player");
    63.                 currentState = EnemyState.idle;
    64.                 break;
    65.             case EnemyState.idle:
    66.                 Idle();
    67.                 break;
    68.             case EnemyState.sawPlayer:
    69.                 SawPlayer();
    70.                 break;
    71.             case EnemyState.chasing:
    72.                 Chasing();
    73.                 break;
    74.             case EnemyState.checkForEnemiesAndPlayer:
    75.                 CheckForEnemiesAndplayer();
    76.                 break;
    77.             case EnemyState.attacking:
    78.                 Attacking();
    79.                 break;
    80.             case EnemyState.damagedealth:
    81.                 DamageDealth();
    82.                 break;
    83.             case EnemyState.fleeing:
    84.                 Fleeing();
    85.                 break;
    86.             case EnemyState.dead:
    87.                 Dead();
    88.                 break;
    89.             default:
    90.                 break;
    91.         }
    92.     }
    93.  
    94.     /*When you add your own methods here they need to be virtual, this is so you can in override them in your own
    95.     class*/
    96.  
    97.     public virtual void Idle()
    98.     {
    99.         EnemyStayCurrentPossition();
    100.         FaceThePlayer();
    101.         isOnRange = true;
    102.         isWalking = false;
    103.         isAttacking = false;
    104.         ChangeAnimationStates();
    105.     }
    106.     public virtual void SawPlayer()
    107.     {
    108.  
    109.  
    110.        
    111.         isOnRange = true;
    112.         isWalking = false;
    113.         isAttacking = false;
    114.         ChangeAnimationStates();
    115.  
    116.  
    117.     }
    118.     public virtual void Chasing()
    119.     {
    120.         transform.position = Vector2.MoveTowards(transform.position, playerReference.transform.position, speed * Time.deltaTime);
    121.        
    122.        
    123.         FaceThePlayer();
    124.         isAttacking = false;
    125.         isOnRange = true;
    126.         isWalking = true;
    127.         ChangeAnimationStates();
    128.  
    129.     }
    130.     public virtual void CheckForEnemiesAndplayer()
    131.     {
    132.         float distanceToPlayer = Vector2.Distance(enemie.transform.position, player.transform.position);
    133.         if(distanceToPlayer <= SightRange )
    134.         {
    135.             PossitionBehind();
    136.         }
    137.         else
    138.         {
    139.             PossitionFront();
    140.         }
    141.     }
    142.     public virtual void Attacking()
    143.     {
    144.         EnemyStayCurrentPossition();
    145.         isAttacking = true;
    146.         isOnRange = true;
    147.         isWalking = false;
    148.         ChangeAnimationStates();
    149.     }
    150.     public virtual void DamageDealth()
    151.     {
    152.         EnemyStayCurrentPossition();
    153.         ResetBoolsToFalse();
    154.         ChangeAnimationStates();
    155.         enemyAnim.SetTrigger("BeenHitTrigger");
    156.         Debug.Log("animacion de daño hecho por player");
    157.     }
    158.     public virtual void Fleeing()
    159.     {
    160.         transform.position = Vector2.MoveTowards(transform.position, playerReference.transform.position, -speed * Time.deltaTime);
    161.         RunAwayFromPlayer();
    162.         isAttacking = false;
    163.         isOnRange = false;
    164.         isWalking = true;
    165.         ChangeAnimationStates();
    166.  
    167.     }
    168.     public virtual void Dead()
    169.     {
    170.  
    171.         ResetBoolsToFalse();
    172.         ChangeAnimationStates();
    173.         enemyAnim.SetTrigger("Dead");
    174.     }
    175.  
    176.  
    177.     void Flip()
    178.     {
    179.         isFacingThePlayer = !isFacingThePlayer;
    180.         Vector3 theScale = enemySprite.transform.localScale;
    181.         theScale.x *= -1;
    182.         enemySprite.transform.localScale = theScale;
    183.     }
    184.     void FaceThePlayer()
    185.     {
    186.         float localOffset = transform.localPosition.x - player.transform.localPosition.x;
    187.         if (localOffset < 0 && !isFacingThePlayer)
    188.         {
    189.             Flip();
    190.         }
    191.         else if (localOffset > 0 && isFacingThePlayer)
    192.         {
    193.             Flip();
    194.         }
    195.  
    196.     }
    197.     void RunAwayFromPlayer()
    198.     {
    199.         float localOffset = transform.localPosition.x - player.transform.localPosition.x;
    200.         if (localOffset > 0 && !isFacingThePlayer)
    201.         {
    202.             Flip();
    203.         }
    204.         else if (localOffset < 0 && isFacingThePlayer)
    205.         {
    206.             Flip();
    207.         }
    208.  
    209.     }
    210.     void ChangeEnemySrotingLayer()
    211.     {
    212.         enemySprite.sortingOrder = (int)(transform.position.y * -2);
    213.     }
    214.     void ChangeAnimationStates()
    215.     {
    216.         enemyAnim.SetBool("isWalking", isWalking);
    217.         enemyAnim.SetBool("isAttacking", isAttacking);
    218.     }
    219.     void EnemyStayCurrentPossition()
    220.     {
    221.         transform.position = new Vector2(transform.position.x, transform.position.y);
    222.     }
    223.     void ResetBoolsToFalse()
    224.     {
    225.         isAttacking = false;
    226.         isOnRange = false;
    227.         isWalking = false;
    228.     }
    229.     void PossitionFront()
    230.     {
    231.         enemySprite.transform.localScale.Equals(1);
    232.     }
    233.     void PossitionBehind()
    234.     {
    235.         enemySprite.transform.localScale.Equals(-1);
    236.     }
    237.    
    238. }
    239.  
    240.  
    241.  
    242.  
    243.  

    this is the script which inherits the enemy behavior class
    Code (csharp):
    1.  
    2. using UnityEngine;
    3. using System.Collections;
    4.  
    5. public class EnemyTest : EnemyBehaviour {
    6.  
    7.  
    8.     CircleCollider2D circleCollider2D;
    9.     GameObject closestEnemy;
    10.  
    11.  
    12.     // Use this for initialization
    13.     void Start () {
    14.  
    15.         circleCollider2D = GetComponent<CircleCollider2D>();
    16.          
    17.    
    18.     }
    19.    
    20.     // Update is called once per frame
    21.     void Update () {
    22.         base.Update();
    23.  
    24.  
    25.         CheckForEnemies();
    26.        
    27.     }
    28.  
    29.  
    30.     void OnTriggerEnter2D(Collider2D col)
    31.  
    32.     {
    33.         if(col.gameObject == playerReference)
    34.         {
    35.             currentState = EnemyState.chasing;
    36.             circleCollider2D.enabled = false;
    37.         }
    38.     }
    39.  
    40.     void OnCollisionEnter2D(Collision2D col)
    41.     {
    42.         if(col.gameObject == playerReference)
    43.         {
    44.             currentState = EnemyState.attacking;
    45.         }
    46.         else
    47.         {
    48.             currentState = EnemyState.chasing;
    49.         }
    50.     }
    51.  
    52.     void OnCollisionExit2D(Collision2D col)
    53.     {
    54.         currentState = EnemyState.chasing;
    55.     }
    56.  
    57.     void CheckForEnemies()
    58.     {
    59.         Collider2D[] enemies = Physics2D.OverlapCircleAll(transform.position, 2, 0);
    60.         if (enemies.Length > 2)
    61.         {
    62.             Debug.Log("Esto se cumplio???");
    63.  
    64.         }
    65.  
    66.            
    67.        
    68.            
    69.     }
    70.  
    71.  
    72.  
    73.     //https://community.unity.com/t5/Scripting/Classic-Beat-em-up-AI/m-p/1771723#M308731
    74.     //http://stackoverflow.com/questions/18019174/playing-two-separate-animations-one-after-another-upon-completion
    75. }
    76.  
    77.  
     
  2. gdossantos87

    gdossantos87

    Joined:
    Mar 18, 2014
    Posts:
    43
    OK my EnemyTest Script has changed and i'm thinking of a new approach, i eliminate de detect player trigger with a better enemyDistance variable and the change in range changes the state of the enemie the closer it gets to the player it changes state

    ok now when the enemy is near 20.0f the enemy changes state to chase and when its near 3.37f it changes to attacking, but before that i want to implement one more state which is taunt, it would mean that enemies will have to wonder around the player while there are 3 in attacking state, i'm not sure how to do this approach

    Code (csharp):
    1.  
    2. using UnityEngine;
    3. using System.Collections;
    4.  
    5. public class EnemyTest : EnemyBehaviour {
    6.  
    7.  
    8.     CircleCollider2D circleCollider2D;
    9.    
    10.    
    11.     float sightRange = 20.0f;
    12.  
    13.  
    14.     // Use this for initialization
    15.     void Start () {
    16.        
    17.         circleCollider2D = GetComponent<CircleCollider2D>();
    18.          
    19.    
    20.     }
    21.    
    22.     // Update is called once per frame
    23.     void Update () {
    24.         base.Update();
    25.  
    26.  
    27.         CheckForEnemiesAndAttackRange();
    28.        
    29.        
    30.     }
    31.  
    32.  
    33.  
    34.     //void OnTriggerEnter2D(Collider2D col)
    35.  
    36.     //{
    37.     //    if (col.gameObject == playerReference)
    38.     //    {
    39.     //        currentState = EnemyState.chasing;
    40.     //        circleCollider2D.enabled = false;
    41.     //    }
    42.     //}
    43.    void CheckForEnemiesAndAttackRange()
    44.     {
    45.         Vector2 enemie = transform.position;
    46.         Vector2 playerPosition = playerReference.transform.position;
    47.        
    48.  
    49.         float distanceToPlayer = Vector2.Distance(enemie, playerPosition);
    50.         float enemiesNearThePlayerRange = 6.0f;
    51.         int numberOfEnemiesNearPlayer = gameObject.GetComponent<EnemyBehaviour>().currentState;??
    52.         float attackRange = 3.37f;
    53.        
    54.         if (distanceToPlayer <= sightRange)
    55.         {
    56.             currentState = EnemyState.chasing;
    57.             if (distanceToPlayer <= enemiesNearThePlayerRange)
    58.             {
    59.                
    60.             }
    61.             if(distanceToPlayer <= attackRange)
    62.             {
    63.                 Debug.Log(distanceToPlayer + "Distance to player");
    64.                 currentState = EnemyState.attacking;
    65.             }
    66.         }
    67.         else
    68.         {
    69.             currentState = EnemyState.idle;
    70.         }
    71.  
    72.     }
    73.  
    74.     void AttackThePlayer()
    75.     {
    76.        
    77.     }
    78.  
    79.     void OnCollisionEnter2D(Collision2D col)
    80.     {
    81.         //if (col.gameObject == playerReference)
    82.         //{
    83.         //    currentState = EnemyState.attacking;
    84.         //    Debug.Log(gameObject + "hizo contacto con " + playerReference);
    85.         //}
    86.         //else
    87.         //{
    88.         //    CheckForEnemies();
    89.         //}
    90.     }
    91.  
    92.    
    93.  
    94.     //void OnCollisionExit2D(Collision2D col)
    95.     //{
    96.     //    currentState = EnemyState.chasing;
    97.     //}
    98.  
    99.  
    100.  
    101.  
    102.  
    103.     //https://community.unity.com/t5/Scripting/Classic-Beat-em-up-AI/m-p/1771723#M308731
    104.     //http://stackoverflow.com/questions/18019174/playing-two-separate-animations-one-after-another-upon-completion
    105. }
    106.  
    107.  
     
  3. Striven

    Striven

    Joined:
    Jul 2, 2015
    Posts:
    2
    Essentially you need some kind of variable that triggers the enemy to go from chasing, to taunting. have a variable that counts how many enemies are chasing him, and when it's hit a peak number the next enemy to hit the chase range will first check that variable, then decide if it will taunt instead of chase. Then you will need some kind of exit from this state, either a timed release to stop that state and go to chasing or some other kind of trigger. (such as when enemy is killed, this is also where you obviously will decrement the chasing enemy counter)
     
  4. gdossantos87

    gdossantos87

    Joined:
    Mar 18, 2014
    Posts:
    43
    i made a method that tries to do that but chasing state and attacking state are conflicted

    Code (csharp):
    1.  
    2.    void CheckForEnemiesAndAttackRange()
    3.     {
    4.         Vector2 enemie = transform.position;
    5.         Vector2 playerPosition = playerReference.transform.position;
    6.      
    7.  
    8.         float distanceToPlayer = Vector2.Distance(enemie, playerPosition);
    9.        // float enemiesNearThePlayerRange = 6.0f;
    10.      
    11.         float attackRange = 3.37f;
    12.      
    13.         if (distanceToPlayer <= sightRange)
    14.         {
    15.             currentState = EnemyState.chasing;
    16.             if (distanceToPlayer <= attackRange)
    17.             {
    18.                 GameObject[] enemies = GameObject.FindGameObjectsWithTag("Enemy");
    19.              
    20.  
    21.                 int numberOfEnemies = enemies.Length;
    22.  
    23.                 currentState = EnemyState.attacking;
    24.                 timer += Time.deltaTime;
    25.                 if (timer >= 3)
    26.                 {
    27.                     currentState = EnemyState.fleeing;
    28.                     if (timer>= 10)
    29.                     {
    30.                         timer = 0;
    31.                        
    32.                     }
    33.                 }
    34.                 //foreach (GameObject enemy in enemies)
    35.                 //{
    36.                 //    enemy.GetComponent<EnemyBehaviour>().isAttacking
    37.                 //}
    38.                 //Debug.Log(numberOfEnemies + "Number of enemies");
    39.                 //if (numberOfEnemies < 5 && !isAttacking )
    40.                 //{
    41.                 //    if (distanceToPlayer <= attackRange)
    42.                 //    {
    43.                 //        Debug.Log(distanceToPlayer + "Distance to player");
    44.                 //        currentState = EnemyState.attacking;
    45.                 //    }
    46.                 //}
    47.                 //else
    48.                 //{
    49.                 //    currentState = EnemyState.taunt;
    50.                 //}
    51.             }        
    52.         }
    53.         else
    54.         {
    55.             currentState = EnemyState.idle;
    56.         }
    57.     }
    58.  
     
  5. gdossantos87

    gdossantos87

    Joined:
    Mar 18, 2014
    Posts:
    43
    this is hard