Forum begins after the advertisement:
[Part 4] heart containers not updating
Home › Forums › Video Game Tutorial Series › Creating a Metroidvania in Unity › [Part 4] heart containers not updating
- This topic has 3 replies, 2 voices, and was last updated 1 month ago by Joseph Tang.
-
AuthorPosts
-
September 7, 2024 at 6:37 pm #15770SOL-4SParticipant::
i’ve added print commands to the SetFilledHearts function to check if it updates correctly to the health, which it does in the video below, i tried fixing it and asked copilot but after a while of tinkering nothing really worked, below is the code in my PlayerController.cs and heartController.cs files, thanks in advance for the help!
and here is the code from my PlayerController:
using System.Collections; using System.Collections.Generic; using UnityEngine; public class PlayerController : MonoBehaviour { [Header("Horizontal Movement Settings")] [SerializeField] private float WalkSpeed = 1; [Header("Vertical Movement Settings")] [SerializeField] private float jumpForce = 45; private float jumpBufferCounter = 0; [SerializeField] private float jumpBufferTime = 0.2f; private float coyoteTimeCounter = 0; [SerializeField] private float coyoteTime; private int airJumpCounter = 0; [SerializeField] private int maxAirJumps; [Header("Ground Check Settings")] [SerializeField] private Transform groundCheckPoint; [SerializeField] private float groundCheckY = 0.2f; [SerializeField] private float groundCheckX = 0.5f; [SerializeField] private LayerMask whatIsGround; [Header("Dash Settings")] [SerializeField] private float dashSpeed; [SerializeField] private float dashTime; [SerializeField] private float dashCooldown; [Header("Jump Settings")] [SerializeField] private float jumpCutOff = 0.5f; [Header("Attack Settings")] [SerializeField] Transform SideAttackTransform; [SerializeField] Vector2 SideAttackArea; [SerializeField] LayerMask Attackable; [SerializeField] private float damage; bool attack = false; float timeBetweenAttack = 0.3f; float timeSinceAttack; bool restoreTime; float restoreTimeSpeed; [Header("Health Settings")] public int health; public int maxHealth; [SerializeField] GameObject bloodSpurt; [SerializeField] float HitFlashSpeed; public delegate void OnHealthChangedDelegate(); [HideInInspector] public OnHealthChangedDelegate onHealthChangedCallback; [HideInInspector] public PlayerStateList pState; private Rigidbody2D rb; private SpriteRenderer sr; //imput variables private float xAxis, yAxis; private float gravity; Animator anim; private bool canDash = true; private bool dashed; public static PlayerController Instance; private void Awake() { if (Instance != null && Instance != this) { Destroy(gameObject); } else { Instance = this; } } void Start() { pState = GetComponent<PlayerStateList>(); rb = GetComponent<Rigidbody2D>(); sr = GetComponent<SpriteRenderer>(); anim = GetComponent<Animator>(); gravity = rb.gravityScale; Health = maxHealth; // Initialize health } private void OnDrawGizmos() { Gizmos.color = Color.red; Gizmos.DrawWireCube(SideAttackTransform.position, SideAttackArea); } void Update() { GetInputs(); if (pState.dashing) return; Flip(); Move(); Jump(); CutJumpShort(); StartDash(); Attack(); RestoreTimeScale(); FlashWhileInvincible(); } private void FixedUpdate() { if (pState.dashing) return; } void GetInputs() { xAxis = Input.GetAxisRaw("Horizontal"); attack = Input.GetButtonDown("Attack"); } void Flip() { if (xAxis < 0) { transform.localScale = new Vector2(5, transform.localScale.y); } else if (xAxis > 0) { transform.localScale = new Vector2(-5, transform.localScale.y); } } 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; // Determine the dash direction based on the character's facing direction float dashDirection = transform.localScale.x > 0 ? -1 : 1; rb.velocity = new Vector2(dashDirection * dashSpeed, 0); yield return new WaitForSeconds(dashTime); rb.gravityScale = gravity; pState.dashing = false; yield return new WaitForSeconds(dashCooldown); canDash = true; } void Attack() { timeSinceAttack += Time.deltaTime; if (attack && timeSinceAttack >= timeBetweenAttack) { timeSinceAttack = 0; anim.SetTrigger("Attack1"); if (yAxis == 0 || yAxis < 0 && Grounded()) { Hit(SideAttackTransform, SideAttackArea); } } } private void Hit(Transform _attackTransform, Vector2 _attackArea) { Collider2D[] objectToHit = Physics2D.OverlapBoxAll(_attackTransform.position, _attackArea, 0, Attackable); List<Enemy> hitEnemies = new List<Enemy>(); if(objectToHit.Length > 0) { } for(int i = 0; i < objectToHit.Length; i++) { Enemy e = objectToHit[i].GetComponent<Enemy>(); if(e && !hitEnemies.Contains(e)) { e.EnemyHit(damage, (transform.position - objectToHit[i].transform.position).normalized, 100); hitEnemies.Add(e); } } } 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; } public void TakeDamage(float _damage) { Health -= Mathf.RoundToInt(_damage); StartCoroutine(StopTakingDamage()); } void RestoreTimeScale() { if(restoreTime) { Time.timeScale += Time.deltaTime * restoreTimeSpeed; } else { Time.timeScale = 1; restoreTime = false; } } public int Health { get { return health; } set { if(health != value) { health = Mathf.Clamp(value, 0, maxHealth); if(onHealthChangedCallback != null) { onHealthChangedCallback.Invoke(); } } } } 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) { jumpBufferCounter -= Time.deltaTime; } if (Input.GetButtonDown("Jump")) { jumpBufferCounter = jumpBufferTime; } if (jumpBufferCounter > 0 && coyoteTimeCounter > 0) { rb.velocity = new Vector2(rb.velocity.x, jumpForce); pState.jumping = true; jumpBufferCounter = 0; } else if (!Grounded() && airJumpCounter < maxAirJumps && Input.GetButtonDown("Jump")) { pState.jumping = true; airJumpCounter++; rb.velocity = new Vector2(rb.velocity.x, jumpForce); } if (Grounded()) { pState.jumping = false; coyoteTimeCounter = coyoteTime; airJumpCounter = 0; } else { coyoteTimeCounter -= Time.deltaTime; } anim.SetBool("Jumping", !Grounded()); } void CutJumpShort() { if (Input.GetButtonUp("Jump") && rb.velocity.y > 0) { rb.velocity = new Vector2(rb.velocity.x, rb.velocity.y * jumpCutOff); } } }
and of heartController:
using System.Collections; using System.Collections.Generic; using UnityEngine; using UnityEngine.UI; public class heartController : MonoBehaviour { PlayerController player; private GameObject[] heartContainers; private Image[] heartFills; public Transform heartsParent; public GameObject heartContainerPrefab; // Start is called before the first frame update void Start() { player = PlayerController.Instance; 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() { for(int i = 0; i < heartContainers.Length; i++) { if(i < PlayerController.Instance.maxHealth) { heartContainers[i].SetActive(true); } else { heartContainers[i].SetActive(false); } } } public void SetFilledHearts() { for(int i = 0; i < heartFills.Length; i++) { if(i < PlayerController.Instance.Health) { heartFills[i].fillAmount = 1; print("Health: " + PlayerController.Instance.Health); } else { heartFills[i].fillAmount = 0; print("Health: " + PlayerController.Instance.Health); } } } 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() { SetHeartContainers(); SetFilledHearts(); } }
September 7, 2024 at 7:27 pm #15772Joseph TangModerator::Hi Sol, taking a look at your code, it seems to be working as intended, including the print logs showing that it is able to take the right value of health.
Thus, it’s likely not a problem of the player’s health, but the Heart containers themselves. Most likely, your HeartFills.
Considering the fact that there is no NullReferenceExceptionError, it’s also likely that your HeartFill is appropriately found by the script.Thus, it’s most probable that you should check if the HeartFill in you Heart Container prefab is set to “Filled” instead of “Simple”.
Just go to your Heart Container Prefab, then check the child HeartFill, and in it’s Image component you will see a dropdown menu for it’s fill typing.If this isnt the issue, do tell me and I’ll take another look into what else could be the issue.
September 7, 2024 at 9:24 pm #15774SOL-4SParticipant::it seems i indeed forgot to change it filled in the prefab, it’s working now, thank you very much!
September 7, 2024 at 9:34 pm #15775Joseph TangModerator::Good to hear that your issue is fixed.
Remember to always check for these things when you face a problem:
Console Errors > Functioning Print Codes > Related Inspector Components. -
AuthorPosts
- You must be logged in to reply to this topic.
Advertisement below: