Determining how much force to add to reach a certain velocity in a certain amount of time and clamping it
So I am working on a 2D player controller with a few things I want to be able to do. Move left and right, jump multiple times, slide down walls and wall jump. Because I want to be able to wall jump I am using RigidBody2D.addForce to move the character around and then checking the characters velocity and clamping it if it goes over the max I want.
I recently found though that the max I set for the velocity isn't really the max the velocity ends up being, say I say it to 3 the velocity ends up being around 9. I noticed this when I was trying to get the wall jumping to work and the player would jump straight up instead of launching away and up from the wall. I take it this happens because the force being added to the player while attempting to move into the wall is too great and basically ignores the forces being exerted on the wall. If I take down the force being applied to the rigidbody when moving it starts feeling weird and takes too long to get to a top speed and I still can't clamp the velocity properly. So what I want to do is work out exactly how much force to apply to get a certain velocity in a certain amount of time and then stop applying force. Drag is disabled and the player slows down when the movement keys are released with a sort of fake friction. I hope this makes sense and isnt too all over the place. Following is my code:
public float movementForce; //The force applied when attempting to move
public float maxHorizontalMovementSpeed; //The max speed the character can move
public float jumpForce; //Force to apply if attempting to jump
public float groundCheckRadius; //The radius at which to check for ground
public float groundFrictionLevel; //Float used to determine how much friction is applied when not moving and is grounded
public float airFrictionLevel; //Float used to determine how much friction is applied when not moving and is not grounded
public float wallCheckDistance; //The distance at which to check for walls
public float maxWallSlidingSpeed; //The speed at which the character slides down walls if wallsliding
public int extraJumps; //The amount of times more than once the player can jump e.g 1 means double jump
public Vector2 jumpUpWall;
public LayerMask whatIsGround; //Used to see if what player is touching is ground
public Transform groundCheck; //The point at which we check for ground
public Transform wallCheck; //The point from where to fire raycast to check for wall
private float movementDirection; //Stores horizontal input to determine which direction to move in and face
private int extraJumpsLeft; //The amount of extra jumps the player ahs left
private bool isFacingRight = true; //Keeps track of if the player sprite is facing right, default is true because player spawns facing right
private bool isGrounded; //Keeps track of if the player is currently touching ground or not
private bool isWallSliding; //Used to keep track of if the player is currently sliding down a wall
private bool canJump; //Keeps track of if the player is currently holding down the space bar or not
private bool hasJumped; //Keeps track of if the player has jumped or not
private bool canMove = true; //Keeps track of if the player is currently allowed/able to move or not
private Vector2 velocity; //Keeps track of the velocity of the player, used for clamping
private RaycastHit2D wallCheckHit; //Used to keep tack of the player is touching a wall
private Rigidbody2D rb; //Used to reference the RigidBody2D on player
private Animator anim; //Used to reference Animator on player
// Use this for initialization
void Start () {
rb = GetComponent<Rigidbody2D>(); //Get's reference to the RigidBody2D attached to the player
anim = GetComponent<Animator>(); //Get's reference to the Animator attached to the player
extraJumpsLeft = extraJumps; //Sets initial values for the amount of times more than once the player can jump
}
// Update is called once per frame
void Update () {
Debug.Log(rb.velocity.x);
movementDirection = Input.GetAxis("Horizontal"); //Set's -1 <= movementDirection <= 1
if (isGrounded && rb.velocity.y == 0) //Used to make it so the player can jump again after toucing the ground
{
canJump = true;
extraJumpsLeft = extraJumps;
}
//Clamp XVelocity
if (rb.velocity.x > maxHorizontalMovementSpeed) //Right
{
rb.velocity = new Vector2(maxHorizontalMovementSpeed, rb.velocity.y);
}
else if (rb.velocity.x < -maxHorizontalMovementSpeed) //Left
{
rb.velocity = new Vector2(-maxHorizontalMovementSpeed, rb.velocity.y);
}
//Clamp YVelocity if wall sliding
if (isWallSliding && rb.velocity.y < 0)
{
if (rb.velocity.y < -maxWallSlidingSpeed) //Clamps downward velocity to max wall sliding velocity
{
velocity.y = -maxWallSlidingSpeed;
velocity.x = rb.velocity.x;
rb.velocity = velocity;
}
}
//CheckFacing Direction
if (isFacingRight && movementDirection < 0) //If Sprite is currently facing right but is attempting to move left
{
Flip();
}
else if(!isFacingRight && movementDirection > 0) //If sprite is currently facing left but is attempting to move right
{
Flip();
}
//Check jump input
if (Input.GetButtonDown("Jump"))
{
Jump();
}
//Check if wallSliding
if(wallCheckHit && !isGrounded)
{
// Debug.Log("WallSliding");
isWallSliding = true;
}
else
{
isWallSliding = false;
}
//Set animator parameters
anim.SetBool("isGrounded", isGrounded);
anim.SetFloat("xSpeed", Mathf.Abs(movementDirection));
anim.SetFloat("ySpeed", rb.velocity.y);
}
void FixedUpdate()
{
//Check if grounded
isGrounded = Physics2D.OverlapCircle(groundCheck.position, groundCheckRadius, whatIsGround);
//Check if touching wall with raycast
wallCheckHit = Physics2D.Raycast(wallCheck.position, wallCheck.right, wallCheckDistance, whatIsGround);
//Move Player
Vector2 forceToAdd = new Vector2(movementForce * movementDirection, 0); //The force to be applied to the rb2d to make it move, accounts for direction
rb.AddForce(forceToAdd, ForceMode2D.Force);
//Apply friction when player is touching ground and not attempring to move
if (isGrounded && movementDirection == 0 && Mathf.Abs(rb.velocity.x) > 0) //Ground
{
//Debug.Log("Applying Ground Friction");
velocity = rb.velocity;
velocity.x *= groundFrictionLevel;
rb.velocity = velocity;
} else if (!isGrounded && movementDirection == 0 && Mathf.Abs(rb.velocity.x) > 0) //Air
{
//Debug.Log("Applying Air Friction");
velocity = rb.velocity;
velocity.x *= airFrictionLevel;
}
}
void Jump()
{
//Apply Jump Forces
if ((canJump || extraJumpsLeft >= 0) && !isWallSliding)
{
rb.velocity = new Vector2(rb.velocity.x, 0);
Vector2 jumpForceToAdd = new Vector2(0, jumpForce);
rb.AddForce(jumpForceToAdd, ForceMode2D.Impulse);
canJump = false;
hasJumped = true;
extraJumpsLeft--;
}else if (isWallSliding)
{
if(isFacingRight)
{
Debug.Log("is attempting to jump up wall");
Vector2 jumpForceToAdd = new Vector2(jumpUpWall.x * jumpForce, jumpUpWall.y * jumpForce);
Debug.Log(jumpForceToAdd);
rb.AddForce(jumpForceToAdd);
}
}
}
void Flip() //Flips player sprite to face right direction
{
isFacingRight = !isFacingRight; //Set's facing direction so we know which way the sprite is facing
transform.Rotate(0.0f, 180.0f, 0f); //Rotates the sprite 180 degrees on z axis, this way any child game objects rotate with it.
}
So long, so many code, so many text...
Try to make is shorter.
PD: Adding force is a vector, is easier to fix the velocity than the vector force.