Forum begins after the advertisement:
[Part 1-2 Bug Fixes] Faulty Double Jump, Jump & Dash, Jittery Sprites
Home › Forums › Video Game Tutorial Series › Creating a Metroidvania in Unity › [Part 1-2 Bug Fixes] Faulty Double Jump, Jump & Dash, Jittery Sprites
- This topic has 3 replies, 4 voices, and was last updated 3 weeks ago by
Terence.
-
AuthorPosts
-
March 22, 2024 at 7:54 pm #13608::
This is a supplementary post written for Part 1 & 2 of our Metroidvania series, and it aims to address 1 things:
- Address common issues / questions that readers run into, and possible solutions for these questions.
For convenience, below are the links to the article and the video:
[Part 1]
- Article Link: https://blog.terresquall.com/2023/04/creating-a-metroidvania-like-hollow-knight-part-1/
- Video Link: https://youtu.be/dYcf9_TdEW4
[Part 2]
- Article Link: https://blog.terresquall.com/2023/05/creating-a-metroidvania-like-hollow-knight-part-2/
- Video Link: https://youtu.be/rAnVMkJjWSI
Table of Contents
[Part 1]
[Part 2]
- Dashing mechanic does not work.
- Double Jump is not working.
- Dashing when left-clicking/attacking [Part 3 mechanic].
- Jump Buffer with adaptive frame rate
[Part 1] Movement & Camera
1. Player is not Jumping
- The Layer for
whatIsGround
has not been set on the Player. Or theFloor
has not been assigned the same Layer aswhatIsGround
. - If the values of
GroundCheck X
&GroundCheck Y
are not big enough. Ground Check Point
gameObject is not assigned or is not low enough, relative to the Player withGroundCheck Y
value included, to reach the LayerGround
beneath the player.
Set your Floor gameobject to the same Layer as ‘What Is Ground’. Set an empty gameobject to the ‘Ground Check Point’. Ensure the Ground Check X
&Y
values are big enough for them to reach the floor. SetWhat Is Ground Layer
to an appropriate Layer.Ensure your empty gameobject used for the Ground Check Point
is low enough/below the player’s model and box collider.
2. The Camera/Player is jittering/glitching.
- Set Player’s RigidBody2D to Interpolate
Set Player’s RigidBody2D to Interpolate to prevent jittering. [Interpolation solves most jittering issues, but when the player is moving at a fast enough speed, we can see the Player jitter backwards a few frames here and there. This is caused by the Camera not being able to match the Player’s speed.]
- Set Player’s
walkSpeed
to a lower value - Or set Camera’s
followSpeed
to a higher value/[1]
Ensure the Walk Speed is not too fast.
Ensure the Camera’s Follow Speed is fast enough.
Note: Setting Camera’s
followSpeed
to [1] will guarantee the Camera will follow the Player exactly.
[Part 2] Dash, Double Jump & Advanced Movement.
1. Dashing mechanic does not work.
[This issue is most commonly caused by a missing piece of code]
- Ensure that all parts of the Dashing mechanic’s code in the PlayerController script is as follows:
private bool canDash = true; private bool dashed; void Update() { GetInputs(); UpdateJumpVariables(); if (pState.dashing) return; Flip(); Move(); Jump(); StartDash(); } 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; }
2. Double Jump is not working.
[While this can be caused by missing code, the actual code for the mechanic relies on variables that can never fire if the value is left at [0]]- Ensure all the values for Double Jump in the Inspector are set.
Ensure that all values are above [0].
3. Dashing when left-clicking/attacking [Part 3 mechanic].
- Remove the “Mouse 0” keybind from “Alt Positive Button” under the Dash input from the Input Manager.
Remove any input under Alt Positive Button.
4. Jump Buffer with adaptive frame rate.
[A suggested fix for high frame rates to handle the low frame count of our jump buffer, by viewer VoxBeats]
private
intfloat jumpBufferCounter = 0; [SerializeField] privateintfloat jumpBufferFrames; void UpdateJumpVariables() { ... if (Input.GetButtonDown("Jump")) { jumpBufferCounter = jumpBufferFrames; } else {jumpBufferCounter--;jumpBufferCounter = jumpBufferCounter - Time.deltaTime * 10; } }
That will be all for Part 1 & 2. I will soon be updating on the common issues for Part 3. Hopefully this can help you on any issues you may have. However, if you find that your issues weren’t addressed or is a unique circumstance, you can submit a forum post to go into detail on your problem for further assistance.
and 1 other person have upvoted this post. July 4, 2024 at 12:09 pm #15181::Just wanted to add an extra note for Part 1 pointed out by @soulhealer6154 on YouTube:
Flipping non-uniformly scaled character sprites
If you have a character sprite that does not have a scale of (1, 1, 1), you may find that when moving the character around, your sprite’s X, Y and Z values will always change to 1. This may distort your sprite.
To fix this, you will need to modify the
Flip()
function onPlayerController
as follows:void Flip() { if (xAxis < 0) { transform.localScale = new Vector2(
-1-Mathf.Abs(transform.localScale.x), transform.localScale.y); } else if (xAxis > 0) { transform.localScale = new Vector2(1Mathf.Abs(transform.localScale.x), transform.localScale.y); } }What the code does is that instead of setting the scale to 1, we are using the GameObject’s current scale value, but using
Mathf.Abs()
on it to make sure that we always take the positive value. Then, when moving left, we take-Maths.Abs()
, so that we always take the negative of the value. Of course, for the right-facing movement, we just take the default (positive) value.July 20, 2025 at 6:36 pm #18535::i have a problem in part 2 with dash if i press dash and walk my dash animation goes interrupted by walking animation and i am unable to fully display dash animation and when i am standing and press dash button then i can properly dash and also in air when i dash then also dash animation goes interrupted into free falling animation as jump animation so i watched you video many times so i didnt see any problem with my code. here is my code
[UPDATE] I fixed this issue by adding transition time; 0.2
using UnityEngine; using System; using System.Collections; public class player_controller : MonoBehaviour { [Header("Player Movement")] private Rigidbody2D rb; [SerializeField] private float speed = 10f; private float xAxis = 0f; Animator anim; public float gravity; player_state_list pstate; // Reference to the player state list script public static player_controller instance; private int jumpbuffercounter = 0; [SerializeField] private int jumpbufferframes; [Header("Ground cheak setting")] [SerializeField] private float jumpforce = 40f; // The upward force applied when the player jumps [SerializeField] private Transform groundCheck;// A reference point (usually at the player's feet) used to check if the player is on the ground [SerializeField] private float groundCheakY = 0.2f;// The vertical distance of the ground-check raycast [SerializeField] private float groundCheakX = 0.5f;// The horizontal offset used for side raycasts during ground detection [SerializeField] private LayerMask whatisground;// Specifies which layers count as ground for the player to stand on [Header("Jumping Variables")] private float cayotecounter = 0; // Counter for coyote time, allowing a jump shortly after leaving the ground [SerializeField] private float cayotetime ; // The time the player can still jump after leaving the ground private int air_jump_counter = 0; [SerializeField] private int max_air_jumps ; [Header("Dash Settings")] [SerializeField] private float dashSpeed; [SerializeField] private float dashDuration; [SerializeField] private float dashCooldown; [SerializeField] GameObject dashEffect; private bool canDash = true; private bool dashed; private void Awake() { if (instance != null && instance != this) { Destroy(gameObject); } else { instance = this; } } void Start() { pstate = GetComponent<player_state_list>(); // Get the player state list component rb = GetComponent<Rigidbody2D>(); anim = GetComponent<Animator>(); gravity = rb.gravityScale; } void Update() { getaxis(); UpdatejumpVariables(); if (pstate.dashing) return; flip(); move(); jump(); StartDash(); void getaxis() { xAxis = Input.GetAxis("Horizontal"); // This line will work if you're using the old Input System } void flip() { if (xAxis < 0) { transform.localScale = new Vector2(-1, transform.localScale.y); // Flips the player to face left } else if (xAxis > 0) { transform.localScale = new Vector2(1, transform.localScale.y); // Flips the player to face right } } } void StartDash() { if (Input.GetButtonDown("Dash") && canDash && !dashed) { StartCoroutine(Dash()); // Start the Dash coroutine if the dash button is pressed and dashing is allowed dashed = true; // Set dashed to true to indicate that the player has dashed } if (Ground()) { dashed = false; } } IEnumerator Dash() { canDash = false; // he wont be able to dash during dash pstate.dashing = true; //yes he is dashing so he is in dashing condition anim.SetTrigger("dashing"); //now dash animation triggers rb.gravityScale = 0; //it means during dashing gravity became zero and it will reset in next line rb.linearVelocity = new Vector2(transform.localScale.x * dashSpeed, 0); // Sets the player's velocity to the dash speed in the direction they are facing if(Ground()) Instantiate(dashEffect, transform); yield return new WaitForSeconds(dashDuration); // Wait for the duration of the dash rb.gravityScale = gravity; // Reset the gravity scale to its original value pstate.dashing = false; // Set the dashing state to false yield return new WaitForSeconds(dashCooldown); // Wait for the cooldown period before allowing another dash canDash = true; // Allow the player to dash again } private void move() { rb.linearVelocity = new Vector2(xAxis * speed, rb.linearVelocity.y); // ✅ fixed anim.SetBool("walking", rb.linearVelocity.x != 0 && Ground()); // This line will set the walking animation when the player is moving and on the ground } public bool Ground() { if (Physics2D.Raycast(groundCheck.position, Vector2.down, groundCheakY, whatisground)// 🔽 Center Raycast — checks directly below the player's feet || Physics2D.Raycast(groundCheck.position + new Vector3(groundCheakX, 0, 0), Vector2.down, groundCheakX, whatisground) // 🔽 Right Raycast — checks slightly to the right of the player's feet || Physics2D.Raycast(groundCheck.position + new Vector3(-groundCheakX, 0, 0), Vector2.down, groundCheakX, whatisground))// 🔽 Left Raycast — checks slightly to the left of the player's feet { return true; // Player is on the ground } else { return false; // Player is not on the ground } } private void jump() { if (Input.GetButtonUp("Jump") && rb.linearVelocity.y > 0) //when player left the jump button then the player will jump small { rb.linearVelocity = new Vector2(rb.linearVelocity.x, 0); // ✅ fixed pstate.jumping = false; // Set the jumping state to false } if (!pstate.jumping) { if (/*Input.GetButtonDown("Jump")*/jumpbuffercounter > 0 && /*Ground()*/cayotecounter > 0) //this line will allow the player to jump only when they are on the ground { rb.linearVelocity = new Vector3(rb.linearVelocity.x, jumpforce);// ✅ fixed pstate.jumping = true; // Set the jumping state to true } else if (!Ground() && air_jump_counter < max_air_jumps && Input.GetButtonDown("Jump")) { pstate.jumping = true; // Set the jumping state to true rb.linearVelocity = new Vector3(rb.linearVelocity.x, jumpforce); // Apply the jump force air_jump_counter++; // Increment the air jump counter } } anim.SetBool("jumping", !Ground()); // This line will set the jumping animation when the player is jumping } void UpdatejumpVariables() { if (Ground()) { pstate.jumping = false; // Player is on the ground, not jumping cayotecounter = cayotetime; // Reset the coyote time counter when grounded air_jump_counter = 0; } else { cayotecounter -= Time.deltaTime; // Decrease the coyote time counter by the time elapsed since the last frame } if (Input.GetButtonDown("Jump")) { jumpbuffercounter = jumpbufferframes; // Reset the jump buffer counter when the jump button is pressed } else { jumpbuffercounter--; // Decrease the jump buffer counter if the button is not pressed } } }
has upvoted this post. July 20, 2025 at 6:46 pm #18536::To address the problem you’re facing with your dash animation being interrupted by the walking or jumping animations, you need to ensure that your animation states are managed correctly. Here’s a step-by-step guide to help you resolve the issue:
-
Animation State Management: Ensure that your Animator controller is set up correctly with proper transitions between animations. You need to make sure that the dash animation has a higher priority or a condition that prevents it from being interrupted by the walking or jumping animations.
-
Modify the Update Method: You should not allow the walking animation to play while dashing. To implement this, you can set conditions to control when the animations can play in the
move()
function. You should only set the walking animation if the player is not dashing.Update your
move()
method to look like this:private void move() { rb.linearVelocity = new Vector2(xAxis * speed, rb.linearVelocity.y); // Set walking animation only if not dashing if (!pstate.dashing) { anim.SetBool("walking", rb.linearVelocity.x != 0 && Ground()); } }
-
Jump Animation Control: Similar to the walking animation, you should also check if the player is dashing before allowing the jumping animation to play. You can modify the
jump()
function to prevent jumping animation from playing while dashing:private void jump() { if (!pstate.dashing) // Check if not dashing { if (Input.GetButtonUp("Jump") && rb.linearVelocity.y > 0) { rb.linearVelocity = new Vector2(rb.linearVelocity.x, 0); pstate.jumping = false; } if (!pstate.jumping) { if (jumpbuffercounter > 0 && cayotecounter > 0) { rb.linearVelocity = new Vector3(rb.linearVelocity.x, jumpforce); pstate.jumping = true; } else if (!Ground() && air_jump_counter < max_air_jumps && Input.GetButtonDown("Jump")) { pstate.jumping = true; rb.linearVelocity = new Vector3(rb.linearVelocity.x, jumpforce); air_jump_counter++; } } anim.SetBool("jumping", !Ground()); } }
-
Dash Animation: Ensure that your dash animation in the Animator has a trigger called "dashing" and that it transitions correctly to and from this animation. You might want to ensure that the transitions to the dash animation do not allow for interruptions from other animations.
-
Testing: After making these changes, test your game again. While dashing, the walking and jumping animations should not trigger, allowing the dash animation to play fully. If you still face issues, consider reviewing your Animator transitions and conditions to ensure they are set up correctly.
By managing the animation states properly, you should be able to resolve the interruptions you're facing during the dash animation.
July 20, 2025 at 10:34 pm #18539::i have a problem in part 2 with dash if i press dash and walk my dash animation goes interrupted by walking animation and i am unable to fully display dash animation and when i am standing and press dash button then i can properly dash and also in air when i dash then also dash animation goes interrupted into free falling animation as jump animation so i watched you video many times so i didnt see any problem with my code. here is my code
@akifbhai awesome stuff. Thanks for the update.[UPDATE] I fixed this issue by adding transition time; 0.2
-
AuthorPosts
- You must be logged in to reply to this topic.
Advertisement below: