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

Viewing 4 posts - 1 through 4 (of 4 total)
  • Author
    Posts
  • #15770
    SOL-4S
    Participant
    Helpful?
    Up
    0
    ::

    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!

    View post on imgur.com

    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();
        }
    }
    #15772
    Joseph Tang
    Moderator
    Helpful?
    Up
    0
    ::

    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.

    #15774
    SOL-4S
    Participant
    Helpful?
    Up
    0
    ::

    it seems i indeed forgot to change it filled in the prefab, it’s working now, thank you very much!

    View post on imgur.com

    #15775
    Joseph Tang
    Moderator
    Helpful?
    Up
    0
    ::

    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.

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

Go to Login Page →


Advertisement below: