Forum begins after the advertisement:


[Part 4] Hearts Hud not going down

Home Forums Video Game Tutorial Series Creating a Metroidvania in Unity [Part 4] Hearts Hud not going down

Viewing 5 posts - 1 through 5 (of 5 total)
  • Author
    Posts
  • #13745
    Ethan
    Level 4
    Participant
    Helpful?
    Up
    0
    ::

    i tried to write a responce on the forum to get helped but it was flagged so im gonna write it here. Im trying to get the Hearts Hud to work in Part4 as when i take damage it doesnt go down. Ive left the code down below and if you can help me that would be great :) Ive put the classes in all caps so if you use shift f you should be able to find them easily.

    //Heart Controller Class

    <code>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);
    
                }
            }
        }
        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()
        {
            SetHeartContainers();
            SetFilledHearts();
        }
    }</code>

    //Enemy class

    <code>using System.Collections;
    using System.Collections.Generic;
    using UnityEngine;
    
    public class Enemy : MonoBehaviour
    {
        [SerializeField] protected float health;
        [SerializeField] protected float recoilLength;
        [SerializeField] protected float recoilFactor;
        [SerializeField] protected bool isRecoiling = false;
    
    
        [SerializeField] protected PlayerController player;
        [SerializeField] protected float speed;
    
        [SerializeField] private float damage;
    
        protected float recoilTimer;
        protected Rigidbody2D rb;
    
        // Start is called before the first frame update
        protected virtual void Start()
        {
    
        }
        protected virtual void Awake()
        {
           rb=GetComponent<Rigidbody2D>(); 
            player = PlayerController.Instance;
        }
    
        // Update is called once per frame
       protected virtual void Update()
        {
            if(health<=0)
            {
                Destroy(gameObject);
            }
            if(isRecoiling)
            {
                if(recoilTimer < recoilLength)
                {
                    recoilTimer += Time.deltaTime;
                }
                else
                {
                    isRecoiling = false;
                    recoilTimer = 0;
                }
            }
        }
        public virtual void EnemyHit(float _damageDone, Vector2 _hitDirection, float _hitForce)
        {
            health -= _damageDone;
            if(!isRecoiling )
            {
                rb.AddForce(-_hitForce * recoilFactor * _hitDirection);
            }
        }
        protected void OnTriggerStay2D(Collider2D _other)
        {
            if (_other.CompareTag("Player")&& !PlayerController.Instance.pState.invincible)
            {
                Attack();
                PlayerController.Instance.HitStopTime(0, 5, 0.5f);
            }
        }
        protected virtual void Attack()
        {
            PlayerController.Instance.TakeDamage(damage);
        }
    }</code>

    //Zombie Class

    <code>using System.Collections;
    using System.Collections.Generic;
    using UnityEngine;
    
    public class Zombie : Enemy
    {
        // Start is called before the first frame update
        void Start()
        {
            rb.gravityScale = 12.0f;
        }
    
        protected override void Awake()
        {
            base.Awake();
        }
    
        // Update is called once per frame
        protected override void Update()
        {
            base.Update();
            if(!isRecoiling)
            {
                transform.position = Vector2.MoveTowards(transform.position, new Vector2(PlayerController.Instance.transform.position.x, transform.position.y), speed * Time.deltaTime);
            }
        }
        public override void EnemyHit(float _damageDone, Vector2 _hitDirection, float _hitForce)
        {
            base.EnemyHit(_damageDone,_hitDirection,_hitForce);
        }
    }</code>

    //PlayerControllerClass

    <code>using System.Collections;
    using System.Collections.Generic;
    using Unity.Mathematics;
    using Unity.VisualScripting;
    using UnityEngine;
    
    public class PlayerController : MonoBehaviour
    {
        [Header("Horizontal Movement")]
        [SerializeField] private float walkSpeed = 20;
        [Space(5)]
    
        [Header("vertical movement")]
        [SerializeField] private float jumpForce = 35f;
        private int jumpBufferCounter=0;
        [SerializeField] private int jumpBufferFrames;
        private float coyoteTimeCounter = 0;
        [SerializeField] private float coyoteTime;
        private int airJumpCounter = 0;
        [SerializeField] private int maxAirJumps;
        [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;
        [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;
        [Space(5)]
    
        [Header("Recoil")]
        [SerializeField] int recoilXSteps = 5;
        [SerializeField] int recoilYSteps = 5;
        [SerializeField] float recoilXSpeed = 100;
        [SerializeField] float recoilYSpeed = 100;
        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;
        [Space(5)]
    
        bool restoreTime;
        float restoreTimeSpeed;
        [Space(5)]
    
       [HideInInspector] public PlayerStateList pState;
        private Rigidbody2D rb;
        private SpriteRenderer sr;
        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;
            }
            Health =maxHealth;
        }
    
        // 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;
        }
        private void OnDrawGizmos()
        {
            Gizmos.color = Color.yellow;
            Gizmos.DrawWireCube(SideAttackTransform.position,SideAttackArea);
            Gizmos.DrawWireCube(UpAttackTransform.position,UpAttackArea);
            Gizmos.DrawWireCube(DownAttackTransform.position,DownAttackArea);
    
        }
        // Update is called once per frame
        void Update()
        {
            GetInputs();
            UpdateJumpVariables();
            if (pState.dashing) return;
            Flip();
            Move();
            Jump();
            StartDash();
            Attack();
            RestoreTimeScale();
            FlashWhileInvincible();
    
        }
    
        private void FixedUpdate()
        {
            if(pState.dashing) return;
            Recoil();
        }
    
        void GetInputs()
        {
            xAxis = Input.GetAxisRaw("Horizontal");
            yAxis = Input.GetAxisRaw("Vertical");
            attack = Input.GetButtonDown("Attack");
    
        }
    
        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 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;
            rb.velocity = new Vector2(transform.localScale.x * 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;
        }
    
        void Attack()
        {
            timeSinceAttack += Time.deltaTime;
            if(attack && timeSinceAttack >= timeBetweenAttack)
            {
                timeSinceAttack = 0;
                anim.SetTrigger("Attacking");
    
                if(yAxis ==0 || yAxis <0 && Grounded())
                {
                    Hit(SideAttackTransform, SideAttackArea, ref pState.recoilingX,recoilXSpeed);
                    Instantiate(slashEffect, SideAttackTransform);
                }
                else if(yAxis>0)
                {
                    Hit(UpAttackTransform, UpAttackArea, ref pState.recoilingY, recoilYSpeed);
                    SlashEffectAtAngle(slashEffect, 90, UpAttackTransform);
                }
                else if (yAxis < 0 && !Grounded())
                {
                    Hit(DownAttackTransform, DownAttackArea, ref pState.recoilingY, recoilYSpeed);
                    SlashEffectAtAngle(slashEffect,-90,DownAttackTransform);
                }
            }
        }
    
        private 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);
                }
            }
        }
    
        void SlashEffectAtAngle(GameObject _slashEffect, int _effectAngle,Transform _attackTransform)
        {
            _slashEffect = Instantiate(_slashEffect, _attackTransform);
            _slashEffect.transform.eulerAngles = new Vector3(0, 0, _effectAngle);
            _slashEffect.transform.localScale = new Vector3(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;
        }
        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);
        }
        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 (Input.GetButtonUp("Jump") && rb.velocity.y > 0)
            {
                rb.velocity = new Vector2(rb.velocity.x, 0);
    
                pState.jumping = false;
            }
            if (!pState.jumping)
            {
                if (jumpBufferCounter >0 && coyoteTimeCounter>0)
                {
                    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);
    
                }
    
            }
            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--;
            }
        }
    }</code>
    #13747
    Joseph Tang
    Level 13
    Moderator
    Helpful?
    Up
    0
    ::

    As far as it goes, your scripts look 1:1 to the final scripts of Part 4.

    To assess the issue further you should look at the game and the scene rather than the script.

    We will need you to take a few screenshots or video, if you could, of;

    1. Your Player taking damage,
    2. or how the heart containers look like,
    3. or any Console error messages while playing the game and getting hit.

    Also, check if your;

    1. Damage value is set on the Enemy to more than [1].
    2. Canvas has a HeartController.cs and set it’s values in the Prefab.
    3. Player’s health is being reduced on hit. (This is to check if the issue is the UI or something else during the process of taking damage)

    The only minor differences in script which i feel are only minor and not so impactful is:

    1. In Enemy.cs, under OnCollisionStay2D(), the Forum lists the if statement as if(_other.<strong>gameObject</strong>.CompareTag("Player"). Whereas your script has it as if(_other.CompareTag("Player")
    2. The exclusion of the Mana, Cast and Heal mechanics which are most definitely intentional for your game.
    #13748
    Ethan
    Level 4
    Participant
    Helpful?
    Up
    0
    ::

    https://1drv.ms/v/s!AmTpK7RPPkWqgiptv8DIlkQgmJY1?e=k7Djyt here is a one dirve link showing my code and some stuff in the editor so you can have a look :)

    #13749
    Joseph Tang
    Level 13
    Moderator
    Helpful?
    Up
    0
    ::

    Your Zombie.cs on your Enemy Game Object has a Damage value of [0] in the inspector. Can you test it again with a Damage value of [1]?

    #13750
    Ethan
    Level 4
    Participant
    Helpful?
    Up
    0
    ::

    It worked thanks so much the issue was that i had not set the value of damage in the Inspector

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

Go to Login Page →


Advertisement below: