Forum begins after the advertisement:

 


[Part 3] Player becomes invincible forever after taking damage

Home Forums Video Game Tutorial Series Creating a Metroidvania in Unity [Part 3] Player becomes invincible forever after taking damage

Viewing 6 posts - 1 through 6 (of 6 total)
  • Author
    Posts
  • #17882
    Lundin Jonsson
    Level 1
    Participant
    Helpful?
    Up
    0
    ::
    using System.Collections;
    using System.Collections.Generic;
    using UnityEditor.Experimental.GraphView;
    using UnityEngine;
    using UnityEngine.Experimental.AI;
    
    public class PlayerController : MonoBehaviour
    {
        [Header("Horizontal Movement Settings:")]
        [SerializeField] private float walkspeed = 1;
        [Space(5)]
    
        [Header("Vertical Movement Settings:")]
        [SerializeField] private float jumpForce = 45;
        private float jumpBufferCounter;
        [SerializeField] private float 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 GroundCheckPoints;
        [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("Attacking")]
        bool attack = false;
        [SerializeField] private float timeBetweenAttack;
        private float timeSinceAttack;
        [SerializeField] Transform SideAttackTransform, UpAttackTransform, DownAttackTransform;
        [SerializeField] Vector2 SideAttackArea, UpAttackArea, DownAttackArea;
        [SerializeField] LayerMask attackableLayer;
        [SerializeField] float damage;
        [SerializeField] GameObject slashEffect;
        bool restoreTime;
        float restoreTimeSpeed;
        [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("Health Settings")]
        public int health;
        public int maxHealth;
        [SerializeField] GameObject bloodSpurt;
        [SerializeField] float hitFlashSpeed;
        public delegate void OnHealthChangedDelegate();
        [HideInInspector] public OnHealthChangedDelegate onHealthChangedCallBack;
        [Space (5)]
    
        [HideInInspector] public PlayerStateList pState;
        private Rigidbody2D rb;
        private float xAxis, yAxis;
        private float gravity;
        private SpriteRenderer sr;
        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.red;
            Gizmos.DrawWireCube(SideAttackTransform.position, SideAttackArea);
            Gizmos.DrawWireCube(UpAttackTransform.position, UpAttackArea);
            Gizmos.DrawWireCube(DownAttackTransform.position, DownAttackArea);
        }
    
    
        // Update is called once per frame
        void Update()
        {
            GetInput();
            UpdateJumpVariables();
    
            if (pState.dashing) return;
            Flip();
            Move();
            Jump();
            StartDash();
            Attack();
            RestoreTimeScale();
            FlashWhileInvincible();
        }
        private void FixedUpdate()
        {
            if (pState.dashing) return;
            Recoil();
        }
    
        void GetInput()
        {
            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()
                {
            if (pState.recoilingX) return;
                    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, 85, 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);
            List<Enemy> HitEnemies = new List<Enemy>();
    
            if(objectsToHit.Length > 0)
            {
                _recoilDir = true;
            }
            for(int i = 0; i < objectsToHit.Length; i++)
            {
                Enemy e = objectsToHit[i].GetComponent<Enemy>();
                if (e && !HitEnemies.Contains(e))
                {
                    e.EnemyHit(damage, (transform.position - objectsToHit[i].transform.position).normalized, _recoilStrength);
                    HitEnemies.Add(e);
                }
            }
        }
        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);
        }
        void Recoil()
        {
            if (pState.recoilingX)
            {
                if (pState.lookingRight)
                {
                    rb.velocity = new Vector2(-recoilXSpeed, 0);
                }
                else
                {
                    rb.velocity = new Vector2(recoilXSpeed, 0);
                }
            }
            if (pState.recoilingY)
            {
                if(yAxis < 0)
                {
                    rb.gravityScale = 0;
                    rb.velocity = new Vector2(rb.velocity.x, recoilYSpeed);
                }
                else
                {
                    rb.gravityScale = 0;
                    rb.velocity = new Vector2(rb.velocity.x, -recoilYSpeed);
                }
                airJumpCounter = 0;
            }
            else
            {
                rb.gravityScale = gravity;
            }
    
            //stop recoil
            if(pState.recoilingX && stepsXRecoiled < recoilXSpeed)
            {
                stepsXRecoiled++;
            }
            else
            {
                StopRecoilX();
            }
            if (pState.recoilingY && stepsYRecoiled < recoilYSpeed)
            {
                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;
        }
        //fix Invincible Forever!!!!
        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(GroundCheckPoints.position, Vector2.down, groundCheckY, whatIsGround)
                || Physics2D.Raycast(GroundCheckPoints.position + new Vector3(groundCheckX, 0, 0), Vector2.down, groundCheckY, whatIsGround)
                || Physics2D.Raycast(GroundCheckPoints.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 = jumpBufferCounter - Time.deltaTime * 10;
            }
        }
    }
    #17883
    Ser Apustaja
    Level 5
    AI Assistant
    (BETA)
    Helpful?
    Up
    0
    ::

    It looks like you’re working on a PlayerController script for a 2D game in Unity, but there are some issues with the formatting of the code, particularly with the use of HTML entities instead of standard comparison operators. These issues can cause compile errors or unexpected behavior.

    Here are some corrections you should make to your code:

    1. Replace all instances of < with “.
    2. Replace all instances of & with &.

    Here’s an example of how to fix the Flip() method:

    void Flip()
    {
        if (xAxis  0)
        {
            transform.localScale = new Vector2(1, transform.localScale.y);
            pState.lookingRight = true;
        }
    }

    Make sure to apply these changes throughout your code. After making these adjustments, your code should compile correctly.

    If you’re still experiencing issues or have specific questions about functionality, feel free to ask!

    #17884
    Lundin Jonsson
    Level 1
    Participant
    Helpful?
    Up
    0
    ::

    This did not work at all

    The problem is something with this code

    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;

    #17885
    Ser Apustaja
    Level 5
    AI Assistant
    (BETA)
    Helpful?
    Up
    0
    ::

    If the StopTakingDamage coroutine isn’t working as expected, there are a few things you can check and adjust:

    1. Check for Null References: Ensure that pState, bloodSpurt, and anim are properly assigned before calling the coroutine. If any of those references are null, it could lead to unexpected behavior or errors.

      if(pState == null || bloodSpurt == null || anim == null)
      {
          Debug.LogError("One of the required components is not set!");
          yield break; // Exit the coroutine early
      }
    2. Coroutine Call: Make sure that you are actually calling StopTakingDamage() correctly from the method that handles damage. For example:

      public void TakeDamage(float _damage)
      {
          Health -= Mathf.RoundToInt(_damage);
          StartCoroutine(StopTakingDamage());
      }
    3. Animation State: Ensure that the “TakeDamage” animation trigger is set up correctly in the Animator. If the animation isn’t playing, you might want to check the transitions and conditions in the Animator window.

    4. Debugging: Add debug logs to see if the coroutine is being called and how long it takes to complete.

      IEnumerator StopTakingDamage()
      {
          Debug.Log("Entering 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;
          Debug.Log("Exiting StopTakingDamage");
      }
    5. Timing Issues: If you’re encountering issues with the invincibility not being applied correctly or lasting longer than expected, ensure there are no other parts of your code that might be affecting pState.invincible at the same time.

    6. Check for Other Coroutine Interference: If you’re calling this coroutine multiple times in quick succession, it could cause issues. Make sure that you’re managing coroutines correctly.

    If you’ve checked all of these and the issue persists, please specify what exactly is not working (e.g., is the animation not playing, is the invincibility not being applied, etc.), and I’ll be happy to help further!

    #17886
    Lundin Jonsson
    Level 1
    Participant
    Helpful?
    Up
    0
    ::

    Its fixed

    #17887
    Ser Apustaja
    Level 5
    AI Assistant
    (BETA)
    Helpful?
    Up
    0
    ::

    Great to hear that you’ve fixed the issue! If you have any more questions or need further assistance with your code or anything else, feel free to ask. Happy coding!

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: