Forum begins after the advertisement:


[Part 10] The Boss have some bugs and cannot respawn from other scene if die

Home Forums Video Game Tutorial Series Creating a Metroidvania in Unity [Part 10] The Boss have some bugs and cannot respawn from other scene if die

Viewing 11 posts - 31 through 41 (of 41 total)
  • Author
    Posts
  • #14170
    Elvin Sim
    Participant

    now I test change the health but randomly it will also cause the boss invincible

    #14171
    Elvin Sim
    Participant
    public class TheHollowKnight : Enemy
    {
        public static TheHollowKnight Instance;
    
        [SerializeField] GameObject slashEffect;
        public Transform SideAttackTransform; 
        public Vector2 SideAttackArea;
    
        public Transform UpAttackTransform;
        public Vector2 UpAttackArea;
    
        public Transform DownAttackTransform;
        public Vector2 DownAttackArea;
    
        public float attackRange;
        public float attackTimer;
    
        [HideInInspector] public bool facingRight;
    
        [Header("Ground Check Settings:")]
        [SerializeField] public Transform groundCheckPoint;
        [SerializeField] private float groundCheckY = 0.2f;
        [SerializeField] private float groundCheckX = 0.5f;
        [SerializeField] private LayerMask whatIsGround;
    
        int hitCounter;
        bool stunned, canStun;
        bool alive;
    
        [HideInInspector] public float runSpeed;
    
        public GameObject impactParticle;
    
        private void Awake()
        {
            if(Instance != null && Instance != this)
            {
                Destroy(gameObject);
            }
            else
            {
                Instance = this;
            }
        }
    
        // Start is called before the first frame update
        protected override void Start()
        {
            base.Start();
            sr = GetComponentInChildren<SpriteRenderer>();
            anim = GetComponentInChildren<Animator>();
            ChangeState(EnemyStates.THK_Stage1);
            alive = true;
        }
    
        public bool Grounded()
        {
            if (Physics2D.Raycast(groundCheckPoint.position, Vector2.down, groundCheckY, whatIsGround)
                || Physics2D.Raycast(groundCheckPoint.position + new Vector3(groundCheckX, 0, 0), Vector2.down, groundCheckY, whatIsGround)
                || Physics2D.Raycast(groundCheckPoint.position + new Vector3(-groundCheckX, 0, 0), Vector2.down, groundCheckY, whatIsGround))
            {
                return true;
            }
            else
            {
                return false;
            }
        }
    
        private void OnDrawGizmos()
        {
            Gizmos.color = Color.red;
            Gizmos.DrawWireCube(SideAttackTransform.position, SideAttackArea);
            Gizmos.DrawWireCube(UpAttackTransform.position, UpAttackArea);
            Gizmos.DrawWireCube(DownAttackTransform.position, DownAttackArea);
        }
    
        float bloodCountdown;
        float bloodTimer;
    
        // Update is called once per frame
        protected override void Update()
        {
            base.Update();
    
            if(health <= 0 && alive)
            {
                Death(0);
            }
    
            if (!attacking)
            {
                attackCountdown -= Time.deltaTime;
            }
    
            if (stunned)
            {
                rb.velocity = Vector2.zero;
            }
    
            bloodCountdown -= Time.deltaTime;
            if(bloodCountdown <= 0 && (currentEnemyState != EnemyStates.THK_Stage1 && currentEnemyState != EnemyStates.THK_Stage2))
            {
                GameObject _orangeBlood = Instantiate(orangeBlood, groundCheckPoint.position, Quaternion.identity);
                Destroy(_orangeBlood, 4f);
                bloodCountdown = bloodTimer;
            }
        }
    
        public void Flip()
        {
            if(PlayerController.Instance.transform.position.x < transform.position.x && transform.localScale.x > 0)
            {
                transform.eulerAngles = new Vector2(transform.eulerAngles.x, 180);
                facingRight = false;
            }
            else
            {
                transform.eulerAngles = new Vector2(transform.eulerAngles.x, 0);
                facingRight = true;
            }
        }
    
        protected override void UpdateEnemyState()
        {
            if(PlayerController.Instance != null)
            {
                switch (GetCurrentEnemyState)
                {
                    case EnemyStates.THK_Stage1:
                        canStun = true;
                        attackTimer = 5;
                        runSpeed = speed;
                        break;
    
                    case EnemyStates.THK_Stage2:
                        canStun = true;
                        attackTimer = 4;
                        break;
    
                    case EnemyStates.THK_Stage3:
                        canStun = false;
                        attackTimer = 7;
                        bloodTimer = 5f;
                        break;
    
                    case EnemyStates.THK_Stage4:
                        canStun = false;
                        attackTimer = 8;
                        runSpeed = speed / 2;
                        bloodTimer = 1.5f;
                        break;
                }
            }
        }
    
        protected override void OnCollisionStay2D(Collision2D _other)
        {
            
        }
    
        #region attacking
        #region variables
        [HideInInspector] public bool attacking;
        [HideInInspector] public float attackCountdown;
        [HideInInspector] public bool damagedPlayer = false;
        [HideInInspector] public bool parrying;
    
        [HideInInspector] public Vector2 moveToPosition;
        [HideInInspector] public bool diveAttack;
        public GameObject divingCollider;
        public GameObject pillar;
    
        [HideInInspector] public bool barrageAttack;
        public GameObject barrageFireball;
        [HideInInspector] public bool outbreakAttack;
    
        [HideInInspector] public bool bounceAttack;
        [HideInInspector] public float rotationDirectionToTarget;
        [HideInInspector] public int bounceCount;
    
        #endregion
    
        #region Control
    
        public void AttackHandler()
        {
            if(currentEnemyState == EnemyStates.THK_Stage1)
            {
                if(Vector2.Distance(PlayerController.Instance.transform.position, rb.position) <= attackRange)
                {
                    StartCoroutine(TripleSlash());
                }
                else
                {
                    StartCoroutine(Lunge()); 
                }
            }
    
            if (currentEnemyState == EnemyStates.THK_Stage2)
            {
                if (Vector2.Distance(PlayerController.Instance.transform.position, rb.position) <= attackRange)
                {
                    StartCoroutine(TripleSlash());
                }
                else
                {
                    int _attackChosen = Random.Range(1, 4);
                    if(_attackChosen == 1)
                    {
                        StartCoroutine(Lunge());
                    }
                    if(_attackChosen == 2)
                    {
                        DiveAttackJump();
                    }
                    if(_attackChosen == 3)
                    {
                        BarrageBendDown();
                    }
                    if(_attackChosen == 4)
                    {
                        DivingPillars();
                    }
                }
            }
    
            if (currentEnemyState == EnemyStates.THK_Stage3)
            {
                int _attackChosen = Random.Range(1, 4);
                if (_attackChosen == 1)
                {
                    OutbreakBendDown();
                }
                if (_attackChosen == 2)
                {
                    DiveAttackJump();
                }
                if (_attackChosen == 3)
                {
                    BarrageBendDown();
                }
                if(_attackChosen == 4)
                {
                    BounceAttack();
                }
                
            }
    
            if (currentEnemyState == EnemyStates.THK_Stage4)
            {
                if (Vector2.Distance(PlayerController.Instance.transform.position, rb.position) <= attackRange)
                {
                    StartCoroutine(Slash());
                }
                else
                {
                    BounceAttack();
                }
            }
        }
    
        public void ResetAllAttacks()
        {
            if (parrying) return;
    
            attacking = false;
    
            StopCoroutine(TripleSlash());
            StopCoroutine(Lunge());
            StopCoroutine(Parry());
            StopCoroutine(Slash());
    
            diveAttack = false;
            barrageAttack = false;
            outbreakAttack = false;
            bounceAttack = false;
        }
        #endregion
    
      
        #region Stage 1
        IEnumerator TripleSlash()
        {
            attacking = true;
            rb.velocity = Vector2.zero;
    
            anim.SetTrigger("Slash");
            SlashAngle();
            yield return new WaitForSeconds(0.3f);
            anim.ResetTrigger("Slash");
    
            anim.SetTrigger("Slash");
            SlashAngle();
            yield return new WaitForSeconds(0.5f);
            anim.ResetTrigger("Slash");
    
            anim.SetTrigger("Slash");
            SlashAngle();
            yield return new WaitForSeconds(0.2f);
            anim.ResetTrigger("Slash");
    
            ResetAllAttacks();
        }
    
        void SlashAngle()
        {
            if(PlayerController.Instance.transform.position.x > transform.position.x || PlayerController.Instance.transform.position.x < transform.position.x)
            {
                Instantiate(slashEffect, SideAttackTransform);
            }
            else if (PlayerController.Instance.transform.position.y > transform.position.y)
            {
                SlashEffectAtAngle(slashEffect, 80, UpAttackTransform);
            }
            else if (PlayerController.Instance.transform.position.y < transform.position.y)
            {
                SlashEffectAtAngle(slashEffect, -90, UpAttackTransform);
            }
        }
    
        void SlashEffectAtAngle(GameObject _slashEffect, int _effectAngle, Transform _attackTransform)
        {
            _slashEffect = Instantiate(_slashEffect, _attackTransform);
            _slashEffect.transform.eulerAngles = new Vector3(0, 0, _effectAngle);
            _slashEffect.transform.localScale = new Vector2(transform.localScale.x, transform.localScale.y);
        }
    
        IEnumerator Lunge()
        {
            Flip();
            attacking = true;
    
            anim.SetBool("Lunge", true);
            yield return new WaitForSeconds(1f);
            anim.SetBool("Lunge", false);
            damagedPlayer = false;
            ResetAllAttacks();
        }
    
        IEnumerator Parry()
        {
            attacking = true;
            rb.velocity = Vector2.zero;
            anim.SetBool("Parry", true);
            yield return new WaitForSeconds(0.8f);
            anim.SetBool("Parry", false);
    
            parrying = false;
            ResetAllAttacks();
        }
    
        IEnumerator Slash()
        {
            attacking = true;
            rb.velocity = Vector2.zero;
    
            anim.SetTrigger("Slash");
            SlashAngle();
            yield return new WaitForSeconds(0.2f);
            anim.ResetTrigger("Slash");
    
            ResetAllAttacks();
        }
    
        #endregion
        #region Stage 2
        void DiveAttackJump()
        {
            attacking = true;
            moveToPosition = new Vector2(PlayerController.Instance.transform.position.x, rb.position.y + 10);
            diveAttack = true;
            anim.SetBool("Jump", true);
        }
    
        public void Dive()
        {
            anim.SetBool("Dive", true);
            anim.SetBool("Jump", false);
        }
    
        private void OnTriggerEnter2D(Collider2D _other)
        {
            if(_other.GetComponent<PlayerController>() != null && (diveAttack || bounceAttack))
            {
                _other.GetComponent<PlayerController>().TakeDamage(damage * 2);
                PlayerController.Instance.pState.recoilingX = true;
            }
        }
    
        public void DivingPillars()
        {
            Vector2 _impactPoint = groundCheckPoint.position;
            float _spawnDistance = 5;
    
            for(int i = 0; i < 10; i++)
            {
                Vector2 _pillarSpawnPointRight = _impactPoint + new Vector2(_spawnDistance, 0);
                Vector2 _pillarSpawnPointLeft = _impactPoint - new Vector2(_spawnDistance, 0);
                Instantiate(pillar, _pillarSpawnPointRight, Quaternion.Euler(0, 0, -90));
                Instantiate(pillar, _pillarSpawnPointLeft, Quaternion.Euler(0, 0, -90));
    
                _spawnDistance += 5;
            }
            ResetAllAttacks();
        }
    
        void BarrageBendDown()
        {
            attacking = true;
            rb.velocity = Vector2.zero;
            barrageAttack = true;
            anim.SetTrigger("BendDown");
        }
    
        public IEnumerator Barrage()
        {
            rb.velocity = Vector2.zero;
    
            float _currentAngle = 30f;
            for(int i = 0; i < 10; i++)
            {
                GameObject _projectile = Instantiate(barrageFireball, transform.position, Quaternion.Euler(0, 0, _currentAngle));
    
                if (facingRight)
                {
                    _projectile.transform.eulerAngles = new Vector3(_projectile.transform.eulerAngles.x, 0, _currentAngle);
                }
                else
                {
                    _projectile.transform.eulerAngles = new Vector3(_projectile.transform.eulerAngles.x, 180, _currentAngle);
                }
    
                _currentAngle += 5f;
    
                yield return new WaitForSeconds(0.3f);
            }
            yield return new WaitForSeconds(0.1f);
            anim.SetBool("Cast", false);
            ResetAllAttacks();
        }
    
        #endregion
        #region Stage 3
        void OutbreakBendDown()
        {
            attacking = true;
            rb.velocity = Vector2.zero;
            moveToPosition = new Vector2(transform.position.x, rb.position.y + 5);
            outbreakAttack = true;
            anim.SetTrigger("BendDown");
        }
    
        public IEnumerator Outbreak()
        {
            yield return new WaitForSeconds(1f);
            anim.SetBool("Cast", true);
    
            rb.velocity = Vector2.zero;
            for(int i = 0; i < 30; i++)
            {
                Instantiate(barrageFireball, transform.position, Quaternion.Euler(0, 0, Random.Range(110, 130))); //downwards
                Instantiate(barrageFireball, transform.position, Quaternion.Euler(0, 0, Random.Range(50, 70))); //diagonally right
                Instantiate(barrageFireball, transform.position, Quaternion.Euler(0, 0, Random.Range(260, 280))); //diagonally left
    
                yield return new WaitForSeconds(0.2f);
            }
            yield return new WaitForSeconds(0.1f);
            rb.constraints = RigidbodyConstraints2D.None;
            rb.constraints = RigidbodyConstraints2D.FreezeRotation;
            rb.velocity = new Vector2(rb.velocity.x, -10);
            yield return new WaitForSeconds(0.1f);
            anim.SetBool("Cast", false);
            ResetAllAttacks();
        }
    
        void BounceAttack()
        {
            attacking = true;
            bounceCount = Random.Range(2, 5);
            BounceBendDown();
        }
        int _bounces = 0;
        public void CheckBounce()
        {
            if(_bounces <  bounceCount - 1)
            {
                _bounces++;
                BounceBendDown();
            }
            else
            {
                _bounces = 0;
                anim.Play("Boss_Run");
            }
        }
    
        public void BounceBendDown()
        {
            rb.velocity = Vector2.zero;
            moveToPosition = new Vector2(PlayerController.Instance.transform.position.x, rb.position.y + 10);
            bounceAttack = true;
            anim.SetTrigger("BendDown");
        }
    
        public void CalculateTargetAngle()
        {
            Vector3 _directionToTarget = (PlayerController.Instance.transform.position - transform.position).normalized;
    
            float _angleOfTarget = Mathf.Atan2(_directionToTarget.y, _directionToTarget.x) * Mathf.Rad2Deg;
            rotationDirectionToTarget = _angleOfTarget;
        }
    
        #endregion
        #endregion
    
        public override void EnemyGetsHit(float _damageDone, Vector2 _hitDirection, float _hitForce)
        {
            if (!stunned)
            {
                if (!parrying)
                {
                    if (canStun)
                    {
                        hitCounter++;
                        if(hitCounter >= 3)
                        {
                            ResetAllAttacks();
                            StartCoroutine(Stunned());
                        }
                    }
                    base.EnemyGetsHit(_damageDone, _hitDirection, _hitForce);
    
                    if (currentEnemyState != EnemyStates.THK_Stage4)
                    {
                        ResetAllAttacks(); //cancel any current attack to avoid bugs
                        StartCoroutine(Parry());
                    }
    
                }
                else
                {
                    StopCoroutine(Parry());
                    ResetAllAttacks();
                    StartCoroutine(Slash()); //riposte
                }
            }
            else
            {
                StopCoroutine(Stunned());
                anim.SetBool("Stunned", false);
                stunned = false;
            }
            #region health to state
            if(health > 20)
            {
                ChangeState(EnemyStates.THK_Stage1);
            }
            if (health <= 15 && health < 10)
            {
                ChangeState(EnemyStates.THK_Stage2);
            }
            if (health <= 10 && health < 5)
            {
                ChangeState(EnemyStates.THK_Stage3);
            }
            if (health < 5)
            {
                ChangeState(EnemyStates.THK_Stage4);
            }
            if(health <= 0)
            {
                Death(0);
            }
            #endregion
        }
    
        public IEnumerator Stunned()
        {
            stunned = true;
            hitCounter = 0;
            anim.SetBool("Stunned", true);
    
            yield return new WaitForSeconds(6f);
            anim.SetBool("Stunned", false);
            stunned = false;
        }
    
        protected override void Death(float _destroyTime)
        {
            ResetAllAttacks();
            alive = false;
            rb.velocity = new Vector2(rb.velocity.x, -25);
            anim.SetTrigger("Die");
            bloodTimer = 0.8f;
        }
    
        public void DestroyAfterDeath()
        {
            Destroy(gameObject);
        }
    }
    #14172
    Elvin Sim
    Participant
    public class THKEvents : MonoBehaviour
    {
        void SlashDamagePlayer()
        {
            if (PlayerController.Instance.transform.position.x > transform.position.x || PlayerController.Instance.transform.position.x < transform.position.x)
            {
                Hit(TheHollowKnight.Instance.SideAttackTransform, TheHollowKnight.Instance.SideAttackArea);
            }
            else if (PlayerController.Instance.transform.position.y > transform.position.y)
            {
                Hit(TheHollowKnight.Instance.UpAttackTransform, TheHollowKnight.Instance.UpAttackArea);
            }
            else if (PlayerController.Instance.transform.position.y < transform.position.y)
            {
                Hit(TheHollowKnight.Instance.DownAttackTransform, TheHollowKnight.Instance.DownAttackArea);
            }
        }
    
        void Hit(Transform _attackTransform, Vector2 _attackArea)
        {
            Collider2D[] _objectsToHit = Physics2D.OverlapBoxAll(_attackTransform.position, _attackArea, 0);
            for(int i = 0; i < _objectsToHit.Length; i++)
            {
                if (_objectsToHit[i].GetComponent<PlayerController>() != null)
                {
                    _objectsToHit[i].GetComponent<PlayerController>().TakeDamage(TheHollowKnight.Instance.damage);
                }
            }
        }
    
        void Parrying()
        {
            TheHollowKnight.Instance.parrying = true;
        }
    
        void BendDownCheck()
        {
            if (TheHollowKnight.Instance.barrageAttack)
            {
                StartCoroutine(BarrageAttackTransition());
            }
    
            if (TheHollowKnight.Instance.outbreakAttack)
            {
                StartCoroutine(OutbreakAttackTransition());
            }
    
            if (TheHollowKnight.Instance.bounceAttack)
            {
                TheHollowKnight.Instance.anim.SetTrigger("Bounce1");
            }
        }
    
        void BarrageOrOutbreak()
        {
            if (TheHollowKnight.Instance.barrageAttack)
            {
                TheHollowKnight.Instance.StartCoroutine(TheHollowKnight.Instance.Barrage());
            }
    
            if (TheHollowKnight.Instance.outbreakAttack)
            {
                TheHollowKnight.Instance.StartCoroutine(TheHollowKnight.Instance.Outbreak());
            }
        }
    
        IEnumerator BarrageAttackTransition()
        {
            yield return new WaitForSeconds(1f);
            TheHollowKnight.Instance.anim.SetBool("Cast", true);
        }
    
        IEnumerator OutbreakAttackTransition()
        {
            yield return new WaitForSeconds(1f);
            TheHollowKnight.Instance.anim.SetBool("Cast", true);
        }
    
        void DestroyAfterDeath()
        {
            SpawnBoss.Instance.IsNotTrigger();
            TheHollowKnight.Instance.DestroyAfterDeath();
        }
    }
    #14173
    Elvin Sim
    Participant
    public class Enemy : MonoBehaviour
    {
        [SerializeField] protected float health;
        [SerializeField] protected float recoilLength;
        [SerializeField] protected float recoilFactor;
        [SerializeField] protected bool isRecoiling = false;
    
        [SerializeField] public float speed;
    
        [SerializeField] public float damage;
        [SerializeField] protected GameObject orangeBlood;
    
        [SerializeField] AudioClip hurtSound;
    
        protected float recoilTimer;
        [HideInInspector] public Rigidbody2D rb;
        protected SpriteRenderer sr;
        public Animator anim;
        protected AudioSource audioSource;
    
        protected enum EnemyStates
        {
            //Crawler
            Crawler_Idle,
            Crawler_Flip,
    
            //Bat
            Bat_Idle,
            Bat_Chase,
            Bat_Stunned,
            Bat_Death,
    
            //Charger
            Charger_Idle,
            Charger_Suprised,
            Charger_Charge,
    
            //Shade
            Shade_Idle,
            Shade_Chase,
            Shade_Stunned,
            Shade_Death,
    
            //THK
            THK_Stage1,
            THK_Stage2,
            THK_Stage3,
            THK_Stage4
        }
        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>();
            audioSource = GetComponent<AudioSource>();
        }
    
        // Update is called once per frame
        protected virtual void Update()
        {
            if (GameManager.Instance.gameIsPaused) return;
    
            if(isRecoiling)
            {
                if(recoilTimer < recoilLength)
                {
                    recoilTimer += Time.deltaTime;
                }
                else
                {
                    isRecoiling = false;
                    recoilTimer = 0;
                }
            }
            else
            {
                UpdateEnemyState();
            }
        }
    
        public virtual void EnemyGetsHit(float _damageDone, Vector2 _hitDirection, float _hitForce)
        {
            health -= _damageDone;
            if (!isRecoiling)
            {
                audioSource.PlayOneShot(hurtSound);
                GameObject _orangeBlood = Instantiate(orangeBlood, transform.position, Quaternion.identity);
                Destroy(_orangeBlood, 5.5f);
                rb.velocity = _hitForce * recoilFactor * _hitDirection;
            }
        }
        protected virtual void OnCollisionStay2D(Collision2D _other)
        {
            if (_other.gameObject.CompareTag("Player") && !PlayerController.Instance.pState.invincible && health > 0)
            {
                Attack();
                if(PlayerController.Instance.pState.alive)
                {
                    PlayerController.Instance.HitStopTime(0, 5, 0.5f);
                }
            }
        }
    
        protected virtual void Death(float _destroyTime)
        {
            Destroy(gameObject, _destroyTime);
        }
    
        protected virtual void UpdateEnemyState() { }
        protected virtual void ChangeCurrentAnimation() { }
    
        protected void ChangeState(EnemyStates _newState)
        {
            GetCurrentEnemyState = _newState;
        }
    
        protected virtual void Attack()
        {
            PlayerController.Instance.TakeDamage(damage);
        }
    }
    #14174
    Elvin Sim
    Participant

    The Slash always Trigger if you see the animator

    View post on imgur.com

    #14175
    #14176
    Elvin Sim
    Participant

    You can see the SlashAngle() and others related to the Slash

    #14177
    Elvin Sim
    Participant

    I am so sorry for bother you all, because I try as much as I could but still cannot find the solution

    #14178
    Joseph Tang
    Moderator

    While testing the bug, can you show the rest of the Boss’ inspector? Particularly the bools.

    Also, at 21 health, the boss shouldn’t be changing states as it should not have crossed the threshold to do so and to be stunned.

    While in your else statement for EnemyGetsHit(), change the following code:

                else
                {
                    StopCoroutine(Parry());
                    parrying = false;
                    ResetAllAttacks();
                    StartCoroutine(Slash());  //riposte
                }
    #14181
    Elvin Sim
    Participant

    Thank you it fix now!!!

    #14228
    Ethan
    Participant

    elvin would you be able to send me your project as im having some bugs and want to compare and see where im going wrong

Viewing 11 posts - 31 through 41 (of 41 total)
  • You must be logged in to reply to this topic.

Go to Login Page →


Advertisement below: