FPS Walker bunny hops if stop walking while on slopes
Surprised not to see a peep nor word about this issue as it's inherent in all the rigidbody FPS scripts that disable gravity i.e. FPSwalker scripts.
My code is specifically using GravityFPSWalker.
When walking up any slope (the more angle the more it happens), if a player stops input, the gameobject will hop. I assume this is an effect of an object colliding into an angle this steep (Like real physics would treat a capsule colliding at this angle) but it kinda ruins the otherwise awesome motion of this script. moving on slopes is very common in my game and not hopping when stopping on slopes is important to me. Any way anyone can see to solve this issue?
using UnityEngine;
/* -------------------------------------------------------------------------------
GravityFPSWalker
This component is added to a GameObject with a RigidBody. It allows the player
to move the RigidBody using the vertical and horizontal inputs, and to jump
using the jump button.
The RigidBody is pushed towards its own custom Gravity vector. The body will
rotate to stay upright with the RotationRate.
This component uses a raycast to determine if the RigidBody is standing on
the ground. The GroundHeight variable should be the distance between the
GameObject transform and a little further than the bottom of the RigidBody.
The LookTransform should be a child GameObject which points in the direction
that the player is looking at. This could for example be a child GameObject
with a camera. The LookTransform is used to determine the movement veectors.
------------------------------------------------------------------------------ */
[RequireComponent(typeof(Rigidbody))]
public class GravityFPSWalker : MonoBehaviour {
public Transform LookTransform;
public Vector3 Gravity = Vector3.down * 9.81f;
public float RotationRate = 0.1f;
public float Velocity = 8;
public float GroundControl = 1.0f;
public float AirControl = 0.2f;
public float JumpVelocity = 5;
public float GroundHeight = 1.1f;
private bool jump;
void Start() {
rigidbody.freezeRotation = true;
rigidbody.useGravity = false;
}
void Update() {
jump = jump || Input.GetButtonDown("Jump");
}
void FixedUpdate() {
// Cast a ray towards the ground to see if the Walker is grounded
bool grounded = Physics.Raycast(transform.position, Gravity.normalized, GroundHeight);
// Rotate the body to stay upright
Vector3 gravityForward = Vector3.Cross(Gravity, transform.right);
Quaternion targetRotation = Quaternion.LookRotation(gravityForward, -Gravity);
rigidbody.rotation = Quaternion.Lerp(rigidbody.rotation, targetRotation, RotationRate);
// Add velocity change for movement on the local horizontal plane
Vector3 forward = Vector3.Cross(transform.up, -LookTransform.right).normalized;
Vector3 right = Vector3.Cross(transform.up, LookTransform.forward).normalized;
Vector3 targetVelocity = (forward * Input.GetAxis("Vertical") + right * Input.GetAxis("Horizontal")) * Velocity;
Vector3 localVelocity = transform.InverseTransformDirection(rigidbody.velocity);
Vector3 velocityChange = transform.InverseTransformDirection(targetVelocity) - localVelocity;
// The velocity change is clamped to the control velocity
// The vertical component is either removed or set to result in the absolute jump velocity
velocityChange = Vector3.ClampMagnitude(velocityChange, grounded ? GroundControl : AirControl);
velocityChange.y = jump && grounded ? -localVelocity.y + JumpVelocity : 0;
velocityChange = transform.TransformDirection(velocityChange);
rigidbody.AddForce(velocityChange, ForceMode.VelocityChange);
// Add gravity
rigidbody.AddForce(Gravity * rigidbody.mass);
jump = false;
}
}
Answer by cjdev · Sep 27, 2015 at 08:13 AM
Your problem is likely that rigidbodys don't stop immediately once they no longer have force applied, they have to slow down first under the effects of drag and inertia. You might try locking the position when there is no user input to overcome the problem.
I was afraid you might say that. Problem with that is if I do that there isn't a smooth transition to a stop which is why I want to use this in the first place...
Also just to let you know, I did try that and it does work so you are right about your suggestion. It just doesn't keep with the fluid motion I'm going for.
Answer by Dorscherl · Jun 21, 2020 at 09:04 PM
As old and irrelevant as this might be for you specifically. I found projecting the velocity change to the ground normal (only when grounded) to be the perfect thing to prevent the rigidbody from launching upward.
savedMovementVelocity = Vector3.Lerp(savedMovementVelocity, targetMovementVelocity, stableMovementSharpness * deltaTime);
Vector3 velocityChange = Vector3.ProjectOnPlane(savedMovementVelocity - velocity, character.GroundNormal);
rigidbody.AddForce(velocityChange, ForceMode.VelocityChange);
Best thing is that it doesn't interupt the rigidbody's natural gravity.
If you want to use it for air movement, i would change the ground normal for the character's up direction (transform.up).
I would consider having the ground checking code get an average of everything underneath the player, either using an array of raycast or all contact points below the collider just because you might have a minor launch issue on drastic changes in slopes (ramps) terrain should be fine