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
- This topic has 4 replies, 2 voices, and was last updated 8 months, 2 weeks ago by Ethan.
-
AuthorPosts
-
April 5, 2024 at 2:17 am #13745::
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>
April 5, 2024 at 2:43 am #13747::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;
- Your Player taking damage,
- or how the heart containers look like,
- or any Console error messages while playing the game and getting hit.
Also, check if your;
- Damage value is set on the Enemy to more than [1].
- Canvas has a HeartController.cs and set it’s values in the Prefab.
- 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:
- In Enemy.cs, under
OnCollisionStay2D()
, the Forum lists the if statement asif(_other.<strong>gameObject</strong>.CompareTag("Player")
. Whereas your script has it asif(_other.CompareTag("Player")
- The exclusion of the Mana, Cast and Heal mechanics which are most definitely intentional for your game.
April 5, 2024 at 3:16 am #13748::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 :)
April 5, 2024 at 3:22 am #13749::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]?
April 5, 2024 at 4:15 am #13750 -
AuthorPosts
- You must be logged in to reply to this topic.
Advertisement below: