Forum begins after the advertisement:
[Part 3] My player knockback wont trigger when i hit the enemy.
Home › Forums › Video Game Tutorial Series › Creating a Metroidvania in Unity › [Part 3] My player knockback wont trigger when i hit the enemy.
- This topic has 0 replies, 2 voices, and was last updated 5 days, 19 hours ago by
Ser Apustaja.
-
AuthorPosts
-
February 22, 2026 at 9:30 am #19259::
using System.Collections; using Unity.VisualScripting; using UnityEngine; public class PlayerController : MonoBehaviour { // Start is called once before the first execution of Update after the MonoBehaviour is created [Header("Horizontal Movement Settings")] private Rigidbody2D rb; [SerializeField] private float walkspeed = 1; [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; [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("Dash Settings")] [SerializeField] private float dashSpeed; [SerializeField] private float dashTime; [SerializeField] private float dashCooldown; [SerializeField] GameObject dashEffect; [Space(5)] [Header("Attack Settingss")] bool attack = false; [SerializeField] private float timeBetweenAttack; [SerializeField] float timeSinceAttack; [SerializeField] Transform SideAttackTransform, UpAttackTransform, DownAttackTransform; [SerializeField] Vector2 SideAttackArea, UpAttackArea, DownAttackArea; [SerializeField] LayerMask attackableLayer; [SerializeField] float damage; [SerializeField] GameObject slashEffect; [Space(5)] [Header("Recoil Settings:")] [SerializeField] private int recoilXSteps = 5; //how many FixedUpdates() the player recoils horizontally for [SerializeField] private int recoilYSteps = 5; //how many FixedUpdates() the player recoils vertically for [SerializeField] private float recoilXSpeed = 100; //the speed of horizontal recoil [SerializeField] private float recoilYSpeed = 100; //the speed of vertical recoil private int stepsXRecoiled, stepsYRecoiled; //the no. of steps recoiled horizontally and verticall [Space(5)] PlayerStateList pstate; private float gravity; Animator anim; private bool canDash = true; private bool dashed; private float xAxis, yAxis; public static PlayerController 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; } // Update is called once per frame void Update() { GetInputs(); UpdateJumpVariables(); if (pstate.dashing) return; Flip(); Move(); Jump(); StartDash(); Attack(); Recoil(); } void GetInputs() { xAxis = Input.GetAxisRaw("Horizontal"); yAxis = Input.GetAxisRaw("Vertical"); attack = Input.GetMouseButtonDown(0); } private void OnDrawGizmos() { Gizmos.color = Color.red; Gizmos.DrawWireCube(SideAttackTransform.position, SideAttackArea); Gizmos.DrawWireCube(UpAttackTransform.position, UpAttackArea); Gizmos.DrawWireCube(DownAttackTransform.position, DownAttackArea); } 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; rb.linearVelocity = 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, 80, 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); 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.linearVelocity = new Vector2(-recoilXSpeed, 0); } else { rb.linearVelocity = new Vector2(recoilXSpeed, 0); } } if (pstate.recoilingY) { if(yAxis < 0) { rb.gravityScale = 0; rb.linearVelocity = new Vector2(rb.linearVelocity.x, recoilYSpeed); } else { rb.linearVelocity = new Vector2(rb.linearVelocity.x, -recoilYSpeed); } } else { rb.gravityScale = gravity; } //stop recoil if(pstate.recoilingX && stepsXRecoiled < recoilXSteps) { stepsXRecoiled++; } { stopRecoilX(); } if (pstate.recoilingY && stepsYRecoiled < recoilYSteps) { stepsYRecoiled++; } { stopRecoilY(); } if (Grounded()) { stopRecoilY(); } } 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 stopRecoilX() { stepsXRecoiled = 0; pstate.recoilingX = false; } void stopRecoilY() { stepsYRecoiled = 0; pstate.recoilingY = false; } 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() { { // 1. CUTTING THE JUMP if (Input.GetButtonUp("Jump") && rb.linearVelocity.y > 0) { rb.linearVelocity = new Vector2(rb.linearVelocity.x, 0); pstate.jumping = false; } // 2. TRIGGERING THE JUMP if (!pstate.jumping) { if (jumpBufferCounter > 0 && coyoteTimeCounter > 0) { rb.linearVelocity = new Vector2(rb.linearVelocity.x, jumpForce); pstate.jumping = true; // CRITICAL: Reset these counters so the jump doesn't loop jumpBufferCounter = 0; coyoteTimeCounter = 0; } } anim.SetBool("Jumping", !Grounded()); } } void UpdateJumpVariables() { if (Grounded()) { pstate.jumping = false; coyoteTimeCounter = coyoteTime; } else { coyoteTimeCounter -= Time.deltaTime; } if (Input.GetButtonDown("Jump")) { jumpBufferCounter = jumpBufferFrames; } else { jumpBufferCounter = jumpBufferCounter - Time.deltaTime * 10; } } }February 22, 2026 at 11:45 am #19260::It looks like you’re working on a player controller script for a 2D character in Unity. The script is quite comprehensive and covers movement, jumping, dashing, attacking, and recoil mechanics.
Here are a few suggestions to fix the issues and improve your code:
-
Resetting Recoil Steps: In your
Recoil()method, you’re stopping recoil immediately after incrementing the steps. This means the recoil might not behave as expected. You should only stop the recoil if the steps have reached the max limit:if (pstate.recoilingX && stepsXRecoiled = recoilXSteps) { stopRecoilX(); } if (pstate.recoilingY && stepsYRecoiled = recoilYSteps) { stopRecoilY(); } -
Ground Check Logic: Your ground check logic looks good, but ensure that the
groundCheckPointis positioned correctly in the Unity editor to properly detect ground. -
Check for Null References: In your
Start()method, ensure thatpstate,rb, andanimare not null after callingGetComponent(). This can help you avoid runtime errors if any of these components are missing. -
Dash Logic: Make sure that the dash effect (
dashEffect) is correctly set in the Unity editor, or else it will not instantiate. -
Animation States: Ensure that you have the appropriate animator states set up in Unity to match the triggers you are calling in your code (like “Dashing”, “Attacking”, “Jumping”, etc.).
-
Testing: After making the changes, thoroughly test each functionality (movement, jumping, dashing, attacking, recoil) to ensure they work as intended.
Here’s a small snippet with some of the discussed changes applied:
void Start() { pstate = GetComponent(); rb = GetComponent(); anim = GetComponent(); gravity = rb.gravityScale; // Optional: Check for null references if (pstate == null || rb == null || anim == null) { Debug.LogError("Missing required components!"); } }Make these adjustments, and your script should run more smoothly. If you encounter specific errors or issues after these changes, feel free to ask for further assistance!
-
-
AuthorPosts
- You must be logged in to reply to this topic.
Advertisement below: