How to avoid CharacterControllers stepOffset launching my character into space?
I'm writing a FPS controller script using CharacterController. I'm trying to keep the momentum of the character and allow some acceleration. To achieve this, I use the 'velocity' parameter every frame (see code below)
My problem:
When CharacterController performs an stepOffset elevation, the character is launched into the air.
The problem source:
When the CharacterController performs a stepOffset, its 'velocity.y' is set really high. I'm guessing equivalent to the height of the elevation divided by time spent getting there.
Also, stepOffset triggering seems to be hidden well within the CharacterController.
Question:
Is there a way to either subtract the vertical stepOffset velocity from the velocity, or otherwise check if the stepOffset did -something- this frame?
Possible fix #1: I tried Increasing the gravity to decrease the launch height, making it look like the character is simply "jumping onto the ledge". This is acceptable, as long as the "jumping onto the ledge" is an acceptable behaviour. In my case, I'd like to avoid it.
Possible fix #2: More speed allows the rounded bottom of the charactercontrollers capsule collider to simply glide up from pure geometry alone. This is less than ideal, since most situations in my game will not involve a speedy character.
Possible fix #3: Set stepOffset to zero and write own collision raycast stepOffset functionality. I'm investigating this further tomorrow, unless any bright ideas pop up.
This is the code that runs every frame with Move():
// CharacterController charCon;
// Move towards horisontal input speed
Vector3 v3BodySpeed = charCon.velocity; // PROBLEM: Triggering a stepOffset sets a high y
// Only movetowards horizontal speed
float fSpeedY = v3BodySpeed.y; // save this while temporarily zeroing out vertical speed
v3BodySpeed.y = 0F; // temp
v3BodySpeed = Vector3.MoveTowards(v3BodySpeed, v3InputBodySpeed, fAccelerationSpeed);
v3BodySpeed.y = fSpeedY; // Reinstated vertical speed
// Jumping is instant instead of "MoveTowards"
if(isStartingAJump){
v3BodySpeed.y += fJumpSpeed;
isStartingAJump = false;
}
// Apply gravity
v3BodySpeed += Physics.gravity * Time.deltaTime;
// Make it move
charCon.Move(v3BodySpeed * Time.deltaTime);
I added a suggestion to exposing some helpful variables: https://feedback.unity3d.com/suggestions/expose-stepoffset-in-charactercontroller
Answer by Hamburgert · Feb 04, 2018 at 10:17 AM
After testing various solutions (all with bigger issues), I have finally found an adequate solution to the stepOffset vertical velocity issue. One downside is that it removes the ability to suddenly propel the character upwards from external forces (I.e. an explosion).
Since the CharacterController does not expose its stepOffset in any way, shape or form (other than the setting), it feels like I'm creating a solution to a problem that should never have existed in the first place.
The solution:
Each frame, detect the stepOffset and subtract its speed from the momentum.
Detecting the stepOffset
In my case, there are three reasons a charactercontroller gains a positive vertical velocity:
1 - Walking uphill
2 - Jumping
3 - stepOffset
(4 - other external forces, e.g. an explosion. Ignored, for now)
Walking uphill (1) will usually produce a low vertical velocity.
Jumping (2) can be tracked (isJumped == true , until isGrounded again)
stepOffset (3) produces a very high vertical velocity.
So the solution is to check the change in characterController.velocity from frame to frame, looking for a suddenly high vertical velocity.
Detection of a stepOffset should trigger If the character is not jumping, but the vertical velocity suddenly increased by a ridiculous amount. In my case, a single-frame vertical speed increase of atleast 3 meters per second seemed good enough.
Remember, even small edges may produce a high vertical velocity, because the sudden movement happens in 16-33 milliseconds (1 frame).
Subtracting the stepOffset speed
Short version is:
newSpeed.y -= detectedStepOffsetSpeed.y
The result is a character that can walk up steps/stairs using stepOffset, and mostly NOT fly up into space. Not perfect, but definitely not bad. Here is the new code.
float stepOffsetThreshold = 3.0F;
private void UpdateBodyPosition()
{
// Current speed
Vector3 v3BodySpeed = charCon.velocity;
// Save a lot of performance when there are many character controllers in the scene
if(isGrounded == true && isStartingAJump == false && v3BodySpeed.magnitude == 0F && v3InputBodySpeed.magnitude == 0F)
return; // No movement, and is standing still on the ground.
// Only movetowards horizontal speed, since jumping is such a delicate creature
float fSpeedY = v3BodySpeed.y; // save this while temporarily zeroing out vertical speed
v3BodySpeed.y = 0F; // temp
v3BodySpeed = Vector3.MoveTowards(v3BodySpeed, v3InputBodySpeed, fAccelerationSpeed);
v3BodySpeed.y = fSpeedY; // Reinstated vertical speed
// Try to detect stepOffset vertical speed
float fDeltaY = v3BodySpeed.y - v3LastBodySpeed.y; // Speed change since last frame
v3LastBodySpeed = v3BodySpeed;
if(isJumped == false && fDeltaY > stepOffsetThreshold){
// IS grounded, and high vertical speed => Probably stepOffset
// Remove the vertical speed from the momentum persistence
v3BodySpeed.y -= fDeltaY;
Debug.Log("StepOffset detected, removed "+fDeltaY+" from vert.speed.");
}
// Jumping is instant instead of "MoveTowards"
if(isStartingAJump == true)
{
v3BodySpeed.y += fJumpSpeed;
isStartingAJump = false;
isJumped = true;
}
// Apply gravity
if(isGrounded == false)
v3BodySpeed += Physics.gravity * Time.deltaTime * 2;
// Make it move
charCon.Move(v3BodySpeed * Time.deltaTime);
// Reset input for the next frame
v3InputBodySpeed = Vector3.zero;
}
I did not mean to bump this... gees.. I just fixed a tiny error in the code.. really now!
Your answer
Follow this Question
Related Questions
Input.GetAxis not going forward 0 Answers
I want to add some Velocity controll to my CharacterController 1 Answer
How do I stop enemy AI from bumping and entering into my player? 1 Answer
Float speed not getting updated on FixedUpdate nor Updated and OnAnimatorMove 1 Answer
Bullet Not moving Forward with speed 1 Answer