Forum begins after the advertisement:
[part 5] mana image doesnt deplete after a scene transition
Home › Forums › Video Game Tutorial Series › Creating a Metroidvania in Unity › [part 5] mana image doesnt deplete after a scene transition
- This topic has 13 replies, 2 voices, and was last updated 1 year ago by Terence.
-
AuthorPosts
-
December 5, 2023 at 3:03 am #12514::
my mana image doesnt show the mana decreasing after i transition to a different scene. IT will deplete in scene 1 but when i go to scene 2 my mana meter image will reset and won’t go down. However when i go back to scene 1, my mana meter will show the amount it should be.
Ive assigned the mana prefab to my player character in all 3 scenes and have a canvas with mana in each scene. Did i miss something in the inspecotr?
December 5, 2023 at 3:15 am #12515::also to be noted: when i load straight from scene 2, my mana image will deplete – only after transitioning from scene 1 to 2
another wierd thing:
when i start from scene 2 and transition to scene 1, then try to take damage, i wont take damage – an error occurs where it says im missing a game object which has been destroyed but im trying to access it — also if i try to use my up mana spell (the only mana spell i used) i will get stuck in the animation after my explosion effect occurs
when i transition from scene 1 to scene 2 and back to scene 1, taking damage will cause all my hearts to dissapear and set my health instantly to zero
rlly weird glitches
December 5, 2023 at 3:22 am #12516::also idk if this helps but my heart containers will carry over scene transitions – just not the mana
December 5, 2023 at 3:25 am #12517December 5, 2023 at 3:31 am #12518::good news – i fixed the issue with the hearts by adding if(HeartContainers != null){} to the entire void start function and void updateheartsHUD of the heart container script
December 5, 2023 at 3:41 am #12519::How i fixed my hearts
using UnityEngine; using UnityEngine.UI; public class HeartController : MonoBehaviour { private GameObject[] heartContainers; private Image[] heartFills; public Transform heartsParent; public GameObject heartContainerPrefab; // Start is called before the first frame update void Start() { heartContainers = new GameObject[PlayerController.Instance.maxHealth]; heartFills = new Image[PlayerController.Instance.maxHealth]; PlayerController.Instance.onHealthChangedCallBack += UpdateHeartsHUD; InstantiateHeartContainers(); UpdateHeartsHUD(); } // Update is called once per frame void Update() { } void SetHeartContainers() { if (heartContainers != null) { for (int i = 0; i < heartContainers.Length; i++) { if (i < PlayerController.Instance.maxHealth) { heartContainers[i].SetActive(true); } else { heartContainers[i].SetActive(false); } } DontDestroyOnLoad(gameObject); } } void SetFilledHearts() { for (int i = 0; i < heartFills.Length; i++) { if (i < PlayerController.Instance.health) { heartFills[i].fillAmount = 1; } else { heartFills[i].fillAmount = 0; } } } void InstantiateHeartContainers() { for (int i = 0; i < PlayerController.Instance.maxHealth; i++) { GameObject temp = Instantiate(heartContainerPrefab); temp.transform.SetParent(heartsParent, false); heartContainers[i] = temp; heartFills[i] = temp.transform.Find("HeartFill").GetComponent<Image>(); } } void UpdateHeartsHUD() { if(heartContainers != null) { SetHeartContainers(); SetFilledHearts(); DontDestroyOnLoad (gameObject); } } }
December 5, 2023 at 3:57 am #12520December 5, 2023 at 1:41 pm #12526December 5, 2023 at 2:43 pm #12528::just so you know those errors i showed for my scene transition script arent happening anymore – still have a broken mana meter though
using System.Collections; using System.Collections.Generic; using UnityEngine; using UnityEngine.UI; using UnityEngine.SceneManagement; public class SceneTransition : MonoBehaviour { [SerializeField] private string transitionTo; //Represents the scene to transition to [SerializeField] private Transform startPoint; //Defines the player's entry point in the scene [SerializeField] private Vector2 exitDirection; //Specifies the direction for the player's exit [SerializeField] private float exitTime; //Determines the time it takes for the player to exit the scene transition // Start is called before the first frame update private void Start() { if (GameManager.Instance.transitionedFromScene == transitionTo) { 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; SceneManager.LoadScene(transitionTo); PlayerController.Instance.pState.cutscene = true; StartCoroutine(UIManager.Instance.sceneFader.FadeAndLoadScene(SceneFader.FadeDirection.In, transitionTo)); } } }
December 5, 2023 at 9:41 pm #12531::For your scene transition, it seems like this is the line that is the problem:
StartCoroutine(UIManager.Instance.sceneFader.FadeAndLoadScene(SceneFader.FadeDirection.In, transitionTo));
You can do a debug log of
UIManager.Instance
andUIManager.Instance.sceneFader
to see which is null.For your mana issue, check your character’s mana stat on the Inspector when playing. Does it decrease? If it does, then it means the mana stat is not properly linked to the UI on the new scene.
December 6, 2023 at 2:28 am #12533::OK im not sure if its right but it fixed the issue:
in scenes 2 and 3 i deleted the mana images from the canvas
now when i go from scene 1 to 2 it stays
i think the mana images were overlapping
just wondering – when i load from scene 2 or 3 my mana wont appear and hearts are set to 0 – is there an easier method of doing what i did without making everything reset unless i start from scene 1
December 6, 2023 at 2:31 am #12534December 6, 2023 at 4:07 am #12536::i tried using a debug log and it works but im still getting these errors at random places:
using System.Collections; using System.Collections.Generic; using UnityEngine; using UnityEngine.UI; using UnityEngine.SceneManagement; public class SceneTransition : MonoBehaviour { [SerializeField] private string transitionTo; //Represents the scene to transition to [SerializeField] private Transform startPoint; //Defines the player's entry point in the scene [SerializeField] private Vector2 exitDirection; //Specifies the direction for the player's exit [SerializeField] private float exitTime; //Determines the time it takes for the player to exit the scene transition // Start is called before the first frame update private void Start() { if (GameManager.Instance.transitionedFromScene == transitionTo) { 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; SceneManager.LoadScene(transitionTo); PlayerController.Instance.pState.cutscene = true; StartCoroutine(UIManager.Instance.sceneFader.FadeAndLoadScene(SceneFader.FadeDirection.In, transitionTo)); Debug.Log("scene fader works"); } } }
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("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("Vertical Movement Settings")] [SerializeField] private float jumpForce = 45; private int jumpBufferCounter = 0; [SerializeField] private int jumpBufferFrames; private float coyoteTimeCounter = 0; [SerializeField] private float coyoteTime; [SerializeField] private float airJumpsCounter = 0; [SerializeField] private int maxAirJumps; [Space(5)] [Header("recoil")] [SerializeField] int recoilXSteps = 5; [SerializeField] int recoilYSteps = 5; [SerializeField] float recoilXSpeed = 100; [SerializeField] float recoilYSpeed = 100; private int stepsXRecoiled, stepsYRecoiled; [Space(5)] [Header("Attack Settings")] private bool attack = false; private float timeBetweenAttack, timeSinceAttack; [SerializeField] Transform SideAttackTransform, BehindAttackTransform, UpAttackTransform, DownAttackTransform; [SerializeField] Vector2 SideAttackArea, BehindAttackArea, UpAttackArea, DownAttackArea; [SerializeField] LayerMask attackableLayer; [SerializeField] float damage; [SerializeField] GameObject slashEffect; [SerializeField] GameObject upSlashEffect; [SerializeField] GameObject downSlashEffect; bool restoreTime; float restoreTimeSpeed; [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("Health")] public int health; public int maxHealth; [SerializeField] GameObject bloodSpurt; [SerializeField] GameObject crackeffect; [SerializeField] float hitFlashSpeed; public delegate void OnHealthChangedDelegate(); // delegate voids can be used on multiple methods [HideInInspector] public OnHealthChangedDelegate onHealthChangedCallBack; 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")] //spell stats [SerializeField] float manaSpellCost; [SerializeField] float timeBetweenCast = 0.5f; float timeSinceCast; [SerializeField] float spellDamage; //spell cast objects [SerializeField] GameObject upSpellExplosion; [Space(5)] [HideInInspector] public playerStatesList pState; private float xAxis, yAxis; private Rigidbody2D rb; private float gravity; private Animator anim; private SpriteRenderer sr; 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<playerStatesList>(); rb = GetComponent<Rigidbody2D>(); anim = GetComponent<Animator>(); sr = GetComponent<SpriteRenderer>(); gravity = rb.gravityScale; Mana = mana; manaStorage.fillAmount = Mana; Health = maxHealth; } //draw out hitboxes for basic attacks private void OnDrawGizmos() { Gizmos.color = Color.red; Gizmos.DrawWireCube(SideAttackTransform.position, SideAttackArea); Gizmos.DrawWireCube(BehindAttackTransform.position, BehindAttackArea); Gizmos.DrawWireCube(UpAttackTransform.position, UpAttackArea); Gizmos.DrawWireCube(DownAttackTransform.position, DownAttackArea); } // Update is called once per frame void Update() { if (pState.cutscene) return; GetInputs(); updateJumpVariable(); 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>().EnemyHit(spellDamage, (_other.transform.position - transform.position).normalized, -recoilYSpeed); } } private void FixedUpdate() { if (pState.cutscene) return; if (pState.dashing) return; Recoil(); } //basic inputs for movement void GetInputs() { xAxis = Input.GetAxisRaw("Horizontal"); yAxis = Input.GetAxisRaw("Vertical"); attack = Input.GetButtonDown("Attack"); } // flip player on x axis when left or right is picked 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; } } //move left and right private void Move() { rb.velocity = new Vector2(walkSpeed * xAxis, rb.velocity.y); anim.SetBool("walking", rb.velocity.x != 0 && Grounded()); } //allows for dashing and air dashing void StartDash() { if(Input.GetButtonDown("Dash") && canDash && !dashed) { StartCoroutine(Dash()); dashed = true; } if (Grounded()) { dashed = false; } } //stops middair infinite dashing 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 you exit upwards if(_exitDir.y > 0) { rb.velocity = jumpForce * _exitDir; } //horizontal exiting if(_exitDir.x != 0) { xAxis = _exitDir.x > 0 ? 1 : -1; Move(); } Flip(); yield return new WaitForSeconds(_delay); pState.cutscene = false; } //create hitboxes for attacks void Attack() { timeSinceAttack += Time.deltaTime; if(attack && timeSinceAttack >= timeBetweenAttack) { timeSinceAttack = 0; //side attack if (yAxis == 0 || yAxis < 0 && Grounded()) { Hit(SideAttackTransform, SideAttackArea, ref pState.recoilingx, recoilXSpeed); Instantiate(slashEffect, SideAttackTransform); anim.SetTrigger("Attacking"); } //up air attack else if (yAxis > 0 && !Grounded()) { Hit(UpAttackTransform, UpAttackArea, ref pState.recoilingy, recoilYSpeed); Instantiate(upSlashEffect, UpAttackTransform); anim.SetTrigger("Attacking"); } //down air attak else if (yAxis < 0 && !Grounded()) { Hit(DownAttackTransform, DownAttackArea, ref pState.recoilingy, recoilYSpeed); Instantiate(downSlashEffect, DownAttackTransform); anim.SetTrigger("Attacking"); } if (yAxis > 0 && Grounded()) { Hit(UpAttackTransform, UpAttackArea, ref pState.recoilingy, recoilYSpeed); Instantiate(upSlashEffect, UpAttackTransform); anim.SetTrigger("upAttack"); } } } //hitting the enemy and adding recoil to attacks void Hit(Transform _attackTransform, Vector2 _attackArea, ref bool _recoilDir, float _recoilStrength) { Collider2D[] objectsToHit = Physics2D.OverlapBoxAll(_attackTransform.position, _attackArea, 0, attackableLayer); if(objectsToHit.Length > 0) { _recoilDir = true; } for(int i = 0; i < objectsToHit.Length; i++) { if (objectsToHit[i].GetComponent<Enemy>() != null) { objectsToHit[i].GetComponent<Enemy>().EnemyHit (damage, (transform.position - objectsToHit[i].transform.position).normalized, _recoilStrength); if (objectsToHit[i].CompareTag("Enemy")) { Mana += manaGain; } } } } //changes the direction of attack based on aerial positions 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 UpSlashEffectAtAngle(GameObject _upSlashEffect, int _effectAngle, Transform _attackTransform) { _upSlashEffect = Instantiate(_upSlashEffect, _attackTransform); _upSlashEffect.transform.eulerAngles = new Vector3(0, 0, _effectAngle); _upSlashEffect.transform.localScale = new Vector2(transform.localScale.x, transform.localScale.y); } void DownSlashEffectAtAngle(GameObject _downSlashEffect, int _effectAngle, Transform _attackTransform) { _downSlashEffect = Instantiate(_downSlashEffect, _attackTransform); _downSlashEffect.transform.eulerAngles = new Vector3(0, 0, _effectAngle); _downSlashEffect.transform.localScale = new Vector2(transform.localScale.x, transform.localScale.y); } //directional player recoil based on main slash attacks, x and y axis movement void Recoil() { //recoiling in the x axis if (pState.recoilingx) { if (pState.lookingRight) { rb.velocity = new Vector2(-recoilXSpeed, 0); } else { rb.velocity = new Vector2(recoilXSpeed, 0); } } //recoil in the y axis 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); } airJumpsCounter = 0; } else { rb.gravityScale = gravity; } //stop recoiling if (pState.recoilingx && stepsXRecoiled < recoilXSteps) { stepsXRecoiled++; } else { StopRecoilX(); } if (pState.recoilingy && stepsYRecoiled < recoilYSteps) { stepsYRecoiled++; } else { StopRecoilY(); } if (Grounded()) { StopRecoilY(); } } //stop recoiling forever void StopRecoilX() { stepsXRecoiled = 0; pState.recoilingx = false; } void StopRecoilY() { stepsYRecoiled = 0; pState.recoilingy = false; } //take damage, set health and gives some invinicibility frames public void TakeDamage(float _damage) { Health -= Mathf.RoundToInt(_damage); StartCoroutine(StopTakingDamage()); } IEnumerator StopTakingDamage() { pState.invincible = true; anim.SetTrigger("takeDamage"); GameObject _bloodSpurtParticles = Instantiate(bloodSpurt, transform.position, Quaternion.identity); GameObject _crack = Instantiate(crackeffect, transform.position, Quaternion.identity); Destroy(_crack, 0.1f); Destroy(_bloodSpurtParticles, 1.5f); yield return new WaitForSeconds(1f); pState.invincible = false; } //flash black and white when hit for i frames void FlashWhileInvincible() { sr.material.color = pState.invincible ? Color.Lerp(Color.white, Color.black, Mathf.PingPong(Time.time * hitFlashSpeed, 1.5f)) : Color.white; } void RestoreTimeScale() { if (restoreTime) { if (Time.timeScale < 1) { Time.timeScale += Time.deltaTime * restoreTimeSpeed; } else { Time.timeScale = 1; restoreTime = false; } } } //create invincibility frames 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; } } //begin taking damage again IEnumerator StartTimeAgain(float _delay) { restoreTime = true; yield return new WaitForSeconds(_delay); } //health and removing health public int Health { get { return health; } set { if (health != value) { health = Mathf.Clamp(value, 0, maxHealth); if (onHealthChangedCallBack != null) { onHealthChangedCallBack.Invoke(); } } } } //heal with mana with a void Heal() { if (Input.GetButton("Heal") && 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; } } //create mana float Mana { get { return mana; } set { //if mana stats change if (mana != value) { mana = Mathf.Clamp(value, 0, 1); manaStorage.fillAmount = Mana; } } } //cast up spell explosion when u have enough mana and its been a long time since your last kaboom void CastSpell() { if (Input.GetButtonDown("CastSpell") && timeSinceCast >= timeBetweenCast && Mana >= manaSpellCost && yAxis > 0) { pState.casting = true; timeSinceCast = 0; StartCoroutine(CastCoroutine()); } else { timeSinceCast += Time.deltaTime; } } IEnumerator CastCoroutine() { anim.SetBool("Casting", true); yield return new WaitForSeconds(0.2f); //up cast if (yAxis > 0) { Instantiate(upSpellExplosion, transform); rb.velocity = Vector2.zero; } Mana -= manaSpellCost; yield return new WaitForSeconds(0.2f); anim.SetBool("Casting", false); pState.casting = false; } //checks if you are grounded 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; } } //makes you jump when pressing spacebar void Jump() { if (jumpBufferCounter > 0 && coyoteTimeCounter > 0 && !pState.jumping) { rb.velocity = new Vector3(rb.velocity.x, jumpForce); pState.jumping = true; } if (!Grounded() && airJumpsCounter < maxAirJumps && Input.GetButtonDown("Jump")) { pState.jumping = true; airJumpsCounter++; 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; } //animate jumping double jump and falling animation anim.SetBool("jumping", !Grounded() && rb.velocity.y > -15 && airJumpsCounter == 0); anim.SetBool("doublejump", !Grounded() && airJumpsCounter == 1); anim.SetBool("falling", !Grounded() && rb.velocity.y < -10); } //coyote time and jump buffering void updateJumpVariable() { //coyote time if (Grounded()) { pState.jumping = false; coyoteTimeCounter = coyoteTime; airJumpsCounter = 0; } else { coyoteTimeCounter -= Time.deltaTime; } //jump buffering if (Input.GetButtonDown("Jump")) { jumpBufferCounter = jumpBufferFrames; } else { jumpBufferCounter--; } } }
December 6, 2023 at 12:59 pm #12539 -
AuthorPosts
- You must be logged in to reply to this topic.
Advertisement below: