Forum begins after the advertisement:


[part 10] dive causes unity to freeze completely

Home Forums Video Game Tutorial Series Creating a Metroidvania in Unity [part 10] dive causes unity to freeze completely

Viewing 15 posts - 1 through 15 (of 45 total)
  • Author
    Posts
  • #15948
    Niyam Shah
    Participant
    Helpful?
    Up
    0
    ::

    So the boss will move above my player, drop down , but when it hits the ground, the entire unity engine freezes….. i have to open task manager to reset unity as i already waiting 10 mins and the whole thing was frozen.Ive tested it and its happened 4 times in a row?? Im not sure if this has something to do with too many pillars being generated? maybe unity cant handle it?? my laptop is high spec so idk if its a processing issue

    ive included code for the dive(animation), my main boss and the pillar

    dive script:

    `

    public class WoodenBotDive : StateMachineBehaviour
    {
        Rigidbody2D rb;
    
        bool callOnce;
    
        // OnStateEnter is called when a transition starts and the state machine starts to evaluate this state
        override public void OnStateEnter(Animator animator, AnimatorStateInfo stateInfo, int layerIndex)
        {
            rb = animator.GetComponentInParent<Rigidbody2D>();
        }
    
        override public void OnStateUpdate(Animator animator, AnimatorStateInfo stateInfo, int layerIndex)
        {
            WoodenBot.Instance.divingCollider.SetActive(true);
    
            if (WoodenBot.Instance.Grounded())
            {
                WoodenBot.Instance.divingCollider.SetActive(false);
    
                if (!callOnce)
                {
                    WoodenBot.Instance.DivingPillars();
                    animator.SetBool("Dive", false);
                    WoodenBot.Instance.ResetAllAttacks();
                    callOnce = true;
                }
    
            }
        }
    
        override public void OnStateExit(Animator animator, AnimatorStateInfo stateInfo, int layerIndex)
        {
            callOnce = false;
        }
    
    }

    THeHollowKnightScript – (my boss is called woodenBot instead of TheHollowKnight)

    using System.Collections;
    using System.Collections.Generic;
    using UnityEngine;
    
    public class WoodenBot : Enemy
    {
    
        public static WoodenBot Instance;
    
        [Header("Ground Check Settings")]
        [SerializeField] private 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 bool facingRight;
        [HideInInspector] public float runSpeed;
        [HideInInspector] public bool damagePlayer = false;
        [HideInInspector] public bool parrying;
    
        [HideInInspector] public Vector2 moveToPosition;
        [HideInInspector] public bool diveAttack;
        public GameObject divingCollider;
        public GameObject pillar;
        [Space(5)]
    
        [Header("attack settings")]
        [SerializeField] public Transform SideAttackTransform, UpAttackTransform, DownAttackTransform;
        [SerializeField] public Vector2 SideAttackArea, UpAttackArea, DownAttackArea;
        [SerializeField] public GameObject slashEffect;
    
        public float attackRange;
        public float attackTimer;
    
        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);
    
        }
    
        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.woodenBot_stage1);
            alive = true;
        }
    
        // Update is called once per frame
        protected override void Update()
        {
            base.Update();
    
            if (!attacking)
            {
                attackCountdown -= Time.deltaTime;
            }
    
        }
    
        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 UpdateEnemyStates()
        {
            if (PlayerController.Instance != null)
            {
                switch (GetCurrentEnemyState)
                {
                    case EnemyStates.woodenBot_stage1:
                        break;
    
                    case EnemyStates.woodenBot_stage2:
                        break;
    
                    case EnemyStates.woodenBot_stage3:
                        break;
    
                    case EnemyStates.woodenBot_stage4:
                        break;
                }
    
            }
        }
    
        protected override void OnCollisionStay2D(Collision2D _other)
        {
    
        }
    
        #region attacking
        #region variables
        [HideInInspector] public bool attacking;
        [HideInInspector] public float attackCountdown;
    
        #endregion
        #endregion
    
        #region AttackHandler
    
        public void AttackHandler()
        {
            if (currentEnemyState == EnemyStates.woodenBot_stage1)
            {
                if (Vector2.Distance(PlayerController.Instance.transform.position, rb.position) <= attackRange)
                {
                    StartCoroutine(TripleSlash());
                }
                else
                {
                    //StartCoroutine(Lunge());
                    DiveAttackJump();
                }
            }
        }
    
        public void ResetAllAttacks()
        {
            attacking = false;
            StopCoroutine(Lunge());
            StopCoroutine(Parry());
            StopCoroutine(TripleSlash());
            StopCoroutine(Slash());
    
            diveAttack = false;
        }
    
        #endregion
    
        #region Stage 1
    
        IEnumerator TripleSlash()
        {
            attacking = true;
            rb.velocity = Vector2.zero;
            anim.SetTrigger("Slash");
            SlashAngle();
            yield return new WaitForSeconds(2);
            anim.SetTrigger("Slash 2");
            SlashAngle();
            yield return new WaitForSeconds(2);
            anim.SetTrigger("Slash 3");
            SlashAngle();
            yield return new WaitForSeconds(2);
    
            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(1);
            damagePlayer = false;
            anim.SetBool("Lunge", 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(2);
    
            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);
    
        }
    
        public void DivingPillars()
        {
            Vector2 _impactPoint = groundCheckPoint.position;
    
            float _spawnDistance = 5;
    
            for(int i = 0; 1 < 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();
        }
    
        private void OnTriggerEnter2D(Collider2D _other)
        {
            if (_other.GetComponent<PlayerController>() != null && diveAttack)
            {
                _other.GetComponent<PlayerController>().TakeDamage(damage * 2);
                PlayerController.Instance.pState.recoilingx = true;
            }
        }
    
        #endregion
    
        public override void EnemyHit(float _damageDone, Vector2 _hitDirection, float _hitForce)
        {
            if (!parrying)
            {
                base.EnemyHit(_damageDone, _hitDirection, _hitForce);
                
                if(currentEnemyState != EnemyStates.woodenBot_stage4)// parry isnt used in final form for a frantic ending
                {
                    ResetAllAttacks();
                    StartCoroutine(Parry());
    
                }
     
            }
            else
            {
                StopCoroutine(Parry());
                ResetAllAttacks();
                parrying = false;
                //parry attack
                StartCoroutine(Slash());
    
            }
    
        }
    }

    Code for the pillar:

    public class DivingPillar : MonoBehaviour
    {
        // Start is called before the first frame update
       void OnTriggerEnter2D(Collider2D _other)
        {
            if (_other.CompareTag("Player"))
            {
                _other.GetComponent<PlayerController>().TakeDamage(WoodenBot.Instance.damage);
            }
    
        }
    }
    #15949
    Niyam Shah
    Participant
    Helpful?
    Up
    1
    ::

    So ive done some debugging and realised that the game only crashes when the Diving collider isnt selected in the inspector? so the diving collider is somehow causing the crash. However without the diving collidrer, the pillars dont spawn in

    #15951
    Terence
    Keymaster
    Helpful?
    Up
    0
    ::

    Hi Niyam, it’s very likely that you are trying to spawn unlimited Diving Pillars because the code is cycling between OnStateUpdate() and OnStateExit(). Even though OnStateUpdate() is only coded to execute the diving pillars once, if your animator for the boss is leaving the state and re-entering, it will cause the callOnce variable to switch back to false, which causes Diving Pillars to execute once again.

    You may want to check your Animator to see if the boss’s animations are bouncing between 2 states.

    In the meantime, you can update the Diving Pillars to track the number of pillars on the screen and prevent diving pillars from spawning if there are too many.

    public class DivingPillar : MonoBehaviour
    {
    
        public static int count = 0;
        // To track how many pillars exist.
        void Start() { count++; }
        void OnDestroy() { count--; }
    
        // Start is called before the first frame update
        void OnTriggerEnter2D(Collider2D _other)
        {
            if (_other.CompareTag("Player"))
            {
                _other.GetComponent<PlayerController>().TakeDamage(WoodenBot.Instance.damage);
            }
    
        }
    }

    And then in OnStateUpdate(), don’t call DivingPillars() if there are already too many:

        override public void OnStateUpdate(Animator animator, AnimatorStateInfo stateInfo, int layerIndex)
        {
            WoodenBot.Instance.divingCollider.SetActive(true);
    
            if (WoodenBot.Instance.Grounded())
            {
                WoodenBot.Instance.divingCollider.SetActive(false);
    
                if (!callOnce && DivingPillars.count < 10)
                {
                    WoodenBot.Instance.DivingPillars();
                    animator.SetBool("Dive", false);
                    WoodenBot.Instance.ResetAllAttacks();
                    callOnce = true;
                }
    
            }
        }

    Hope this makes sense!

    The boss scripts are too messy. I’m hoping to simplify it in a future stream.

    #15953
    Niyam Shah
    Participant
    Helpful?
    Up
    0
    ::

    Thanks for your help -the engine no longer freezes but the pillars wont spawn? ive narrowed down the problem area to the WoodenBotDive script

    From the debug logs in place, “callOnce is true” doesnt play however, “callOnce is false” does play about 2 seconds after my boss lands on the ground

    The main error is the ‘if (!callOnce && DivingPillar.count > 10)’, more specifically the ‘DivingPillar.count > 10)’ part as this is the reason it doesnt work – removing it crashes the game, changing the > to a < crashes the game so idk what to do??

    using System.Collections;
    using System.Collections.Generic;
    using UnityEngine;
    
    public class WoodenBotDive : StateMachineBehaviour
    {
        Rigidbody2D rb;
    
        bool callOnce;
    
        // OnStateEnter is called when a transition starts and the state machine starts to evaluate this state
        override public void OnStateEnter(Animator animator, AnimatorStateInfo stateInfo, int layerIndex)
        {
            rb = animator.GetComponentInParent<Rigidbody2D>();
        }
    
        override public void OnStateUpdate(Animator animator, AnimatorStateInfo stateInfo, int layerIndex)
        {
            WoodenBot.Instance.divingCollider.SetActive(true);
    
            if (WoodenBot.Instance.Grounded())
            {
                WoodenBot.Instance.divingCollider.SetActive(false);
    
                if (!callOnce && DivingPillar.count > 10)
                {
                    Debug.Log("callOnce is true");
                    WoodenBot.Instance.DivingPillars();
                    animator.SetBool("Dive", false);
                    WoodenBot.Instance.ResetAllAttacks();
                    callOnce = true;
                }
    
            }
        }
    
        override public void OnStateExit(Animator animator, AnimatorStateInfo stateInfo, int layerIndex)
        {
            callOnce = false;
            Debug.Log("CallOnce is false");
        }
    
    }
    #15954
    Terence
    Keymaster
    Helpful?
    Up
    0
    ::

    Sorry, I got the code wrong. It should be DivingPillar.Count < 10, not DivingPillarCount > 10.

    I’ve corrected my post above.

    #15956
    Niyam Shah
    Participant
    Helpful?
    Up
    0
    ::

    Unfortunately that still causes unity to freeze???

    #15957
    Terence
    Keymaster
    Helpful?
    Up
    0
    ::

    Let’s look into this and see whether DivingPillars() is the line that causes the freeze. Comment it out from your code and try again:

                if (!callOnce && DivingPillar.count > 10)
                {
                    Debug.Log("callOnce is true");
                    //WoodenBot.Instance.DivingPillars();
                    animator.SetBool("Dive", false);
                    WoodenBot.Instance.ResetAllAttacks();
                    callOnce = true;
                }

    Does it still crash?

    #15962
    Niyam Shah
    Participant
    Helpful?
    Up
    0
    ::

    Nope my game doesn’t crash when this happens

    Must be a fault of the “diving pillars” function but from what I’ve seen it’s word for word with ur code?

    #15963
    Niyam Shah
    Participant
    Helpful?
    Up
    0
    ::

    Also there’s a weird yellow error that says ResetAllAttacks() was unreachable code so I moved it above??? Just in this diving pillars function?

    public void DivingPillars()
        {
            Vector2 _impactPoint = groundCheckPoint.position;
    
            float _spawnDistance = 5;
    
    ResetAllAttacks();
    
            for(int i = 0; 1 < 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;
            }
           
        }
    #15964
    Terence
    Keymaster
    Helpful?
    Up
    0
    ::

    I found the issue. Your DivingPillars() is the problem:

            for(int i = 0; 1i < 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;
            }

    It shouldn't be 1 < 10. It should be i < 10. 1 < 10 will always be true, hence the crashing and the unreachable code.

    #15965
    Niyam Shah
    Participant
    Helpful?
    Up
    0
    ::

    ah yes silly me – it works now

    for the pillars are u able to give an explanation of how to change the scale of the pillar with the animation as the video went through this really quickly and i didnt understand it fully?? this is just so my box collider aligns with the pillar size

    #15966
    Niyam Shah
    Participant
    Helpful?
    Up
    0
    ::

    never mind – works completely fine

    I have a question though – this doesnt effect anyhting with the boss but the entire time ive been gettin an error saying “Animation event has no function name specified”

    idk what that means but my boss does work perfectly fine and all animation events are set correctly?? will this be a problem later on or nah

    #15967
    Terence
    Keymaster
    Helpful?
    Up
    0
    ::

    I have a question though – this doesnt effect anyhting with the boss but the entire time ive been gettin an error saying “Animation event has no function name specified”

    This just means that you have some Animation Events in your Animations without a function assigned. Check all your Animation Events and remove all of them without a function assigned, since they don’t do anything.

    #15968
    Niyam Shah
    Participant
    Helpful?
    Up
    0
    ::

    Ah ok yeah I think I spammed the button when I didn’t know how it worked lol

    Thanks for the help!

    I have a 2 small unrelated issues – I moved my unity project to my new computer and everythin was fine except for 2 things-

    the bat enemy now moves extremely slowly despite the speed (it needs to go in a fixed update function but idk how to do that since it comes up with errors?

    and the camera is very jittery when following the player? I’ve tried changing follow speed and everything but nothing changes?? My camera is identical to the final camera in the videos- just wondering why?

    I heard ur thinking of ideas for part11 and beyond so I got some ideas:

    Simple cutscenes
    More enemy types (ai with enemies similar to the boss ai)
    A combat system with combos and such(I’ve already made this myself but it’ll be cool to show)
    And maybe some deeper polish updates such smoother movement and time manipulation to make attacks feel powerful

    #15971
    Terence
    Keymaster
    Helpful?
    Up
    0
    ::

    What are the errors on the bat?

    For the jittery camera, let me know if this article helps: https://blog.terresquall.com/2021/12/fix-jittery-camera-movement-in-unity-with-rigidbody-interpolate/

    We are going to be doing a Save system for Part 11, but thanks for the ideas! Will keep it in mind for future parts.

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

Go to Login Page →


Advertisement below: