Search Unity

Classic Beat'em up AI

Discussion in 'Scripting' started by ypsilon, Apr 14, 2014.

  1. ypsilon

    ypsilon

    Joined:
    Feb 23, 2014
    Posts:
    5
    Hi everyone,

    I'm making a classic beat'em up game, in the style of Streets of Rage. It's my first game, so it's my first time programming game AI. This is maybe the most important part of the game, because it basically defines the way the game is going to be played. I have studied Software Engineering so I'm not a complete ignorant on the subject.

    I was thinking about making the enemies have a state machine, and basically make them change between states randomly. These states would be something like ATTACK, APPROACH, and PROWL. Apart from that randomness, the states would change according to key movements of the player in relation to the enemy. For example, in case the player got too close to the enemy, the enemy would probably attack. I though that maybe I can achieve an apparently complex behaviour by implementing a "simple" set of rules, which I think is probably what they did back in the day.

    I just wanted to get some feedback on these points, and maybe some ideas on the implementation. Not every enemy will have the same exact behaviour, although the main idea should be the same for everyone.

    Thanks!
     
    Last edited: Apr 14, 2014
  2. Gustav_Bok

    Gustav_Bok

    Joined:
    Dec 6, 2012
    Posts:
    35
    Hi Mate,

    Here is some code I've written. I also attach a manual that goes a bit more indepth if you dont understand it.
    Code (csharp):
    1.  
    2. using UnityEngine;
    3. using System.Collections;
    4.  
    5. public abstract class EnemyBehaviour : MonoBehaviour {
    6.  
    7.     /*If you need guidance see the manual.txt, and if that didn't help you can email me at gustavbok@gmail.com!*/
    8.  
    9.     /*Here you can add and remove states for the enemy, see the manual.txt for guidance!*/
    10.     public enum EnemyState
    11.     {
    12.         initializing,
    13.         idle,
    14.         sawPlayer,
    15.         chasing,
    16.         attacking,
    17.         fleeing
    18.     }
    19.     /*This is the currentState of the Enemy, this is what you'll change in the child-Class*/
    20.     public EnemyState currentState;
    21.  
    22.     public GameObject playerReference;
    23.  
    24.     void Start () {
    25.         currentState = EnemyState.initializing;
    26.     }
    27.    
    28.     /*In here there is a switch-statement which handles which method that is going
    29.     * to be updating, this is chosen by the currentState of the enemy.
    30.      It is in here that you will add your own EnemyState.yourState-case and call for your own method below*/
    31.     public virtual void Update () {
    32.  
    33.         switch (currentState) {
    34.         case EnemyState.initializing:
    35.             /*filling in the player reference for easier access*/
    36.             playerReference = GameObject.Find ("Player");
    37.             currentState = EnemyState.idle;
    38.             break;
    39.         case EnemyState.idle:
    40.             Idle();
    41.             break;
    42.         case EnemyState.sawPlayer:
    43.             SawPlayer();
    44.             break;
    45.         case EnemyState.chasing:
    46.             Chasing();
    47.             break;
    48.         case EnemyState.attacking:
    49.             Attacking();
    50.             break;
    51.         case EnemyState.fleeing:
    52.             Fleeing();
    53.             break;
    54.         default:
    55.             break;
    56.         }
    57.     }
    58.  
    59.     /*When you add your own methods here they need to be virtual, this is so you can in override them in your own
    60.      class*/
    61.  
    62.     public virtual void Idle()
    63.     {
    64.     }
    65.     public virtual void SawPlayer()
    66.     {
    67.     }
    68.     public virtual void Chasing()
    69.     {
    70.     }
    71.     public virtual void Attacking()
    72.     {
    73.     }
    74.     public virtual void Fleeing()
    75.     {
    76.     }
    77. }
    78.  
    Manual(Not completed):
     
  3. Gustav_Bok

    Gustav_Bok

    Joined:
    Dec 6, 2012
    Posts:
    35
    Enjoy :) I also make this into a new topic so it will be easier to find for others who search for finite state machines.
     
  4. ypsilon

    ypsilon

    Joined:
    Feb 23, 2014
    Posts:
    5
    Hey man! Thank you for your answer, it's actually the way I was thinking about implementing it.

    I think I'm going to use a probabilistic state machine to approach the transition between states. So, with this approach, all the enemies will have the same set of states, but the probability of transitioning between them will change according to the type of enemy. For example, there'll be more aggressive enemies, which will attack more often, so the probability to transition to the ATTACK state will be higher for them.

    Doing it like this, I can just create a superclass for the AI and make each type of enemy inherit from it, simply changing the probabilities and maybe some other stuff if necessary.
     
    Last edited: Apr 14, 2014
  5. Gustav_Bok

    Gustav_Bok

    Joined:
    Dec 6, 2012
    Posts:
    35
    Sounds like an really interesting approach! You can have that while using this basic script. Here, I made these two classes that inherits from the base class:

    Code (csharp):
    1.  
    2. using UnityEngine;
    3. using System.Collections;
    4.  
    5. public class Enemy1 : EnemyBehaviour {
    6.  
    7.     // Use this for initialization
    8.     void Start () {
    9.    
    10.     }
    11.  
    12.     /*Now you will need to override the methods you want to use for the enemy, these can be
    13.      * found in the bottom of the EnemyBehaviour-class*/
    14.  
    15.     public override void Idle()
    16.     {
    17.         /*Add your idle-behaviour here*/
    18.  
    19.         /*could be:*/
    20.         /*Update Patrolling the area*/
    21.         /* if in range of the player*/
    22.             /*Raycast toward player*/
    23.             /*if raycast hit - change currentState to sawPlayer*/
    24.  
    25.         //currentState = EnemyState.sawPlayer;
    26.     }
    27.     public override void SawPlayer()
    28.     {
    29.         /*Add your SawPlayer-behaviour here*/
    30.  
    31.         /*could be:*/
    32.         /* face the player */
    33.         /* play surprise animation */
    34.         /* when animation is done - change currentState to chasing*/
    35.  
    36.         //currentState = EnemyState.chasing;
    37.     }
    38.     public override void Chasing()
    39.     {
    40.         /*Add your Chasing-behaviour here*/
    41.  
    42.         /*could be:*/
    43.         /* if hp < 30 - change currentState to Flee*/
    44.             //currentState = EnemyState.fleeing;
    45.  
    46.         /* if not close enough to attack the player - move towards the player. It is here you choose
    47.          * if the enemy is either melee or ranged */
    48.  
    49.         /*else change currentState to attack*/
    50.             //currentState = EnemyState.attacking; 
    51.     }
    52.     public override void Attacking()
    53.     {
    54.         /*Add your Attacking-behaviour here*/
    55.  
    56.         /*could be:*/
    57.         /*if still close enough, do attack*/
    58.         /*else change to chase*/
    59.     }
    60.     public override void Fleeing()
    61.     {
    62.         /*Add your Fleeing-behaviour here*/
    63.  
    64.         /*could be:*/
    65.         /* if EnemyHp < 30*/
    66.             /*raycast to player and run the other way, simplest to implement*/
    67.         /*else*/
    68.             /*change state to chase*/
    69.     }
    70. }
    71.  
    And for a filled out one, I made an ORC that has special behaviours in it:

    Code (csharp):
    1.  
    2. using UnityEngine;
    3. using System.Collections;
    4.  
    5. public class EnemyOrc : EnemyBehaviour {
    6.    
    7.     AudioSource attackSoundEffect;
    8.     AudioSource lookSurprisedSoundEffect;
    9.    
    10.     bool hasSpottedPlayer;
    11.     float reactionTime;
    12.    
    13.     GameObject weaponHitBox;
    14.     Vector3 weaponStartPos;
    15.     Vector3 weaponEndPos;
    16.     float weaponSwingTimer;
    17.     float weaponSpeed;
    18.     float attackTimer;
    19.     bool weaponsOut;
    20.  
    21.     float healthRegeneration;
    22.     float healthRegenTimer;
    23.  
    24.     int hp;
    25.     // Use this for initialization
    26.     void Start () {
    27.  
    28.         hp = 10;
    29.         reactionTime = 50;
    30.         healthRegeneration = 2;
    31.         healthRegenTimer = 0;
    32.         weaponSpeed = 3;
    33.         weaponsOut = false;
    34.         attackTimer = 100;
    35.  
    36.         /*have not yet spotted player*/
    37.         hasSpottedPlayer = false;
    38.        
    39.         weaponSwingTimer = 0;
    40.     }
    41.  
    42.     public override void Idle()
    43.     {
    44.         if(Vector3.Distance(transform.position,playerReference.transform.position) < 5)
    45.         {
    46.             currentState = EnemyState.sawPlayer;
    47.         }
    48.     }  
    49.     public override void SawPlayer()
    50.     {
    51.         if(!hasSpottedPlayer)
    52.         {
    53.             /*visualisation of enemy animation*/
    54.             transform.localScale = new Vector3(transform.localScale.x*2,transform.localScale.x*2,transform.localScale.x*2);
    55.             hasSpottedPlayer = true;
    56.         }
    57.  
    58.         if(reactionTime < 0)
    59.         {
    60.             reactionTime = 50;
    61.             currentState = EnemyState.chasing;
    62.         }
    63.         {
    64.             reactionTime -= 1;
    65.         }
    66.        
    67.     }
    68.     public override void Chasing()
    69.     {
    70.         transform.LookAt(playerReference.transform);
    71.         if(Vector3.Distance(transform.position,playerReference.transform.position) > 2)
    72.         {
    73.             Vector3 moveTowards = playerReference.transform.position -transform.position;
    74.             moveTowards.Normalize();
    75.             float dampening = 10;
    76.             transform.position = new Vector3(transform.position.x+moveTowards.x/dampening,
    77.                                                             transform.position.y,
    78.                                                             transform.position.z+moveTowards.z/dampening);
    79.         }
    80.         else
    81.         {
    82.             currentState = EnemyState.attacking;
    83.         }
    84.     }
    85.     public override void Attacking()
    86.     {
    87.         if(weaponHitBox != null)
    88.         {
    89.             weaponSwingTimer += Time.deltaTime* weaponSpeed;
    90.             weaponHitBox.transform.position = Vector3.Slerp(weaponStartPos,
    91.                                                             weaponEndPos,
    92.                                                             weaponSwingTimer);
    93.         }
    94.  
    95.         if(weaponsOut)
    96.         {
    97.             GameObject.Destroy(weaponHitBox);
    98.             weaponHitBox = GameObject.CreatePrimitive(PrimitiveType.Cylinder);
    99.             weaponHitBox.name = "weaponHitBox";
    100.             weaponHitBox.AddComponent<CapsuleCollider>();
    101.             weaponHitBox.transform.position = transform.position;
    102.             weaponStartPos = weaponHitBox.transform.position;
    103.            
    104.             Vector3 towardsPlayer = playerReference.transform.position -
    105.                 transform.position;
    106.            
    107.             towardsPlayer.Normalize();
    108.             towardsPlayer *= 2;
    109.            
    110.             weaponEndPos = new Vector3(
    111.                 transform.position.x + towardsPlayer.x,
    112.                 transform.position.y + towardsPlayer.y,
    113.                 transform.position.z + towardsPlayer.z);
    114.            
    115.             weaponHitBox.transform.parent = transform;
    116.             weaponHitBox.transform.LookAt(playerReference.transform);
    117.             weaponHitBox.transform.rotation = Quaternion.Euler(
    118.                 new Vector3(weaponHitBox.transform.rotation.eulerAngles.x+90,
    119.                         weaponHitBox.transform.rotation.eulerAngles.y,
    120.                         weaponHitBox.transform.rotation.eulerAngles.z));
    121.             weaponsOut = true;
    122.         }
    123.         if(attackTimer < 0)
    124.         {
    125.             GameObject.Destroy(weaponHitBox);
    126.             weaponSwingTimer = 0;
    127.             currentState = EnemyState.chasing;
    128.             weaponsOut = false;
    129.         }
    130.         else
    131.             attackTimer -= 1;
    132.  
    133.         if(hp < 5)
    134.         {
    135.             GameObject.Destroy(weaponHitBox);
    136.             weaponSwingTimer = 0;
    137.             currentState = EnemyState.fleeing;
    138.         }
    139.     }
    140.     public override void Fleeing()
    141.     {
    142.         Vector3 awayFromPlayer = transform.position - playerReference.transform.position;
    143.         awayFromPlayer.Normalize();
    144.        
    145.         transform.LookAt(transform.position + awayFromPlayer);
    146.         if(hp < 5)
    147.         {
    148.             float dampening = 30;
    149.             transform.position = new Vector3(transform.position.x+awayFromPlayer.x/dampening,
    150.                                                             transform.position.y,
    151.                                                             transform.position.z+awayFromPlayer.z/dampening);
    152.             healthRegenTimer += Time.deltaTime;
    153.            
    154.             if(healthRegenTimer >= healthRegeneration)
    155.             {
    156.                 Debug.Log("Enemy new hp: "+ hp);
    157.                 hp++;
    158.                 healthRegenTimer = 0;
    159.             }
    160.         }
    161.         else
    162.         {
    163.             currentState = EnemyState.chasing;
    164.         }
    165.     }
    166. }
    167.  
     
  6. bigmisterb

    bigmisterb

    Joined:
    Nov 6, 2010
    Posts:
    4,221
    Heh, I would have just linked him over to the other thread. ;)
     
  7. Gustav_Bok

    Gustav_Bok

    Joined:
    Dec 6, 2012
    Posts:
    35
    I totaly agree bigmisterb, but this thread was created before the other one and was the inspiration to make a totally new thread about it :)
     
  8. ypsilon

    ypsilon

    Joined:
    Feb 23, 2014
    Posts:
    5
    I am stuck thinking about a good way to implement a "WANDER" or "PROWL" state... Do you guys have any ideas or suggestions?
     
  9. msl_manni

    msl_manni

    Joined:
    Jul 5, 2011
    Posts:
    272