Forum begins after the advertisement:


[Part 6] Enemies move in an instant and can’t register ledges

Home Forums Video Game Tutorial Series Creating a Metroidvania in Unity [Part 6] Enemies move in an instant and can’t register ledges

Viewing 12 posts - 1 through 12 (of 12 total)
  • Author
    Posts
  • #12228
    Kenura
    Former Patron

    the enemy ai in part 6 of the series stops my enemies from moving gradually from one end to the other after i remove the update function

    #12229
    Kenura
    Former Patron

    the enemy just keeps on going back and forth

    #12230
    Terence
    Keymaster

    Hi Kenura, can you paste a copy of the code with the Update() removed and a copy with Update() still in your script? Let me know which script you are removing code from as well.

    #12232
    Kenura
    Former Patron

    By Update function i was talking about the movetowards player method

    using System.Collections;
    using System.Collections.Generic;
    using UnityEngine;
    
    public class Mushroom : Enemy
    {
        [SerializeField] private float flipWaitTime;
        [SerializeField] private float ledgeCheckX;
        [SerializeField] private float ledgeCheckY;
        [SerializeField] private LayerMask whatIsGround;
    
        float timer;
    
        // Start is called before the first frame update
        protected override void Start()
        {
            base.Start();
            rb.gravityScale = 12f;
        }
    
        protected override void Update()
        {
            base.Update();
            if (!isRecoiling)
            {
                transform.position = Vector2.MoveTowards
                    (transform.position, new Vector2(PlayerController.Instance.transform.position.x, transform.position.y),
                    speed * Time.deltaTime);
            }
        }
    
        protected override void UpdateEnemyStates()
        {
            if (health <= 0)
            {
                Death(0.05f);
            }
    
            switch (GetCurrentEnemyState)
            {
                case EnemyStates.Mushroom_Idle:
                    Vector3 _ledgeCheckStart = transform.localScale.x > 0 ? new Vector3(ledgeCheckX, 0) : new Vector3(-ledgeCheckX, 0);
                    Vector2 _wallCheckDir = transform.localScale.x > 0 ? transform.right : -transform.right;
    
                    if (!Physics2D.Raycast(transform.position + _ledgeCheckStart, Vector2.down, ledgeCheckY, whatIsGround)
                        || Physics2D.Raycast(transform.position, _wallCheckDir, ledgeCheckX, whatIsGround))
                    {
                        ChangeState(EnemyStates.Mushroom_Flip);
                    }
    
                    if (transform.localScale.x > 0)
                    {
                        rb.velocity = new Vector2(speed, rb.velocity.y);
                    }
                    else
                    {
                        rb.velocity = new Vector2(-speed, rb.velocity.y);
                    }
                    break;
    
                case EnemyStates.Mushroom_Flip:
                    timer += Time.deltaTime;
    
                    if (timer > flipWaitTime)
                    {
                        timer = 0;
                        transform.localScale = new Vector2(transform.localScale.x * -1, transform.localScale.y);
                        ChangeState(EnemyStates.Mushroom_Idle);
                    }
                    break;
            }
        }
    }
    
    #12233
    Kenura
    Former Patron

    Without it the enemy just stays in the same position and flips after the flip wait time is done

    
    using System.Collections;
    using System.Collections.Generic;
    using UnityEngine;
    
    public class Mushroom : Enemy
    {
        [SerializeField] private float flipWaitTime;
        [SerializeField] private float ledgeCheckX;
        [SerializeField] private float ledgeCheckY;
        [SerializeField] private LayerMask whatIsGround;
    
        float timer;
    
        // Start is called before the first frame update
        protected override void Start()
        {
            base.Start();
            rb.gravityScale = 12f;
        }
    
        protected override void UpdateEnemyStates()
        {
            if (health <= 0)
            {
                Death(0.05f);
            }
    
            switch (GetCurrentEnemyState)
            {
                case EnemyStates.Mushroom_Idle:
                    Vector3 _ledgeCheckStart = transform.localScale.x > 0 ? new Vector3(ledgeCheckX, 0) : new Vector3(-ledgeCheckX, 0);
                    Vector2 _wallCheckDir = transform.localScale.x > 0 ? transform.right : -transform.right;
    
                    if (!Physics2D.Raycast(transform.position + _ledgeCheckStart, Vector2.down, ledgeCheckY, whatIsGround)
                        || Physics2D.Raycast(transform.position, _wallCheckDir, ledgeCheckX, whatIsGround))
                    {
                        ChangeState(EnemyStates.Mushroom_Flip);
                    }
    
                    if (transform.localScale.x > 0)
                    {
                        rb.velocity = new Vector2(speed, rb.velocity.y);
                    }
                    else
                    {
                        rb.velocity = new Vector2(-speed, rb.velocity.y);
                    }
                    break;
    
                case EnemyStates.Mushroom_Flip:
                    timer += Time.deltaTime;
    
                    if (timer > flipWaitTime)
                    {
                        timer = 0;
                        transform.localScale = new Vector2(transform.localScale.x * -1, transform.localScale.y);
                        ChangeState(EnemyStates.Mushroom_Idle);
                    }
                    break;
            }
        }
    }
    
    #12234
    Terence
    Keymaster

    You have to put the Update() with the MoveTowards() call in your Mushroom as well then. The flipping still happens because, without an Update() function defined, the Mushroom will use the Enemy script’s Update(), which only handles the recoil force, as well as calling UpdateEnemyStates() when no recoil is happening.

    This is the Enemy’s Update() function.

        protected virtual void Update()
        {
    
            if(isRecoiling)
            {
                if(recoilTimer < recoilLength)
                {
                    recoilTimer += Time.deltaTime;
                }
                else
                {
                    isRecoiling = false;
                    recoilTimer = 0;
                }
            }
            else
            {
                UpdateEnemyStates();
            }
        }

    When you do an override of the Update() function, you are adding extra instructions to tell it to move about.

    Does that answer your question?

    #12235
    Kenura
    Former Patron

    i already have that update function in the enemy parent class

    
    using System.Collections;
    using System.Collections.Generic;
    using UnityEngine;
    
    public class Enemy : MonoBehaviour
    {
        [SerializeField] protected float health;
        [SerializeField] protected float recoilLength;
        [SerializeField] protected float recoilFactor;
        [SerializeField] protected bool isRecoiling = false;
        [SerializeField] protected float speed;
        [SerializeField] protected float damage;
    
        protected float recoilTimer;
        protected Rigidbody2D rb;
        protected SpriteRenderer sr;
        protected Animator anim;
    
        protected enum EnemyStates
        {
            //Crawler
            Mushroom_Idle,
            Mushroom_Flip,
    
            //Bat
            Bat_Idle,
            Bat_Chase,
            Bat_Stunned,
            Bat_Death,
    
            //Charger
            Charger_Idle,
            Charger_Suprised,
            Charger_Charge
        }
        protected EnemyStates currentEnemyState;
    
        protected virtual EnemyStates GetCurrentEnemyState
        {
            get { return currentEnemyState; }
            set
            {
                if (currentEnemyState != value)
                {
                    currentEnemyState = value;
    
                    ChangeCurrentAnimation();
                }
            }
        }
    
        // Start is called before the first frame update
        protected virtual void Start()
        {
            rb = GetComponent<Rigidbody2D>();
            sr = GetComponent<SpriteRenderer>();
            anim = GetComponent<Animator>();
        }
        // Update is called once per frame
        protected virtual void Update()
        {
    
            if (isRecoiling)
            {
                if (recoilTimer < recoilLength)
                {
                    recoilTimer += Time.deltaTime;
                }
                else
                {
                    isRecoiling = false;
                    recoilTimer = 0;
                }
            }
            else
            {
                UpdateEnemyStates();
            }
        }
    
        public virtual void EnemyGetsHit(float _damageDone, Vector2 _hitDirection, float _hitForce)
        {
            health -= _damageDone;
            if (!isRecoiling)
            {
                rb.velocity = _hitForce * recoilFactor * _hitDirection;
            }
        }
        protected void OnCollisionStay2D(Collision2D _other)
        {
            if (_other.gameObject.CompareTag("Player") && !PlayerController.Instance.pState.invincible && health > 0)
            {
                Attack();
                PlayerController.Instance.HitStopTime(0, 5, 0.5f);
            }
        }
    
        protected virtual void Death(float _destroyTime)
        {
            Destroy(gameObject, _destroyTime);
        }
    
        protected virtual void UpdateEnemyStates() { }
        protected virtual void ChangeCurrentAnimation() { }
    
        protected void ChangeState(EnemyStates _newState)
        {
            GetCurrentEnemyState = _newState;
        }
    
        protected virtual void Attack()
        {
            PlayerController.Instance.TakeDamage(damage);
        }
    
    }
    
    #12236
    Terence
    Keymaster

    Yes, but the Update() function is necessary in the Mushroom as well. It is correctly coded, and it is not redundant, because it adds extra instructions. The highlighted portion below…

    protected override void Update()
    {
        base.Update();
        if (!isRecoiling)
        {
            transform.position = Vector2.MoveTowards
                (transform.position, new Vector2(PlayerController.Instance.transform.position.x, transform.position.y),
                speed * Time.deltaTime);
        }
    }

    …is the one that gives your Mushroom movement. The base.Update() calls the Update() function from the parent Enemy class. So essentially your Mushroom’s Update() function uses the Enemy’s Update() function, plus additional instructions unique to the Mushroom class.

    #12237
    Kenura
    Former Patron

    but would that not make the enemy move towards me even if they are far away and on a platform?
    also in the guide video i was told to get rid of it? https://youtu.be/4J1c5LHk2TE?si=nwjerh8z3bgoBDxl&t=131

    #12238
    Terence
    Keymaster

    My bad, you are right. There is already code for making it move in the Idle state (highlighted below). I missed that part.

        protected override void UpdateEnemyStates()
        {
            if (health <= 0)
            {
                Death(0.05f);
            }
    
            switch (GetCurrentEnemyState)
            {
                case EnemyStates.Mushroom_Idle:
                    Vector3 _ledgeCheckStart = transform.localScale.x > 0 ? new Vector3(ledgeCheckX, 0) : new Vector3(-ledgeCheckX, 0);
                    Vector2 _wallCheckDir = transform.localScale.x > 0 ? transform.right : -transform.right;
    
                    if (!Physics2D.Raycast(transform.position + _ledgeCheckStart, Vector2.down, ledgeCheckY, whatIsGround)
                        || Physics2D.Raycast(transform.position, _wallCheckDir, ledgeCheckX, whatIsGround))
                    {
                        ChangeState(EnemyStates.Mushroom_Flip);
                    }
    
                    if (transform.localScale.x > 0)
                    {
                        rb.velocity = new Vector2(speed, rb.velocity.y);
                    }
                    else
                    {
                        rb.velocity = new Vector2(-speed, rb.velocity.y);
                    }
                    break;
    
                case EnemyStates.Mushroom_Flip:
                    timer += Time.deltaTime;
    
                    if (timer > flipWaitTime)
                    {
                        timer = 0;
                        transform.localScale = new Vector2(transform.localScale.x * -1, transform.localScale.y);
                        ChangeState(EnemyStates.Mushroom_Idle);
                    }
                    break;
            }
        }

    The movement is done by setting the velocity of the rigidbody. So if it is not moving, it means that:

    1. Something else is setting the velocity back to 0, or;
    2. It has too much friction with the ground / too much drag set on the rigidbody

    You can inspect (i.e. look at it on the Inspector) your Mushroom’s Rigidbody’s velocity attribute to see what the x value is as it is flipping around. That can give us more insight into what is wrong.

    #12239
    Kenura
    Former Patron

    it does move if increase the speed but its more like a sudden acceleration

    #12240
    Terence
    Keymaster

    I’m not the series creator so I’m not 100% on this, but the reason why it’s like that is probably because the velocity is only applied once, then the state is set to something else (Flip perhaps?). Hence, whenever it flips, for 1 frame, the movement force is applied to the Mushroom, but then friction slows it down to a stop.

    The series creators probably coded it like this to make movement look more organic.

Viewing 12 posts - 1 through 12 (of 12 total)
  • You must be logged in to reply to this topic.

Go to Login Page →


Advertisement below: