Forum begins after the advertisement:
[Part 3] Recoil whenever I attack not only when i hit a enemy
Home › Forums › Video Game Tutorial Series › Creating a Metroidvania in Unity › [Part 3] Recoil whenever I attack not only when i hit a enemy
- This topic has 1 reply, 3 voices, and was last updated 2 days, 10 hours ago by
Terence.
-
AuthorPosts
-
November 20, 2025 at 9:39 am #19055::
For some reason my player will recoil whenever I attack it used to only do this when i down slashed but I started adding more code and now it does it whenever I attack. here is the code
using System.Collections; using System.Collections.Generic; using Unity.Mathematics; using Unity.VisualScripting; using UnityEngine; using UnityEngine.InputSystem.LowLevel; using UnityEngine.UI; [RequireComponent(typeof(Rigidbody2D))] public class PlayerMovement : MonoBehaviour { [Header("Horizontal Movement Settings")] [SerializeField] private float walkspeed = 5; [Space(5)] [Header("Vertical Movement Settings")] [SerializeField] private float jumpForce = 45; private float jumpBufferCounter = 0; [SerializeField] private float jumpBufferFrames; private float coyoteTimeCounter = 0; [SerializeField] private float coyoteTime; private int airJumpCounter = 0; [SerializeField] private int maxAirJumps; [Space(5)] [Header("Jump Settings")] [SerializeField] private Transform groundCheckPoint; [SerializeField] private float groundCheckX = 0.5f; [SerializeField] private float groundCheckY = 0.2f; [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")] [SerializeField] float timeBetweenAttack; float timeSinceAttack; [SerializeField] Transform SideAttackTransform, UpAttackTransform, DownAttackTransform; [SerializeField] Vector2 SideAttackArea, UpAttackArea, DownAttackArea; [SerializeField] LayerMask AttackableLayer; [SerializeField] float damage; [SerializeField] GameObject slashEffect; private bool attack = false; bool restoreTime; float restoreTimeSpeed; [Space(5)] [Header("Recoil Settings")] [SerializeField] int recoilXSteps = 5; [SerializeField] int recoilYSteps = 5; [SerializeField] float recoilXSpeed = 100; [SerializeField] float recoilYSpeed = 100; int stepsXrecoil, stepsYrecoil; [Header("Health Settings")] [SerializeField] GameObject bloodSpurt; [SerializeField] float hitFlashSpeed; public int health; public int maxHealth; public delegate void OnHealthChangedDelegate(); [HideInInspector] public OnHealthChangedDelegate OnHealthChangedCallback; float healTimer; [SerializeField] float timeToHeal; [Space(5)] [HideInInspector]public PlayerStateScript pState; private Rigidbody2D rb; private float xAxis, yAxis; private float gravity; Animator anim; private bool canDash = true; private bool dashed; private SpriteRenderer sr; public static PlayerMovement Instance; private void Awake() { if (Instance != null && Instance != this) { Destroy(gameObject); } else { Instance = this; } DontDestroyOnLoad(gameObject); } // Start is called once before the first execution of Update after the MonoBehaviour is created void Start() { pState = GetComponent<PlayerStateScript>(); sr = GetComponent<SpriteRenderer>(); rb = GetComponent<Rigidbody2D>(); anim = GetComponent<Animator>(); gravity = rb.gravityScale; Health = maxHealth; } 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() { GetInputs(); UpdateJumpVariables(); if (pState.dashing) return; Flip(); Move(); Jump(); StartDash(); Attack(); RestoreTimeScale(); FlashWhileInvincible(); Heal(); } 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.linearVelocity = new Vector2(walkspeed * xAxis, rb.linearVelocity.y); anim.SetBool("Walking", rb.linearVelocity.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; int _dir = pState.lookingRight ? 1 : -1; rb.linearVelocity = new Vector2(_dir * 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); } } } 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.linearVelocity = new Vector2(-recoilXSpeed, 0); } else { rb.linearVelocity = new Vector2(recoilXSpeed, 0); } } if (pState.recoilingY) { rb.gravityScale = 0; if (yAxis < 0) { rb.linearVelocity = new Vector2(rb.linearVelocity.x, recoilYSpeed); } else { rb.linearVelocity = new Vector2(rb.linearVelocity.x, -recoilYSpeed); } airJumpCounter = 0; } else { rb.gravityScale = gravity; } //stop recoil if (pState.recoilingX && stepsXrecoil < recoilXSteps) { stepsXrecoil++; } else { StopRecoilX(); } if (pState.recoilingY && stepsYrecoil < recoilYSteps) { stepsYrecoil++; } else { StopRecoilY(); } if (Grounded()) { StopRecoilY(); } } void StopRecoilX() { stepsXrecoil = 0; pState.recoilingX = false; } void StopRecoilY() { stepsYrecoil = 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(); } } } } void Heal() { if (Input.GetButton("Healing") && Health < maxHealth && !pState.dashing) { pState.healing = true; anim.SetBool("Healing", true); // Heal timer healTimer += Time.deltaTime; if(healTimer >= timeToHeal) { health++; healTimer = 0; } } else { pState.healing = false; anim.SetBool("Healing", false); healTimer = 0; } } 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 (jumpBufferCounter > 0 && coyoteTimeCounter > 0 && !pState.jumping) { jumpBufferCounter = 0; coyoteTimeCounter = 0; rb.linearVelocity = new Vector2(rb.linearVelocity.x, jumpForce); pState.jumping = true; } if (!Grounded() && airJumpCounter < maxAirJumps && Input.GetButtonDown("Jump")) { pState.jumping = true; airJumpCounter++; rb.linearVelocity = new Vector2(rb.linearVelocity.x, jumpForce); } if (Input.GetButtonUp("Jump") && rb.linearVelocity.y > 3) { rb.linearVelocity = new Vector2(rb.linearVelocity.x, 0); pState.jumping = false; } 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; } } }November 20, 2025 at 9:39 am #19056::Hello Jackson! It looks like you’re experiencing an issue with your player character recoiling whenever you attack, even when it shouldn’t. This could be due to how the recoil logic is being triggered in your
AttackandRecoilmethods.Here are a few things to check and adjust in your code:
-
Recoil Logic in the
HitMethod: In yourHitmethod, you’re settingpState.recoilingXorpState.recoilingYtotruewhenever an enemy is hit. This means that if your attack hits an enemy, the recoil will be triggered. If you want to prevent recoil when attacking without hitting an enemy, you might want to add a condition to check if any enemies were actually hit before setting the recoil state.if (objectsToHit.Length > 0) { _recoilDir = true; // This will trigger recoil }You could modify this to only set
_recoilDirif there are hits:if (objectsToHit.Length > 0) { _recoilDir = true; // Trigger recoil only if we hit an enemy for (int i = 0; i < objectsToHit.Length; i++) { Enemy e = objectsToHit[i].GetComponent(); if (e && !hitEnemies.Contains(e)) { e.EnemyHit(damage, (transform.position - objectsToHit[i].transform.position).normalized, _recoilStrength); hitEnemies.Add(e); } } } -
Recoil Steps: In your
Recoilmethod, you are incrementingstepsXrecoilandstepsYrecoilbased on whether the player is recoiling. Ensure that these steps are reset properly when the recoil ends. If the recoil is being triggered multiple times without resetting, it could cause continuous recoil. -
Check Attack Conditions: Ensure that the attack logic is only being executed when you intend it to. You might want to add a check to see if the player is currently recoiling before allowing another attack.
if (attack && timeSinceAttack >= timeBetweenAttack && !pState.recoilingX && !pState.recoilingY) { // Execute attack logic } -
Debugging: Add some debug logs to see when the recoil states are being set. This can help you trace if the recoil is being triggered unexpectedly.
Debug.Log("Recoil X: " + pState.recoilingX + ", Recoil Y: " + pState.recoilingY);
By implementing these changes, you should be able to control when the recoil occurs more effectively. If the issue persists, please provide more details about when the recoil happens, and we can dig deeper! Happy coding!
November 22, 2025 at 3:53 pm #19058 -
-
AuthorPosts
- You must be logged in to reply to this topic.
Advertisement below: