Forum begins after the advertisement:


[Part 8 Bug Fixes] Mana Orbs not gaining Mana, Faulty WallJump

Home Forums Video Game Tutorial Series Creating a Metroidvania in Unity [Part 8 Bug Fixes] Mana Orbs not gaining Mana, Faulty WallJump

Viewing 1 post (of 1 total)
  • Author
    Posts
  • #14653
    Joseph Tang
    Level 13
    Moderator
    Helpful?
    Up
    0
    ::

    This is a supplementary post written for Part 8 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. Extra Mana orbs don’t gain Mana while the Mana State is halfMana
    2. Non-functional WallJump/WallJump flips my player permanently
    3. Collectibles do not disappear when picking them up until after the pickup canvas has disappeared
    4. MaxHealth is not saved and does not appear correctly on start

    Common Issues

    1. Player is sent flying upwards after walljumping.

    Missing Information in the Video

    1. Extra Mana orbs don’t gain Mana while the Mana State is halfMana

        void Hit(Transform _attackTransform, Vector2 _attackArea, ref bool _recoilBool, Vector2 _recoilDir, float _recoilStrength)
        {
            ...
            for(int i = 0; i < objectsToHit.Length; i++)
            {
                if (objectsToHit[i].GetComponent() != null)
                {
                    objectsToHit[i].GetComponent().EnemyHit
                        (damage,  _recoilDir, _recoilStrength);
    
                    if (objectsToHit[i].CompareTag("Enemy"))
                    {
                        if (Mana < 1) if(!halfMana && Mana < 1 || (halfMana && Mana < 0.5))
                        {
                            Mana += manaGain;
                        }
                        else
                        {
                            manaOrbsHandler.UpdateMana(manaGain * 3);
                        }
                    }
                }
            }
        }

    2. Non-functional WallJump/WallJump flips my player permanently

    We’ve updated this since encountering another issue with an updated Flip() function in Part 6

    • Remove the if statement in the PlayerController.cs WallJump() method.
    • Then add a line to reverse the playerstate bool of lookingRight
    • We’ll then create a new float in the method called jumpDirection which we’ll set to either 0 or 180 depending on the pState.lookingRight.
    • Next we’ll set the eulerAngles to Vector2(transform.eulerAngles.x, jumpDirection).
    • Finally, reset the eulerAngles in StopWallJumping().
        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;
    
                float jumpDirection = pState.lookingRight ? 0 : 180;
                transform.eulerAngles = new Vector2(transform.eulerAngles.x, jumpDirection);
    
                if(pState.lookingRight && transform.eulerAngles.y == 0) || (!pState.lookingRight && transform.eulerAngles.y != 0)
                {
                    pState.lookingRight = !pstate.lookingRight;
                    int _yRotation = pState.lookingRight ? 0 : 180;
    
                    transform.eulerAngles = new Vector2(transform.eulerAngles.x, _yRotation);
                }
    
                Invoke(nameof(StopWallJumping), wallJumpingDuration);
            }
        }
        void StopWallJumping()
        {
            isWallJumping = false;
            transform.eulerAngles = new Vector2(transform.eulerAngles.x, 0);
        }

    Explanation of issue: Euler Angles
    When using “int _yRotation = pState.lookingRight ? 0 : 180;” in “WallJump()”, it sets the “eulerAngles.y” to either [0] or [180], depending on the Player’s “lookingRight” bool. The idea is to have the player bounce off the wall and face away from the wall when they do so. The issue with this code is that “eulerAngles” aren’t affected by normal forms of rotation, like movement keys in game or the Transform Rotation in the inspector, as can be seen in here:
    https://docs.unity3d.com/ScriptReference/Transform-eulerAngles.html#:~:text=eulerAngles%20represents%20rotation%20in%20world,localEulerAngles.

    Rather, they have to be reset by code back to a [0] value / normal to have your Player matching up correctly with your intended actions. Thus, we reset the “eulerAngles.y” to 0 when we write the “StopWallJumping()” code, where the player bounces away from the wall and stays that way for the set “wallJumpingDuration” before being free to move.
    Understanding how Euler Angles work, we can remove the differentiated “pState.lookingRight” code from “WallJump()” as they do not affect the “eulerAngles” as intended. Thus, allowing both directions of wall jumping to bounce off the wall.


    3. Collectibles do not disappear when picking them up until after the pickup canvas has disappeared

    • Add “gameObject.GetComponent[SpriteRenderer]().enabled = false;” into all Collectibles’ scripts with ShowUI()
        IEnumerator ShowUI()
        {
            GameObject _particles = Instantiate(particles, transform.position, Quaternion.identity);
            Destroy(_particles, 0.5f);
            yield return new WaitForSeconds(0.5f);
            gameObject.GetComponent[SpriteRenderer]().enabled = false;
    
            canvasUI.SetActive(true);
    
            yield return new WaitForSeconds(4f);
            PlayerController.Instance.unlockedWallJump = true;
            canvasUI.SetActive(false);
            Destroy(gameObject);
        }

    4. MaxHealth is not saved and does not appear correctly on start

    [This issue is caused by a missing piece of code]

    SaveData.cs

    [System.Serializable]
    public struct SaveData
    {
        public static SaveData Instance;
    
        //player stuff
        public int playerHealth;
        public int playerMaxHealth;
        public int playerHeartShards;
        
        ...
    
        #region Player stuff
        public void SavePlayerData()
        {
            using(BinaryWriter writer = new BinaryWriter(File.OpenWrite(Application.persistentDataPath + "/save.player.data")))
            {
                playerHealth = PlayerController.Instance.Health;
                writer.Write(playerHealth);
                playerMaxHealth = PlayerController.Instance.maxHealth;
                writer.Write(playerMaxHealth);
                playerHeartShards = PlayerController.Instance.heartShards;
                writer.Write(playerHeartShards);
    
                playerMana = PlayerController.Instance.Mana;
                writer.Write(playerMana);
                ...
            }
            Debug.Log("saved player data");
            
    
        }
        public void LoadPlayerData()
        {
            if(File.Exists(Application.persistentDataPath + "/save.player.data"))
            {
                using(BinaryReader reader = new BinaryReader(File.OpenRead(Application.persistentDataPath + "/save.player.data")))
                {
                    playerHealth = reader.ReadInt32();
                    playerMaxHealth = reader.ReadInt32();
                    playerHeartShards = reader.ReadInt32();
    
                    playerMana = reader.ReadSingle();
                    playerHalfMana = reader.ReadBoolean();
    
                    ...
    
                    lastScene = reader.ReadString();
    
                    SceneManager.LoadScene(lastScene);
                    PlayerController.Instance.transform.position = playerPosition;
                    PlayerController.Instance.halfMana = playerHalfMana;
                    PlayerController.Instance.Health = playerHealth;
                    PlayerController.Instance.maxHealth = playerMaxHealth;
                    PlayerController.Instance.heartShards = playerHeartShards;
                    PlayerController.Instance.Mana = playerMana;
    
                    ...
                }
                Debug.Log("load player data");
                Debug.Log(playerHalfMana);
            }
        }
        else
        {
            ...
            PlayerController.Instance.heartShards = 0;
        }
    
        #endregion
    }

    HeartController.cs

        public void Start()
        {
            heartContainers = new GameObject[PlayerController.Instance.maxTotalHealth];
            heartFills = new Image[PlayerController.Instance.maxTotalHealth];
    
            InstantiateHeartContainers();
            PlayerController.Instance.onHealthChangedCallback += UpdateHeartsHUD;
            UpdateHeartsHUD();
        }
    
        public void InstantiateHeartContainers()
        {
            heartContainers = new GameObject[PlayerController.Instance.maxHealth];
            heartFills = new Image[PlayerController.Instance.maxHealth];
    
            for (int i = 0; i < PlayerController.Instance.maxTotalHealth; i++)
            {
                GameObject temp = Instantiate(heartContainerPrefab);
                temp.transform.SetParent(heartsParent, false);
                heartContainers[i] = temp;
                heartFills[i] = temp.transform.Find("HeartFill").GetComponent();
            }
    
            PlayerController.Instance.onHealthChangedCallback += UpdateHeartsHUD;
            UpdateHeartsHUD();
        }

    PlayerController.cs

        // Start is called before the first frame update
        void Start()
        {
            ...
    
            SaveData.Instance.LoadPlayerData();
            if (halfMana)
            {
                UIManager.Instance.SwitchMana(UIManager.ManaState.HalfMana);
            }
            else
            {
                UIManager.Instance.SwitchMana(UIManager.ManaState.FullMana);
            }
    
            FindObjectOfType[HeartController]().InstantiateHeartContainers();
    
            ...
        }

    4.5. Alternative Method.

    [We’ve found a simpler method for this issue after the change in SaveData.cs]

    • In HeartController.cs Start(), change the heartContainers and heartFills to reference the maxTotalHealth instead of maxHealth.
    • in InstantiateHeartContainers(), we’ll also reference the maxTotalHealth.
    • Finally, in our PlayerController.cs Start(), invoke the onHealthChangedCallback to ensure that when we load our player data, our maxHealth is also reset correctly on Start().

    HeartController.cs

        void Start()
        {
            heartContainers = new GameObject[PlayerController.Instance.maxHealth maxTotalHealth];
            heartFills = new Image[PlayerController.Instance.maxHealth maxTotalHealth];
    
            ...
        }
    
        void InstantiateHeartContainers()
        {
            for(int i = 0; i < PlayerController.Instance.maxHealth maxTotalHealth; i++)
            {
                GameObject temp = Instantiate(heartContainerPrefab);
                temp.transform.SetParent(heartsParent, false);
                heartContainers[i] = temp;
                heartFills[i] = temp.transform.Find("HeartFill").GetComponent();
            }
        }

    PlayerController.cs

        void Start()
        {
            ...
            SaveData.Instance.LoadPlayerData();
            ...
    
            onHealthChangedCallback.Invoke();
    
            if (Health == 0)
            {
                pState.alive = false;
                GameManager.Instance.RespawnPlayer();
            }
        }

    Common Issues

    5. Player is sent flying upwards after walljumping.

    [This issue is caused by Wall Jumping Power Y being too high of a value]

    Demonstration:

    View post on imgur.com


    • 1. You can reduce the Wall Jumping Power to a lower more manageable value.

    Lower Wall Jumping Power Y value.

    • 2. Set the player’s Rigidbody2D’s Y velocity to [0] in StopWallJumping()
        void StopWallJumping()
        {
            isWallJumping = false;
            rb.velocity = new Vector2(rb.velocity.x, 0);
            transform.eulerAngles = new Vector2(transform.eulerAngles.x, 0);
        }

    That will be all for Part 8.
    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.

Viewing 1 post (of 1 total)
  • You must be logged in to reply to this topic.

Go to Login Page →


Advertisement below: