Forum begins after the advertisement:


[Part 6] Charger Not Flipping and Detecting Player

Home Forums Video Game Tutorial Series Creating a Metroidvania in Unity [Part 6] Charger Not Flipping and Detecting Player

Viewing 7 posts - 1 through 7 (of 7 total)
  • Author
    Posts
  • #14524
    Alex
    Level 10
    Former Patron
    Helpful?
    Up
    0
    ::

    I’m having issues where the charger isn’t charging at the player and also not flipping (it will continue running into the wall).

    Demo

    View post on imgur.com

    Here’s my code Charger.cs

    <code>using System.Collections;
    using System.Collections.Generic;
    using UnityEngine;
    
    public class Charger : Enemy
    {
        [SerializeField] private float ledgeCheckX;
        [SerializeField] private float ledgeCheckY;
        [SerializeField] private float chargeSpeedMultiplier;
        [SerializeField] private float chargeDuration;
        [SerializeField] private float jumpForce;
        [SerializeField] private LayerMask whatIsGround;
    
        float timer;
    
        protected override void Start()
        {
            base.Start();
            ChangeState(EnemyStates.Charger_Idle);
            rb.gravityScale = 12f;
        }
    
        protected override void UpdateEnemyStates()
        {
            if(health <= 0) {
                Death(0.05f);
            }
    
            Vector3 _ledgeCheckStart = transform.localScale.x > 0 ? new Vector3(ledgeCheckX, 0) : new Vector3(-ledgeCheckX, 0);
            Vector2 _wallCheckDir = transform.localScale.x > 0 ? transform.right : -transform.right;
    
            switch (GetCurrentEnemyState)
            {
                case EnemyStates.Charger_Idle:
    
                    if (!Physics2D.Raycast(transform.position + _ledgeCheckStart, Vector2.down, ledgeCheckY, whatIsGround) || Physics2D.Raycast(transform.position, _wallCheckDir, ledgeCheckX, whatIsGround))
                    {
                        transform.localScale = new Vector2(transform.localScale.x * -1, transform.localScale.y);
                    }
    
                    RaycastHit2D _hit = Physics2D.Raycast(transform.position + _ledgeCheckStart, _wallCheckDir, ledgeCheckX * 10);
                    if (_hit.collider != null && _hit.collider.gameObject.CompareTag("Player")) {
                        ChangeState(EnemyStates.Charger_Surprised);
                    }
    
                    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.Charger_Surprised: 
                    rb.velocity = new Vector2(0, jumpForce);
                    ChangeState(EnemyStates.Charger_Charge);
                    break;
    
                case EnemyStates.Charger_Charge:
                    timer += Time.deltaTime;
                    if(timer < chargeDuration) {
                        if(Physics2D.Raycast(transform.position, Vector2.down, ledgeCheckY, whatIsGround)) {
                            if (transform.localScale.x > 0) {
                                rb.velocity = new Vector2(speed * chargeSpeedMultiplier, rb.velocity.y);
                            } else {
                                rb.velocity = new Vector2(-speed * chargeSpeedMultiplier, rb.velocity.y);
                            }
                        } else {
                            rb.velocity = new Vector2(0, rb.velocity.y);
                        }
                    } else {
                        timer = 0;
                        ChangeState(EnemyStates.Charger_Idle);
                    }
                    break;
            }
        }
    
        protected override void ChangeCurrentAnimation()
        {
            if(GetCurrentEnemyState == EnemyStates.Charger_Idle) {
                anim.speed = 1;
            }
    
            if(GetCurrentEnemyState == EnemyStates.Charger_Charge) {
                anim.speed = chargeSpeedMultiplier;
            }
        }
    }
    </code>

    Enemy.cs

    <code>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 playerController player;
        [SerializeField] protected float speed;
    
        [SerializeField] protected float damage;
        [SerializeField] protected GameObject enemyBlood;
    
        protected float recoilTimer;
        protected Rigidbody2D rb;
        protected SpriteRenderer sr;
        protected Animator anim;
    
        protected enum EnemyStates {
            // Crawler
            Crawler_Idle,
            Crawler_Flip,
    
            // Bat
            Bat_Idle,
            Bat_Chase,
            Bat_Stunned,
            Bat_Death,
    
            // Charger
            Charger_Idle,
            Charger_Surprised,
            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>();
        }
    
        protected virtual void Awake() {
            rb = GetComponent<Rigidbody2D>();
            player = playerController.Instance;
        }
    
        // 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 EnemyHit(float _damageDone, Vector2 _hitDirection, float _hitForce) {
            health -= _damageDone;
            if(!isRecoiling) {
                GameObject _enemyBlood = Instantiate(enemyBlood, transform.position, Quaternion.identity);
                Destroy(_enemyBlood, 5.5f);
                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);
        }
    }</code>
    #14527
    Joseph Tang
    Level 13
    Moderator
    Helpful?
    Up
    0
    ::

    From the looks of your code, nothing is different from the code given in the Forums. Which means it could be the scene’s setup which is the issue.

    I tested your code and it works fine, leading me to believe that what’s not working is your raycast detection. More particularly, the raycast targets don’t fit the parameters for the Charger to function.

    In your video, the Charger was still in Idle state and was walking into the wall. I’ll need you to check 2 things:

    1. Your Player is Tagged “Player”
    2. Your Walls are Layer “Ground”

    My guess is that your Player is not tagged appropriately and the Charger cannot sense the Player to switch to Surprised state. As for the Charger walking into the wall, I think it is either the wall not having the required whatIsGround layer or the ledgeCheckX is too small of a value [You can play around with higher values to test out the range of detection for your Charger.]

    I’d recommend you add Debug.log() or print() to both if statements in Charger.cs, under UpdateEnemyStates(), here, to see if either are being triggered:

                    if (!Physics2D.Raycast(transform.position + _ledgeCheckStart, Vector2.down, ledgeCheckY, whatIsGround) || Physics2D.Raycast(transform.position, _wallCheckDir, ledgeCheckX, whatIsGround))
                    {
                        print("Turn Around");
                        transform.localScale = new Vector2(transform.localScale.x * -1, transform.localScale.y);
                    }
    
                    RaycastHit2D _hit = Physics2D.Raycast(transform.position + _ledgeCheckStart, _wallCheckDir, ledgeCheckX * 10);
                    if (_hit.collider != null && _hit.collider.gameObject.CompareTag("Player")) {
                        print("Found Player");
                        ChangeState(EnemyStates.Charger_Surprised);
                    }
    #14530
    Alex
    Level 10
    Former Patron
    Helpful?
    Up
    0
    ::

    Oops, yeah, my walls were not set to the ground layer, so that fixed the running into the wall issue. As for the charger not detecting the player. I had the Player set to the Player tag already, but the charger still doesn’t detect the player. I tried the print statement out and nothing is printed. I also noticed that the charger goes into the surprised state when I jump on top of the charger; this happens randomly, too. It could either take one jump or two jumps.

    #14534
    Joseph Tang
    Level 13
    Moderator
    Helpful?
    Up
    0
    ::

    Okay, It sounds like the code may be working but there’s an issue with the Raycast2D.

    Before we try anything, I would like you to implement this code into your Charger.cs:

        private Vector3 ledgeCheckStart;
        private Vector2 wallCheckDir;
    
        private void OnDrawGizmos()
        {
            Gizmos.color = Color.red;
            Gizmos.DrawRay(transform.position + ledgeCheckStart, wallCheckDir * (ledgeCheckX * 10));
        }
    
        protected override void UpdateEnemyStates()
        {
            if (health <= 0)
            {
                Death(0.05f);
            }
    
            Vector3 _ledgeCheckStart = transform.localScale.x > 0 ? new Vector3(ledgeCheckX, 0) : new Vector3(-ledgeCheckX, 0);
            Vector2 _wallCheckDir = transform.localScale.x > 0 ? transform.right : -transform.right;
    
            ledgeCheckStart = _ledgeCheckStart;
            wallCheckDir = _wallCheckDir;
            switch (GetCurrentEnemyState)
            {
                case EnemyStates.Charger_Idle:
                    if (!Physics2D.Raycast(transform.position + _ledgeCheckStart, Vector2.down, ledgeCheckY, whatIsGround)
                        || Physics2D.Raycast(transform.position, _wallCheckDir, ledgeCheckX, whatIsGround))
                    {
                        transform.localScale = new Vector2(transform.localScale.x * -1, transform.localScale.y);
                    }

    This code will help draw out a line [only while in game since the wallCheckDir and ledgeCheckstart has not been set yet] to represent your raycast. By right, your ray cast should be in a perfectly normal straight line in the game. If it appears shorter, you just need to tweak the ledgeCheckX value, or simply change out ledgeCheckX for a number instead of calling a variable. Otherwise, you may need to change your code values here:

                    RaycastHit2D _hit = Physics2D.Raycast(transform.position + _ledgeCheckStart, _wallCheckDir, ledgeCheckX * 10);

    Change the values to some solid floats or integers for the moment and test it out like that.

    #14542
    Alex
    Level 10
    Former Patron
    Helpful?
    Up
    0
    ::

    I did what you told me to do, and I see a perfectly straight line on the charger enemy and at a perfect length, not too short, not too long. Along with adding the code you provided, this error popped up

    
    NullReferenceException: Object reference not set to an instance of an object
    UnityEditor.Graphs.Edge.WakeUp () (at <5fb17ff8fbc54f4294a2425fd91a4819>:0)
    UnityEditor.Graphs.Graph.DoWakeUpEdges (System.Collections.Generic.List<code>1[T] inEdges, System.Collections.Generic.List</code>1[T] ok, System.Collections.Generic.List`
    1[T] error, System.Boolean inEdgesUsedToBeValid) (at <5fb17ff8fbc54f4294a2425fd91a4819>:0)
    UnityEditor.Graphs.Graph.WakeUpEdges (System.Boolean clearSlotEdges) (at <5fb17ff8fbc54f4294a2425fd91a4819>:0)
    UnityEditor.Graphs.Graph.WakeUp (System.Boolean force) (at <5fb17ff8fbc54f4294a2425fd91a4819>:0)
    UnityEditor.Graphs.Graph.WakeUp () (at <5fb17ff8fbc54f4294a2425fd91a4819>:0)
    UnityEditor.Graphs.Graph.OnEnable () (at <5fb17ff8fbc54f4294a2425fd91a4819>:0)
    #14543
    Terence
    Level 30
    Keymaster
    Helpful?
    Up
    0
    ::
    I did what you told me to do, and I see a perfectly straight line on the charger enemy and at a perfect length, not too short, not too long. Along with adding the code you provided, this error popped up
    Alex, just closing and reopening Unity should fix this issue.
    #14546
    Joseph Tang
    Level 13
    Moderator
    Helpful?
    Up
    0
    ::

    Let’s try something else,

                    RaycastHit2D _hit = Physics2D.Raycast(transform.position + _ledgeCheckStart, _wallCheckDir, ledgeCheckX * 10);
                    if (_hit.collider != null && _hit.collider.gameObject.CompareTag("Player") _hit.collider.gameObject.GetComponent<PlayerController>()) {
                        ChangeState(EnemyStates.Charger_Surprised);
                    }

    We’ll replace the compare tag parameter and swap it out with a simpler identifier of the player to see if that helps with the detection. [It doesn’t come out properly here as the angle brackets are being confused here, but use _hit.collider.gameObject.GetComponent<strong>"PlayerController"</strong>()]

    If nothing changes, the raycast is still faulty and we’ll need to find out why. Could you send a video of you doing this

    I also noticed that the charger goes into the surprised state when I jump on top of the charger; this happens randomly, too. It could either take one jump or two jumps.

    Then can you also try playing around with adjusting the scale of the Charger while in play?

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

Go to Login Page →


Advertisement below: