Forum begins after the advertisement:


Search Results for 'fader'

Home Forums Search Search Results for 'fader'

Viewing 15 results - 31 through 45 (of 77 total)
  • Author
    Search Results
  • Hello, currently I’m finishing part 9 of your tutorial and throughout following it I’ve ran into a few issues I couldn’t find answers for here on forum for and also got a few questions myself.

    1. So the first thing is an error I’m receiving in the console as soon as I hit play for the first time after opening the project.
    Error

    My UIManager script that the error mentions looks like that:

    using System.Collections;
    using UnityEngine;
    
    public class UIManager : MonoBehaviour
    {
        public static UIManager Instance;
        public SceneFader sceneFader;
        [SerializeField] GameObject deathScreen;
        public GameObject mapHandler;
        public GameObject inventory;
    
        private void Awake()
        {
            if (Instance != null && Instance != this) 
            {
                Destroy(gameObject);
            }
            else
            {
                Instance = this;
            }
            <strong>DontDestroyOnLoad(gameObject);</strong>
    
            sceneFader = GetComponentInChildren<SceneFader>();
        }
    
        public IEnumerator ActivateDeathScreen()
        {
            yield return new WaitForSeconds(0.8f);
            StartCoroutine(sceneFader.Fade(SceneFader.FadeDirection.In));
    
            yield return new WaitForSeconds(0.8f);
            deathScreen.SetActive(true);
        }
        public IEnumerator DeactivateDeathScreen()
        {
            yield return new WaitForSeconds(0.5f);
            deathScreen.SetActive(false);
            StartCoroutine(sceneFader.Fade(SceneFader.FadeDirection.Out));
        }
    }
    

    2. My bat enemy gets stunned even when I set stun duration to 0 and he also gets knockbacked and stunned after his hp reaches 0 (which I believe shouldn’t happen?)

    Bat problem

    Here’s my bat script:

    using UnityEngine;
    
    public class Bat : Enemy
    {
        [SerializeField] private float chaseDistance;
        [SerializeField] private float stunDuration;
        float timer;
    
        protected override void Start()
        {
            base.Start();
            ChangeState(EnemyStates.Bat_Idle);
        }
    
        protected override void Update()
        {
            base.Update();
            if (!PlayerController.Instance.pState.alive)
            {
                ChangeState(EnemyStates.Bat_Idle);
            }
        }
    
        protected override void UpdateEnemyStates()
        {
            float distance = Vector2.Distance(transform.position, PlayerController.Instance.transform.position);
    
            switch (GetCurrentEnemyState)
            {
                case EnemyStates.Bat_Idle:
                    rb.velocity = new Vector2(0, 0);
                    if (distance < chaseDistance)
                    {
                        ChangeState(EnemyStates.Bat_Chase);
                    }
                    break;
    
                case EnemyStates.Bat_Chase:
                    rb.MovePosition(Vector2.MoveTowards(transform.position, PlayerController.Instance.transform.position, Time.deltaTime * speed));
                    FlipBat();
                    if (distance > chaseDistance)
                    {
                        ChangeState(EnemyStates.Bat_Idle);
                    }
                    break;
    
                case EnemyStates.Bat_Stunned:
                    timer += Time.deltaTime;
                    if (timer >= stunDuration)
                    {
                        ChangeState(EnemyStates.Bat_Idle);
                        timer = 0;
                    }
    
                    break;
    
                case EnemyStates.Bat_Death:
                    Death(Random.Range(5,10));
                    break;
    
            }
        }
    
        public override void EnemyHit(float damageDealt, Vector2 hitDirection, float hitForce)
        {
            base.EnemyHit(damageDealt, hitDirection, hitForce);
    
            if (health > 0)
            {
                ChangeState(EnemyStates.Bat_Stunned);
            }
            else
            {
                ChangeState(EnemyStates.Bat_Death);
            }
        }
    
        protected override void Death(float destroyTime)
        {
            rb.gravityScale = 12;
            base.Death(destroyTime);
        }
    
        protected override void ChangeCurrentAnimation()
        {
            anim.SetBool("Idle", GetCurrentEnemyState == EnemyStates.Bat_Idle);
            anim.SetBool("Chase", GetCurrentEnemyState == EnemyStates.Bat_Chase);
            anim.SetBool("Stunned", GetCurrentEnemyState == EnemyStates.Bat_Stunned);
    
            if(GetCurrentEnemyState == EnemyStates.Bat_Death)
            {
                anim.SetTrigger("Death");
                int LayerIgnorePlayer = LayerMask.NameToLayer("Ignore Player");
                gameObject.layer = LayerIgnorePlayer;
            }
        }
    
        void FlipBat()
        {
            if(PlayerController.Instance.transform.position.x < transform.position.x)
            {
                sr.flipX = true;
            }
            else
            {
                sr.flipX = false;
            }
        }
    
    }
    

    3. My player jumping method seems to be malfunctioning as sometimes when I barely tap jump button, the character jumps very high as shown in the video below:

    Broken jumping

    My jump method:

    private void Jump()
    {
        if (!pState.jumping && jumpBufferCounter > 0 && coyoteTimeCounter > 0)
        {
            rb.velocity = new Vector3(rb.velocity.x, jumpForce);
            pState.jumping = true;
        }
        if (!IsGrounded() && airJumpCounter < maxAirJumps && Input.GetButtonDown("Jump") && unlockedDoubleJump)
        {
            pState.jumping = true;
    
            airJumpCounter++;
    
            rb.velocity = new Vector3(rb.velocity.x, jumpForce);
        }
    
        if (Input.GetButtonUp("Jump") && rb.velocity.y > 3)
        {
            rb.velocity = new Vector2(rb.velocity.x, 0);
    
            pState.jumping = false;
        }
    
        rb.velocity = new Vector2(rb.velocity.x, Mathf.Clamp(rb.velocity.y, -maxFallingSpeed, rb.velocity.y));
        anim.SetBool("Jumping", !IsGrounded());
    }

    4. Walljumping seems to be working as intended when the wall I’m walljumping on is on the right side, while when I’m walljumping on left-side wall it’s working in a wrong way

    Weird walljumping

    My walljumping method:

    private void WallJump()
    {
        if (isWallSliding)
        {
            isWallJumping = false;
            wallJumpingDirection = !pState.lookingRight ? 1: -1;
            CancelInvoke(nameof(StopWallJumping));
        }
    
        if (Input.GetButtonDown("Jump") && isWallSliding)
        {
            isWallJumping = true;
            rb.velocity = new Vector2(wallJumpingDirection * wallJumpingPower.x, wallJumpingPower.y);
            dashed = false;
            airJumpCounter = 0;
    
            pState.lookingRight = !pState.lookingRight;
            transform.eulerAngles = new Vector2(transform.eulerAngles.x, 180);
    
            Invoke(nameof(StopWallJumping), wallJumpingDuration);
        }
    }
    
    private void StopWallJumping()
    {
        isWallJumping = false;
        transform.eulerAngles = new Vector2(transform.eulerAngles.x, 0);
    
        rb.velocity = new Vector2(rb.velocity.x, 0);
    }

    5. If I put a transition to the other scene in a place that’s being “operated” by camera that is not the default one on the current scene, how do I make it so when I return from that other scene to the first scene, the active camera is set to the one where the scene transition is at (place that is not being “operated” by scene’s default camera)? You can see what I mean on the video below:

    Camera

    6. How to code the ability to unpause the game using esc key except only just pressing resume with your mouse?

    #15325
    MI NI
    Bronze Supporter (Patron)

    Sorry for the late reply, I tested your suggestion and it doesn’t seem to work.

        private void OnTriggerEnter2D(Collider2D _other)
        {
            if(_other.CompareTag("Player"))
            {
                StopCoroutine(PlayerController.Instance.StopTakingDamage());
    
                GameManager.Instance.transitionedFromScene = SceneManager.GetActiveScene().name;
    
                PlayerController.Instance.pState.cutscene = true;
                PlayerController.Instance.pState.invincible = true; //切換場景時無敵
    
                //SceneManager.LoadScene(transitionTo);
                StartCoroutine(UIManager.Instance.sceneFader.FadeAndLoadScene(SceneFader.FadeDirection.In, transitionTo));
            }
        }

    MI NI
    Participant

    I’ve made the fix for the issue in part five, but I’m finding that in some cases the game freezes when the transition scene is hit, and I’m not sure why.
    I recorded a video for reference. You can see the time when time was frozen. The player seemed to have left the invincible state when changing scenes and was hit, causing time to freeze.

    If you need any more information, please let me know.

    PlayerController.cs

    using System.Collections;
    using System.Collections.Generic;
    using UnityEngine;
    using UnityEngine.UIElements;
    using UnityEngine.UI;
    
    public class PlayerController : MonoBehaviour
    {
        [Header("水平移動")]
        [SerializeField] private float walkSpeed = 1; //走路速度
        [Space(5)]
    
        [Header("垂直移動")]
        [SerializeField] private float jumpForce = 45; //跳躍速度
        private int jumpBufferCounter = 0; //按下按鈕儲存指令
        [SerializeField] private int jumpBufferFrames; //按下按鈕儲存指令時間/偵
        private float coyoteTimeCounter = 0; //土狼時間
        [SerializeField] private float coyoteTime; //土狼時間/偵
        private int airJumpCounter = 0;
        [SerializeField] private int maxAirJumps;
    
        [SerializeField] private float fallMultiplier = 2.5f;
        [SerializeField] private float lowJumpMulitiplier = 2f;
    
        [Space(5)]
    
        [Header("WallJump")]
        [SerializeField] private float wallSlidingSpeed; //滑行速度
        [SerializeField] private Transform wallCheck; //牆壁檢查點
        [SerializeField] private LayerMask wallLayer;
        [SerializeField] private float wallJumpingDuration; //蹬牆跳持續時間
        [SerializeField] private Vector2 wallJumpPower;
        float wallJumpingDirection;
        bool isWallSliding;
        bool isWallJumping;
        [Space(5)]
    
        [Header("地面檢查")]
        [SerializeField] private Transform groundCheckPoint; //地面檢查點
        [SerializeField] private float groundCheckY = 0.2f; //
        [SerializeField] private float groundCheckX = 0.5f; //
        [SerializeField] private LayerMask whatIsGround; //檢查是否在地面
        [Space(5)]
    
        [Header("衝刺")]
        [SerializeField] private float dashSpeed; //衝刺速度
        [SerializeField] private float dashTime;  //衝刺時間
        [SerializeField] private float dashCooldown; //衝刺冷卻
        [Space(5)]
    
        [Header("攻擊")]
    
        [SerializeField] private float timeBetweenAttack; //攻擊時間
        [SerializeField] Transform SideAttackTransform, UpAttackTransform, DownAttackTransform; //不同方向攻擊位置
        [SerializeField] Vector2 SideAttackArea, UpAttackArea, DownAttackArea; //不同方向攻擊範圍
        [SerializeField] LayerMask attackableLayer; //可攻擊的對象圖層
        [SerializeField] float damage; //傷害
        [SerializeField] GameObject slashEffect; //劍氣效果
        private bool attack = false;
        private float timeSinceAttack;
    
        bool restoreTime;
        public float restoreTimeSpeed;
    
        [Space(5)]
    
        [Header("後座力")]
        [SerializeField] int recilXSteps = 5;
        [SerializeField] int recilYSteps = 5;
        [SerializeField] float recilXSpeed = 5;
        [SerializeField] float recilYSpeed = 5;
    
        private int stepsXRecoiled, stepsYRecoiled;
        [Space(5)]
    
        [Header("血量")]
        public float health;  //當前血量
        public float maxHealth; //最大血量
        [SerializeField] GameObject hitEffect; //被擊效果
        [SerializeField] float hitFlashSpeed; //受傷角色閃爍
        float healTimer;
        [SerializeField] float timeToheal;
        [Space(5)]
    
        [Header("血量圖片")]
        public Text healthText; //血量文字
        public UnityEngine.UI.Image healthImage; //血量圖片
        public UnityEngine.UI.Image healthEffect; //血量緩衝特效
        public float effectTime = 0.5f; //緩衝持續時間
        private Coroutine updateCoroutine;
        [Space(5)]
    
        [Header("魔力")]
        [SerializeField] float mana; //魔力量
        [SerializeField] float maxMana; //最大魔力量
        [SerializeField] float manaDrainSpeed; //魔力消耗速度
        [SerializeField] float manaGain; //獲得魔力
        [SerializeField] UnityEngine.UI.Image manaImage; //魔力圖片
    
        [Header("技能")]
        [SerializeField] float manaSpellCost = 0.3f; //法術消耗
        [SerializeField] float timeBetweenCast = 0.5f; //攻擊間隔速度
        [SerializeField] GameObject sideSpellFireball; //火球物件Prefab
        float timeSinceCast;
        float castOrHealtimer;
    
        [Header("相機")]
        [SerializeField] private float playerFallSpeedTheshold = -10;
    
        [HideInInspector] public PlayerStateList pState; //取得緩衝輸入腳本
        [HideInInspector] public Rigidbody2D rb;
        private Animator anim;
        private SpriteRenderer sr;
    
        private float gravity; //重力
        private float xAxis, yAxis; //取得XY軸輸入
        private bool canDash = true;
        private bool dashed;
        private bool canFlash = true;
        bool openMap; //開啟地圖
        bool openInventory; //開啟背包
    
        //解鎖能力
        public bool unlockWallJump;
        public bool unlockDash;
        public bool unlockAirJump;
        public bool unlockSideSpell = false;
        
    
        public static PlayerController Instance; //防止多個重複的玩家腳本出現
        private void Awake()
        {
            if(Instance != null && Instance != this)
            {
                Destroy(gameObject);
            }
            else
            {
                Instance = this;
            }
    
            DontDestroyOnLoad(gameObject);
    
            
        }
    
        void Start()
        {
            pState = GetComponent<PlayerStateList>();
    
            rb = GetComponent<Rigidbody2D>();
    
            sr = GetComponent<SpriteRenderer>();
    
            anim = GetComponent<Animator>();
    
            gravity = rb.gravityScale;
    
            
    
            Health = maxHealth; //Save之後不再需要
            Mana = maxMana;
    
            healthImage.fillAmount = health / maxHealth;
            healthEffect.fillAmount = health / maxHealth;
            healthText.text = health.ToString() + "/" + maxHealth.ToString();
            manaImage.fillAmount = Mana;
    
            SaveData.Instance.LoadPlayerData();
    
            if (Health == 0)
            {
                pState.alive = false;
                GameManager.Instance.RespawnPlayer();
            }
        }
    
        private void OnDrawGizmos()
        {
            Gizmos.color = Color.red;
            Gizmos.DrawWireCube(SideAttackTransform.position, SideAttackArea);
            Gizmos.DrawWireCube(UpAttackTransform.position, UpAttackArea);
            Gizmos.DrawWireCube(DownAttackTransform.position, DownAttackArea);
            //Gizmos.DrawWireCube(wallCheck.position, )
        }
    
        void Update()
        {
            if (GameManager.Instance.gameIsPaused) return;
    
            if (pState.cutscene) return;
            if (pState.alive)
            {
                GetInputs();
                ToggleMap();
                ToggleInventory();
            }
            UpdateJumpVariables();
            RestoreTimeScale();
            UpdateCameraYDampForPlayerFall();
    
            if(pState.alive)
            {
                Heal();
            }
    
            if (pState.dashing || pState.healing) return;
    
            if(pState.alive)
            {
                if(!isWallJumping)
                {
                    Flip();
                    Move();
                    Jump();
                }
                if(unlockWallJump)
                {
                    WallSlide();
                    WallJump();
                }
                if(unlockDash)
                {
                    StartDash();
                }
                
    
                
                Attack();
                CastSpell();
            }
            FlashWhileInvincible();
        }
    
        private void FixedUpdate()
        {
            if (pState.cutscene) return;
    
            if (pState.dashing || pState.healing) return;
            Recoil();
        }
    
        void GetInputs() //獲取輸入
        {
            xAxis = Input.GetAxisRaw("Horizontal");
            yAxis = Input.GetAxisRaw("Vertical");
            attack = Input.GetButtonDown("Attack");
            openMap = Input.GetButton("Map");
            openInventory = Input.GetButton("Inventory");
    
            if (Input.GetButton("Cast/Heal"))
            {
                castOrHealtimer += Time.deltaTime;
            }
            /*
            else
            {
                castOrHealtimer = 0;
            }
            */
        }
    
        void ToggleMap()
        {
            if(openMap)
            {
                UIManager.Instance.mapHandler.SetActive(true);
            }
            else
            {
                UIManager.Instance.mapHandler.SetActive(false);
            }
        }
    
        void ToggleInventory()
        {
            if (openInventory)
            {
                UIManager.Instance.inventory.SetActive(true);
            }
            else
            {
                UIManager.Instance.inventory.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() //移動
        {
            if (pState.healing) rb.velocity = new Vector2(0, 0); //如果玩家正在治療則無法移動
            rb.velocity = new Vector2(walkSpeed * xAxis ,rb.velocity.y);
            anim.SetBool("Walk", rb.velocity.x !=0 && Grounded());
        }
    
        void UpdateCameraYDampForPlayerFall()
        {
            //if falling past a certain speed threshold
            if (rb.velocity.y < playerFallSpeedTheshold && !CameraManager.Instance.isLerpingYDamp && !CameraManager.Instance.hasLerpingYDamping)
            {
                StartCoroutine(CameraManager.Instance.LerpYDamping(true));
            }
            //if standing stil or moveing up
            if(rb.velocity.y >= 0 && !CameraManager.Instance.isLerpingYDamp && CameraManager.Instance.hasLerpingYDamping)
            {
                //reset camera funtion
                CameraManager.Instance.hasLerpingYDamping = false;
                StartCoroutine(CameraManager.Instance.LerpYDamping(false));
            }
        }
    
        void StartDash() //衝刺
        {
            if(Input.GetButtonDown("Dash") && canDash && !dashed)
            {
                StartCoroutine(Dash());
                dashed = true;
            }
    
            if(Grounded())
            {
                dashed = false;
            }
        }
    
        IEnumerator Dash()
        {
            canDash = false; 
            pState.dashing = true;
            anim.SetTrigger("Dash");
            rb.gravityScale = 0;
            int _dir = pState.lookingRight ? 1 : -1;
            rb.velocity = new Vector2(_dir * dashSpeed, 0);
            yield return new WaitForSeconds(dashTime);
            rb.gravityScale = gravity;
            pState.dashing = false;
            yield return new WaitForSeconds(dashCooldown);
            canDash = true;
    
        }
    
        public IEnumerator WalkIntoNewScene(Vector2 _exitDir, float _delay) //過場動畫
        {
            pState.invincible = true;
    
            if(_exitDir.y > 0) //如果退出位置為向上
            {
                rb.velocity = jumpForce * _exitDir;
            }
    
            //如果退出位置為水平移動
            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 >= timeBetweenAttack)
            {
                timeSinceAttack = 0;
                anim.SetTrigger("Attack");
    
                if(yAxis == 0 || yAxis < 0 && Grounded()) //如果玩家在地面上且沒有按住"上",正常攻擊
                {
                    int _recoilLeftOrRight = pState.lookingRight ? 1 : -1; 
    
                    Hit(SideAttackTransform, SideAttackArea, ref pState.recoilingX, Vector2.right * _recoilLeftOrRight, recilXSpeed);
                    Instantiate(slashEffect, SideAttackTransform);
                }
                else if(yAxis > 0) //如果按住"上",上攻擊
                {
                    Hit(UpAttackTransform, UpAttackArea, ref pState.recoilingY, Vector2.up, recilYSpeed);
                    SlashEffectAtAngle(slashEffect, 90, UpAttackTransform);
                }
                else if (yAxis < 0 && !Grounded()) //如果不在地面且按住"下",下攻擊
                {
                    Hit(DownAttackTransform, DownAttackArea, ref pState.recoilingY, Vector2.down, recilYSpeed);
                    SlashEffectAtAngle(slashEffect, -90, DownAttackTransform);
                }
    
            }
        }
    
        private void Hit(Transform _attackTransfrom, Vector2 _attackArea, ref bool _recoilBool,Vector2 _recoilDir , float _recoilStrength) //給予傷害
        {
            Collider2D[] objectsToHit = Physics2D.OverlapBoxAll(_attackTransfrom.position, _attackArea, 0, attackableLayer);
            List<Enemy> hitEnemise = new List<Enemy>();
    
            if(objectsToHit.Length > 0)
            {
                //Debug.Log("Hit");
                _recoilBool = true;
    
            }
            for(int i = 0; i < objectsToHit.Length; i++)
            {
                if(objectsToHit[i].GetComponent<Enemy>() != null)
                {
                    objectsToHit[i].GetComponent<Enemy>().EnemyHit(damage, _recoilDir , _recoilStrength)  ;
                    /*
                    Enemy e = objectsToHit[i].GetComponent<Enemy>();
                    if(e && !hitEnemise.Contains(e))
                    {
                        e.EnemyHit(damage, (transform.position - objectsToHit[i].transform.position).normalized, _recoilStrength);
                        hitEnemise.Add(e);
                    }
                    */
                    if (objectsToHit[i].CompareTag("Monster")) //打到怪物增加魔力
                    {
                        Mana += manaGain;
                    }
                }
            }
        }
        
        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.velocity = new Vector2(-recilXSpeed, 0);
                }
                else
                {
                    rb.velocity = new Vector2(recilXSpeed, 0);
                }
            }
    
            if(pState.recoilingY)
            {
                rb.gravityScale = 0;
                if (yAxis < 0)
                {
                    rb.velocity = new Vector2(rb.velocity.x, recilYSpeed);
                }
                else
                {
                    rb.velocity = new Vector2(rb.velocity.x, -recilYSpeed);
                }
    
                airJumpCounter = 0;
    
            }
            else
            {
                rb.gravityScale = gravity;
            }
            
            //Stop recoil 停止反衝
            if(pState.recoilingX && stepsXRecoiled < recilXSteps)
            {
                stepsXRecoiled++;
            }
            else
            {
                StopRecoilX();
            }
    
            if (pState.recoilingY && stepsYRecoiled < recilYSteps)
            {
                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());
    
                    healthImage.fillAmount = health / maxHealth; //血量圖片
                    healthText.text = health.ToString() + "/" + maxHealth.ToString(); //血量文字
    
                    if (updateCoroutine != null)
                    {
                        StopCoroutine(updateCoroutine);
                    }
                    updateCoroutine = StartCoroutine(UpdateHealthEffect());
                } 
            }
        }
    
        private IEnumerator UpdateHealthEffect() //血量緩降協程
        {
            float effectLength = healthEffect.fillAmount - healthImage.fillAmount;
            float elapsedTime = 0f;
    
            while (elapsedTime < effectTime && effectLength != 0)
            {
                elapsedTime += Time.deltaTime;
                healthEffect.fillAmount = Mathf.Lerp
                (healthImage.fillAmount + effectLength, healthImage.fillAmount, elapsedTime / effectTime);
                yield return null;
            }
            healthEffect.fillAmount = healthImage.fillAmount;
        }
    
        IEnumerator StopTakingDamage() //無敵偵
        {
            pState.invincible = true;
            GameObject _hitEffect = Instantiate(hitEffect, transform.position, Quaternion.identity);
            Destroy(_hitEffect, 1.5f);
            anim.SetTrigger("Hurt");
            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()
        {
            //將渲染改為"PingPongLerp" 如果玩家是無敵狀態則將其從白色改為黑色 否則將玩家設定為白色
            //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.2f && canFlash) 
                {
                    StartCoroutine(Flash()); 
                }
            }
            else
            {
                sr.enabled = true;
            }
        }
    
        void RestoreTimeScale() //受傷暫停時間
        {
            if(restoreTime) //檢查是否為真
            {
                if(Time.timeScale < 1) //如果小於1 時間速度加快到達1
                {
                    Time.timeScale += Time.unscaledDeltaTime * restoreTimeSpeed;
                }
                else //如果超過1 時間為1
                {
                    Time.timeScale = 1;
                    restoreTime = false;
                }
            }
        }
    
        public void HitStopTime(float _newTimeScale, int _restoreSpeed, float _delay)
        {
            restoreTimeSpeed = _restoreSpeed;
            Time.timeScale = _newTimeScale;
    
            if(_delay > 0)
            {
                StopCoroutine(StartTimeAgain(_delay));
                StartCoroutine(StartTimeAgain(_delay));
            }
            else
            {
                restoreTime = true;
            }
        }
    
        IEnumerator StartTimeAgain(float _delay)
        {
            yield return new WaitForSecondsRealtime(_delay);
            restoreTime = true;
        }
    
        IEnumerator Death()
        {
            pState.alive = false;
            Time.timeScale = 1f; //避免受傷暫停時間導致出錯
            //可在此呼叫粒子效果
            anim.SetTrigger("Death");
    
            rb.constraints = RigidbodyConstraints2D.FreezePosition;
            rb.constraints = RigidbodyConstraints2D.FreezeRotation;
            GetComponent<BoxCollider2D>().enabled = false;
    
            yield return new WaitForSeconds(0.9f);
            StartCoroutine(UIManager.Instance.ActiveDeathScreen());
        }
    
        public void Respawned()
        {
            if(!pState.alive)
            {
                rb.constraints = RigidbodyConstraints2D.None;
                rb.constraints = RigidbodyConstraints2D.FreezeRotation;
                GetComponent<BoxCollider2D>().enabled = true;
    
                pState.alive = true;
                Health = maxHealth;
                anim.Play("PlayerIdle");
            }
        }
    
        public float Health //血量
        {
            get { return health; }
            set
            {
                if(health != value)
                {
                    health = Mathf.Clamp(value, 0, maxHealth);
    
                }
            }
        }
    
        void Heal()
        {
            if(Input.GetButton("Cast/Heal") && castOrHealtimer > 0.5f && health < maxHealth && Mana > 0 && Grounded() && !pState.dashing)
            {
                pState.healing = true;
                anim.SetBool("Heal", true);
    
                //補血
                healTimer += Time.deltaTime;
                if(healTimer >= timeToheal)
                { 
                    health += 5; //恢復血量
                    healTimer = 0; //血量計時器
                    healthImage.fillAmount = health / maxHealth; //血量圖片
                    healthEffect.fillAmount = health / maxHealth; //血量效果圖片
                    healthText.text = health.ToString() + "/" + maxHealth.ToString(); //血量文字
                }
                Mana -= Time.deltaTime * manaDrainSpeed;//魔力消耗
            }
            else
            {
                pState.healing = false;
                anim.SetBool("Heal", false);
                healTimer = 0;
            }
    
            
        }
    
        public float Mana
        {
            get { return mana; }
            set
            {
                if(mana != value)
                {
                    mana = Mathf.Clamp(value, 0, maxMana);
                    manaImage.fillAmount = Mana;
                }
    
            }
        }
    
        void CastSpell()
        {
            if(Input.GetButtonUp("Cast/Heal") && castOrHealtimer <= 0.5f && timeSinceCast >= timeBetweenCast && Mana > manaSpellCost)
            {
                pState.casting = true;
                timeSinceCast = 0;
                StartCoroutine(CastCoroutine());
            }
            else
            {
                timeSinceCast += Time.deltaTime;
            }
            
            if(!Input.GetButton("Cast/Heal"))
            {
                castOrHealtimer = 0;
            }
            
        }
    
        IEnumerator CastCoroutine()
        {
            //橫向技能
            if((yAxis == 0 || (yAxis < 0 && Grounded())) && unlockSideSpell ) //沒有垂直輸入時播放 目前無需要按住上下才能施放的技能
            {
                anim.SetBool("Cast", true);
                yield return new WaitForSeconds(0.1f); //此為等待動畫到達正確播放時間 
    
                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); //如果不是面向右邊翻轉180度
                }
                pState.recoilingX = true; //給予玩家反衝
    
                Mana -= manaSpellCost; //魔力減少
                yield return new WaitForSeconds(0.3f); //此為動畫撥放完整時間
            }
    
            anim.SetBool("Cast", false);
            pState.casting = false;
    
        }
    
        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.velocity = new Vector3(rb.velocity.x, jumpForce);
    
                pState.jumping = true;
            }
            //二段跳
            if (!Grounded() && airJumpCounter < maxAirJumps && Input.GetButtonDown("Jump") && unlockAirJump && !isWallSliding)
            {
                
                pState.jumping = true;
    
                airJumpCounter++;
    
                rb.velocity = new Vector3(rb.velocity.x, jumpForce);
            }
            
            if(rb.velocity.y < 0)
            {
                rb.velocity += Vector2.up * Physics2D.gravity.y * (fallMultiplier - 1) * Time.fixedDeltaTime;
    
                pState.jumping = false;
    
            }
            else if(Input.GetButtonUp("Jump") && rb.velocity.y > 0)
            {
                rb.velocity += Vector2.up * Physics2D.gravity.y * (lowJumpMulitiplier - 1) * Time.fixedDeltaTime;
    
                pState.jumping = false;
            }
            
            /*
            if (Input.GetButtonUp("Jump") && rb.velocity.y > 0)
            {
                rb.velocity = new Vector2(rb.velocity.x, 0);
    
                pState.jumping = false;
            }
            */
            anim.SetBool("Jump", !Grounded());
        }
    
        void UpdateJumpVariables()
        {
            if(Grounded())
            {
                pState.jumping = false;
                coyoteTimeCounter = coyoteTime;
                airJumpCounter = 0;  
            }
            else
            {
                coyoteTimeCounter -= Time.deltaTime;
            }
    
            if(Input.GetButtonDown("Jump") )
            {
                jumpBufferCounter = jumpBufferFrames;
            }
            else
            {
                jumpBufferCounter--;
            }
    
        }
    
        private bool Walled()
        {
            return Physics2D.OverlapCircle(wallCheck.position, 0.2f, wallLayer);
        }
    
        void WallSlide()
        {
            if(Walled() && !Grounded() && xAxis !=0)
            {
                isWallSliding = true;
                rb.velocity = new Vector2(rb.velocity.x, Mathf.Clamp(rb.velocity.y, -wallSlidingSpeed, float.MaxValue));
                anim.SetBool("Slide", true);
    
                transform.eulerAngles = new Vector2(transform.eulerAngles.x, 0);
            }
            else
            {
                isWallSliding = false;
                anim.SetBool("Slide", false);
            }
        }
    
        void WallJump()
        {
            if(isWallSliding)
            {
                isWallJumping = false;
                wallJumpingDirection = !pState.lookingRight ? 1 : -1;
    
                CancelInvoke(nameof(StopWallJumping));
            }
    
            if(Input.GetButtonDown("Jump") && isWallSliding)
            {
                anim.SetBool("WallJump", true);
    
                isWallJumping = true;
                rb.velocity = new Vector2(wallJumpingDirection * wallJumpPower.x, wallJumpPower.y);
    
                dashed = false;
                airJumpCounter = 0;
    
                pState.lookingRight = !pState.lookingRight;
                transform.eulerAngles = new Vector2(transform.eulerAngles.x, 180);
    
                Invoke(nameof(StopWallJumping), wallJumpingDuration);
            }
        }
    
        void StopWallJumping()
        {
            anim.SetBool("WallJump", false);
            isWallJumping = false;
            rb.velocity = new Vector2(rb.velocity.x, 0);
            transform.eulerAngles = new Vector2(transform.eulerAngles.x, 0);
        }
    
    }
    

    SceneTransition.cs

    using System.Collections;
    using System.Collections.Generic;
    using UnityEngine;
    using UnityEngine.SceneManagement;
    
    public class SceneTransition : MonoBehaviour
    {
        [SerializeField] private string transitionTo; //通往哪裡
        [SerializeField] private Transform startPoint; //玩家於場景開始的位置
        [SerializeField] private Vector2 exitDirection; //玩家退出方向 1代表面向右邊 -1左
        [SerializeField] private float exitTime; //玩家退出場景所需的時間
    
        private void Start()
        {
            //Debug.Log("Scene transitioning from: " + GameManager.Instance.transitionedFromScene);
            //Debug.Log("Scene transitioning to: " + transitionTo);
            if (transitionTo == GameManager.Instance.transitionedFromScene) //檢查起點是否相同
            {
                //Debug.Log("Scene transition object: " + startPoint.name);
                //如果為真,將玩家的位置設定為startPoint位置
                PlayerController.Instance.transform.position = startPoint.position;
    
                StartCoroutine(PlayerController.Instance.WalkIntoNewScene(exitDirection, exitTime));
            }
            StartCoroutine(UIManager.Instance.sceneFader.Fade(SceneFader.FadeDirection.Out));
        }
    
        private void OnTriggerEnter2D(Collider2D _other)
        {
            if(_other.CompareTag("Player"))
            {
                GameManager.Instance.transitionedFromScene = SceneManager.GetActiveScene().name;
    
                PlayerController.Instance.pState.cutscene = true;
                PlayerController.Instance.pState.invincible = true; //切換場景時無敵
    
                //SceneManager.LoadScene(transitionTo);
                StartCoroutine(UIManager.Instance.sceneFader.FadeAndLoadScene(SceneFader.FadeDirection.In, transitionTo));
            }
        }
    
    }
    
    #14848
    Mr Thinker
    Participant

    It worked thank you. But i am experiencing problems with the Respawn button, it wourl not let me click it, i’ve alreday tries to solve the problem with other posts but it seems to not let me use it, could you help me with this?

    View post on imgur.com

    using System.Collections;
    using System.Collections.Generic;
    using UnityEngine;
    using UnityEngine.UI;
    
    public class UI_Manager : MonoBehaviour
    {
        public static UI_Manager Instance;
        public SceneFader sceneFader;
    
        [SerializeField] GameObject deathScreen;
        public GameObject mapHandler;
        [SerializeField] GameObject halfEnergy, fullEnergy;
        [SerializeField] public Image halfEnergyImage, fullEnergyImage;
    
        public enum EnergyState
        {
            FullEnergy,
            HalfEnergy
        }
    
        public EnergyState energyState;
    
        private void Awake()
        {        
            if (Instance != null && Instance != this)
            {
                Destroy(gameObject);
            }
            else
            {
                Instance = this;
            }
            DontDestroyOnLoad(gameObject);
        }
    
        
    
        private void Start()
        {
            sceneFader = GetComponentInChildren<SceneFader>();
        }
    
        public IEnumerator ActivateDeathScreen()
        {
            yield return new WaitForSeconds(0.8f);
            StartCoroutine(sceneFader.Fade(SceneFader.FadeDirection.In));
    
            yield return new WaitForSeconds(0.8f);
            deathScreen.SetActive(true);
        }
    
        public IEnumerator DeactivateDeathScreen()
        {
            yield return new WaitForSeconds(0.5f);
            deathScreen.SetActive(false);
            StartCoroutine(sceneFader.Fade(SceneFader.FadeDirection.Out));
        }
    
        public void SwitchEnergy(EnergyState _energyState)
        {
            switch (_energyState)
            {
                case EnergyState.FullEnergy:
                    halfEnergy.SetActive(false);
                    fullEnergy.SetActive(true);
                    break;
                case EnergyState.HalfEnergy:
                    halfEnergy.SetActive(true);
                    fullEnergy.SetActive(false);
                    break;
            }
    
            energyState = _energyState;
        }
    }
    using System.Collections;
    using System.Collections.Generic;
    using Unity.VisualScripting;
    using UnityEngine;
    using UnityEngine.SceneManagement;
    
    public class GestorPrograma : MonoBehaviour
    {
        public static GestorPrograma Instancia { get; private set; }
        public GameObject audiosorce;
        public string transitionedFromScene;
        public Vector2 platformingRespawnPoint;
        public Vector2 respawnPoint;
        [SerializeField] Flag flag;
        public GameObject shade;
        public bool inicialized=false;
        private string utilizador;
        public string Utilizador {
            get { return utilizador; }
            set { utilizador = value; }
        }
        private int pontuacao;
        public int Pontuacao
        {
            get { return pontuacao; }
            set { pontuacao = value; }
        }
    
        private int id;
        public int ID
        {
            get { return id; }
            set { id = value; }
        }
    
        public float volume;
        public float Volume
        {
            get { return volume; }
            set { volume = value; }
        }
    
        //Verificar se sessão do utilizador já está iniciada
        public bool SessaoIniciada() {
    
            if(utilizador != null){
                return true;
            }
            else
            {
                return false;
            }
        }
        //Fazer reset ao utilizador
        public void LoggOut()
        {
            utilizador = null;
        }
    
        private void Awake()
        {
            if (inicialized == false)
            {
                SaveData.Instance.Initialize();
                inicialized = true;
            }
            
            StartCoroutine(StartingSound());
            if (Instancia != null && Instancia!=this)
            {
                Destroy(gameObject);
            }
            else
            {
                Instancia = this;
            }
    
            if (PlayerController.Instance != null)
            {
                if (PlayerController.Instance.halfEnergy)
                {
                    SaveData.Instance.LoadShadeData();
                    if(SaveData.Instance.sceneWithShade==SceneManager.GetActiveScene().name || SaveData.Instance.sceneWithShade == "")
                    {
                        Instantiate(shade, SaveData.Instance.shadePos, SaveData.Instance.shadeRot);
                    }
                }
            }
            //Método que informa a aplicação para não destruir o objeto marcado quando é carregada uma cena.
            DontDestroyOnLoad(gameObject);
            flag=FindObjectOfType<Flag>();
            SaveScene();
        }
    
        private IEnumerator StartingSound()
        {
           
            AudioSource startingsound= audiosorce.GetComponent<AudioSource>();
              float waitValue = (startingsound.clip.length);
              startingsound.Play();
              yield return new WaitForSeconds(waitValue);
    
                Destroy(audiosorce);
    
        }
    
        public void RespawnPlayer()
        {
            SaveData.Instance.LoadBench();
            if (SaveData.Instance.flagSceneName != null)
            {
                SceneManager.LoadScene(SaveData.Instance.flagSceneName);
            }
            if (SaveData.Instance.flagPos !=null)
            {
                respawnPoint = SaveData.Instance.flagPos;
            }
            else
            {
                respawnPoint = platformingRespawnPoint;
            }
    
            PlayerController.Instance.transform.position=respawnPoint;
    
            StartCoroutine(UI_Manager.Instance.DeactivateDeathScreen());
    
            PlayerController.Instance.Respawned();
        }
    
        public void SaveScene()
        {
            string currentSceneName = SceneManager.GetActiveScene().name;
            SaveData.Instance.sceneName.Add(currentSceneName);
        }
    }
    
    Joseph Tang
    Moderator

    This is a supplementary post written for Part 7 of our Metroidvania series, and it aims to address 2 things:

    1. Missing information in the video, and;
    2. Address common issues / questions that readers run into, and possible solutions for these questions.

    For this part, there are less bugs to be addressed, However, check this post to see if your issue is addressed there:

    [Part 7] SceneFader NullReferenceException and SaveData EndOfStreamException

    For convenience, below are the links to the article and the video:


    Table of Contents

    Missing Information in the Video

    1. Starting fresh game causes player to not be able to land or load save data.
    2. Player respawns at 0 health after leaving game from death. + Moving Player corpse.
    3. Bench interaction is inconsistent.
    4. Capping Player falling speed.

    Common Issues

    1. Unable to click on Respawn Button.

    Missing Information in the Video

    1. Starting fresh game causes player to not be able to land or load save data.

    [Caused by LoadPlayerData() trying to load a save file when the player has never called SavePlayerData(), and thus prevents the rest of Start() to not be called.]

    • Move the called LoadPlayerData() to the bottom of Start(). This will make sure the rest of the Start() code is called and allows the player to play the game even if LoadPlayerData() fails.
    • Move the if statement which checks for half mana, to below the LoadPlayerData().
        // Start is called before the first frame update
        void Start()
        {
            pState = GetComponent();
    
            rb = GetComponent();
            sr = GetComponent();
    
            anim = GetComponent();
    
            SaveData.Instance.LoadPlayerData();
    
            gravity = rb.gravityScale;
    
            if (halfMana)
            {
                UIManager.Instance.SwitchMana(UIManager.ManaState.HalfMana);
            }
            else
            {
                UIManager.Instance.SwitchMana(UIManager.ManaState.FullMana);
            }
    
            Mana = mana;
            manaStorage.fillAmount = Mana;
    
            Health = maxHealth;
    
            SaveData.Instance.LoadPlayerData();
            if (halfMana)
            {
                UIManager.Instance.SwitchMana(UIManager.ManaState.HalfMana);
            }
            else
            {
                UIManager.Instance.SwitchMana(UIManager.ManaState.FullMana);
            }
        }

    2. Player respawns at 0 health after leaving game from death. + Moving Player corpse.

    [Caused by SavePlayerData() taking place after the death of the player, and not respawning to reset this fact.]

    • Add a code to PlayerController.cs Start() to call the RespawnPlayer() method in GameManager.cs, as well as set the pState.alive to false if the Player’s health is at [0]. This will respawn the player if they are loaded in with 0 health.
    • In the Death() method, set the player’s Rigidbody2D to freeze position, and BoxCollider2D to false. This prevents the dead player from acting as a collider or being influenced by gravity to fall or be pushed by other moving entities to a scene transition.
    • In the Respawned() method, reset the Rigidbody2D constraints and reenable the BoxCollider2D.
        // Start is called before the first frame update
        void Start()
        {
            ...
            {
                UIManager.Instance.SwitchMana(UIManager.ManaState.FullMana);
            }
    
            if (Health == 0)
            {
                pState.alive = false;
                GameManager.Instance.RespawnPlayer();
            }
        }
    
        IEnumerator Death()
        {
            ...
            anim.SetTrigger("Death");
            rb.constraints = RigidbodyConstraints2D.FreezePosition;
            GetComponent[BoxCollider2D]().enabled = false;
    
            yield return new WaitForSeconds(0.9f);
            ...
        }
    
        public void Respawned()
        {
            if(!pState.alive)
            {
                rb.constraints = RigidbodyConstraints2D.None;
                rb.constraints = RigidbodyConstraints2D.FreezeRotation;
                GetComponent[BoxCollider2D]().enabled = true;
                pState.alive = true;
                ...
            }
        }

    Note: The “[]” for BoxCollider2D are to be replaced by angle brackets, but I cannot use it on HTML.


    3. Bench interaction is inconsistent.

    [Caused by inefficient code, explanation in link below]

    public class Bench : MonoBehaviour
    {
        bool inRange = false;
        public bool interacted;
    
        // Update is called once per frame
        void Update()
        {
            if(Input.GetButtonDown("Interact") && inRange)
            {
                interacted = true;
    
                SaveData.Instance.benchSceneName = SceneManager.GetActiveScene().name;
                SaveData.Instance.benchPos = new Vector2(gameObject.transform.position.x, gameObject.transform.position.y);
                SaveData.Instance.SaveBench();
                SaveData.Instance.SavePlayerData();
    
                Debug.Log("benched");
            }
        }
    
        private void OnTriggerStay2D(Collider2D _collision)
        {
            if(_collision.CompareTag("Player") && Input.GetButtonDown("Interact"))
            {
                interacted = true;
    
                SaveData.Instance.benchSceneName = SceneManager.GetActiveScene().name;
                SaveData.Instance.benchPos = new Vector2(gameObject.transform.position.x, gameObject.transform.position.y);
                SaveData.Instance.SaveBench();
                SaveData.Instance.SavePlayerData();
    
                Debug.Log("benched");
            }
        }
    
        void OnTriggerEnter2D(Collider2D _collision)
        {
            if(_collision.CompareTag("Player")) inRange = true;
        }
    
        private void OnTriggerExit2D(Collider2D _collision)
        {
            if (_collision.CompareTag("Player"))
            {
                inRange = false;
            }
        }
    }

    4. Capping Player falling speed.

    • Set the player’s velocity in Jump() to it’s current velocity, but clamp the y value to a new variable, maxFallingSpeed.
        [SerializeField] private int maxFallingSpeed; //max fall speed
    
        void Jump()
        {
            ...
    
            if (Input.GetButtonUp("Jump") && rb.velocity.y > 3)
            {
                pState.jumping = false;
    
                rb.velocity = new Vector2(rb.velocity.x, 0);
            }
    
            rb.velocity = new Vector2(rb.velocity.x, Mathf.Clamp(rb.velocity.y, -maxFallingSpeed, rb.velocity.y));
    
            anim.SetBool("Jumping", !Grounded());
        }

    Common Issues

    5. Unable to click on Respawn Button.

    [This solution is only if you are unable to interact with the button (It is not highlighted/changes colour when hovered by mouse and cannot be clicked):]

    • 1. Ensure that your Button is at the bottom of the Canvas Hierarchy [Move the game object with the button to the last position]. This will place your button in front of all other game objects in the canvas, preventing Raycast blocking.
    • 2. Turn off “RayCast Target” on your other game objects.
    • 3. Ensure your scene has an “EventSystem” component.

    How to fix an unclickable Button in Unity’s Canvas UI system


    That will be all for Part 7.
    Hopefully this can help you on any issues you may have. However, if you find that your issues weren’t addressed or is a unique circumstance, you can submit a forum post to go into detail on your problem for further assistance.

    Joseph Tang
    Moderator

    This is a supplementary post written for Part 6 of our Metroidvania series, and it aims to address 1 thing:

    1. Missing information in the video.

    For convenience, below are the links to the article and the video:


    Table of Contents

    Missing Information in the Video

    1. Bat doesn’t stop chasing and gets recoiled infinitely.
    2. Hit by spikes freezes the game.
    3. Bat corpse can be attacked and recoils player.

    Missing Information in the Video

    1. Bat doesn’t stop chasing and gets recoiled infinitely.

    [This is caused by missing code that lets the Bat return to an Idle state from Chase state. Furthermore, there is no velocity change code to change the Bat’s recoil velocity after being recoiled.]

    • In Bat.cs script, add if(_dist > chaseDistance) {ChangeState(EnemyStates.Bat_Idle);} under case EnemyStates.Bat_Chase: before break;. This should change the Bat to an Idle state if the player exits it’s chaseDistance.
    • Add a code under case EnemyStates.Bat_Idle to reset it’s velocity, rb.velocity = new Vector2(0, 0);. This will let the Bat stabilize its velocity and stop recoiling without needing to be stopped by a collision.
                case EnemyStates.Bat_Idle:
                    rb.velocity = new Vector2(0, 0);
                    if(_dist < chaseDistance)
                    {
                        ChangeState(EnemyStates.Bat_Chase);
                    }
                    break;
    
                case EnemyStates.Bat_Chase:
                    rb.MovePosition(Vector2.MoveTowards(transform.position, PlayerController.Instance.transform.position, Time.deltaTime * speed));
    
                    FlipBat();
                    if(_dist > chaseDistance)
                    {
                        ChangeState(EnemyStates.Bat_Idle);
                    }
                    break;

    2. Hit by spikes freezes the game.

    [This is caused by “IEnumerator RespawnPoint()” in Spikes.cs scaling time to 0 and using “WaitForSeconds”]

    • Change all “WaitForSeconds” codes in Spikes.cs to “WaitForSecondsRealTime”. This allows the enumerator to continue as intended while in stopped time without freezing the game.
        IEnumerator RespawnPoint()
        {
            ...
            yield return new WaitForSeconds WaitForSecondsRealtime(1f);
            ...
            yield return new WaitForSeconds WaitForSecondsRealtime(UIManager.Instance.sceneFader.fadeTime);
            ...
        }

    3. Bat corpse can be attacked and recoils player.

    [There are different ways to solve this bug but the simplest is to change the Bat’s layer to anything other than “Attackable”.]

    • First, create a new layer, and call it “Ignore Player” or something identifiable.
    • In Bat.cs, under ChangeCurrentAnimation() add int LayerIgnorePlayer = LayerMask.NameToLayer("Ignore Player"); gameObject.layer = LayerIgnorePlayer; to the If statement for the death state:
    • Go to Edit > Project Settings > Physics2D > Layer Collision Matrix. Here you can set collision based on Layers.
    • Then, turn off collision for both “Default”, “Attackable” & “Ignore Player”. This prevents the corpse from blocking enemies, stacking on each other and player’s movement and attacks.
        protected override void ChangeCurrentAnimation()
        {
            anim.SetBool("Idle", GetCurrentEnemyState == EnemyStates.Bat_Idle);
    
            anim.SetBool("Chase", GetCurrentEnemyState == EnemyStates.Bat_Chase);
    
            anim.SetBool("Stunned", GetCurrentEnemyState == EnemyStates.Bat_Stunned);
    
            if(GetCurrentEnemyState == EnemyStates.Bat_Death)
            {
                anim.SetTrigger("Death");
                int LayerIgnorePlayer = LayerMask.NameToLayer("Ignore Player");
                gameObject.layer = LayerIgnorePlayer;
            }
        }

    Edit > Project Settings > Physics2D > Layer Collision Matrix

    Note: Depending on your game setup, change the Layers to however you feel is needed.


    That will be all for Part 6.
    Hopefully this can help you on any issues you may have. However, if you find that your issues weren’t addressed or is a unique circumstance, you can submit a forum post to go into detail on your problem for further assistance.

    Joseph Tang
    Moderator

    This is a supplementary post written for Part 5 of our Metroidvania series, and it aims to address 2 things:

    1. Missing information in the video, and;
    2. Address common issues / questions that readers run into, and possible solutions for these questions.

    For convenience, below are the links to the article and the video:


    Table of Contents

    Missing Information in the Video

    1. SceneFader NullReferenceException Error
    2. Player hit while in scene transition stops cutscene

    Common Issues

    1. Errors after transitioning scenes
    2. Player casts after healing

    Missing Information in the Video

    1. SceneFader NullReferenceException Error

    [Caused by the SceneTransition script firing before the SceneFader is initialised.]

    • In SceneFader.cs script, change Start() to Awake() while leaving the code fadeOutUIImage = GetComponent<Image>(); the same.
        private void Start Awake()
        {
            fadeOutUIImage = GetComponent<Image>();
        }

    2. Player hit while in scene transition stops cutscene

    [Caused by inefficient code that does not prevent player from taking damage while in cutscene]

    • In PlayerController.cs WalkIntoNewScene(), set pState.invincibility to true in the first line, and set to false in the last.
    • Then, add a line in FlashWhileInvincible() to prevent the player from flashing while pState.cutscene is true.
    • Finally, in SceneTransition.cs OnTriggerEnter2D(), set the player’s pState.invincible to true before loading scene.

    PlayerController.cs

        public IEnumerator WalkIntoNewScene(Vector2 _exitDir, float _delay)
        {
            pstate.invincible = true;
    
            //If exit direction is upwards
            if(_exitDir.y > 0)
            {
                rb.velocity = jumpForce * _exitDir;
            }
    
            //If exit direction requires horizontal movement
            if(_exitDir.x != 0)
            {
                xAxis = _exitDir.x > 0 ? 1 : -1;
                Move();
            }
    
            Flip();
            yield return new WaitForSeconds(_delay);
            pstate.invincible = false;
            pState.cutscene = false;
        }
    
        void FlashWhileInvincible()
        {
            if (pState.invincible && !pState.cutscene)
            {
                if(Time.timeScale > 0.2 && canFlash)
                {
                    StartCoroutine(Flash());
                }
            }
            else
            {
                sr.enabled = true;
            }
        }

    SceneTransition.cs

        private void OnTriggerEnter2D(Collider2D _other)
        {
            if (_other.CompareTag("Player"))
            {
                GameManager.Instance.transitionedFromScene = SceneManager.GetActiveScene().name;
    
                PlayerController.Instance.pState.cutscene = true;
                PlayerController.Instance.pState.invincible = true;
    
                StartCoroutine(UIManager.Instance.sceneFader.FadeAndLoadScene(SceneFader.FadeDirection.In, transitionTo));
            }
        }

    Common Issues

    3. Errors after transitioning scenes.

    [There are multiple problems that can arise from this, some listed here, that can be solved by a note found in the Forum Post:]

    • 1. Mana isn’t being reset
    • 2. Invincibility and time stop is non-functional
    • 3. Hearts healed to full

    Make sure that there is only one scene containing your Prefabs with singleton scripts such as the Player and Canvas. Entering a scene with a second singleton instance will cause NullReferenceException errors as scripts cannot differentiate or single out one script instance from multiple.


    4. Player casts after healing.

    [This is addressed in [Part 6] Article of the Metroidvania, but not in the video.]

    In our PlayerController Script, let’s make a change to reset the castOrHealTimer in the CastSpell() method instead of GetInputs(). This relocation ensures that the timer doesn’t reset when you release the Cast/Heal button before CastSpell() is called.

    PlayerController.cs

        void GetInputs()
        {
            xAxis = Input.GetAxisRaw("Horizontal");
            yAxis = Input.GetAxisRaw("Vertical");
            attack = Input.GetButtonDown("Attack");
    
            if (Input.GetButton("Cast/Heal"))
            {
                castOrHealTimer += Time.deltaTime;
            }
            else
            {
                castOrHealTimer = 0;
            }
        }
    
        void CastSpell()
        {
            if (Input.GetButtonUp("Cast/Heal") && castOrHealTimer <= 0.05f && timeSinceCast >= timeBetweenCast && Mana >= manaSpellCost)
            {
                pState.casting = true;
                timeSinceCast = 0;
                StartCoroutine(CastCoroutine());
            }
            else
            {
                timeSinceCast += Time.deltaTime;
            }
    
            if (!Input.GetButton("Cast/Heal"))
            {
                castOrHealTimer = 0;
            }
    
            if(Grounded())
            {
                //disable downspell if on the ground
                downSpellFireball.SetActive(false);
            }
            ...
        }

    Note: Change the if parameter of both Heal() and CastSpell() to a higher float value if needed as 0.05f is a very limited time for a player to tap and fire a spell.


    That will be all for Part 5.
    Hopefully this can help you on any issues you may have. However, if you find that your issues weren’t addressed or is a unique circumstance, you can submit a forum post to go into detail on your problem for further assistance.

    #14535
    Joseph Tang
    Moderator

    So far, your code seems correct. So let’s start looking through your Scene’s inspector while in this death screen that can’t be removed.
    Please open up the Canvas and look through it’s inspector values while you are dead, as well as the respawn button under “Death Screen” for it’s event system to see if it still has the GameManager on it.

    I’ll also need you to manually deactivate the death screen during the bug and see if the player has respawned, thus meaning for some reason, only the DeathScreen is still active, or everything for the respawn did not function.

    Then, we’re going to need to see if all parts of the code are being called correctly, add print code’s to your GameManager.cs RespawnPlayer():

        public void RespawnPlayer()
        {
            if(bench.interacted) {
                respawnPoint = bench.transform.position;
            } else {
                respawnPoint = platformRespawnPoint;
            }
            playerController.Instance.transform.position = respawnPoint;
            print("player position set")
            StartCoroutine(UIManager.Instance.DeactivateDeathScreen());
            print("deactivate death called")
            playerController.Instance.Respawned();
            print("player called to respawn")
        }

    Then do the same for your UIManager.cs:

        public IEnumerator DeactivateDeathScreen() {
            print("UIman deactive started")
            yield return new WaitForSeconds(0.5f);
            deathScreen.SetActive(false);
            print("screen setactive false")
            StartCoroutine(sceneFader.Fade(SceneFader.FadeDirection.Out));
            print("fade called")
        }
    #14533
    Alex
    Participant

    I was, in fact, missing the EventSystem in my other scenes; adding it allowed me to be able to click the respawn button, but it still did not spawn me back in. This only occurs in cave 2 and Cave 3. Cave 1 is working perfectly fine.

    Demo

    View post on imgur.com

    playerController.cs

    using System.Collections;
    using System.Collections.Generic;
    using Unity.VisualScripting;
    using UnityEngine;
    using UnityEngine.UI;
    
    public class playerController : MonoBehaviour
    {
        [Header("Horizontal Movement Settings")]
        [SerializeField] private float walkSpeed = 1;
        [Space(5)]
    
        [Header("Vertical Movement Settings")]
        [SerializeField] private float jumpForce = 45;
        private int jumpBufferCounter = 0;
        [SerializeField] private int jumpBufferFrames;
        private float coyoteTimeCounter = 0;
        [SerializeField] private float coyoteTime;
        private int airJumpCounter = 0;
        [SerializeField] private int maxAirJumps;
        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 Settings")]
        bool attack = false;
        float timeBetweenAttack, timeSinceAttack;
        [SerializeField] Transform SideAttackTransform, UpAttackTransform, DownAttackTransform;
        [SerializeField] Vector2 SideAttackArea, UpAttackArea, DownAttackArea;
        [SerializeField] LayerMask attackableLayer;
        [SerializeField] float damage;
        [SerializeField] GameObject slashEffect;
    
        bool restoreTime;
        float restoreTimeSpeed;
        [Space(5)]
    
        [Header("Recoil Settings")]
        [SerializeField] int recoilXSteps = 5;
        [SerializeField] int recoilYSteps = 5;
        [SerializeField] float recoilXSpeed = 100;
        [SerializeField] float recoilYSpeed = 100;
        private int stepsXRecoiled, stepsYRecoiled;
        [Space(5)]
    
        [Header("Health Settings")]
        public int health;
        public int maxHealth;
        [SerializeField] GameObject bloodSpurt;
        [SerializeField] float hitFlashSpeed;
        public delegate void OnHealthChangedDelegate();
        [HideInInspector] public OnHealthChangedDelegate onHealthChangedCallback;
        float healTimer;
        [SerializeField] float timeToHeal;
        [Space(5)]
    
        [Header("Mana Settings")]
        [SerializeField] Image manaStorage;
        [SerializeField] float mana;
        [SerializeField] float manaDrainSpeed;
        [SerializeField] float manaGain;
        [Space(5)]
    
        [Header("Spell Settings")]
        // Spell Stats
        [SerializeField] float manaSpellCost = 0.3f;
        [SerializeField] float timeBetweenCast = 0.5f;
        [SerializeField] float spellDamage; // Up Spell Explosion and Down Spell Fireball
        [SerializeField] float downSpellForce; // Dive Spell
    
        // Spell Cast Objects
        [SerializeField] GameObject sideSpellFireball;
        [SerializeField] GameObject upSpellExplosion;
        [SerializeField] GameObject downSpellFireball;
        float timeSinceCast;
        float castOrHealtimer;
        [Space(5)]
    
        [HideInInspector] public PlayerStateList pState;
        [HideInInspector] public Rigidbody2D rb;
        Animator anim;
        private SpriteRenderer sr;
        private float xAxis, yAxis;
        public static playerController Instance;
    
        private void Awake()
        {
            if (Instance != null && Instance != this)
            {
                Destroy(gameObject);
            }
            else
            {
                Instance = this;
            }
            DontDestroyOnLoad(gameObject);
        }
    
        // Start is called before the first frame update
        void Start()
        {
            pState = GetComponent<PlayerStateList>();
            rb = GetComponent<Rigidbody2D>();
            sr = GetComponent<SpriteRenderer>();
            anim = GetComponent<Animator>();
            gravity = rb.gravityScale;
            Mana = mana;
            manaStorage.fillAmount = Mana;
            if (Health == 0) {
                pState.alive = false;
                GameManager.Instance.RespawnPlayer();
            }
            Health = maxHealth;
        }
    
        private void OnDrawGizmos()
        {
            Gizmos.color = Color.red;
            Gizmos.DrawWireCube(SideAttackTransform.position, SideAttackArea);
            Gizmos.DrawWireCube(UpAttackTransform.position, UpAttackArea);
            Gizmos.DrawWireCube(DownAttackTransform.position, DownAttackArea);
    
        }
    
        // Update is called once per frame
        void Update()
        {
            if (pState.cutscene) return;
            if (pState.alive) {
                GetInputs();
            }
            UpdateJumpVariables();
            RestoreTimeScale();
    
            if (pState.dashing) return;
            FlashWhileInvincible();
            if (pState.alive) {
                Move();
                Heal();
                CastSpell();
                Flip();
                Jump();
                StartDash();
                Attack();
            }
            if (pState.healing) return;
        }
    
        private void OnTriggerEnter2D(Collider2D _other) // For up and down cast spell
        {
            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();
        }
    
        void GetInputs()
        {
            xAxis = Input.GetAxisRaw("Horizontal");
            yAxis = Input.GetAxisRaw("Vertical");
            attack = Input.GetButtonDown("Attack");
    
            if (Input.GetButton("Cast/Heal"))
            {
                castOrHealtimer += Time.deltaTime;
            }
        }
    
        void Flip()
        {
            if (xAxis < 0)
            {
                // transform.localScale = new Vector2(-1, transform.localScale.y);
                transform.eulerAngles = new Vector2(0, 180);
                pState.lookingRight = false;
            }
            else if (xAxis > 0)
            {
                // transform.localScale = new Vector2(1, transform.localScale.y);
                transform.eulerAngles = new Vector2(0, 0);
                pState.lookingRight = true;
            }
        }
    
        private void Move()
        {
            rb.velocity = new Vector2(walkSpeed * xAxis, rb.velocity.y);
            anim.SetBool("Walking", rb.velocity.x != 0 && Grounded());
        }
    
        void StartDash()
        {
            if (Input.GetButtonDown("Dash") && canDash && !dashed)
            {
                StartCoroutine(Dash());
                dashed = true;
            }
    
            if (Grounded())
            {
                dashed = false;
            }
        }
    
        IEnumerator Dash()
        {
            canDash = false;
            pState.dashing = true;
            anim.SetTrigger("Dashing");
            rb.gravityScale = 0;
            int _dir = pState.lookingRight ? 1 : -1;
            rb.velocity = new Vector2(_dir * dashSpeed, 0);
            if (Grounded()) Instantiate(dashEffect, transform);
            yield return new WaitForSeconds(dashTime);
            rb.gravityScale = gravity;
            pState.dashing = false;
            yield return new WaitForSeconds(dashCooldown);
            canDash = true;
        }
    
        public IEnumerator WalkIntoNewScene(Vector2 _exitDir, float _delay)
        {
            // IF exit is upwards
            if (_exitDir.y > 0)
            {
                rb.velocity = jumpForce * _exitDir;
            }
    
            // IF exit direction required horizontal movement
            if (_exitDir.x != 0)
            {
                xAxis = _exitDir.x > 0 ? 1 : -1;
                Move();
            }
            yield return new WaitForSeconds(_delay);
            pState.cutscene = false;
        }
    
        void Attack()
        {
            timeSinceAttack += Time.deltaTime;
            if (attack && timeSinceAttack >= timeBetweenAttack)
            {
                timeSinceAttack = 0;
                anim.SetTrigger("Attacking");
    
                if (yAxis == 0 || yAxis < 0 && Grounded())
                {
                    int _recoilLeftOrRight = pState.lookingRight ? 1 : -1;
                    Hit(SideAttackTransform, SideAttackArea, ref pState.recoilingX, Vector2.right * _recoilLeftOrRight, recoilXSpeed);
                    Instantiate(slashEffect, SideAttackTransform);
                }
                else if (yAxis > 0)
                {
                    Hit(UpAttackTransform, UpAttackArea, ref pState.recoilingY, Vector2.up, recoilYSpeed);
                    SlashEffectAtAngle(slashEffect, 80, UpAttackTransform);
                }
                else if (yAxis < 0 && !Grounded())
                {
                    Hit(DownAttackTransform, DownAttackArea, ref pState.recoilingY, Vector2.down, recoilYSpeed);
                    SlashEffectAtAngle(slashEffect, -90, DownAttackTransform);
                }
            }
        }
    
        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;
                    }
                }
            }
        }
    
        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.velocity = new Vector2(-recoilXSpeed, 0);
                }
                else
                {
                    rb.velocity = new Vector2(recoilXSpeed, 0);
                }
            }
    
            if (pState.recoilingY)
            {
                rb.gravityScale = 0;
                if (yAxis < 0)
                {
                    rb.velocity = new Vector2(rb.velocity.x, recoilYSpeed);
                }
                else
                {
                    rb.velocity = new Vector2(rb.velocity.x, -recoilYSpeed);
                }
                airJumpCounter = 0;
            }
            else
            {
                rb.gravityScale = gravity;
            }
    
            // Stop Recoil
            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;
        }
    
        void FlashWhileInvincible()
        {
            sr.material.color = pState.invincible ?
                Color.Lerp(Color.white, Color.black, Mathf.PingPong(Time.time * hitFlashSpeed, 1.0f)) :
                Color.white;
        }
    
        void RestoreTimeScale()
        {
            if (restoreTime)
            {
                if (Time.timeScale < 1)
                {
                    Time.timeScale += Time.deltaTime * restoreTimeSpeed;
                }
                else
                {
                    Time.timeScale = 1;
                    restoreTime = false;
                }
            }
        }
    
        public void HitStopTime(float _newTimeScale, int _restoreSpeed, float _delay)
        {
            restoreTimeSpeed = _restoreSpeed;
            Time.timeScale = _newTimeScale;
            if (_delay > 0)
            {
                StopCoroutine(StartTimeAgain(_delay));
                StartCoroutine(StartTimeAgain(_delay));
            }
            else
            {
                restoreTime = true;
            }
        }
    
        IEnumerator StartTimeAgain(float _delay)
        {
            restoreTime = true;
            yield return new WaitForSeconds(_delay);
        }
    
        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.8f); 
            StartCoroutine(UIManager.Instance.ActivateDeathScreen());
        }
    
        public void Respawned()
        {
            if(!pState.alive)
            {
                rb.constraints = RigidbodyConstraints2D.None;
                rb.constraints = RigidbodyConstraints2D.FreezeRotation;
                GetComponent<BoxCollider2D>().enabled = true;
                pState.alive = true;
                Health = maxHealth;
                anim.Play("player_Idle");
            }
        }
    
        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.05f && Health < maxHealth && Mana > 0 && !pState.jumping && !pState.dashing)
            {
                pState.healing = true;
                anim.SetBool("Healing", true);
    
                // Healing
                healTimer += Time.deltaTime;
                if (healTimer >= timeToHeal)
                {
                    Health++;
                    healTimer = 0;
                }
    
                // Drain mana
                Mana -= Time.deltaTime * manaDrainSpeed;
            }
            else
            {
                pState.healing = false;
                anim.SetBool("Healing", false);
                healTimer = 0;
            }
        }
    
        float Mana
        {
            get { return mana; }
            set
            {
                if (mana != value)
                {
                    mana = Mathf.Clamp(value, 0, 1);
                    manaStorage.fillAmount = Mana;
                }
            }
        }
    
        void CastSpell()
        {
            if (Input.GetButtonUp("Cast/Heal") && castOrHealtimer <= 0.15f && timeSinceCast >= timeBetweenCast && Mana >= manaSpellCost)
            {
                pState.casting = true;
                timeSinceCast = 0;
                StartCoroutine(CastCoroutine());
            }
            else
            {
                timeSinceCast += Time.deltaTime;
            }
    
            if (!Input.GetButton("Cast/Heal"))
            {
                castOrHealtimer = 0;
            }
    
            if (Grounded())
            {
                // Disable down spell if on the ground
                downSpellFireball.SetActive(false);
            }
            // If down spell is active, force player down until grounded
            if (downSpellFireball.activeInHierarchy)
            {
                rb.velocity += downSpellForce * Vector2.down;
            }
        }
    
        IEnumerator CastCoroutine()
        {
            anim.SetBool("Casting", true);
            yield return new WaitForSeconds(0.15f);
    
            // Side Spell Cast
            if (yAxis == 0 || (yAxis < 0 && Grounded()))
            {
                GameObject _fireBall = Instantiate(sideSpellFireball, SideAttackTransform.position, Quaternion.identity);
    
                // Flip Fireball
                if (pState.lookingRight)
                {
                    _fireBall.transform.eulerAngles = Vector3.zero; // if facing right, fireball continues as per normal
                }
                else
                {
                    _fireBall.transform.eulerAngles = new Vector2(_fireBall.transform.eulerAngles.x, 180);
                    // if not facing right, rotate the fireball 180 deg
                }
                pState.recoilingX = true;
            }
            // Up Spell Cast
            else if (yAxis > 0)
            {
                Instantiate(upSpellExplosion, transform);
                rb.velocity = Vector2.zero;
            }
    
            // Down  Spell Cast
            else if (yAxis < 0 && !Grounded())
            {
                downSpellFireball.SetActive(true);
            }
    
            Mana -= manaSpellCost;
            yield return new WaitForSeconds(0.35f);
            anim.SetBool("Casting", false);
            pState.casting = false;
        }
    
        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.velocity = new Vector3(rb.velocity.x, jumpForce);
                pState.jumping = true;
            }
    
            if (!Grounded() && airJumpCounter < maxAirJumps && Input.GetButtonDown("Jump"))
            {
                pState.jumping = true;
                airJumpCounter++;
                rb.velocity = new Vector3(rb.velocity.x, jumpForce);
            }
    
            if (Input.GetButtonUp("Jump") && rb.velocity.y > 3)
            {
                rb.velocity = new Vector2(rb.velocity.x, 0);
                pState.jumping = false;
            }
    
            anim.SetBool("Jumping", !Grounded());
        }
    
        void UpdateJumpVariables()
        {
            if (Grounded())
            {
                pState.jumping = false;
                coyoteTimeCounter = coyoteTime;
                airJumpCounter = 0;
            }
            else
            {
                coyoteTimeCounter -= Time.deltaTime;
            }
    
            if (Input.GetButtonDown("Jump"))
            {
                jumpBufferCounter = jumpBufferFrames;
            }
            else
            {
                jumpBufferCounter--;
            }
        }
    }
    

    GameManager.cs

    using System.Collections;
    using System.Collections.Generic;
    using Unity.VisualScripting;
    using UnityEngine;
    
    public class GameManager : MonoBehaviour
    {
        public string transitionedFromScene;
        public Vector2 platformRespawnPoint;
        public Vector2 respawnPoint;
        [SerializeField] Bench bench;
    
        public static GameManager Instance { get; private set; }
    
        private void Awake()
        {
            if (Instance != null && Instance != this)
            {
                Destroy(gameObject);
            }
            else
            {
                Instance = this;
            }
            DontDestroyOnLoad(gameObject);
            bench = FindAnyObjectByType<Bench>();
        }
    
        public void RespawnPlayer()
        {
            if(bench.interacted) {
                respawnPoint = bench.transform.position;
            } else {
                respawnPoint = platformRespawnPoint;
            }
            playerController.Instance.transform.position = respawnPoint;
            StartCoroutine(UIManager.Instance.DeactivateDeathScreen());
            playerController.Instance.Respawned();
        }
    }
    

    UIManager.cs

    using System.Collections;
    using System.Collections.Generic;
    using Unity.VisualScripting;
    using UnityEditor.SearchService;
    using UnityEngine;
    
    public class UIManager : MonoBehaviour
    {
        public SceneFader sceneFader;
        public static UIManager Instance; 
        [SerializeField] GameObject deathScreen;
    
        private void Awake() {
            if(Instance != null && Instance != this) {
                Destroy(gameObject);
            }
            else {
                Instance = this;
            }
            DontDestroyOnLoad(gameObject);
            sceneFader = GetComponentInChildren<SceneFader>();
        }
    
        public IEnumerator ActivateDeathScreen() {
            yield return new WaitForSeconds(0.8f);
            StartCoroutine(sceneFader.Fade(SceneFader.FadeDirection.In));
    
            yield return new WaitForSeconds(0.9f);
            deathScreen.SetActive(true);
        }
    
        public IEnumerator DeactivateDeathScreen() {
            yield return new WaitForSeconds(0.5f);
            deathScreen.SetActive(false);
            StartCoroutine(sceneFader.Fade(SceneFader.FadeDirection.Out));
        }
    }
    
    #14157
    Elvin Sim
    Participant

    SceneFader Script

    View post on imgur.com

    #13983
    Pete
    Participant

    As I tried your “1) Move your UpdateCameraYDampForPlayerFall(); under your Move() in Update()” the character can move horizontally but other movement is not working ex) attacking, jumping, skills

    and after I attached SceneFader to UIManager script, I think scene transitioning is working.

    But still there are repetitive error message on console.

    I’ll show the video of result.

    #13964
    Joseph Tang
    Moderator

    For the SceneFader error:
    Add this code under UIManager.cs:

    private void Awake()
        {
            if (Instance != null && Instance != this)
            {
                Destroy(gameObject);
            }
            else
            {
                Instance = this;
            }
            DontDestroyOnLoad(gameObject);
            sceneFader = GetComponentInChildren();
        }

    As for your movement and camera issue. I’ll need to check so give me a moment.

    #13963
    Pete
    Participant

    Here’s my code

    “UIManager.cs”

    
    using System.Collections;
    using System.Collections.Generic;
    using UnityEngine;
    
    public class UIManager : MonoBehaviour
    {
        public static UIManager Instance;
    
        private void Awake()
        {
            if (Instance != null && Instance != this)
            {
                Destroy(gameObject);
            }
            else
            {
                Instance = this;
            }
            DontDestroyOnLoad(gameObject);
        }
    
        public SceneFader sceneFader;
    
        private void Start()
        {
            sceneFader = GetComponentInChildren<SceneFader>();
        }
    }
    

    “PlayerController.cs”

    
    using System.Collections;
    using System.Collections.Generic;
    using UnityEngine;
    using UnityEngine.UIElements;
    using UnityEngine.UI;
    using Unity.VisualScripting;
    
    public class PlayerController : MonoBehaviour
    {
        [Header("Horizontal Movement Settings:")]
        [SerializeField] private float walkSpeed = 1; //sets the players movement speed on the ground
        [Space(5)]
    
        [Header("Vertical Movement Settings")]
        [SerializeField] private float jumpForce = 45f; //sets how hight the player can jump
    
        private int jumpBufferCounter = 0; //stores the jump button input
        [SerializeField] private int jumpBufferFrames; //sets the max amount of frames the jump buffer input is stored
    
        private float coyoteTimeCounter = 0; //stores the Grounded() bool
        [SerializeField] private float coyoteTime; ////sets the max amount of frames the Grounded() bool is stored
    
        private int airJumpCounter = 0; //keeps track of how many times the player has jumped in the air
        [SerializeField] private int maxAirJumps; //the max no. of air jumps
    
        private float gravity; //stores the gravity scale at start
        [Space(5)]
    
        [Header("Ground Check Settings:")]
        [SerializeField] private Transform groundCheckPoint; //point at which ground check happens
        [SerializeField] private float groundCheckY = 0.2f; //how far down from ground chekc point is Grounded() checked
        [SerializeField] private float groundCheckX = 0.5f; //how far horizontally from ground chekc point to the edge of the player is
        [SerializeField] private LayerMask whatIsGround; //sets the ground layer
        [Space(5)]
    
        [Header("Dash Settings")]
        [SerializeField] private float dashSpeed; //speed of the dash
        [SerializeField] private float dashTime; //amount of time spent dashing
        [SerializeField] private float dashCooldown; //amount of time between dashes
        [SerializeField] GameObject dashEffect;
        private bool canDash = true, dashed;
        [Space(5)]
    
        [Header("Attack Settings:")]
        [SerializeField] private Transform SideAttackTransform; //the middle of the side attack area
        [SerializeField] private Vector2 SideAttackArea; //how large the area of side attack is
    
        [SerializeField] private Transform UpAttackTransform; //the middle of the up attack area
        [SerializeField] private Vector2 UpAttackArea; //how large the area of side attack is
    
        [SerializeField] private Transform DownAttackTransform; //the middle of the down attack area
        [SerializeField] private Vector2 DownAttackArea; //how large the area of down attack is
    
        [SerializeField] private LayerMask attackableLayer; //the layer the player can attack and recoil off of
    
        private float timeBetweenAttack, timeSinceAttck;
    
        [SerializeField] private float damage; //the damage the player does to an enemy
    
        [SerializeField] private GameObject slashEffect; //the effect of the slashs
    
        bool restoreTime;
        float restoreTimeSpeed;
        [Space(5)]
    
      
    
        [Header("Recoil Settings:")]
        [SerializeField] private int recoilXSteps = 5; //how many FixedUpdates() the player recoils horizontally for
        [SerializeField] private int recoilYSteps = 5; //how many FixedUpdates() the player recoils vertically for
    
        [SerializeField] private float recoilXSpeed = 100; //the speed of horizontal recoil
        [SerializeField] private float recoilYSpeed = 100; //the speed of vertical recoil
    
        private int stepsXRecoiled, stepsYRecoiled; //the no. of steps recoiled horizontally and verticall
        [Space(5)]
    
        [Header("Health Settings")]
        public int health;
        public int maxHealth;
        [SerializeField] GameObject bloodSpurt;
        [SerializeField] float hitFlashSpeed;
    
        public delegate void OnHealthChangedDelegate();
        [HideInInspector] public OnHealthChangedDelegate onHealthChangedCallback;
        [Space(5)]
    
        float healTimer;
        [SerializeField] float timeToHeal;
        [Space(5)]
    
        [Header("Mana Settings")]
        [SerializeField] UnityEngine.UI.Image manaStorage;
    
        [SerializeField] float mana;
        [SerializeField] float manaDrainSpeed;
        [SerializeField] float manaGain;
        [Space(5)]
    
        [Header("Spell Settings")]
        [SerializeField] float manaSpellCost = 0.3f;
        [SerializeField] float timeBetweenCast = 0.5f;
        [SerializeField] float spellDamage; //upspellexplosion and downspellexplosion only
        [SerializeField] float downSpellForce; //desolate dive only
        //spell cast objects
        [SerializeField] GameObject sideSpellFireball;
        [SerializeField] GameObject upSpellFireball;
        [SerializeField] GameObject downSpellFireball;
        float timeSinceCast;
        float castOrHealthtimer;
        [Space(5)]
    
        [Header("Camera Stuff")]
        [SerializeField] private float playerFallSpeedThreshold = -10;
    
        [HideInInspector] public PlayerStateList pState;
        [HideInInspector] public Rigidbody2D rb;
        private Animator anim;
        private SpriteRenderer sr;
    
        //Input Variables
        private float xAxis, yAxis;
        private bool attack = false;
        private bool canFlash = true;
    
        public static PlayerController Instance;
    
        private void Awake()
        {
            if (Instance != null && Instance != this)
            {
                Destroy(gameObject);
            }
            else
            {
                Instance = this;
            }
            DontDestroyOnLoad(gameObject);
        }
    
        // Start is called before the first frame update
        void Start()
        {
            pState = GetComponent<PlayerStateList>();
    
            rb = GetComponent<Rigidbody2D>();
            sr = GetComponent<SpriteRenderer>();
    
            anim = GetComponent<Animator>();
    
            gravity = rb.gravityScale;
    
            Mana = mana;
            manaStorage.fillAmount = Mana;
    
            health = maxHealth;
        }
    
        private void OnDrawGizmos()
        {
            Gizmos.color = Color.red;
            Gizmos.DrawWireCube(SideAttackTransform.position, SideAttackArea);
            Gizmos.DrawWireCube(UpAttackTransform.position, UpAttackArea);
            Gizmos.DrawWireCube(DownAttackTransform.position, DownAttackArea);
        }
    
        // Update is called once per frame
        void Update()
        {
            if (pState.cutscene) return;
    
            GetInputs();
            UpdateJumpVariables();
            UpdateCameraYDampForPlayerFall();
            RestoreTimeScale();
    
            if (pState.dashing) return;
            Flip();
            Move();
            Jump();
            StartDash();
            Attack();
            FlashWhileInvincible();
            Heal();
            CastSpell();
        }
    
        private void OnTriggerEnter2D(Collider2D _other) //for up and down cast spell
        {
            if(_other.GetComponent<Enemy>() != null && pState.casting)
            {
                _other.GetComponent<Enemy>().EnemyGetsHit(spellDamage, (_other.transform.position - transform.position).normalized, -recoilYSpeed); 
            }
        }
    
        private void FixedUpdate()
        {
            if (pState.cutscene) return;
    
            if (pState.dashing) return;
            Recoil();
        }
    
        void GetInputs()
        {
            xAxis = Input.GetAxisRaw("Horizontal");
            yAxis = Input.GetAxisRaw("Vertical");
            attack = Input.GetButtonDown("Attack");
    
            if(Input.GetButtonDown("Cast/Heal"))
            {
                castOrHealthtimer += Time.deltaTime;
            }
            else
            {
                castOrHealthtimer = 0;
            }
        }
    
        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.velocity = new Vector2(walkSpeed * xAxis, rb.velocity.y);
            anim.SetBool("Walking", rb.velocity.x != 0 && Grounded());
        }
    
        void UpdateCameraYDampForPlayerFall() 
        {
            //if falling past a certain speed threshold
            if(rb.velocity.y < playerFallSpeedThreshold && !CameraManager.Instance.isLerpingYDamping && !CameraManager.Instance.hasLerpedYDamping)
            {
                StartCoroutine(CameraManager.Instance.LerpYDamping(true));
            }
            //if standing still or moving up
            if(rb.velocity.y >= 0 && !CameraManager.Instance.isLerpingYDamping && CameraManager.Instance.hasLerpedYDamping)
            {
                //reset camera function
                CameraManager.Instance.hasLerpedYDamping = false;
                StartCoroutine(CameraManager.Instance.LerpYDamping(false));
            }
        }
    
        void StartDash()
        {
            if (Input.GetButtonDown("Dash") && canDash && !dashed)
            {
                StartCoroutine(Dash());
                dashed = true;
            }
    
            if (Grounded())
            {
                dashed = false;
            }
        }
    
        IEnumerator Dash()
        {
            canDash = false;
            pState.dashing = true;
            anim.SetTrigger("Dashing");
            rb.gravityScale = 0;
            int _dir = pState.lookingRight ? 1 : -1;
            rb.velocity = new Vector2(_dir * dashSpeed, 0);
            if (Grounded()) Instantiate(dashEffect, transform);
            yield return new WaitForSeconds(dashTime);
            rb.gravityScale = gravity;
            pState.dashing = false;
            yield return new WaitForSeconds(dashCooldown);
            canDash = true;
        }
    
        public IEnumerator WalkIntoNewScene(Vector2 _exitDir, float _delay)
        {
            //If exit direction is upwards
            if (_exitDir.y != 0)
            {
                rb.velocity = jumpForce * _exitDir;
            }
    
            //If exit direction requires horizontal movement
            if (_exitDir.x != 0)
            {
                xAxis = _exitDir.x > 0 ? 1 : -1;
    
                Move();
            }
    
            Flip();
            yield return new WaitForSeconds(_delay);
            pState.cutscene = false;
        }
    
        void Attack()
        {
            timeSinceAttck += Time.deltaTime;
            if (attack && timeSinceAttck >= timeBetweenAttack)
            {
                timeSinceAttck = 0;
                anim.SetTrigger("Attacking");
    
                if (yAxis == 0 || yAxis < 0 && Grounded())
                {
                    int _recoilLeftOrRight = pState.lookingRight ? 1 : -1;
    
                    Hit(SideAttackTransform, SideAttackArea, ref pState.recoilingX, Vector2.right * _recoilLeftOrRight, recoilXSpeed);
                    Instantiate(slashEffect, SideAttackTransform);
                }
                else if (yAxis > 0)
                {
                    Hit(UpAttackTransform, UpAttackArea, ref pState.recoilingY, Vector2.up, recoilYSpeed);
                    SlashEffectAtAngle(slashEffect, 80, UpAttackTransform);
                }
                else if (yAxis < 0 && !Grounded())
                {
                    Hit(DownAttackTransform, DownAttackArea, ref pState.recoilingY, Vector2.down, recoilYSpeed);
                    SlashEffectAtAngle(slashEffect, -90, DownAttackTransform);
                }
            }
    
        }
        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>().EnemyGetsHit(damage, _recoilDir, _recoilStrength);
    
                    if (objectsToHit[i].CompareTag("Enemy"))
                    {
                        Mana += manaGain;
                    }
                }
            }
        }
        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.velocity = new Vector2(-recoilXSpeed, 0);
                }
                else
                {
                    rb.velocity = new Vector2(recoilXSpeed, 0);
                }
            }
    
            if (pState.recoilingY)
            {
                rb.gravityScale = 0;
                if (yAxis < 0)
                {
                    rb.velocity = new Vector2(rb.velocity.x, recoilYSpeed);
                }
                else
                {
                    rb.velocity = new Vector2(rb.velocity.x, -recoilYSpeed);
                }
                airJumpCounter = 0;
            }
            else
            {
                rb.gravityScale = gravity;
            }
    
            //stop recoil
            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)
        {
            Health -= Mathf.RoundToInt(_damage);
            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.1f);
            canFlash = true;
        }
    
        void FlashWhileInvincible()
        {
            if (pState.invincible)
            {
                if (Time.timeScale > 0.2 && canFlash)
                {
                    StartCoroutine(Flash());
                }
            }
            else
            {
                sr.enabled = true;
            }
        }
        void RestoreTimeScale()
        {
            if (restoreTime)
            {
                if (Time.timeScale < 1)
                {
                    Time.timeScale += Time.deltaTime * restoreTimeSpeed;
                }
                else
                {
                    Time.timeScale = 1;
                    restoreTime = false;
                }
            }
        }
        public void HitStopTime(float _newTimeScale, int _restoreSpeed, float _delay)
        {
            restoreTimeSpeed = _restoreSpeed;
            if (_delay > 0)
            {
                StopCoroutine(StartTimeAgain(_delay));
                StartCoroutine(StartTimeAgain(_delay));
            }
            else
            {
                restoreTime = true;
            }
            Time.timeScale = _newTimeScale;
        }
        IEnumerator StartTimeAgain(float _delay)
        {
            restoreTime = true;
            yield return new WaitForSeconds(_delay);
        }
        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") && castOrHealthtimer >  0.05f && Health < maxHealth && Mana > 0 && !pState.jumping && !pState.dashing)
            {
                pState.healing = true;
                anim.SetBool("Healing", true);
    
                //healing
                healTimer += Time.deltaTime;
                if(healTimer >= timeToHeal)
                {
                    Health++;
                    healTimer = 0;
                }
                //drain mana
                Mana -= Time.deltaTime * manaDrainSpeed;
            }
            else
            {
                pState.healing = false;
                anim.SetBool("Healing", false);
                healTimer = 0;
            }
    
            
        }
    
        float Mana
        {
            get { return mana; }
            set
            {
                //if mana stats change
                if (mana != value)
                {
                    mana = Mathf.Clamp(value, 0, 1);
                    manaStorage.fillAmount = Mana;
                }
            }
        }
    
        void CastSpell()
        {
            if(Input.GetButtonUp("Cast/Heal") && castOrHealthtimer <= 0.05f && timeSinceCast >= timeBetweenCast && Mana >= manaSpellCost)
            {
                pState.casting = true;
                timeSinceCast = 0;
                StartCoroutine(CastCoroutine());
            }
            else
            {
                timeSinceCast += Time.deltaTime;
            }
    
            if(Grounded())
            {
                //disable downspell if on the ground
                downSpellFireball.SetActive(false);
            }
            //if down spell is active, force player down until grounded
            if (downSpellFireball.activeInHierarchy)
            {
                rb.velocity += 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 fireball
                if(pState.lookingRight)
                {
                    _fireBall.transform.eulerAngles = Vector3.zero; //if facing right, fireball continues as per normal
                }
                else
                {
                    _fireBall.transform.eulerAngles = new Vector2(_fireBall.transform.eulerAngles.x, 180);
                    //if not facing right, rotate the fireball 180 degree
                }
                pState.recoilingX = true;
            }
    
            //up cast
            else if (yAxis > 0)
            {
                Instantiate(upSpellFireball, transform);
                rb.velocity = Vector2.zero;
            }
    
            //down cast
            else if (yAxis < 0 && !Grounded())
            {
                downSpellFireball.SetActive(true);
            }
    
            Mana -= manaSpellCost;
            yield return new WaitForSeconds(0.09f);
            anim.SetBool("Casting", false);
            pState.casting = false;
        }
    
        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.velocity = new Vector3(rb.velocity.x, jumpForce);
    
                pState.jumping = true;
            }
            else if (!Grounded() && airJumpCounter < maxAirJumps && Input.GetButtonDown("Jump"))
            {
                pState.jumping = true;
    
                airJumpCounter++;
    
                rb.velocity = new Vector3(rb.velocity.x, jumpForce);
            }
    
            if (Input.GetButtonUp("Jump") && rb.velocity.y > 3)
            {
                rb.velocity = new Vector2(rb.velocity.x, 0);
    
                pState.jumping = false;
            }
    
            anim.SetBool("Jumping", !Grounded());
        }
    
        void UpdateJumpVariables()
        {
            if (Grounded())
            {
                pState.jumping = false;
                coyoteTimeCounter = coyoteTime;
                airJumpCounter = 0;
            }
            else
            {
                coyoteTimeCounter -= Time.deltaTime;
            }
    
            if (Input.GetButtonDown("Jump"))
            {
                jumpBufferCounter = jumpBufferFrames;
            }
            else
            {
                jumpBufferCounter--;
            }
        }
    }
    
    #13961
    Joseph Tang
    Moderator

    As can be seen in the video, your Canvas has nothing set as it’s Scene Fader.

    As for the fact that you cannot move, I believe something went wrong with your RigidBody2D which is also causing the NullReferenceException Errors for your UpdateCameraYDampForPlayerFall() method.

    Can you copy and paste the entire code for your UIManager.cs & your PlayerController.cs?

    At the same time, check if the Scene Fader game object under your Canvas has a SceneFader.cs script on it.

    #13955
    Joseph Tang
    Moderator

    Hi sorry, but I’ll need more videos, particularly, you showing how the Canvas Prefab inspector is like while in game + Moving out of the camera bounds + scene transitions. You can take one video straight, selecting different objects to show their insectors.

    From the looks of the normal inspectors, either there is no SceneFader set on your UIManager.cs, or it will be automatically set by a SceneFader.instance if there is one in the scene.

Viewing 15 results - 31 through 45 (of 77 total)

Advertisement below: