Forum begins after the advertisement:

 


[Part 7] Issues after changing Scene

Home Forums Video Game Tutorial Series Creating a Metroidvania in Unity [Part 7] Issues after changing Scene

Viewing 12 posts - 1 through 12 (of 12 total)
  • Author
    Posts
  • #18066
    Rafael Santos
    Level 3
    Participant
    Helpful?
    Up
    0
    ::

    When i go to other scene, and i take damage im always invincible and i cant take more damage, and when i press L key to die, after i go to other scene, the player stop and i cant move

    MY PlayerController:

    using System.Collections;
    using System.Collections.Generic;
    using Unity.VisualScripting;
    using UnityEngine;
    using UnityEngine.UIElements;
    using static TMPro.SpriteAssetUtilities.TexturePacker_JsonArray;
    using UnityEngine.UI;
    using UnityEngine.UIElements.Experimental;
    
    public class PlayerController : MonoBehaviour
    {           
    
        [Header("Horizontal Movement Settings")]   //header serve para criar categorias, no unity no inspector do player
        [SerializeField] private float walkSpeed = 1;
    
        [Space(5)]
    
        [Header("Vertical Movement Settings")]
        [SerializeField] private float jumpForce = 45f; 
        private int jumpBufferCounter = 0;
        [SerializeField] private int jumpBufferFrames;
        private float coyoteTimeCounter  = 0;
        [SerializeField] private float coyoteTime;    //coyoteTime é pra qnd o player da miss no saldo ele ainda tem um pouco de tempo para saltar em vez de cair
        private int airJumpCounter = 0;
        [SerializeField] private int maxAirJumps; //airjump é o duble jumpe
        private float gravity;
        [Space(5)]
    
        [Header("Ground Check Settings")]
        [SerializeField] private Transform groundCheckPoint;
        [SerializeField] private float groundCheckY = 0.2f;
        [SerializeField] private float groundCheckX = 0.5f;
        [SerializeField] private LayerMask whatIsGround;
        [Space(5)]
    
        [Header("Dash Settings")]
        [SerializeField] private float dashSpeed;
        [SerializeField] private float dashTime;
        [SerializeField] private float dashCooldown;
        [SerializeField] GameObject dashEffect;
        private bool canDash = true;
        private bool dashed;
        [Space(5)]
    
        [Header("Attack")]
        [SerializeField] private Transform SideAttacktransform;
        [SerializeField] private Transform UpAttacktransform;
        [SerializeField] private Transform DownAttacktransform;
        [SerializeField] private Vector2 SideAttackArea, UpAttackArea, DownAttackArea;
        [SerializeField] private LayerMask attackableLayer;
        private bool attack = false;
        [SerializeField] private float timeBetwenAttack;
        private float timeSinceAttack;
        [SerializeField] private float damage;
        [SerializeField] private GameObject slashEffect;
        bool restoreTime;
        float restoreTimeSpeed;
        [Space(5)]
    
        [Header("Recoil")]
        [SerializeField] private int recoilXSteps = 5;
        [SerializeField] private int recoilYSteps = 5;
        [SerializeField] private float recoilXSpeed = 100;
        [SerializeField] private float recoilYSpeed = 100;
        private int stepsXRecoiled, stepsYRecoiled;
        [Space(5)]
    
        [Header("Health")]
        public int health;
        public int maxHealth;
        [SerializeField] GameObject bloodSpurt;
        [SerializeField] float hitFlashSpeed;
        public delegate void OnHealthChangedDelegate(); //delagete permite executares divesas funções de uma só vez
        [HideInInspector] public OnHealthChangedDelegate OnHealthChangedCallback;
    
        float healTimer;
        [SerializeField] float timeToHeal;
        [Space(5)]
    
        [Header("Mana")]
        [SerializeField] UnityEngine.UI.Image manaStorage;
    
        [SerializeField] float mana;
        [SerializeField] float manaDrainSpeed;
        [SerializeField] float manaGain;
        bool halfMana;
        [Space(5)]
    
        [Header("Spell")]
        //spell status
        [SerializeField] float manaSpellCost = 0.3f;
        [SerializeField] float timeBetweenCast = 0.5f;
        [SerializeField] float spellDamage; //so para o spell pra baixo e para cima
        [SerializeField] float downSpellForce;
        //spell cast objects
        [SerializeField] GameObject sideSpellFireball;
        [SerializeField] GameObject upSpellExplosion;
        [SerializeField] GameObject downSpellFireball;
        float timeSinceCast;
        float castOrHealtimer;
        [Space(5)]
    
        [Header("Camera")]
        [SerializeField] private float playerFallSpeedThreshold = -10;
    
    
    
        [HideInInspector]public PlayerStateList pState;
        public Rigidbody2D rb;
        private float xAxis, yAxis;
        private Animator anim;
        private SpriteRenderer sr;
        private bool canFlash = true;
        [SerializeField] private float maxFallSpeed = 10f;
        bool openMap;
    
        public static PlayerController Instance;
    
        private void Awake()
        {
            if (Instance != null && Instance != this)
            {
                Destroy (gameObject);
            }
            else
            {
                Instance = this;
            }
            DontDestroyOnLoad(gameObject);
    
            Health = maxHealth;
        }
    
        // Isto começa antes do primeiro frame update
        void Start()
        {
            pState = GetComponent<PlayerStateList>();
    
            rb = GetComponent<Rigidbody2D>();
            sr = GetComponent<SpriteRenderer>();
    
            anim = GetComponent<Animator>();
    
            gravity = rb.gravityScale;
    
            Mana = mana;
            manaStorage.fillAmount = Mana;
        }
    
        private void OnDrawGizmos() 
        {
            Gizmos.color = Color.red;
            Gizmos.DrawWireCube(SideAttacktransform.position, SideAttackArea);
            Gizmos.DrawWireCube(UpAttacktransform.position, UpAttackArea);
            Gizmos.DrawWireCube(DownAttacktransform.position, DownAttackArea);
        }
    
        // Update é chamado 1 vez por segundo
        void Update()
        {
    
            //if (!canMove) return;
            //{ 
            //    rb.linearVelocityX = Input.GetAxisRaw("Horizontal");
            //    rb.linearVelocityY = Input.GetAxisRaw("Vertical");
            //}
    
            if (pState.cutscene) return;
            if (pState.alive)
            {
                GetInputs();
                ToggleMap();
            }
            RestoreTimeScale();
            UpdateJumpVariables();
            UpdateCameraYDampForPlayerFall();
    
            if (pState.Healing || pState.dashing) return;
            if (pState.alive)
            {
                Flip();
                Move();
                Jump();
                StartDash();
                Attack();
                Heal();
                CastSpell();
            }
            FlashWhileInvincible();
    
            if (Input.GetKeyDown(KeyCode.L))
            {
                StartCoroutine(Death());
            }
        }
    
        private void OnTriggerEnter2D(Collider2D _other)
        {
            if (_other.GetComponent<Enemy>() != null && pState.casting)
            {
                _other.GetComponent<Enemy>().EnemyHit(spellDamage, (_other.transform.position - transform.position).normalized, -recoilYSpeed);
            }
        }
    
        private void FixedUpdate() 
        {
            if (pState.cutscene) return;
    
            if (pState.dashing) return;
            Recoil();
    
            if (rb.linearVelocity.y < maxFallSpeed)
            {
                rb.linearVelocity = new Vector2(rb.linearVelocity.x, maxFallSpeed);
            }
        }
    
        void GetInputs()
        {
            xAxis = Input.GetAxisRaw("Horizontal");
            yAxis = Input.GetAxisRaw("Vertical");
            attack = Input.GetButtonDown("Attack");
            openMap = Input.GetButton("Map");
    
            if(Input.GetButton("Cast/Heal"))
            {
                castOrHealtimer += Time.deltaTime;
            }
        }
    
        void ToggleMap()
        {
            if(openMap)
            {
                UIManager.Instance.mapHandler.SetActive(true);
            }
            else
            {
                UIManager.Instance.mapHandler.SetActive(false);
            }
        }
    
        void Flip()
        {
            if (xAxis < 0)
            {
                transform.localScale = new Vector2(-1, transform.localScale.y);
                pState.lookingRight = false;
            }
            else if (xAxis > 0)
            {
                transform.localScale = new Vector2(1, transform.localScale.y);
                pState.lookingRight = true;
            }
        }
    
        private void Move()
        {
    
            rb.linearVelocity = new Vector2(walkSpeed * xAxis, rb.linearVelocity.y);
            anim.SetBool("Walking", rb.linearVelocity.x != 0 && Grounded());
        }
    
        void UpdateCameraYDampForPlayerFall()
        {
            if (rb.linearVelocityY < playerFallSpeedThreshold && !CameraManager.Instance.isLerpingYDamping && !CameraManager.Instance.hasLerpedYDamping)
            {
                StartCoroutine(CameraManager.Instance.LerpedYDamping(true));
            }
    
            if (rb.linearVelocityY >= 0 && !CameraManager.Instance.isLerpingYDamping && CameraManager.Instance.isLerpingYDamping)
            {
                CameraManager.Instance.hasLerpedYDamping = false;
                StartCoroutine(CameraManager.Instance.LerpedYDamping(false));
            }
        }
        void StartDash()
        {
            if (Input.GetButtonDown("Dash") && canDash && !dashed)
            {
                StartCoroutine(Dash());
                dashed = true;
            }
    
            if (Grounded())
            {
                dashed = false;
            }
        }
    
        IEnumerator Dash()
        {
            canDash = false;
            pState.dashing = true;
            //pState.invincible = true;//fica invincible
            anim.SetTrigger("Dashing");
            rb.gravityScale = 0; //isto é para o player dar dash sem ser afetado pela gravidade duranto o dash
            int _dir = pState.lookingRight ? 1 : -1;
            rb.linearVelocity = new Vector2(_dir * dashSpeed, 0); //dar dash para o lado que o player está virado
    
            if (Grounded()) Instantiate(dashEffect, transform); //isto é pra qnd o player dar dash no chão faz o dasheffect
    
            yield return new WaitForSeconds(dashTime); //duração do dash
    
            rb.gravityScale = gravity;
            pState.dashing = false;
            //pState.invincible = false; // para de estar invincible
    
            yield return new WaitForSeconds(dashCooldown); //cooldown do dash
            canDash = true;
        }
    
        public IEnumerator WalkIntoNewScene(Vector2 _exitDir, float _delay)
        {
            pState.invincible = true;
    
            //se a saida é pra cima
            if (_exitDir.y > 0)
            {
                rb.linearVelocity = jumpForce * _exitDir;
            }
            //se a saida é na horizontal
            if (_exitDir.x != 0)
            {
                xAxis = _exitDir.x > 0 ? 1 : -1;
    
                Move();
            }
    
            Flip();
            yield return new WaitForSeconds(_delay);
            pState.invincible = false;
            pState.cutscene = false;
        }
    
        void Attack()
        {
            timeSinceAttack += Time.deltaTime;
            if (attack && timeSinceAttack >= timeBetwenAttack)
            {
                timeSinceAttack = 0;
                anim.SetTrigger("Attacking");
    
                if (yAxis == 0 || yAxis < 0 && Grounded()) //isto é para ele dar o Side atk se n tiver a pressionar no "S" ou no "W" ou então se ele tiver a pressionar "S" mas tiver no chão
                {
                    int _recoilLeftOrRight = pState.lookingRight ? 1 : -1;
    
                    Hit(SideAttacktransform, SideAttackArea, ref pState.recoilingX, Vector2.right * _recoilLeftOrRight, recoilXSpeed);
                    Instantiate(slashEffect, SideAttacktransform);
                }
                else if (yAxis > 0)//up atk
                {
                    Hit(UpAttacktransform, UpAttackArea, ref pState.recoilingY, Vector2.up, recoilYSpeed);
                    SlashEffectAtAngle(slashEffect, 80, UpAttacktransform); // os "90" representa os graus de rotação da figura
                 }
                else if (yAxis < 0 && !Grounded())//down atk
                {
                    Hit(DownAttacktransform, DownAttackArea, ref pState.recoilingY,Vector2.down, recoilYSpeed);
                    SlashEffectAtAngle(slashEffect, -90, DownAttacktransform);
                }
    
            }
        }
    
    
        //alterei com o blackbox
        void Hit(Transform _attackTransform, Vector2 _attackArea, ref bool _recoilBool, Vector2 _recoilDir, float _recoilStrength)
        {
            Collider2D[] objectsToHit = Physics2D.OverlapBoxAll(_attackTransform.position, _attackArea, 0, attackableLayer);
    
            if (objectsToHit.Length > 0)
            {
                _recoilBool = true;
            }
            for (int i = 0; i < objectsToHit.Length; i++)
            {
                if (objectsToHit[i].GetComponent<Enemy>() != null)
                {
                    objectsToHit[i].GetComponent<Enemy>().EnemyHit(damage, _recoilDir, _recoilStrength);
    
                    if (objectsToHit[i].CompareTag("Enemy"))
                    {
                        Mana += manaGain; // Recupera mana ao atacar um inimigo
                    }
                }
            }
        }
        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);
        }
    
        void Recoil()
        {
            if(pState.recoilingX)
            {
                if(pState.lookingRight)
                {
                    rb.linearVelocity = new Vector2(-recoilXSpeed, 0); 
                }
                else
                {
                    rb.linearVelocity = new Vector2 (recoilXSpeed, 0);
                }
            }
    
            if(pState.recoilingY)
            {
                rb.gravityScale = 0; //para a gravidade n afetar o recoil qnd damos um atk para baixo
    
                if(yAxis < 0)// so é utilizado esse recoil qnd o eixo do Y for maior q 0 ou seja, no ar
                {
                    rb.linearVelocity = new Vector2(rb.linearVelocity.x, recoilYSpeed); 
                }
                else
                {
                   rb.linearVelocity = new Vector2(rb.linearVelocity.x, -recoilYSpeed); //qnd atacamos para cima temos o recoil para baixo 
                }
                airJumpCounter = 0;
            }
            else
            {
                rb.gravityScale = gravity;
            }
    
            if(pState.recoilingX && stepsXRecoiled < recoilXSteps)
            {
                stepsXRecoiled++;
            }
            else
            {
                StopRecoilX();
            }
            if (pState.recoilingY && stepsYRecoiled < recoilYSteps)
            {
                stepsYRecoiled++;
            }
            else
            {
                StopRecoilY();
            }
    
            if(Grounded())
            {
                StopRecoilY();
            }
        }
    
        void StopRecoilX()
        {
            stepsXRecoiled = 0;
            pState.recoilingX = false;
        }
    
        void StopRecoilY()
        {
            stepsYRecoiled = 0;
            pState.recoilingY = false;
        }
    
        public void TakeDamage(float _damage)
        {
            if (pState.alive)
            {
                Health -= Mathf.RoundToInt(_damage);
                if (health <= 0)
                {
                    health = 0;
                    StartCoroutine(Death());
                }
                else
                {
                    StartCoroutine(StopTakingDamage());
                }
            }
        }
    
        IEnumerator StopTakingDamage()
        {
            pState.invincible = true;
            GameObject _bloodSpurtParticles = Instantiate(bloodSpurt, transform.position, Quaternion.identity);
            Destroy(_bloodSpurtParticles, 1.5f);
            anim.SetTrigger("TakeDamage");
            yield return new WaitForSeconds(1f);
            pState.invincible = false;
        }
    
        IEnumerator Flash()
        {
            sr.enabled = !sr.enabled;
            canFlash = false;
            yield return new WaitForSeconds(0.2f);
            canFlash = true;
        }
    
        void FlashWhileInvincible()
        {
            //sr.material.color = pState.invincible ? Color.Lerp(Color.white, Color.black, Mathf.PingPong(Time.time * hitFlashSpeed, 1.0f)) : Color.white;
            if (pState.invincible && !pState.cutscene)
            {
                if (Time.timeScale > 0.2 && canFlash)
                {
                    StartCoroutine(Flash());
                }
            }
            else
            { 
                sr.enabled = true;
            }
        }
    
        void RestoreTimeScale()
        {
            if(restoreTime)
            {
                if (Time.timeScale < 1) //se o timeScale é menor q 1, sobe ate ao 1
                {
                    Time.timeScale += Time.unscaledDeltaTime * restoreTimeSpeed;
                }
                else
                {
                    Time.timeScale = 1;
                    restoreTime = false;
                }
            }
        }
    
        public void HitStopTime(float _newTimeScale, int _restoreSpeed, float _delay) //quando levo um hit o boneco para por um tempo
        {
            restoreTimeSpeed = _restoreSpeed;
    
            if (_delay > 0)
            {
                StopCoroutine(StartTimeAgain(_delay));
                StartCoroutine(StartTimeAgain(_delay));
            }
            else
            {
                restoreTime = true;
            }
            Time.timeScale = _newTimeScale;
        }
    
        IEnumerator StartTimeAgain(float _delay)
        {
            yield return new WaitForSecondsRealtime(_delay);
            restoreTime = true;
        }
    
        IEnumerator Death ()
        {
            pState.alive = false;
            Time.timeScale = 1f;
            GameObject _bloodSpurtParticles = Instantiate(bloodSpurt, transform.position, Quaternion.identity);
            Destroy(_bloodSpurtParticles, 1.5f);
            anim.SetTrigger("Death");
    
            yield return new WaitForSeconds(0.9f);
            StartCoroutine(UIManager.Instance.ActivateDeathScreen());
    
            yield return new WaitForSeconds(0.9f);
            Instantiate(GameManager.Instance.shade, transform.position, Quaternion.identity);
        }
    
        public void Respawned()
        {
            if (!pState.alive)
            {
                pState.alive = true;
                halfMana = true;
                UIManager.Instance.SwitchMana(UIManager.ManaState.HalfMana);
                Mana = 0;
                Health = maxHealth;
                anim.Play("Player_Idle");
            }
        }
    
        public void RestoreMana()
        {
            halfMana = false;
            UIManager.Instance.SwitchMana(UIManager.ManaState.FullMana);
        }
    
        public int Health
        {
            get { return health; }
            set
            {
                if (health != value)
                {
                    health = Mathf.Clamp(value, 0, maxHealth);
    
                    if (OnHealthChangedCallback != null)
                    {
                        OnHealthChangedCallback.Invoke();
                    }
                }
            }
        }
    
        void Heal()
        {
            if(Input.GetButton("Cast/Heal") && castOrHealtimer > 0.1f && Health < maxHealth && Mana > 0 && Grounded() && !pState.dashing)
            {
                pState.Healing = true;
                rb.linearVelocity = new Vector2(0, rb.linearVelocity.y);
    
                if (pState.Healing)
                {
                    anim.SetBool("Healing", true);
                }
    
                //heal
                healTimer += Time.deltaTime;
                if(healTimer >= timeToHeal)
                {
                    Health++;
                    healTimer = 0;
                }
    
                Mana -= Time.deltaTime * manaDrainSpeed;
    
            }
            else
            {
                pState.Healing = false;
                anim.SetBool("Healing", false);
                healTimer = 0; 
            }
        }
    
        float Mana
        {
            get { return mana; }
            set
            { 
                if(mana != value)
                {
                    if (!halfMana)
                    {
                        mana = Mathf.Clamp(value, 0, 1);
                    }
                    else
                    {
                        mana = Mathf.Clamp(value, 0, 0.5f);
                    }
                    manaStorage.fillAmount = Mana;
                }
            }
        }
    
        void CastSpell()
        {
            if (Input.GetButtonUp("Cast/Heal"))
            {
                castOrHealtimer = 0;
            }
            if (Input.GetButtonUp("Cast/Heal") && castOrHealtimer <= 0.1f && timeSinceCast >= timeBetweenCast && Mana >= manaSpellCost)
            {
                pState.casting = true;
                timeSinceCast = 0;
                StartCoroutine(CastCoroutine());
            }
            else
            {
                timeSinceCast += Time.deltaTime;
            }
    
            if (Grounded())
            {
                //desativa o downcast se ta grounded
                downSpellFireball.SetActive(false);
            }
    
            //se o down spell for ativo ele so para quando chegar ao cão
            if (downSpellFireball.activeInHierarchy)
            {
                rb.linearVelocity += downSpellForce * Vector2.down;
            }
        }
    
        IEnumerator CastCoroutine()
        {
            anim.SetBool("Casting", true);
            yield return new WaitForSeconds(0.09f);
    
            //side cast
            if (yAxis == 0 || (yAxis < 0 && Grounded()))
            {
                GameObject _fireBall = Instantiate(sideSpellFireball, SideAttacktransform.position, Quaternion.identity);
    
                //flip
                if (pState.lookingRight)
                {
                    _fireBall.transform.eulerAngles = Vector3.zero;
                }
                else
                {
                    _fireBall.transform.eulerAngles = new Vector2(_fireBall.transform.eulerAngles.x, 180);
                }
                pState.recoilingX = true;
            }
    
            //up cast
            else if (yAxis > 0)
            {
                Instantiate(upSpellExplosion, transform);
                rb.linearVelocity = Vector2.zero;
            }
    
            //down cast
            else if (yAxis < 0 && !Grounded())
            {
                downSpellFireball.SetActive(true);
            }
    
            Mana -= manaSpellCost;
            yield return new WaitForSeconds(0.20f);
            anim.SetBool("Casting", false);
            maxFallSpeed = -100f;
        }
    
        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;
            }
        }
        void Jump()
        {
            if (jumpBufferCounter > 0 && coyoteTimeCounter > 0 && !pState.jumping)
            {
                rb.linearVelocity = new Vector3(rb.linearVelocity.x, jumpForce);
    
                pState.jumping = true;
            }
    
            if (!Grounded() && airJumpCounter < maxAirJumps && Input.GetButtonDown("Jump"))
            {
                pState.jumping = true;
    
                airJumpCounter++;
    
                rb.linearVelocity = new Vector3(rb.linearVelocity.x, jumpForce);
            }
    
            //isto é para quanto mais carregas no espaço mal alto saltas
            if (Input.GetButtonUp("Jump") && rb.linearVelocity.y > 3)
            {
                pState.jumping = false;
    
                rb.linearVelocity = new Vector2(rb.linearVelocity.x, 0);
            }
            anim.SetBool("Jumping", !Grounded());
        }
    
        //mete o jumpBufferCounter ao max do jumpBufferFrames e reduz em 1 cada frame ou seja tornar o salto mais smooth
        void UpdateJumpVariables()
        {
            if (Grounded())
            {
                pState.jumping = false;
                coyoteTimeCounter = coyoteTime;
                airJumpCounter = 0;
            }
            else
            {
                //object time.deltaTime é a quantidade de tempo entre cada frame
                coyoteTimeCounter -= Time.deltaTime;
                //isto faz com que o coyoteTimeCounter -1 a cada segundo
            }
    
            if (Input.GetButtonDown("Jump"))
            {
                jumpBufferCounter = jumpBufferFrames;
            }
            else
            {
                jumpBufferCounter--;
            }
        }
    }using System.Collections;
    using System.Collections.Generic;
    using Unity.VisualScripting;
    using UnityEngine;
    using UnityEngine.UIElements;
    using static TMPro.SpriteAssetUtilities.TexturePacker_JsonArray;
    using UnityEngine.UI;
    using UnityEngine.UIElements.Experimental;
    
    public class PlayerController : MonoBehaviour
    {           
    
        [Header("Horizontal Movement Settings")]   //header serve para criar categorias, no unity no inspector do player
        [SerializeField] private float walkSpeed = 1;
    
        [Space(5)]
    
        [Header("Vertical Movement Settings")]
        [SerializeField] private float jumpForce = 45f; 
        private int jumpBufferCounter = 0;
        [SerializeField] private int jumpBufferFrames;
        private float coyoteTimeCounter  = 0;
        [SerializeField] private float coyoteTime;    //coyoteTime é pra qnd o player da miss no saldo ele ainda tem um pouco de tempo para saltar em vez de cair
        private int airJumpCounter = 0;
        [SerializeField] private int maxAirJumps; //airjump é o duble jumpe
        private float gravity;
        [Space(5)]
    
        [Header("Ground Check Settings")]
        [SerializeField] private Transform groundCheckPoint;
        [SerializeField] private float groundCheckY = 0.2f;
        [SerializeField] private float groundCheckX = 0.5f;
        [SerializeField] private LayerMask whatIsGround;
        [Space(5)]
    
        [Header("Dash Settings")]
        [SerializeField] private float dashSpeed;
        [SerializeField] private float dashTime;
        [SerializeField] private float dashCooldown;
        [SerializeField] GameObject dashEffect;
        private bool canDash = true;
        private bool dashed;
        [Space(5)]
    
        [Header("Attack")]
        [SerializeField] private Transform SideAttacktransform;
        [SerializeField] private Transform UpAttacktransform;
        [SerializeField] private Transform DownAttacktransform;
        [SerializeField] private Vector2 SideAttackArea, UpAttackArea, DownAttackArea;
        [SerializeField] private LayerMask attackableLayer;
        private bool attack = false;
        [SerializeField] private float timeBetwenAttack;
        private float timeSinceAttack;
        [SerializeField] private float damage;
        [SerializeField] private GameObject slashEffect;
        bool restoreTime;
        float restoreTimeSpeed;
        [Space(5)]
    
        [Header("Recoil")]
        [SerializeField] private int recoilXSteps = 5;
        [SerializeField] private int recoilYSteps = 5;
        [SerializeField] private float recoilXSpeed = 100;
        [SerializeField] private float recoilYSpeed = 100;
        private int stepsXRecoiled, stepsYRecoiled;
        [Space(5)]
    
        [Header("Health")]
        public int health;
        public int maxHealth;
        [SerializeField] GameObject bloodSpurt;
        [SerializeField] float hitFlashSpeed;
        public delegate void OnHealthChangedDelegate(); //delagete permite executares divesas funções de uma só vez
        [HideInInspector] public OnHealthChangedDelegate OnHealthChangedCallback;
    
        float healTimer;
        [SerializeField] float timeToHeal;
        [Space(5)]
    
        [Header("Mana")]
        [SerializeField] UnityEngine.UI.Image manaStorage;
    
        [SerializeField] float mana;
        [SerializeField] float manaDrainSpeed;
        [SerializeField] float manaGain;
        bool halfMana;
        [Space(5)]
    
        [Header("Spell")]
        //spell status
        [SerializeField] float manaSpellCost = 0.3f;
        [SerializeField] float timeBetweenCast = 0.5f;
        [SerializeField] float spellDamage; //so para o spell pra baixo e para cima
        [SerializeField] float downSpellForce;
        //spell cast objects
        [SerializeField] GameObject sideSpellFireball;
        [SerializeField] GameObject upSpellExplosion;
        [SerializeField] GameObject downSpellFireball;
        float timeSinceCast;
        float castOrHealtimer;
        [Space(5)]
    
        [Header("Camera")]
        [SerializeField] private float playerFallSpeedThreshold = -10;
    
    
    
        [HideInInspector]public PlayerStateList pState;
        public Rigidbody2D rb;
        private float xAxis, yAxis;
        private Animator anim;
        private SpriteRenderer sr;
        private bool canFlash = true;
        [SerializeField] private float maxFallSpeed = 10f;
        bool openMap;
    
        public static PlayerController Instance;
    
        private void Awake()
        {
            if (Instance != null && Instance != this)
            {
                Destroy (gameObject);
            }
            else
            {
                Instance = this;
            }
            DontDestroyOnLoad(gameObject);
    
            Health = maxHealth;
        }
    
        // Isto começa antes do primeiro frame update
        void Start()
        {
            pState = GetComponent<PlayerStateList>();
    
            rb = GetComponent<Rigidbody2D>();
            sr = GetComponent<SpriteRenderer>();
    
            anim = GetComponent<Animator>();
    
            gravity = rb.gravityScale;
    
            Mana = mana;
            manaStorage.fillAmount = Mana;
        }
    
        private void OnDrawGizmos() 
        {
            Gizmos.color = Color.red;
            Gizmos.DrawWireCube(SideAttacktransform.position, SideAttackArea);
            Gizmos.DrawWireCube(UpAttacktransform.position, UpAttackArea);
            Gizmos.DrawWireCube(DownAttacktransform.position, DownAttackArea);
        }
    
        // Update é chamado 1 vez por segundo
        void Update()
        {
    
            //if (!canMove) return;
            //{ 
            //    rb.linearVelocityX = Input.GetAxisRaw("Horizontal");
            //    rb.linearVelocityY = Input.GetAxisRaw("Vertical");
            //}
    
            if (pState.cutscene) return;
            if (pState.alive)
            {
                GetInputs();
                ToggleMap();
            }
            RestoreTimeScale();
            UpdateJumpVariables();
            UpdateCameraYDampForPlayerFall();
    
            if (pState.Healing || pState.dashing) return;
            if (pState.alive)
            {
                Flip();
                Move();
                Jump();
                StartDash();
                Attack();
                Heal();
                CastSpell();
            }
            FlashWhileInvincible();
    
            if (Input.GetKeyDown(KeyCode.L))
            {
                StartCoroutine(Death());
            }
        }
    
        private void OnTriggerEnter2D(Collider2D _other)
        {
            if (_other.GetComponent<Enemy>() != null && pState.casting)
            {
                _other.GetComponent<Enemy>().EnemyHit(spellDamage, (_other.transform.position - transform.position).normalized, -recoilYSpeed);
            }
        }
    
        private void FixedUpdate() 
        {
            if (pState.cutscene) return;
    
            if (pState.dashing) return;
            Recoil();
    
            if (rb.linearVelocity.y < maxFallSpeed)
            {
                rb.linearVelocity = new Vector2(rb.linearVelocity.x, maxFallSpeed);
            }
        }
    
        void GetInputs()
        {
            xAxis = Input.GetAxisRaw("Horizontal");
            yAxis = Input.GetAxisRaw("Vertical");
            attack = Input.GetButtonDown("Attack");
            openMap = Input.GetButton("Map");
    
            if(Input.GetButton("Cast/Heal"))
            {
                castOrHealtimer += Time.deltaTime;
            }
        }
    
        void ToggleMap()
        {
            if(openMap)
            {
                UIManager.Instance.mapHandler.SetActive(true);
            }
            else
            {
                UIManager.Instance.mapHandler.SetActive(false);
            }
        }
    
        void Flip()
        {
            if (xAxis < 0)
            {
                transform.localScale = new Vector2(-1, transform.localScale.y);
                pState.lookingRight = false;
            }
            else if (xAxis > 0)
            {
                transform.localScale = new Vector2(1, transform.localScale.y);
                pState.lookingRight = true;
            }
        }
    
        private void Move()
        {
    
            rb.linearVelocity = new Vector2(walkSpeed * xAxis, rb.linearVelocity.y);
            anim.SetBool("Walking", rb.linearVelocity.x != 0 && Grounded());
        }
    
        void UpdateCameraYDampForPlayerFall()
        {
            if (rb.linearVelocityY < playerFallSpeedThreshold && !CameraManager.Instance.isLerpingYDamping && !CameraManager.Instance.hasLerpedYDamping)
            {
                StartCoroutine(CameraManager.Instance.LerpedYDamping(true));
            }
    
            if (rb.linearVelocityY >= 0 && !CameraManager.Instance.isLerpingYDamping && CameraManager.Instance.isLerpingYDamping)
            {
                CameraManager.Instance.hasLerpedYDamping = false;
                StartCoroutine(CameraManager.Instance.LerpedYDamping(false));
            }
        }
        void StartDash()
        {
            if (Input.GetButtonDown("Dash") && canDash && !dashed)
            {
                StartCoroutine(Dash());
                dashed = true;
            }
    
            if (Grounded())
            {
                dashed = false;
            }
        }
    
        IEnumerator Dash()
        {
            canDash = false;
            pState.dashing = true;
            //pState.invincible = true;//fica invincible
            anim.SetTrigger("Dashing");
            rb.gravityScale = 0; //isto é para o player dar dash sem ser afetado pela gravidade duranto o dash
            int _dir = pState.lookingRight ? 1 : -1;
            rb.linearVelocity = new Vector2(_dir * dashSpeed, 0); //dar dash para o lado que o player está virado
    
            if (Grounded()) Instantiate(dashEffect, transform); //isto é pra qnd o player dar dash no chão faz o dasheffect
    
            yield return new WaitForSeconds(dashTime); //duração do dash
    
            rb.gravityScale = gravity;
            pState.dashing = false;
            //pState.invincible = false; // para de estar invincible
    
            yield return new WaitForSeconds(dashCooldown); //cooldown do dash
            canDash = true;
        }
    
        public IEnumerator WalkIntoNewScene(Vector2 _exitDir, float _delay)
        {
            pState.invincible = true;
    
            //se a saida é pra cima
            if (_exitDir.y > 0)
            {
                rb.linearVelocity = jumpForce * _exitDir;
            }
            //se a saida é na horizontal
            if (_exitDir.x != 0)
            {
                xAxis = _exitDir.x > 0 ? 1 : -1;
    
                Move();
            }
    
            Flip();
            yield return new WaitForSeconds(_delay);
            pState.invincible = false;
            pState.cutscene = false;
        }
    
        void Attack()
        {
            timeSinceAttack += Time.deltaTime;
            if (attack && timeSinceAttack >= timeBetwenAttack)
            {
                timeSinceAttack = 0;
                anim.SetTrigger("Attacking");
    
                if (yAxis == 0 || yAxis < 0 && Grounded()) //isto é para ele dar o Side atk se n tiver a pressionar no "S" ou no "W" ou então se ele tiver a pressionar "S" mas tiver no chão
                {
                    int _recoilLeftOrRight = pState.lookingRight ? 1 : -1;
    
                    Hit(SideAttacktransform, SideAttackArea, ref pState.recoilingX, Vector2.right * _recoilLeftOrRight, recoilXSpeed);
                    Instantiate(slashEffect, SideAttacktransform);
                }
                else if (yAxis > 0)//up atk
                {
                    Hit(UpAttacktransform, UpAttackArea, ref pState.recoilingY, Vector2.up, recoilYSpeed);
                    SlashEffectAtAngle(slashEffect, 80, UpAttacktransform); // os "90" representa os graus de rotação da figura
                 }
                else if (yAxis < 0 && !Grounded())//down atk
                {
                    Hit(DownAttacktransform, DownAttackArea, ref pState.recoilingY,Vector2.down, recoilYSpeed);
                    SlashEffectAtAngle(slashEffect, -90, DownAttacktransform);
                }
    
            }
        }
    
    
        //alterei com o blackbox
        void Hit(Transform _attackTransform, Vector2 _attackArea, ref bool _recoilBool, Vector2 _recoilDir, float _recoilStrength)
        {
            Collider2D[] objectsToHit = Physics2D.OverlapBoxAll(_attackTransform.position, _attackArea, 0, attackableLayer);
    
            if (objectsToHit.Length > 0)
            {
                _recoilBool = true;
            }
            for (int i = 0; i < objectsToHit.Length; i++)
            {
                if (objectsToHit[i].GetComponent<Enemy>() != null)
                {
                    objectsToHit[i].GetComponent<Enemy>().EnemyHit(damage, _recoilDir, _recoilStrength);
    
                    if (objectsToHit[i].CompareTag("Enemy"))
                    {
                        Mana += manaGain; // Recupera mana ao atacar um inimigo
                    }
                }
            }
        }
        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);
        }
    
        void Recoil()
        {
            if(pState.recoilingX)
            {
                if(pState.lookingRight)
                {
                    rb.linearVelocity = new Vector2(-recoilXSpeed, 0); 
                }
                else
                {
                    rb.linearVelocity = new Vector2 (recoilXSpeed, 0);
                }
            }
    
            if(pState.recoilingY)
            {
                rb.gravityScale = 0; //para a gravidade n afetar o recoil qnd damos um atk para baixo
    
                if(yAxis < 0)// so é utilizado esse recoil qnd o eixo do Y for maior q 0 ou seja, no ar
                {
                    rb.linearVelocity = new Vector2(rb.linearVelocity.x, recoilYSpeed); 
                }
                else
                {
                   rb.linearVelocity = new Vector2(rb.linearVelocity.x, -recoilYSpeed); //qnd atacamos para cima temos o recoil para baixo 
                }
                airJumpCounter = 0;
            }
            else
            {
                rb.gravityScale = gravity;
            }
    
            if(pState.recoilingX && stepsXRecoiled < recoilXSteps)
            {
                stepsXRecoiled++;
            }
            else
            {
                StopRecoilX();
            }
            if (pState.recoilingY && stepsYRecoiled < recoilYSteps)
            {
                stepsYRecoiled++;
            }
            else
            {
                StopRecoilY();
            }
    
            if(Grounded())
            {
                StopRecoilY();
            }
        }
    
        void StopRecoilX()
        {
            stepsXRecoiled = 0;
            pState.recoilingX = false;
        }
    
        void StopRecoilY()
        {
            stepsYRecoiled = 0;
            pState.recoilingY = false;
        }
    
        public void TakeDamage(float _damage)
        {
            if (pState.alive)
            {
                Health -= Mathf.RoundToInt(_damage);
                if (health <= 0)
                {
                    health = 0;
                    StartCoroutine(Death());
                }
                else
                {
                    StartCoroutine(StopTakingDamage());
                }
            }
        }
    
        IEnumerator StopTakingDamage()
        {
            pState.invincible = true;
            GameObject _bloodSpurtParticles = Instantiate(bloodSpurt, transform.position, Quaternion.identity);
            Destroy(_bloodSpurtParticles, 1.5f);
            anim.SetTrigger("TakeDamage");
            yield return new WaitForSeconds(1f);
            pState.invincible = false;
        }
    
        IEnumerator Flash()
        {
            sr.enabled = !sr.enabled;
            canFlash = false;
            yield return new WaitForSeconds(0.2f);
            canFlash = true;
        }
    
        void FlashWhileInvincible()
        {
            //sr.material.color = pState.invincible ? Color.Lerp(Color.white, Color.black, Mathf.PingPong(Time.time * hitFlashSpeed, 1.0f)) : Color.white;
            if (pState.invincible && !pState.cutscene)
            {
                if (Time.timeScale > 0.2 && canFlash)
                {
                    StartCoroutine(Flash());
                }
            }
            else
            { 
                sr.enabled = true;
            }
        }
    
        void RestoreTimeScale()
        {
            if(restoreTime)
            {
                if (Time.timeScale < 1) //se o timeScale é menor q 1, sobe ate ao 1
                {
                    Time.timeScale += Time.unscaledDeltaTime * restoreTimeSpeed;
                }
                else
                {
                    Time.timeScale = 1;
                    restoreTime = false;
                }
            }
        }
    
        public void HitStopTime(float _newTimeScale, int _restoreSpeed, float _delay) //quando levo um hit o boneco para por um tempo
        {
            restoreTimeSpeed = _restoreSpeed;
    
            if (_delay > 0)
            {
                StopCoroutine(StartTimeAgain(_delay));
                StartCoroutine(StartTimeAgain(_delay));
            }
            else
            {
                restoreTime = true;
            }
            Time.timeScale = _newTimeScale;
        }
    
        IEnumerator StartTimeAgain(float _delay)
        {
            yield return new WaitForSecondsRealtime(_delay);
            restoreTime = true;
        }
    
        IEnumerator Death ()
        {
            pState.alive = false;
            Time.timeScale = 1f;
            GameObject _bloodSpurtParticles = Instantiate(bloodSpurt, transform.position, Quaternion.identity);
            Destroy(_bloodSpurtParticles, 1.5f);
            anim.SetTrigger("Death");
    
            yield return new WaitForSeconds(0.9f);
            StartCoroutine(UIManager.Instance.ActivateDeathScreen());
    
            yield return new WaitForSeconds(0.9f);
            Instantiate(GameManager.Instance.shade, transform.position, Quaternion.identity);
        }
    
        public void Respawned()
        {
            if (!pState.alive)
            {
                pState.alive = true;
                halfMana = true;
                UIManager.Instance.SwitchMana(UIManager.ManaState.HalfMana);
                Mana = 0;
                Health = maxHealth;
                anim.Play("Player_Idle");
            }
        }
    
        public void RestoreMana()
        {
            halfMana = false;
            UIManager.Instance.SwitchMana(UIManager.ManaState.FullMana);
        }
    
        public int Health
        {
            get { return health; }
            set
            {
                if (health != value)
                {
                    health = Mathf.Clamp(value, 0, maxHealth);
    
                    if (OnHealthChangedCallback != null)
                    {
                        OnHealthChangedCallback.Invoke();
                    }
                }
            }
        }
    
        void Heal()
        {
            if(Input.GetButton("Cast/Heal") && castOrHealtimer > 0.1f && Health < maxHealth && Mana > 0 && Grounded() && !pState.dashing)
            {
                pState.Healing = true;
                rb.linearVelocity = new Vector2(0, rb.linearVelocity.y);
    
                if (pState.Healing)
                {
                    anim.SetBool("Healing", true);
                }
    
                //heal
                healTimer += Time.deltaTime;
                if(healTimer >= timeToHeal)
                {
                    Health++;
                    healTimer = 0;
                }
    
                Mana -= Time.deltaTime * manaDrainSpeed;
    
            }
            else
            {
                pState.Healing = false;
                anim.SetBool("Healing", false);
                healTimer = 0; 
            }
        }
    
        float Mana
        {
            get { return mana; }
            set
            { 
                if(mana != value)
                {
                    if (!halfMana)
                    {
                        mana = Mathf.Clamp(value, 0, 1);
                    }
                    else
                    {
                        mana = Mathf.Clamp(value, 0, 0.5f);
                    }
                    manaStorage.fillAmount = Mana;
                }
            }
        }
    
        void CastSpell()
        {
            if (Input.GetButtonUp("Cast/Heal"))
            {
                castOrHealtimer = 0;
            }
            if (Input.GetButtonUp("Cast/Heal") && castOrHealtimer <= 0.1f && timeSinceCast >= timeBetweenCast && Mana >= manaSpellCost)
            {
                pState.casting = true;
                timeSinceCast = 0;
                StartCoroutine(CastCoroutine());
            }
            else
            {
                timeSinceCast += Time.deltaTime;
            }
    
            if (Grounded())
            {
                //desativa o downcast se ta grounded
                downSpellFireball.SetActive(false);
            }
    
            //se o down spell for ativo ele so para quando chegar ao cão
            if (downSpellFireball.activeInHierarchy)
            {
                rb.linearVelocity += downSpellForce * Vector2.down;
            }
        }
    
        IEnumerator CastCoroutine()
        {
            anim.SetBool("Casting", true);
            yield return new WaitForSeconds(0.09f);
    
            //side cast
            if (yAxis == 0 || (yAxis < 0 && Grounded()))
            {
                GameObject _fireBall = Instantiate(sideSpellFireball, SideAttacktransform.position, Quaternion.identity);
    
                //flip
                if (pState.lookingRight)
                {
                    _fireBall.transform.eulerAngles = Vector3.zero;
                }
                else
                {
                    _fireBall.transform.eulerAngles = new Vector2(_fireBall.transform.eulerAngles.x, 180);
                }
                pState.recoilingX = true;
            }
    
            //up cast
            else if (yAxis > 0)
            {
                Instantiate(upSpellExplosion, transform);
                rb.linearVelocity = Vector2.zero;
            }
    
            //down cast
            else if (yAxis < 0 && !Grounded())
            {
                downSpellFireball.SetActive(true);
            }
    
            Mana -= manaSpellCost;
            yield return new WaitForSeconds(0.20f);
            anim.SetBool("Casting", false);
            maxFallSpeed = -100f;
        }
    
        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;
            }
        }
        void Jump()
        {
            if (jumpBufferCounter > 0 && coyoteTimeCounter > 0 && !pState.jumping)
            {
                rb.linearVelocity = new Vector3(rb.linearVelocity.x, jumpForce);
    
                pState.jumping = true;
            }
    
            if (!Grounded() && airJumpCounter < maxAirJumps && Input.GetButtonDown("Jump"))
            {
                pState.jumping = true;
    
                airJumpCounter++;
    
                rb.linearVelocity = new Vector3(rb.linearVelocity.x, jumpForce);
            }
    
            //isto é para quanto mais carregas no espaço mal alto saltas
            if (Input.GetButtonUp("Jump") && rb.linearVelocity.y > 3)
            {
                pState.jumping = false;
    
                rb.linearVelocity = new Vector2(rb.linearVelocity.x, 0);
            }
            anim.SetBool("Jumping", !Grounded());
        }
    
        //mete o jumpBufferCounter ao max do jumpBufferFrames e reduz em 1 cada frame ou seja tornar o salto mais smooth
        void UpdateJumpVariables()
        {
            if (Grounded())
            {
                pState.jumping = false;
                coyoteTimeCounter = coyoteTime;
                airJumpCounter = 0;
            }
            else
            {
                //object time.deltaTime é a quantidade de tempo entre cada frame
                coyoteTimeCounter -= Time.deltaTime;
                //isto faz com que o coyoteTimeCounter -1 a cada segundo
            }
    
            if (Input.GetButtonDown("Jump"))
            {
                jumpBufferCounter = jumpBufferFrames;
            }
            else
            {
                jumpBufferCounter--;
            }
        }
    }
    #18067
    Ser Apustaja
    Level 6
    AI Assistant
    (BETA)
    Helpful?
    Up
    0
    ::

    It seems like you’re encountering two issues with your PlayerController script:

    1. Player remains invincible after taking damage: This indicates that the pState.invincible flag is not being reset correctly after the player takes damage.
    2. Player stops moving after respawning: This could be due to the player state not being properly initialized or set when the player respawns.

    Here are a few suggestions to troubleshoot and fix these issues:

    1. Handling Invincibility

    Make sure that the invincibility state is reset when the player respawns or after a certain duration. Check the StopTakingDamage coroutine where you set pState.invincible to false. Ensure that there’s no other part of your code that inadvertently sets pState.invincible to true again.

    You could add debug logs to track when pState.invincible is being set to true and false:

    IEnumerator StopTakingDamage()
    {
        pState.invincible = true;
        Debug.Log("Player is now invincible");
    
        GameObject _bloodSpurtParticles = Instantiate(bloodSpurt, transform.position, Quaternion.identity);
        Destroy(_bloodSpurtParticles, 1.5f);
        anim.SetTrigger("TakeDamage");
    
        yield return new WaitForSeconds(1f);
    
        pState.invincible = false;
        Debug.Log("Player is no longer invincible");
    }

    2. Movement After Respawning

    When you respawn, ensure that all necessary player states are properly initialized. Check the Respawned method and ensure that you are resetting the player’s state correctly:

    public void Respawned()
    {
        if (!pState.alive)
        {
            pState.alive = true;
            halfMana = true;
            UIManager.Instance.SwitchMana(UIManager.ManaState.HalfMana);
            Mana = 0;
            Health = maxHealth; // Ensure health is set to max
            rb.velocity = Vector2.zero; // Reset velocity to prevent sticking
            anim.Play("Player_Idle");
        }
    }

    Make sure to reset the player’s velocity when respawning, as shown above. This will prevent the player from being stuck in place if they were moving before dying.

    Additional Considerations

    • Debugging Movement: Add logs in the Update method to check if the input is being received after respawning:
      if (pState.alive)
      {
        Debug.Log("Player is alive and receiving input");
        GetInputs();
        //...rest of your code
      }
    • Cutscene Handling: If the player is in a cutscene (pState.cutscene), ensure that the cutscene logic is correctly allowing player movement after the cutscene ends.

    Summary

    By adding debug logs and ensuring states are reset correctly, you should be able to identify why the player remains invincible and why they cannot move after respawning. Make sure to check for any conditions that might inadvertently keep pState.invincible true and ensure the player’s state is fully reset upon respawn.

    #18068
    Terence
    Level 31
    Keymaster
    Helpful?
    Up
    0
    ::

    Hi Rafael, for the player not moving, can you add the following line and let me know if it shows up on the Console when you take damage?

    IEnumerator StopTakingDamage()
    {
        pState.invincible = true;
        GameObject _bloodSpurtParticles = Instantiate(bloodSpurt, transform.position, Quaternion.identity);
        Destroy(_bloodSpurtParticles, 1.5f);
        anim.SetTrigger("TakeDamage");
        yield return new WaitForSeconds(1f);
        print("Turning player's invincibility off");
        pState.invincible = false;
    }

    As for the player not moving when in a new Scene, after transitioning to your new Scene, in your Hierarchy, type t:PlayerController and see if anything shows up on the Hierarchy. I suspect there may not be a PlayerController in your other Scenes, causing this issue.

    #18110
    Rafael Santos
    Level 3
    Participant
    Helpful?
    Up
    0
    ::

    it shows on the console “Missing ReferenceException: The object of type ‘UnityEngine.GameObject’ has been destroyed but you are still trying to access it. Your script should either check if it is null or you should not destroy the object. Parameter name: data”, why its bieng destroyed after going to other scene

    #18111
    Ser Apustaja
    Level 6
    AI Assistant
    (BETA)
    Helpful?
    Up
    0
    ::

    The “Missing ReferenceException” you’re encountering typically occurs when you try to access a GameObject that has been destroyed or is no longer available in the current scene. This can happen if you have a reference to a GameObject that is destroyed when you load a new scene.

    Here are a few things to check:

    1. Check for Destroyed Objects: Ensure that you are not trying to access any GameObjects that are destroyed when transitioning between scenes. You can add null checks before accessing any GameObject references.

      For example:

      if (myGameObject != null)
      {
          // Access myGameObject
      }
    2. DontDestroyOnLoad: If you want certain GameObjects to persist across scenes, make sure you are using DontDestroyOnLoad(gameObject) correctly in your scripts. This will prevent them from being destroyed when loading a new scene.

    3. Scene Management: Review your scene transition logic. If you are destroying objects that are still referenced in your scripts, you will encounter this error. Make sure to manage your references properly when changing scenes.

    4. Debugging: Add debug logs to track when and where the GameObject is being destroyed. This can help you identify if the destruction is happening earlier than expected.

    Could you provide more details about which GameObject is being referenced when the error occurs? This will help in diagnosing the issue more accurately.

    #18112
    Terence
    Level 31
    Keymaster
    Helpful?
    Up
    0
    ::

    Which is the script that is triggering a MissingReferenceException? Can you pinpoint the script and the line that is causing the error for me?

    If we fix that, we may very well fix your issue.

    #18113
    Rafael Santos
    Level 3
    Participant
    Helpful?
    Up
    0
    ::

    Its on Update

     void Update()
     {
         if (pState.cutscene) return;
         if (pState.alive)
         {
             GetInputs();
             ToggleMap();
         }
         RestoreTimeScale();
         UpdateJumpVariables();
         UpdateCameraYDampForPlayerFall();
    
         if (pState.Healing || pState.dashing) return;
         if (pState.alive)
         {
             Flip();
             Move();
             Jump();
             StartDash();
             Attack();
             Heal();
             CastSpell();
         }
         FlashWhileInvincible();
    
         if (Input.GetKeyDown(KeyCode.L)) 
         {
             StartCoroutine(Death()); //here its the error
         }
     }
    
    i think that maybe on the IEnumerator death is missing something to not destroy,
    
    IEnumerator Death ()
    {
        pState.alive = false;
        Time.timeScale = 1f;
        GameObject _bloodSpurtParticles = Instantiate(bloodSpurt, transform.position, Quaternion.identity);
        Destroy(_bloodSpurtParticles, 1.5f);
        anim.SetTrigger("Death");
    
        yield return new WaitForSeconds(0.9f);
        StartCoroutine(UIManager.Instance.ActivateDeathScreen());
    
        yield return new WaitForSeconds(0.9f);
        Instantiate(GameManager.Instance.shade, transform.position, Quaternion.identity);
    }

    the same error shows when i take damage (on StartCoroutine(StopTakingDamage()); ), or press L key to die (On StartCoroutine(Death()); )

    #18114
    Ser Apustaja
    Level 6
    AI Assistant
    (BETA)
    Helpful?
    Up
    1
    ::

    The error you’re encountering, “Missing ReferenceException: The object of type ‘UnityEngine.GameObject’ has been destroyed but you are still trying to access it,” likely stems from the Death coroutine or the Update method trying to access a GameObject that has already been destroyed.

    Here are a few points to consider:

    1. Check bloodSpurt Reference: Ensure that the bloodSpurt GameObject is properly assigned in the inspector and that it exists when the Death coroutine is called. If bloodSpurt is destroyed or not assigned, trying to instantiate it will lead to issues.

    2. Check anim Reference: Make sure that the anim (Animator) reference is valid and not destroyed when you call anim.SetTrigger("Death");. If the Animator component is on a GameObject that gets destroyed, this will cause the error.

    3. Coroutine Execution: If the Death coroutine is called while the player is already in the process of dying (e.g., if the Death coroutine is still running), it may lead to multiple calls that can cause references to be lost. You might want to add a check to prevent the coroutine from being called again if the player is already dead.

    4. Null Checks: Before accessing any GameObject or component, you can add null checks to ensure they are still valid. For example:

      if (bloodSpurt != null)
      {
          GameObject _bloodSpurtParticles = Instantiate(bloodSpurt, transform.position, Quaternion.identity);
          Destroy(_bloodSpurtParticles, 1.5f);
      }
    5. Debugging: Add debug logs to track the state of your GameObjects and components before they are accessed. This can help identify what is null or destroyed at the time of the error.

    Here’s a modified version of your Death coroutine with added null checks:

    IEnumerator Death ()
    {
       pState.alive = false;
       Time.timeScale = 1f;
    
       if (bloodSpurt != null)
       {
           GameObject _bloodSpurtParticles = Instantiate(bloodSpurt, transform.position, Quaternion.identity);
           Destroy(_bloodSpurtParticles, 1.5f);
       }
       else
       {
           Debug.LogWarning("bloodSpurt is null!");
       }
    
       if (anim != null)
       {
           anim.SetTrigger("Death");
       }
       else
       {
           Debug.LogWarning("Animator is null!");
       }
    
       yield return new WaitForSeconds(0.9f);
       StartCoroutine(UIManager.Instance.ActivateDeathScreen());
    
       yield return new WaitForSeconds(0.9f);
       if (GameManager.Instance.shade != null)
       {
           Instantiate(GameManager.Instance.shade, transform.position, Quaternion.identity);
       }
       else
       {
           Debug.LogWarning("shade is null!");
       }
    }

    This should help you identify the source of the error. If the issue persists, could you provide more details about the bloodSpurt, anim, and GameManager.Instance.shade references?

    has upvoted this post.
    #18117
    Rafael Santos
    Level 3
    Participant
    Helpful?
    Up
    1
    ::

    THIS WORKED, but ill try to do the same on TAkedamage, its a better way for doing this? maybe all coroutines are getting this error, i already have the dont destroy on load, on the awake of playerController

    has upvoted this post.
    #18118
    Ser Apustaja
    Level 6
    AI Assistant
    (BETA)
    Helpful?
    Up
    1
    ::

    Yes, applying the same null checks and safety measures in your TakeDamage method and any other coroutines is a good practice. This will help prevent the “Missing ReferenceException” from occurring when you try to access destroyed GameObjects or components.

    Here’s how you can implement similar checks in your TakeDamage method:

    public void TakeDamage(float _damage)
    {
        if (pState.alive)
        {
            Health -= Mathf.RoundToInt(_damage);
            if (health <= 0)
            {
                health = 0;
                StartCoroutine(Death());
            }
            else
            {
                StartCoroutine(StopTakingDamage());
            }
        }
    }
    
    IEnumerator StopTakingDamage()
    {
        pState.invincible = true;
    
        if (bloodSpurt != null)
        {
            GameObject _bloodSpurtParticles = Instantiate(bloodSpurt, transform.position, Quaternion.identity);
            Destroy(_bloodSpurtParticles, 1.5f);
        }
        else
        {
            Debug.LogWarning("bloodSpurt is null!");
        }
    
        if (anim != null)
        {
            anim.SetTrigger("TakeDamage");
        }
        else
        {
            Debug.LogWarning("Animator is null!");
        }
    
        yield return new WaitForSeconds(1f);
        pState.invincible = false;
    }

    Additional Recommendations:

    1. Centralized Error Handling: If you find that many coroutines are causing similar issues, consider creating a centralized method for handling instantiation and destruction of GameObjects. This method can include null checks and logging.

    2. State Management: Ensure that your player state (like pState.alive) is managed correctly throughout the game. If the player is dead, you should prevent any further actions that could lead to accessing destroyed objects.

    3. Coroutines Management: If you have multiple coroutines that can be called in quick succession, consider using a boolean flag to prevent them from being called again while one is already running. For example:

      private bool isTakingDamage = false;
      
      IEnumerator StopTakingDamage()
      {
          if (isTakingDamage) yield break; // Prevents re-entry
          isTakingDamage = true;
      
          // Your existing code...
      
          isTakingDamage = false; // Reset flag after execution
      }

    Clarifying Question:

    Are there any specific coroutines or methods other than TakeDamage that you are concerned about, or would you like to review how to implement these checks in other parts of your code?

    has upvoted this post.
    #18119
    Rafael Santos
    Level 3
    Participant
    Helpful?
    Up
    1
    ::

    So i trie to discover the issues and its on the blood Splurt, i use the same Ienumerator as before, but on the bloodsplurt i use the if that this AI give me, and it worked

    has upvoted this post.
    #18120
    Ser Apustaja
    Level 6
    AI Assistant
    (BETA)
    Helpful?
    Up
    1
    ::

    It’s great to hear that you were able to identify the issue with the blood spurt and that implementing the null checks resolved the problem!

    Using the same approach of checking for null references before instantiating or accessing GameObjects is a solid strategy to prevent runtime errors. This practice will help ensure that your game runs smoothly without unexpected crashes or exceptions.

    If you have any other specific areas of your code that you’re concerned about or if there are additional features you want to implement, feel free to share! Is there anything else you would like assistance with regarding your game development?

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

Go to Login Page →


Advertisement below: