Wayback Machinekoobas.hobune.stream
May JUN Jul
Previous capture 14 Next capture
2021 2022 2023
2 captures
13 Jun 22 - 14 Jun 22
sparklines
Close Help
  • Products
  • Solutions
  • Made with Unity
  • Learning
  • Support & Services
  • Community
  • Asset Store
  • Get Unity

UNITY ACCOUNT

You need a Unity Account to shop in the Online and Asset Stores, participate in the Unity Community and manage your license portfolio. Login Create account
  • Blog
  • Forums
  • Answers
  • Evangelists
  • User Groups
  • Beta Program
  • Advisory Panel

Navigation

  • Home
  • Products
  • Solutions
  • Made with Unity
  • Learning
  • Support & Services
  • Community
    • Blog
    • Forums
    • Answers
    • Evangelists
    • User Groups
    • Beta Program
    • Advisory Panel

Unity account

You need a Unity Account to shop in the Online and Asset Stores, participate in the Unity Community and manage your license portfolio. Login Create account

Language

  • Chinese
  • Spanish
  • Japanese
  • Korean
  • Portuguese
  • Ask a question
  • Spaces
    • Default
    • Help Room
    • META
    • Moderators
    • Topics
    • Questions
    • Users
    • Badges
  • Home /
avatar image
0
Question by Spritely · Aug 27, 2020 at 06:38 AM · jumpingplayer movement2d platformerdashknockback

Unity 2D: Properly Implementing Player Movement with a dash, jump, and knockback?

Hi. So I'm very new to Unity and coding as a whole. I've been fiddling around a lot with player controllers and trying to make one for a 2D platformer. I have read a lot about the advantages and disadvantages of using Unity physics for platformers. I have decided that although there may be better systems for platformers, that as a beginner the physics system is easiest for me to get started and learn with.


I've put together a controller below which seems to work just fine. The player can move, jump, double jump, dash, and get knocked back. I had a ton of issues with knockback not working as I was setting the velocity directly so any AddForce for a knockback was being overridden instantly. I have opted to have the player movement be overridden extremely briefly by AddForce when the player is knocked back so that it simulates being pushed away. Right now it only works in one direction but it works!


Onto my question. Is there anything in this code that should be improved or changed? I am not happy that my jump movement is being handled in Update() while all my other movement is being handled in FixedUpdate(). I also feel like I am missing Time.fixedDeltaTime in my movement? My code just seems sloppy and not optimal.

 using System.Collections;
 using System.Collections.Generic;
 using UnityEngine;
 
 public class MoveDashJump : MonoBehaviour
 {
     // Standard Movement
     [SerializeField] float currentMoveSpeed;
     [SerializeField] float baseMoveSpeed;
     private float moveInput;

 // Jumping
 [SerializeField] float jumpForce;
 private bool isGrounded;
 [SerializeField] Transform groundCheck;
 [SerializeField] float checkRadius;
 [SerializeField] LayerMask whatIsGround;
 private int extraJumps;
 [SerializeField] int extraJumpValue;

 // Rigidbody2D reference
 private Rigidbody2D playerRB;

 // Dashing
 [SerializeField] float dashSpeed;
 [SerializeField] float dashDuration;
 bool isDashing;

 // Knockback
 [SerializeField] float knockbackForce;
 bool beingKnockedBack;
 [SerializeField] float knockbackDuration;

 void Start()
 {
 // Setting values to their defaults and grabbing a rigidbody reference
     beingKnockedBack = false;
     currentMoveSpeed = baseMoveSpeed;
     isDashing = false;
     extraJumps = extraJumpValue;
     playerRB = GetComponent<Rigidbody2D>();
 }

 // Update is called once per frame
 void Update()
 {
 // Gathering input for movement and handling jump
     moveInput = Input.GetAxisRaw("Horizontal");

     if (isGrounded == true)
     {
         extraJumps = extraJumpValue;
     }

     if (Input.GetKeyDown(KeyCode.B) && extraJumps > 0)
     {
         playerRB.velocity = Vector2.up * jumpForce;
         extraJumps--;
     }

     if (Input.GetKeyDown(KeyCode.Space))
     {
         isDashing = true;
     }

     if (Input.GetKeyDown(KeyCode.K))
     {
         beingKnockedBack = true;
     }
 }

 private void FixedUpdate()
 {
     isGrounded = Physics2D.OverlapCircle(groundCheck.position, checkRadius, whatIsGround);

     if (isDashing)
     {
         StartCoroutine(Dash());
     }

     if (beingKnockedBack)
     {
         StartCoroutine(KnockBack());
     }

     if (beingKnockedBack)
     {
         //playerRB.velocity = new Vector2(knockbackForce, playerRB.velocity.y);
         playerRB.AddForce(new Vector2(knockbackForce, 0f), ForceMode2D.Impulse);
     }
     else
     {
         playerRB.velocity = new Vector2(moveInput * currentMoveSpeed, playerRB.velocity.y);
     }
 }

 IEnumerator Dash()
 {
     currentMoveSpeed = dashSpeed;
     yield return new WaitForSeconds(dashDuration);
     currentMoveSpeed = baseMoveSpeed;
     isDashing = false;
 }

 IEnumerator KnockBack()
 {
     yield return new WaitForSeconds(knockbackDuration);
     beingKnockedBack = false;
 }


To clarify how I want things to work:

  • dashing gives the player a short movement speed increase but they can still control movement

  • jumping simply sends them straight up and back down

  • knockback abruptly jerks them in a direction based (getting hit by an enemy or something)

Comment
Add comment
10 |3000 characters needed characters left characters exceeded
▼
  • Viewable by all users
  • Viewable by moderators
  • Viewable by moderators and the original poster
  • Advanced visibility
Viewable by all users

2 Replies

· Add your reply
  • Sort: 
avatar image
2

Answer by OutramJamie · Aug 27, 2020 at 11:39 AM

There's no 'correct' way of doing things but typically when using Rigidbodies its best to stick to just using AddForce OR Adding to the current velocity vector. Mixing the two can make balancing resistances for the desired motion much harder. (Velocity Ignores the mass of the object, whilst drag and AddForce take mass into account.) In my experience velocity is mostly useful if you want to stop an objects motion completely.


If you want a constant move speed on the ground and in the air I recommend making the physics material with 0 friction applied to either your player or ground collider. You can then balance the linear drag, mass and forces to get your desired behavior.


Because you're setting the velocity every update rather than incrementing it there is no need to scale with Time.deltaTime as it is already independent from the frame-rate.


Below is my attempt at solving your problem using only AddForce: Note: It is indeed beneficial to perform physics updates within FixedUpdate() but as down input detection is badly behaved I've just left it all in Update()


Edit: Added Snappy movement with Add Force

PlayerMovement.cs

 using UnityEngine;
 public class PlayerMovementB : MonoBehaviour
 {
     InputManager inputs;
     Rigidbody2D rb;
 
     // Standard Movement
     [SerializeField] float currentMoveSpeed;
     private float CurrentMoveSpeed
     {
         get { return currentMoveSpeed; }
         set
         {
             currentMoveSpeed = value;
             isMoveDown = true;
         }
     }
 
     [SerializeField] float baseMoveSpeed = 500f;
     private int moveInput;
 
     // Jumping
     [SerializeField] float jumpForce = 10f;
     [SerializeField] int jumps = 3;
     private int remainingJumps;
 
     // Dashing
     [SerializeField] float dashSpeed = 800f;
     [SerializeField] float dashDuration = 4;
     
     // Knockback
     bool beingKnockedBack;
     [SerializeField] float knockbackForce = 10;
     [SerializeField] float knockbackDuration = 1;
 
     //adjust this to accelerate faster or slower
     [SerializeField] float accelBoost = 10;
 
     //Flag for direction faced
     int facing;
     bool isMoveDown;
     bool isMoveUp;
     
 
     void Awake()
     {
         //Find components attached to this game object, returns null if not found
         inputs = GetComponent<InputManager>();
         rb = GetComponent<Rigidbody2D>();
         facing = 1;
         remainingJumps = jumps;
         CurrentMoveSpeed = baseMoveSpeed;
         isMoveDown = false;
         isMoveUp = false;
     }
 
     // Update is called once per frame
     void Update()
     {
         
         if (inputs.GetKeyDown(Action.dash))
         {
             Debug.Log("Dash");
             Dash();
         }
         
         if (inputs.GetKeyDown(Action.jump))
         {
             Debug.Log("Jump");
             Jump();
         }
 
         //Calculate Horizontal motion
         moveInput = 0;
         if (inputs.GetKey(Action.moveLeft))
         {
             //Debug.Log("Left");
             moveInput--;
             facing = -1;
         }
         if (inputs.GetKey(Action.moveRight))
         {
             moveInput++;
             facing = 1;
         }
         if (!isMoveDown) isMoveDown = inputs.GetKeyDown(Action.moveLeft) || inputs.GetKeyDown(Action.moveRight);
         
         if(!isMoveUp) isMoveUp = inputs.GetKeyUp(Action.moveLeft) || inputs.GetKeyUp(Action.moveRight);
 
         if (inputs.GetKeyDown(Action.knockback))
         {
             Debug.Log("Knockback");
             Knockback();
         }
 
     }
 
     int previousMoveInput = 0;
 
     private void FixedUpdate()
     {
         if (!beingKnockedBack)
         {
             if ((moveInput != 0) && !(isMoveUp && isMoveDown))
             {
                 Vector2 addedForce = Vector2.right * CurrentMoveSpeed * Time.fixedDeltaTime; // The force added each FixedUpdate
 
                 //If the first frame when pressed add large impulse;
                 if (isMoveDown)
                 {
                     isMoveDown = false;
                     rb.AddForce(Vector2.right * moveInput * GetTopSpeed(rb, addedForce) * rb.mass, ForceMode2D.Impulse);
                 }
                 else
                 {
                     rb.AddForce(addedForce * moveInput);
                 }
             }
             else if (isMoveUp || isMoveDown)
             {
                 //Stop the object;
                 rb.AddForce(Vector2.right * -rb.velocity.x * rb.mass, ForceMode2D.Impulse);
                 isMoveUp = false;
             }
         }
         else if (moveInput != 0) isMoveDown = true;
         previousMoveInput = moveInput;
     }
 
     //This Estimates the terminal velocity of the object.
     private float GetTopSpeed(Rigidbody2D rb, Vector2 repeatedForce)
     {
         float topSpeed = ((repeatedForce.magnitude / rb.drag) - Time.fixedDeltaTime * repeatedForce.magnitude) / rb.mass;
         return topSpeed;
     }
 
     private void Knockback()
     {
         if (IsInvoking("CancelKnockback")) return;
         beingKnockedBack = true;
         rb.AddForce(Vector2.left*knockbackForce*facing, ForceMode2D.Impulse);
         Invoke("CancelKnockback", knockbackDuration);
     }
 
     void CancelKnockback()
     {
         beingKnockedBack = false;
     }
 
     void Jump()
     {
         if (remainingJumps > 0) {
             rb.AddForce(Vector2.up*jumpForce, ForceMode2D.Impulse);
             remainingJumps--;
         }
     }
 
     void OnCollisionEnter2D(Collision2D collision)
     {
         //Check if collided with ground only when collision detected
         if (collision.gameObject.CompareTag("Ground"))
         {
             //reset jumps
             remainingJumps = jumps;
         }
     }
 
     void Dash()
     {
         if (!IsInvoking("CancelDash"))
         {
             CurrentMoveSpeed = dashSpeed;
             Invoke("CancelDash", dashDuration);
         }
     }
 
     void CancelDash()
     {
         CurrentMoveSpeed = baseMoveSpeed;
     }
 }
 

InputManager.cs

 using System;
 using System.Collections;
 using System.Collections.Generic;
 using UnityEngine;
 
 public enum Action
 {
     moveLeft,
     moveRight,
     jump,
     dash,
     knockback,
 }
 
 public class InputManager : MonoBehaviour
 {
     //Keyboard inputs indexable by InputCode enumeration
     private KeyCode[] InputMap;
 
     public void Awake()
     {
         InputMap = new KeyCode[] {
             KeyCode.LeftArrow,
             KeyCode.RightArrow,
             KeyCode.B,
             KeyCode.Space,
             KeyCode.K,
         };
     }
     
     public bool GetKey(Action inputCode)
     {
         return Input.GetKey(InputMap[(int)inputCode]);
     }
     public bool GetKeyDown(Action inputCode)
     {
         return Input.GetKeyDown(InputMap[(int)inputCode]);
     }
 
     public bool GetKeyUp(Action inputCode)
     {
         return Input.GetKeyUp(InputMap[(int)inputCode]);
     }
 
 }
 
Comment
Add comment · Show 3 · Share
10 |3000 characters needed characters left characters exceeded
▼
  • Viewable by all users
  • Viewable by moderators
  • Viewable by moderators and the original poster
  • Advanced visibility
Viewable by all users
avatar image Spritely · Aug 27, 2020 at 07:21 PM 0
Share

Does this bit of code for moving horizontally make it so the player slowly increases speed in a direction? I am trying to get some snappy movement which is why I was setting velocity directly but I really like the idea of using AddForce instead.

if (!beingKnockedBack) { //move horizontally rb.AddForce(Vector2.right current$$anonymous$$oveSpeed moveInput * Time.deltaTime); }

avatar image OutramJamie Spritely · Aug 28, 2020 at 01:42 AM 0
Share

You can adjust how snappy the movement is to some degree by tweaking forces and resistances but it will never be instant unless you set the velocity at the start. There is code available for predicting the ter$$anonymous$$al velocity from a given force; so the most 'sensible' thing is likely to set the horizontal velocity to this ter$$anonymous$$al velocity OnKeyDown to get the initial speed then continue with add force (make sure to preserve the vertical y motion or jumps will be cut off)

However I have a very over engineered solution here; Instead of setting the velocity it multiplies the force when stationary or moving slowly and reduces the multiplier to 1 using Linear interpolation dependent on the current velocity. The benefit here is you can still have external forces effect the player and can keep a very slight ramping up and down of their speed with easy control. Downside is all the overhead :/.

//Replacing if (!beingKnockedBack) condition in update() from previous code

 private void FixedUpdate()
     {
         if (!beingKnockedBack)
         {
             //This really needs to be in fixed update now
             Vector2 addedForce = Vector2.right * current$$anonymous$$oveSpeed * Time.fixedDeltaTime; // The force added each FixedUpdate
 
             //The following calculation could be cashed and updated when movment speed is changed
             //This Estimates the ter$$anonymous$$al velocity of the object and uses it to scale the acceleration forces to be larger.
             float topVelocity = ((addedForce.magnitude / rb.drag) - Time.fixedDeltaTime * addedForce.magnitude) / rb.mass;
             
 
             if (moveInput != 0)
             {
                 topVelocity *= moveInput; //Convert speed to velocity
                 float t = $$anonymous$$athf.Clamp(rb.velocity.x / topVelocity, 0, 1);
                 float impulseScale = $$anonymous$$athf.Lerp(accelBoost, 1, t);
                 Vector2 force = addedForce * moveInput * impulseScale;
                 
                 //move horizontally
                 rb.AddForce(force);
             }
             else if($$anonymous$$ath.Abs(rb.velocity.x) > 0.01f)
             {
                 //Oppose $$anonymous$$otion
                 if (rb.velocity.x > 0)
                 {
                     topVelocity *= -1;
                     addedForce *= -1;
                 }
 
                 //Arrest motion just as quickly by adding extra drag force
                 float t = $$anonymous$$athf.Clamp(1 - ($$anonymous$$ath.Abs(rb.velocity.x / topVelocity)), 0, 1);
                 //AccelBoost can be swapped here for a seperate deceleration variable for more control.
                 float impulseScale = $$anonymous$$athf.Lerp(accelBoost, 1, t);
                 Vector2 dragForce = addedForce * impulseScale;
                 //Debug.Log(dragForce);
                 rb.AddForce(dragForce);
             }
         }
     }

  
avatar image OutramJamie Spritely · Aug 28, 2020 at 02:06 PM 0
Share

After some research I've found it is actually possible to use Rigidbody2D.Impulse to apply a single force to get your character up to speed in one frame. Doing this means you wont mess with any collisions by setting the velocity directly. This is likely better than my previous solution as it's not trying to reach the top speed every frame but only on the first. (e.g some crates with weight and friction could slow or stop the player still)


 rb.AddForce(Vector2.right* moveInput * GetTopSpeed(rb, addedForce) * rb.mass, Force$$anonymous$$ode2D.Impulse);
 
     //This Estimates the ter$$anonymous$$al velocity of the object.
     private float GetTopSpeed(Rigidbody2D rb, Vector2 repeatedForce)
     {
         float topSpeed = ((repeatedForce.magnitude / rb.drag) - Time.fixedDeltaTime * repeatedForce.magnitude) / rb.mass;
         return topSpeed;
     }



avatar image
0

Answer by xxmariofer · Aug 27, 2020 at 06:42 AM

going first into the specific quesstions:

"I am not happy that my jump movement is being handled in Update() while all my other movement is being handled in FixedUpdate()" the code is fine, inputs need to be called in the Update event or you will miss some of the input clicks but all the physics need to be in the fixedupdate

"I also feel like I am missing Time.fixedDeltaTime in my movement?" no you are not, it is completly optional since fixedDeltaTime is a constant, dont mix Time.deltaTime in movements with fixedDeltaTime in physicis they are not the same, fixedDeltaTime is a constant and there is no need to use it.

As a personal advice i would separate input handling from the movement itself, but the code is fine i dont see any specific issues

Comment
Add comment · Share
10 |3000 characters needed characters left characters exceeded
▼
  • Viewable by all users
  • Viewable by moderators
  • Viewable by moderators and the original poster
  • Advanced visibility
Viewable by all users

Your answer

Hint: You can notify a user about this post by typing @username

Up to 2 attachments (including images) can be used with a maximum of 524.3 kB each and 1.0 MB total.

Follow this Question

Answers Answers and Comments

139 People are following this question.

avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image

Related Questions

2d platfromer player controller will not jump 0 Answers

I need help with a movement script 1 Answer

Event Trigger Pointer Exit sometimes not recognized 0 Answers

My 2d player controller doesnt work very well. Any tips on fixing it? 1 Answer

How to move Rigidbody2D on Player a certain amount at a certain speed, for a dash move and knockback? 2 Answers


Enterprise
Social Q&A

Social
Subscribe on YouTube social-youtube Follow on LinkedIn social-linkedin Follow on Twitter social-twitter Follow on Facebook social-facebook Follow on Instagram social-instagram

Footer

  • Purchase
    • Products
    • Subscription
    • Asset Store
    • Unity Gear
    • Resellers
  • Education
    • Students
    • Educators
    • Certification
    • Learn
    • Center of Excellence
  • Download
    • Unity
    • Beta Program
  • Unity Labs
    • Labs
    • Publications
  • Resources
    • Learn platform
    • Community
    • Documentation
    • Unity QA
    • FAQ
    • Services Status
    • Connect
  • About Unity
    • About Us
    • Blog
    • Events
    • Careers
    • Contact
    • Press
    • Partners
    • Affiliates
    • Security
Copyright © 2020 Unity Technologies
  • Legal
  • Privacy Policy
  • Cookies
  • Do Not Sell My Personal Information
  • Cookies Settings
"Unity", Unity logos, and other Unity trademarks are trademarks or registered trademarks of Unity Technologies or its affiliates in the U.S. and elsewhere (more info here). Other names or brands are trademarks of their respective owners.
  • Anonymous
  • Sign in
  • Create
  • Ask a question
  • Spaces
  • Default
  • Help Room
  • META
  • Moderators
  • Explore
  • Topics
  • Questions
  • Users
  • Badges