Forum begins after the advertisement:


[Part 10] How to let the boss Just Spawn Once when I translate to other scene

Home Forums Video Game Tutorial Series Creating a Metroidvania in Unity [Part 10] How to let the boss Just Spawn Once when I translate to other scene

Viewing 15 posts - 1 through 15 (of 15 total)
  • Author
    Posts
  • #14936
    Elvin Sim
    Participant
    public class SpawnBoss : MonoBehaviour
    {
        public static SpawnBoss Instance;
        [SerializeField] Transform spawnPoint;
        [SerializeField] GameObject boss;
        [SerializeField] Vector2 exitDirection;
        bool callOnce;
        BoxCollider2D col;
    
        private void Awake()
        {
            if(Instance != null && Instance != this)
            {
                Destroy(gameObject);
            }
            else
            {
                Instance = this;
            }
        }
    
        // Start is called before the first frame update
        void Start()
        {
            col = GetComponent<BoxCollider2D>();
        }
    
        // Update is called once per frame
        void Update()
        {
            
        }
    
        private void OnTriggerEnter2D(Collider2D _other)
        {
            if (_other.CompareTag("Player"))
            {
                if (!callOnce)
                {
                    StartCoroutine(WalkIntoRoom());
                    callOnce = true;
                }
            }
        }
        
        IEnumerator WalkIntoRoom()
        {
            StartCoroutine(PlayerController.Instance.WalkIntoNewScene(exitDirection, 1));
            yield return new WaitForSeconds(0.3f);
            col.isTrigger = false;
            Instantiate(boss, spawnPoint.position, Quaternion.identity);
        }
    
        public void IsNotTrigger()
        {
            col.isTrigger = true;
        }
    }
    #14937
    #14950
    Joseph Tang
    Moderator

    Take a look at point [6] in this post to see if it fixes the issue

    [Part 10] Article Changes, Common Issues & Bugfixes

    #14962
    Elvin Sim
    Participant

    Thanks I will test it later, and I also want to ask that why when I attack the Hollow Knight in the diving attack form, before it is ok, now it will stop and no parry.

    #14963
    #14964
    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;
    
        public Transform wallCheckPoint; //point at which wall check happens
    
        [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;
            }
        }
    
        public bool TouchedWall()
        {
            if (Physics2D.Raycast(wallCheckPoint.position, Vector2.down, groundCheckY, whatIsGround)
        || Physics2D.Raycast(wallCheckPoint.position + new Vector3(groundCheckX, 0, 0), Vector2.down, groundCheckY, whatIsGround)
        || Physics2D.Raycast(wallCheckPoint.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 = 2;
                        runSpeed = speed;
                        break;
    
                    case EnemyStates.THK_Stage2:
                        canStun = true;
                        attackTimer = 2;
                        break;
    
                    case EnemyStates.THK_Stage3:
                        canStun = true;
                        attackTimer = 3;
                        bloodTimer = 5f;
                        break;
    
                    case EnemyStates.THK_Stage4:
                        canStun = false;
                        attackTimer = 3;
                        runSpeed = speed / 2;
                        bloodTimer = 1.5f;
                        break;
                }
            }
        }
    
        protected override 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);
                }
            }
    
        }
    
        #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, 3);
                    if(_attackChosen == 1)
                    {
                        StartCoroutine(Lunge());
                    }
                    if(_attackChosen == 2)
                    {
                        DiveAttackJump();
                    }
                    if(_attackChosen == 3)
                    {
                        BarrageBendDown();
                    }
                }
            }
    
            if (currentEnemyState == EnemyStates.THK_Stage3)
            {
                int _attackChosen = Random.Range(1, 3);
                if (_attackChosen == 1)
                {
                    OutbreakBendDown();
                }
                if (_attackChosen == 2)
                {
                    DiveAttackJump();
                }
                if (_attackChosen == 3)
                {
                    BarrageBendDown();
                }
                
            }
    
            if (currentEnemyState == EnemyStates.THK_Stage4)
            {
                if (Vector2.Distance(PlayerController.Instance.transform.position, rb.position) <= attackRange)
                {
                    OutbreakBendDown();
                }
                else
                {
                    DiveAttackJump();
                }
            }
        }
    
        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, DownAttackTransform);
            }
        }
    
        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;
                ResetAllAttacks();
            }
        }
    
        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;
            rb.constraints = RigidbodyConstraints2D.FreezePosition;
            barrageAttack = true;
            anim.SetTrigger("BendDown");
        }
    
        public IEnumerator Barrage()
        {
            rb.velocity = Vector2.zero;
            rb.constraints = RigidbodyConstraints2D.FreezePosition;
    
            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);
            rb.constraints = RigidbodyConstraints2D.None;
            rb.constraints = RigidbodyConstraints2D.FreezeRotation;
            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)
                        {
                            if (!attacking)
                            {
                                ResetAllAttacks();
                                StartCoroutine(Stunned());
                            }
                        }
                    }
                    base.EnemyGetsHit(_damageDone, _hitDirection, _hitForce);
    
                    if (currentEnemyState != EnemyStates.THK_Stage4)
                    {
                        if (!attacking)
                        {
                            ResetAllAttacks(); //cancel any current attack to avoid bugs
                            StartCoroutine(Parry());
                        } 
                    }
    
                }
                else
                {
                    StopCoroutine(Parry());
                    parrying = false;
                    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 <= 20)
            {
                ChangeState(EnemyStates.THK_Stage2);
            }
            if (health <= 15)
            {
                ChangeState(EnemyStates.THK_Stage3);
            }
            if (health <= 10)
            {
                ChangeState(EnemyStates.THK_Stage4);
            }
            if(health <= 0 && alive)
            {
                Death(0);
            }
            #endregion
        }
    
        public IEnumerator Stunned()
        {
            stunned = true;
            hitCounter = 0;
            anim.SetBool("Stunned", true);
    
            yield return new WaitForSeconds(5f);
            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);
        }
    }
    #14965
    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 && !PlayerController.Instance.pState.invincible)
                {
                    _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();
        }
    }
    #14966
    Joseph Tang
    Moderator

    Check point [1] of the post above to see if it helps.

    #14970
    #14971
    Elvin Sim
    Participant

    I don’t why but it show error when I start play

    #14974
    Elvin Sim
    Participant

    Sorry it works now, it is the File.Exists(savePath) && new FileInfo(savePath).Length > 0 doen’t add to the code in loadBossData

    #14979
    Elvin Sim
    Participant

    And now I reset the data and start the game all over again, the boss cannot spawn

    #14981
    #14982
    Joseph Tang
    Moderator

    For it not being able to find the GameManager, that means that the GameManager.cs has not been able to create a public instance of itself fast enough. Which should not be an issue if it is made before entering the scene.

    This in itself is not really an issue as the backup of the OnTriggerEnter2D() method’s if statement also searching for the GameManager will prevent it from spawning the boss if it really has been defeated.

    if(_other.CompareTag("Player") && !callOnce && !GameManager.Instance.THKDefeated)


    If your boss cannot spawn, simply test out if the single time spawn is working as intended by selecting the game object used as the wall/spawn boss script holder, turning callOnce false & setting it’s box collider trigger true.

    When callOnce is false, this will allow you to spawn the boss again. If this works, then the code is functional.

    If you want to be able to spawn the boss again, you will have to reset the saved boss.data of the boss in SaveData.cs by doing a similar method of resetting the saved data as in the previous post for resetting saved data

    [Part 7] My Maps cannot update when I quit the game

    #14983
    Elvin Sim
    Participant

    sorry It works again XD, I go to the savedatamanager.cs that you taught me before and add the /save.boss.data, now it works

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

Go to Login Page →


Advertisement below: