1 Replies - 656 Views - Last Post: 16 January 2018 - 12:18 PM Rate Topic: -----

#1 Redlime   User is offline

  • New D.I.C Head

Reputation: 0
  • View blog
  • Posts: 1
  • Joined: 16-January 18

2.5D Beat 'Em Up Enemy AI Stuttering/Jerking

Posted 16 January 2018 - 08:57 AM

I am a game development student working on a 2.5D beat 'em up brawler game as my final year project and I've been working on the ai for the enemies in the game. However, I've been unable to get the enemies to working properly as the enemies would be constantly stuttering and jerking while trying to follow the player. I've created a foreach statement and added some conditions that call for the robots to spread out and stay away from the player if it is not in an engaging enum state. This has been an issue plaguing the ai for a while now, analysing the animator, it seems like the robot's movement is instantaneously stopping and going which might be the reason causing this stuttering. But I'm not sure how to prevent the robot's movement from stopping and going. If anyone knows the reason why this is happening to the robots it would be much appreciated as my project is due soon and I'm still unable to fix such a game breaking bug! I've included a few videos below showing what is happening to my robots and also the stuttering shown inside the animator. I've also included the full script for my robots.

Robots Stuttering
Robots Stuttering Animator

Script for my Robots
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
using Random = UnityEngine.Random;

public class Robot : MonoBehaviour
{
    [SerializeField]
    private bool robotAlreadyStandby;
    [SerializeField]
    private bool robotAlreadyEngaging; // Check if robot is already engaging prevent continuous increment
    public bool pushPlayerForth; // Bool to check if player should be pushed back or forth

    private Rigidbody myRigidbody;
    public float movementSpeed;
    private Animator myAnimator;
    public float robotHealth;
    private bool attack;

    [SerializeField]
    private float velocity;
    private Vector3 previous;

    Transform target;
    private Vector3 retreatLeft;
    private Vector3 retreatRight;
    

    private bool facingRight;

    [SerializeField]
    private bool cluttered;

    public float enemiesEngaged;
    public float xSpeed;
    public float ySpeed;
    public float engagementDistance;
    public float attackDistance;
    public float targetDistance;
    public float minTargetDistance;
    public float retreatingDistance;
    private float horizontalSpeed;
    private float removeDelay = 1f;
    public GameObject robot;

    [SerializeField]
    private BoxCollider robotCollider;
    [SerializeField]
    private BoxCollider robotBodyCollider;

    [SerializeField]
    private Rigidbody robotRigidbody;
    [SerializeField]
    private float fillAmount;
    [SerializeField]
    private Image content;
    [SerializeField]
    private Image robotHealthBar;
    [SerializeField]
    private Canvas healthCanvas;

    public GameObject healthDrop;

    public float attackTime;
    public float attackTimer;
    private EnemyStateCounter stateCounterScript;
    private Player playerScript;
    public GameObject PlayerObject;
    [SerializeField]
    private GameObject [] EnemiesNearby;
    private float minEnemyDistance;
    private float clutteredEnemyDist;
    [SerializeField]
    private bool closeToMinDist;
    [SerializeField]
    private bool retreating;
    [SerializeField]
    private bool stopMoving;
    public enum RobotState { ENGAGING, STANDBY, STANDBY_LOW, DYING, RETREATING }
   



    // Audio
    public AudioSource robotClampSound;
    public AudioSource robotAttackSound;
    public AudioSource robotExplodeSound;
    public AudioSource robotMoveSound;
    public AudioSource robotPunch;
    public AudioSource robotClankSound;
    public RobotState state = RobotState.STANDBY;

    

    void Start()
    {

        //healthDrop = GameObject.Find("health drop");

        robotAlreadyStandby = false;
        robotAlreadyEngaging = false; // Robot starts not engaging
        EnemyStateCounter.enemyCount++;
        stopMoving = false;
        minEnemyDistance = Random.Range(4f, 5f); // Give variation to min enemydistance
        clutteredEnemyDist = Random.Range(3f, 3.9f); // Slightly lesser than MinEnemyEngagementDist

        myRigidbody = GetComponent<Rigidbody>();
        movementSpeed = 5f;
        robotHealth = 100;
        myAnimator = GetComponent<Animator>();


        facingRight = false;
        //target = GameObject.Find("Player");
        xSpeed = myRigidbody.velocity.x;
        ySpeed = myRigidbody.velocity.z;

        robot = GameObject.Find("Robot");
        target = GameObject.Find("Player").transform;

        attackTime = 1.5f;
        attackTimer = attackTime;

        PlayerObject = GameObject.Find("Player");
        playerScript = PlayerObject.GetComponent<Player>();
        stateCounterScript = PlayerObject.GetComponent<EnemyStateCounter>();                                                                                
        healthCanvas = transform.GetComponentInChildren<Canvas>();

        if (robotAlreadyStandby == false)
        {
            if (robotHealth >= 30f)
            {
                EnemyStateCounter.enemiesStandbyCount++;
                state = RobotState.STANDBY;
                robotAlreadyStandby = true;
            }
        }

    }

    void Update()
    {
        retreatLeft = new Vector3(transform.position.x - 5, transform.position.y, transform.position.z + (transform.position.z - target.position.z));
        retreatRight = new Vector3(transform.position.x + 5, transform.position.y, transform.position.z + (transform.position.z - target.position.z));

        // Get Accurate velocity of robots
        velocity = ((transform.position - previous).magnitude) / Time.deltaTime;
        previous = transform.position;

        EnemiesNearby = GameObject.FindGameObjectsWithTag("Enemy"); // Constantly find for enemies in the update loop;

        HandleBar();
        RobotStateHandler();
        AvoidEnemies();
        //FindClosestEnemy();

        if (facingRight)
        {
            pushPlayerForth = true; // Push player forward when facing right;
        }
        if (!facingRight)
        {
            pushPlayerForth = false; // Push playerwards when not facing right;
        }

        // If in range
        if (targetDistance <= 8f)
        {
            // If while on standby in target range, if less than 2 enemies are engaging transistion into engaging
            if (state == RobotState.STANDBY && EnemyStateCounter.enemiesEngagingCount < 2)
            {
                EnemyStateCounter.enemiesStandbyCount--;
                EnemyStateCounter.enemiesEngagingCount++;
                state = RobotState.ENGAGING;
            }
        }

        // If out of range
        if (targetDistance > 8f)
        {
            if (state == RobotState.ENGAGING)
            {
                EnemyStateCounter.enemiesEngagingCount--;
                EnemyStateCounter.enemiesStandbyCount++;
                state = RobotState.STANDBY;
            }
        }



    }
     

    void FixedUpdate()
    {   

        // Robot movement Script
        if (playerScript.playerHealth > 0 && !this.myAnimator.GetCurrentAnimatorStateInfo(0).IsTag("Damage"))
        {
            // Follow player
            if (!this.myAnimator.GetCurrentAnimatorStateInfo(0).IsTag("Attack") && robotHealth > 0)
            {
                // prevent robots from moving straight into player & stop moving is bool is decalred true & not retreating
                if (targetDistance >= minTargetDistance  && retreating == false) 
                {
                    // Makes robot move towards player
                    transform.position = Vector3.MoveTowards(transform.position , target.position, movementSpeed * Time.deltaTime);
                }

                // Make the robots retreate when they too close to player && not engaging
                if (targetDistance < retreatingDistance && state == RobotState.STANDBY || targetDistance < retreatingDistance && state == RobotState.STANDBY_LOW)
                {
                    retreating = true;
                    if (facingRight)
                    { 
                        transform.position = Vector3.MoveTowards(transform.position, retreatLeft, movementSpeed * Time.deltaTime);
                    }
                    else if (!facingRight)
                    {
                        transform.position = Vector3.MoveTowards(transform.position, retreatRight, movementSpeed * Time.deltaTime);
                    }      
                }               
                else if (targetDistance >= retreatingDistance)
                {
                    retreating = false;
                }
               
               
            } 
        }


        Vector3 movementVector = new Vector3(Mathf.Round(target.position.x - transform.position.x), 0, Mathf.Round(target.position.z - transform.position.z));

        Debug.Log(xSpeed);
        Debug.Log(ySpeed);
        Flip(horizontalSpeed);     

        horizontalSpeed = movementVector.x;

        myAnimator.SetFloat("XSpeed", Mathf.Abs(velocity));
        myAnimator.SetFloat("YSpeed", Mathf.Abs(velocity));

        attackTimer -= Time.deltaTime;

        targetDistance = Vector3.Distance(target.position, transform.position);

        // Attack player if distance from target is within attacking distance && can attack if player gets too close on standby state
        if (targetDistance < attackDistance && attackTimer < 0 && playerScript.playerHealth > 0)
        {
            myAnimator.SetTrigger("Attack");
            attackTimer = attackTime;
        }

    }


    // flip robot 
    private void Flip(float horizontalSpeed)
    {
        if (horizontalSpeed > 0 && !facingRight && robotHealth > 0 || horizontalSpeed < 0 && facingRight && robotHealth > 0 && !this.myAnimator.GetCurrentAnimatorStateInfo(0).IsTag("Attack"))
        {
            // Find temp pos of healthCanvas
            Transform tmp = healthCanvas.transform;
            // Temp store canvas pos to brind back
            Vector3 pos = tmp.position;
            // Remove Canvas from robot to prevent flipping along with
            tmp.SetParent(null);
            facingRight = !facingRight;
            Vector3 theScale = transform.localScale;
            theScale.x *= -1;
            transform.localScale = theScale;
            // Return canvas back to robot after flip
            tmp.SetParent(transform);

            // Put health bar back to correct pos
            tmp.position = pos;
      
        }
    }

    private void RobotAttack()
    {
        robotCollider.enabled = !robotCollider.enabled;
    }


    public void TakeDamage(int damage)
    {

        // Prevent Robot From Dying Again
        if (robotHealth > 0)
        {
            robotHealth -= damage;

            if (robotHealth >= 30)
            {
                myAnimator.SetTrigger("Damage");
            }


            // Robot Death
            if (robotHealth <= 0)
            {
                myAnimator.SetTrigger("Death");
                StartCoroutine("RemoveRobot");
                robotCollider.enabled = false;
                robotBodyCollider.enabled = false; // Disable body collider on robot if dead to prevent re-triggering death
                robotRigidbody.isKinematic = true; // Freeze rigidbody on robot if dead to prevent falling through floor
                // Disable both healthbar and healthbar border when dead
                content.enabled = false;
                robotHealthBar.enabled = false;

                playerScript.score = playerScript.score + 10;
                GameObject healthDrop1 = Instantiate(healthDrop, transform.position, Quaternion.identity);
                GameObject healthDrop2 = Instantiate(healthDrop, transform.position, Quaternion.identity);

                EnemyStateCounter.enemyCount--;
                if (state == RobotState.ENGAGING)
                {
                    stateCounterScript.enemiesEngaging--; // Decrease enemies engaging by 1 if dead while engaging
                    state = RobotState.DYING; // Send robot into dying state instead of staying in Engaging state
                    EnemyStateCounter.enemiesEngagingCount--;
                }

                if (state == RobotState.STANDBY)
                {
                    EnemyStateCounter.enemiesStandbyCount++;             
                    stateCounterScript.enemiesStandby--; // Decrease enemies engaging by 1 if dead while on standby
                    state = RobotState.DYING; // Send robot into dying state instead of staying in Standby state
                }
                if (state == RobotState.STANDBY_LOW)
                {
                    EnemyStateCounter.enemiesStandbyLowCount--;
                    //stateCounterScript.enemiesStandbyLow--; // Decrease enemies engaging by 1 if dead while on low standby
                    state = RobotState.DYING; // // Send robot into dying state instead of staying in Standby_Low state
                }
            }
        }
    }

    public void RobotStateHandler()
    {


        if (state == RobotState.STANDBY)
        {
            minTargetDistance = 7f; 
        }

        if (state == RobotState.ENGAGING)
        {

            minTargetDistance = 2f;
            // only go on standby low if low health & more than 1 enemy is on standby
            if (robotHealth <= 30f && EnemyStateCounter.enemiesStandbyCount >= 1)
            {
                EnemyStateCounter.enemiesEngagingCount--;
                EnemyStateCounter.enemiesStandbyLowCount++;
                state = RobotState.STANDBY_LOW;
            }
        }

        if (state == RobotState.STANDBY_LOW)
        {
            minTargetDistance = 8f;
            // If no enemies on standby left & no more than 2 enemies are engaging, transistion back to engaging
            if (EnemyStateCounter.enemiesStandbyCount <= 0 && EnemyStateCounter.enemiesEngagingCount < 2)
            {
                EnemyStateCounter.enemiesEngagingCount++;
                EnemyStateCounter.enemiesStandbyLowCount--;
                state = RobotState.ENGAGING;
            }
        }



    }

        public void AvoidEnemies()
    {
        foreach (GameObject enemy in EnemiesNearby)
        {
            // Find distance between robot and other enemies       
            float enemyDistance = Vector3.Distance(transform.position, enemy.transform.position);
            // Find distance between other robots and player
            float robotsDistanceToPlayer = Vector3.Distance(enemy.transform.position, target.transform.position);

            if (enemyDistance < minEnemyDistance) 
            {           
                if (retreating == false) // Don't spread out yet if retreating
                {
                    if (state == RobotState.STANDBY || state == RobotState.STANDBY_LOW)
                    {
                        if (targetDistance > robotsDistanceToPlayer) // Only spread out if the one further back
                        {
                            transform.position = Vector3.MoveTowards(transform.position, new Vector3(transform.position.x + (transform.position.x - enemy.transform.position.x), transform.position.y, transform.position.z + (transform.position.z - enemy.transform.position.z)), movementSpeed * Time.deltaTime);
                            // Include some way of making enemies move away from each other
                        }
                    }
                }
            }

            else if (enemyDistance >= minEnemyDistance)
            {

                // While enemy distance is more or equal to minEnemyDistance, if its distance is within a small distance to
                // minEnemyDistance, prevent them from moving causing jittering
                if (enemyDistance <= (minEnemyDistance + 1.5f))
                {
                    closeToMinDist = true;
                }
                if (enemyDistance > (minEnemyDistance + 1.5f))
                {                
                    closeToMinDist = false; // false if more that a 0.5f for away from min enemy dist
                }
                // If robot is further away from player than other robots & closeToMinDist is true
                if (targetDistance > robotsDistanceToPlayer && closeToMinDist == true)
                {
                    // Ensure its in Stanby and Standby Low State
                    if (state == RobotState.STANDBY || state == RobotState.STANDBY_LOW)
                    {    
                        // Make robotstop moving towards player unity robot infront is out of range
                        stopMoving = true;
                    }
                    
                }
                else 
                {
                    // Or else keep moving
                    stopMoving = false;
                }
            }

            if (enemyDistance < clutteredEnemyDist)
            {
                cluttered = true; // Cluttered is true if is below clutteredDist
            }
            if (enemyDistance >= clutteredEnemyDist)
            {
                cluttered = false; // Cluttered is false if is equal or above clutteredDist
            }
            
        }


    }

    private void HandleBar()
    {
        content.fillAmount = Map(robotHealth, 0, 100, 0, 1);
    }

    private float Map(float value, float inMin, float inMax, float outMin, float outMax)
    {
        return (value - inMin) * (outMax - outMin) / (inMax - inMin) + outMin;
        // (Current Health - Min Health) * (1 - 0) / (Max Health - Min Health) + 0;
    }

    private void OnTriggerEnter(Collider other)
    {
        if (other.gameObject.tag == "Mop")
        {
            TakeDamage(20);
            robotPunch.Play();
            robotClankSound.Play();
            // Take knock only when there's health
            if (!facingRight && robotHealth > 0)
            {
                myRigidbody.velocity = new Vector3(10f, 1f, 0f);
            }
            else if (facingRight && robotHealth > 0)
            {
                myRigidbody.velocity = new Vector3(-10f, 1f, 0f);
            }
        }

        if (other.gameObject.tag == "HeavyAttack")
        {
            // Take knock only when there's health
            if (!facingRight && robotHealth > 0)
            {
                myRigidbody.velocity = new Vector3(10f, 1f, 0f);
            }
            else if (facingRight && robotHealth > 0)
            {
                myRigidbody.velocity = new Vector3(-10f, 1f, 0f);
            }
            TakeDamage(40);
            robotPunch.Play();
            robotClankSound.Play();
        }

        if (other.gameObject.tag == "Projectile")
        {
            TakeDamage(60);
        }
    }

    private void OnTriggerStay(Collider other)
    {
        if (other.gameObject.tag == "Laser")
        {
            TakeDamage(10);
        }
    }

    public IEnumerator RemoveRobot()
    {
        yield return new WaitForSeconds(removeDelay);
        Destroy(gameObject);
    }

    private void RobotAttackSound()
    {
        if (robotHealth > 0)
        {
            robotAttackSound.volume = Random.Range(0.6f, 1f);
            robotAttackSound.pitch = Random.Range(0.7f, 1f);
            robotAttackSound.Play();
        }

    }

    private void RobotClampSound()
    {
        if (robotHealth > 0)
        {
            robotClampSound.volume = Random.Range(0.3f, 0.6f);
            robotClampSound.pitch = Random.Range(0.8f, 1f);
            robotClampSound.Play();
        }
    }

    private void RobotExplodeSound()
    {
        robotExplodeSound.Play();
    }

    private void RobotMoveSound()
    {
        robotMoveSound.pitch = Random.Range(0.7f, 1f);
        robotMoveSound.Play();
    }









}




Is This A Good Question/Topic? 0
  • +

Replies To: 2.5D Beat 'Em Up Enemy AI Stuttering/Jerking

#2 snoopy11   User is offline

  • Engineering ● Software
  • member icon

Reputation: 1554
  • View blog
  • Posts: 4,930
  • Joined: 20-March 10

Re: 2.5D Beat 'Em Up Enemy AI Stuttering/Jerking

Posted 16 January 2018 - 12:18 PM

Its probably down to conflicting logic and lack of a hysteresis or dead-zone

see control theory...

https://en.wikipedia.../Control_theory

// If in range
168
        if (targetDistance <= 8f)
169
        {
170
            // If while on standby in target range, if less than 2 enemies are engaging transistion into engaging
171
            if (state == RobotState.STANDBY && EnemyStateCounter.enemiesEngagingCount < 2)
172
            {
173
                EnemyStateCounter.enemiesStandbyCount--;
174
                EnemyStateCounter.enemiesEngagingCount++;
175
                state = RobotState.ENGAGING;
176
            }
177
        }
178
 
179
        // If out of range
180
        if (targetDistance > 8f)
181
        {
182
            if (state == RobotState.ENGAGING)
183
            {
184
                EnemyStateCounter.enemiesEngagingCount--;
185
                EnemyStateCounter.enemiesStandbyCount++;
186
                state = RobotState.STANDBY;
187
            }
188
        }





As it stands if the target ie the player moves one pixel from 8.0f pixels it sends the robot flipping from attack to standby or standby to attack...hence the stutter

Adding a hysteresis or dead-zone will probably eliminate most of the stutter

Looking for logic inconsistencies will eliminate the rest of the stutter.
Was This Post Helpful? 1
  • +
  • -

Page 1 of 1