Forum begins after the advertisement:
[part 3] My character doesn’t take damage?
Home › Forums › Video Game Tutorial Series › Creating a Metroidvania in Unity › [part 3] My character doesn’t take damage?
- This topic has 5 replies, 2 voices, and was last updated 1 year ago by Terence.
-
AuthorPosts
-
November 15, 2023 at 2:06 am #12202::
So everything in part 3 makes complete sense except the end where we make the enemy player class. I will be able to make the zombie script move towards my player. But when I add health to my player, when it’s collider and the enemy trigger collider interact, the players health doesn’t decrease and the hurt animation won’t play for the player? I’ve tried everything… I’ve set “player” to player in the enemy script, made 2 colliders with 1 being a trigger, set damage of enemy and max health of player, set rb of enemy to interlope and continuous. So I’m confused what’s wrong – not getting any code errors?!?!?)
November 15, 2023 at 7:34 pm #12206::using System.Collections; using System.Collections.Generic; using Unity.VisualScripting; using UnityEngine; 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; [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; [Space(5)] [HideInInspector] public playerStatesList pState; private float xAxis, yAxis; private Rigidbody2D rb; private float gravity; private Animator anim; 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<playerStatesList>(); rb = GetComponent<Rigidbody2D>(); anim = GetComponent<Animator>(); gravity = rb.gravityScale; } //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() { GetInputs(); updateJumpVariable(); if (pState.dashing) return; Flip(); Move(); Jump(); StartDash(); Attack(); } //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; 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; } //create hitboxes for attacks void Attack() { timeSinceAttack += Time.deltaTime; if(attack && timeSinceAttack >= timeBetweenAttack) { timeSinceAttack = 0; anim.SetTrigger("Attacking"); //side attack if (yAxis == 0 || yAxis < 0 && Grounded()) { Hit(SideAttackTransform, SideAttackArea, ref pState.recoilingx, recoilXSpeed); Instantiate(slashEffect, SideAttackTransform); } //up air attack else if (yAxis > 0 && !Grounded()) { Hit(UpAttackTransform, UpAttackArea, ref pState.recoilingy, recoilYSpeed); SlashEffectAtAngle(slashEffect, 80, UpAttackTransform); anim.SetTrigger("Attacking"); } //down air attak else if (yAxis < 0 && !Grounded()) { Hit(DownAttackTransform, DownAttackArea, ref pState.recoilingy, recoilYSpeed); SlashEffectAtAngle(slashEffect, -80, DownAttackTransform); anim.SetTrigger("Attacking"); } if (yAxis > 0 && Grounded()) { Hit(UpAttackTransform, UpAttackArea, ref pState.recoilingy, recoilYSpeed); SlashEffectAtAngle(slashEffect, 95, UpAttackTransform); } } } //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); } } } //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); } //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); Debug.Log("hurt"); StartCoroutine(StopTakingDamage()); } IEnumerator StopTakingDamage() { pState.invincible = true; anim.SetTrigger("TakeDamage"); ClampHealth(); yield return new WaitForSeconds(1f); pState.invincible = false; Debug.Log("inv"); } void ClampHealth() { health = Mathf.Clamp(health, 0, maxHealth); } //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(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() && airJumpsCounter < maxAirJumps && Input.GetButtonDown("Jump")) { pState.jumping = true; airJumpsCounter++; rb.velocity = new Vector3(rb.velocity.x, jumpForce); } } //animate jumping double jump and falling animation anim.SetBool("jumping", !Grounded() && rb.velocity.y > -20 && 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--; } } } using System.Collections; using System.Collections.Generic; using UnityEngine; public class playerStatesList : MonoBehaviour { public bool jumping = false; public bool dashing = false; public bool recoilingx, recoilingy; public bool lookingRight; public bool invincible; } using System.Collections; using System.Collections.Generic; using Unity.VisualScripting; 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 float damage; protected float recoilTimer; protected Rigidbody2D rb; [SerializeField] protected PlayerController player; [SerializeField] protected float speed; // 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 //enemy death and recoil protected virtual void Update() { if(health <= 0) { Destroy(gameObject); } if(isRecoiling) { if(recoilTimer < recoilLength) { recoilTimer += Time.deltaTime; } else { isRecoiling = false; recoilTimer = 0; } } } //enemy takes damage and has recoil public virtual void EnemyHit(float _damageDone, Vector2 _hitDirection, float _hitForce) { health -= _damageDone; if (!isRecoiling) { rb.AddForce(-_hitForce * recoilFactor * _hitDirection); } } //make player take damage when trigger collider touches player collider - not when player is invincible protected void OnTriggerStay2D(Collider2D _other) { if (_other.CompareTag("Player") && !PlayerController.Instance.pState.invincible) { Debug.Log("Attack"); Attack(); } } protected virtual void Attack() { PlayerController.Instance.TakeDamage(damage); } } using System.Collections; using System.Collections.Generic; using UnityEngine; public class Zombie : Enemy { // Start is called before the first frame update protected override void Start() { rb.gravityScale = 12f; } protected override void Awake() { base.Awake(); } // Update is called once per frame //make enemy follow player on x axis 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); } } //send recoil from Enemy script to zombie script public override void EnemyHit(float _damageDone, Vector2 _hitDirection, float _hitForce) { base.EnemyHit(_damageDone, _hitDirection, _hitForce); } }
November 15, 2023 at 8:36 pm #12207::Actually never mind i fixed my error
i had to create a tag on the player and call it “Player” like in the script
that’s what compare tag was used for
thanks for your help thoughNovember 15, 2023 at 9:28 pm #12208::Hi Niyam, glad you managed to fix the error!
I formatted the post where you shared your code to make it easier to read.
November 15, 2023 at 11:16 pm #12210::Ok cool thanks.
Ik ur done with the metroidvania series but it would be cool if u continued and made more mechanics aside from ones in hollow knight.
E.g u could make a character switch mechanic with each character having different sprites and moves – you could make a directional button input grappling hook – specific walls u can climb – charged attacks for more damage – a parry which stuns the enemy – homing attacks – swimming mechanics – maybe even elemental effects and how they would workAll just suggestions
November 16, 2023 at 1:16 am #12211 -
AuthorPosts
- You must be logged in to reply to this topic.
Advertisement below: