Forum begins after the advertisement:

 


[Part 3] Recoil problem when player hits the enemy

Home Forums Video Game Tutorial Series Creating a Metroidvania in Unity [Part 3] Recoil problem when player hits the enemy

Viewing 6 posts - 1 through 6 (of 6 total)
  • Author
    Posts
  • #17871
    leovzy abc
    Level 1
    Participant
    Helpful?
    Up
    0
    ::

    I’m new to game development and I’m having trouble with hits and recoil when adding recoil to a character. Example video:https://www.youtube.com/watch?v=-WqSn5Q-FBI

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

    It sounds like you’re looking to implement a recoil system for your character in a game. Here are some general steps and concepts you can follow to achieve this:

    1. Understanding Recoil

    Recoil is typically the result of a character firing a weapon. It usually involves two main effects:

    • Camera movement: The view shifts slightly to simulate the kickback from firing a weapon.
    • Character movement: The character may also be pushed back or experience some form of body movement.

    2. Implementing Recoil

    Here’s a simple way to implement recoil in a game engine:

    a. Camera Shake

    You can create a camera shake effect when the character fires a weapon. This can be done using a coroutine or a simple timer. Here’s a pseudo-code example:

    IEnumerator CameraShake(float duration, float magnitude)
    {
        Vector3 originalPosition = Camera.main.transform.localPosition;
    
        float elapsed = 0.0f;
        while (elapsed < duration)
        {
            float x = Random.Range(-1f, 1f) * magnitude;
            float y = Random.Range(-1f, 1f) * magnitude;
    
            Camera.main.transform.localPosition = new Vector3(x, y, originalPosition.z);
    
            elapsed += Time.deltaTime;
    
            yield return null;
        }
    
        Camera.main.transform.localPosition = originalPosition;
    }

    b. Character Recoil

    You can apply a force or change the position of your character when shooting. For example:

    void ApplyRecoil()
    {
        // Adjust these values according to your needs
        float recoilAmount = 0.1f;
        Vector3 recoilDirection = new Vector3(0, recoilAmount, 0); // Upwards recoil
    
        transform.position += recoilDirection;
        StartCoroutine(CameraShake(0.1f, 0.1f)); // Call camera shake
    }

    3. Triggering Recoil

    You’ll want to call the ApplyRecoil method when the player fires their weapon. This would typically be within your shooting logic:

    void Update()
    {
        if (Input.GetButtonDown("Fire1")) // Assuming Fire1 is your shoot button
        {
            // Fire the weapon
            ApplyRecoil();
        }
    }

    4. Fine-Tuning

    • Adjust the recoil values: Experiment with the magnitude and duration to find what feels right for your game.
    • Add additional effects: You could also add sound effects, animations, or particle effects to enhance the shooting experience.

    5. Testing

    Make sure to test the recoil in different scenarios to ensure it feels natural and not too jarring for the player.

    Feel free to ask if you have specific parts of your code you want help with or if you need further clarification on any of these steps!

    #17873
    Terence
    Level 31
    Keymaster
    Helpful?
    Up
    0
    ::

    @leovzyabc can you describe the issue in a bit more detail? The recoil in your video looks like it is working. What did you want to change about it?

    Do share the scripts for your player and enemy as well.

    #17875
    leovzy abc
    Level 1
    Participant
    Helpful?
    Up
    0
    ::

    Enemy recoil works but player recoil doesnt work (Player does not move back after attacking) and Even if I don’t attack the enemy, when I hit the enemy it is considered a hit. Player recoil and movement script:

    using System.Collections;
    using System.Collections.Generic;
    using UnityEngine;
    using UnityEngine.Animations;
    
    public class playermovement : MonoBehaviour
    {
        [Header("Horizontal movement settings")] 
        [SerializeField] private float walkspeed = 1;
        [Space(5)]
        private float xAxis, yAxis;
        private Rigidbody2D rb;
    
    
        private bool canDash = true;
        private bool dashed;
    
        [Header("Vertical Movement Settings")]
        [SerializeField] private float jumpforce = 20;
        private int jumpBufferCount = 0;
        [SerializeField] private int jumpbufferFrames;
        private float coyoTimecounter = 0;
        [SerializeField] private float coyoteTime;
        private int airJumpCounter = 0;
        [SerializeField] private int maxAirJumps;
        [Space(5)]
    
    
    
        Animator anim;
        private float gravity;
    
        [Header("Ground check settings")]
        [SerializeField] private Transform groundcheck;
        [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;
        float timebetweenAttack, timesinceAttack;
        [SerializeField] Transform SideAttackTransform, UpAttackTranform, Downattacktransfrom;
        [SerializeField] Vector2 SideAttackArea,UpAttackArea,DownAttackArea;
        [SerializeField] LayerMask attackableLayer;
        [SerializeField] float damage;
        [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)]
    
    
        PlayerstateList pState;
    
    
        public static playermovement Instance;
    
    
        private void Awake()
        {
            if(Instance != null && Instance != this)
            {
                Destroy(gameObject);
            }
            else
            {
                Instance = this;
            }
        }
    
    
        void Start()
        {
            pState = GetComponent<PlayerstateList>();
            rb = GetComponent<Rigidbody2D>();
            anim = GetComponent<Animator>();
            gravity = rb.gravityScale;
        }
    
        private void ODrawGizmos()
        {
            Gizmos.color = Color.red;
            Gizmos.DrawWireCube(SideAttackTransform.position, SideAttackArea);
            Gizmos.DrawWireCube(UpAttackTranform.position, UpAttackArea);
            Gizmos.DrawWireCube(Downattacktransfrom.position, DownAttackArea);
        }
    
    
        void Update()
        {
           GetInputs();
           UpdateJumpVariables();
           startDash();
           if (pState.dashing) return;
           Flip();
           Move();
           Jump();
           Attack();
    
    
    
        }
    
        void GetInputs()
        {
            xAxis = Input.GetAxisRaw("Horizontal");
            yAxis = Input.GetAxisRaw("Vertical");
            attack = Input.GetMouseButtonDown(0);
    
        }
    
        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.velocity = new Vector2(transform. localScale.x * dashSpeed, 0);
            if(grounded()) Instantiate(dashEffect, transform);
            rb.gravityScale = 0;
            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);
                }
                else if(yAxis > 0)
                {
                    Hit(UpAttackTranform, UpAttackArea, ref pState.recoilingY, recoilYspeed);
                }
                else if(yAxis > 0 && !grounded())
                {
                    Hit(Downattacktransfrom, DownAttackArea, ref pState.recoilingY, recoilYspeed);
                }
            }
        }
    
        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 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 bool grounded()
        {
            if(Physics2D.Raycast(groundcheck.position, Vector2.down, groundcheckY, whatIsground) 
            || Physics2D.Raycast(groundcheck.position + new Vector3(groundcheckX,0, 0), Vector2.down, groundcheckY, whatIsground) 
            || Physics2D.Raycast(groundcheck.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(jumpBufferCount > 0 && coyoTimecounter > 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;
                    coyoTimecounter = coyoteTime;
                    airJumpCounter = 0;
                }
                else
                {
                    coyoTimecounter -= Time.deltaTime;
                }
    
                if (Input.GetButtonDown("Jump"))
                {
                    jumpBufferCount = jumpbufferFrames;
                }
                else
                {
                    jumpBufferCount--;
                }
    
            }
    
    
    }
    
    
    
    

    Enemy script:

    using System.Collections;
    using System.Collections.Generic;
    using UnityEngine;
    
    public class Enemy : MonoBehaviour
    {
        [SerializeField] float health;
        [SerializeField] float recoilLength;
        [SerializeField] float recoilFactor;
        [SerializeField] bool isRecoiling = false;
        float recoilTimer;
        Rigidbody2D rb;
        void Start()
        {
    
        }
    
        void Awake()
        {
          rb = GetComponent<Rigidbody2D>();  
        }
    
        void Update()
        {
            if(health <= 0)
            {
                Destroy(gameObject);
            }
    
            if(isRecoiling)
            {
                if(recoilTimer < recoilLength)
                {
                    recoilTimer+= Time.deltaTime;
                }
                else
                {
                    isRecoiling = false;
                    recoilTimer = 0;
                }
            }
        }
        public void enemyHit(float _damageDone, Vector2 _hitDirection, float _hitForce)
        {
            health -= _damageDone;
            if (!isRecoiling)
            {
                rb.AddForce(-_hitForce * recoilFactor * _hitDirection);
            }
        }
    }
    
    
    
    #17876
    Ser Apustaja
    Level 5
    AI Assistant
    (BETA)
    Helpful?
    Up
    0
    ::

    To address the issues you’re facing with player recoil and unintended hits on the enemy, consider the following points:

    1. Player Recoil Not Working: Ensure that you are calling the Recoil() method in your Update() function after the Attack() method. It seems you have implemented the recoil logic, but it may not be executed if Recoil() is not called. Add Recoil(); at the end of your Update() method.

    2. Unintended Hits on Enemy: The condition to set _recoilDir to true in the Hit() method is currently based on whether any objects are detected in the attack area. This means that even if the player attacks without hitting an enemy, it will still register a hit. You should only set _recoilDir to true if the player actually hits an enemy. Move the line _recoilDir = true; inside the loop that checks for enemies:

      for(int i = 0; i < objectsToHit.Length; i++)
      {
          if (objectsToHit[i].GetComponent() != null)
          {
              _recoilDir = true; // Move this line here
              objectsToHit[i].GetComponent().enemyHit(damage, (transform.position - objectsToHit[i].transform.position).normalized, _recoilStrength);
          }
      }

    By making these adjustments, you should see the player recoil correctly after attacking and ensure that hits are only registered when an enemy is actually hit. If you have further questions or need additional clarification, feel free to ask!

    #17879
    Terence
    Level 31
    Keymaster
    Helpful?
    Up
    0
    ::

    Check out this post here. Make the same change on your end and the recoil should start working for you: https://blog.terresquall.com/community/topic/part-3-recoil-will-still-not-work-no-matter-what-i-try/#post-17525

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: