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?

Viewing 6 posts - 1 through 6 (of 6 total)
  • Author
    Posts
  • #12202
    Niyam Shah
    Participant

    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?!?!?)

    #12206
    Niyam Shah
    Participant
    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);
        }
    }
    #12207
    Niyam Shah
    Participant

    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 though

    #12208
    Terence
    Keymaster

    Hi Niyam, glad you managed to fix the error!

    I formatted the post where you shared your code to make it easier to read.

    #12210
    Niyam Shah
    Participant

    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 work

    All just suggestions

    #12211
    Terence
    Keymaster

    These are definitely something that I can consider taking up after I’m done with the Vampire Survivors series. Thanks for the suggestions!

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

Go to Login Page →


Advertisement below: