RigidBodyFPSWalker sliding around

I’m using the extremely useful [removed old link] script, but I’m having a little trouble with it. Whenever I have a collider (a boat, say) moving under the player, whether through physics or animation, the player just slides over it. No matter what materials I use, or how high I set friction, it doesn’t work. Other high-friction objects move with the object underneath them, but the player refuses to. The player behaves perfectly well on these objects

I’m sure the problem comes from the script, but I can’t figure out what exactly is happening or how I could fix it while retaining the other behaviour of the walker. Has anyone met with this problem and come up with a good solution?

Here is an updated rigidbody based fps controller script that handles, when you stand on moving rigidbodies. It also adds in air control.

var speed = 8.0;
var gravity = 10.0;
var maxVelocityChange = 10.0;
var inAirControl = 0.1;
var canJump = true;
var jumpHeight = 2.0;
private var grounded = false;
private var groundVelocity : Vector3;
private var capsule : CapsuleCollider;
 
@script RequireComponent(Rigidbody, CapsuleCollider)
 
function Awake ()
{
	rigidbody.freezeRotation = true;
	rigidbody.useGravity = false;
	capsule = GetComponent(CapsuleCollider);
}
 
function FixedUpdate ()
{
	if (grounded)
	{
		// Calculate how fast we should be moving
		var targetVelocity = new Vector3(Input.GetAxis("Horizontal"), 0, Input.GetAxis("Vertical"));
		targetVelocity = transform.TransformDirection(targetVelocity);
		targetVelocity *= speed;
		
		// Apply a force that attempts to reach our target velocity
		var velocity = rigidbody.velocity;
		var velocityChange = (targetVelocity - velocity) + groundVelocity;
		velocityChange.x = Mathf.Clamp(velocityChange.x, -maxVelocityChange, maxVelocityChange);
		velocityChange.z = Mathf.Clamp(velocityChange.z, -maxVelocityChange, maxVelocityChange);
		velocityChange.y = 0;
		rigidbody.AddForce(velocityChange, ForceMode.VelocityChange);
	
		// Jump
		if (canJump  Input.GetButton("Jump"))
		{
			rigidbody.velocity = Vector3(velocity.x, CalculateJumpVerticalSpeed(), velocity.z);
		}
		
		grounded = false;
	}
	else
	{
		// Add in air 
		targetVelocity = new Vector3(Input.GetAxis("Horizontal"), 0, Input.GetAxis("Vertical"));
		targetVelocity = transform.TransformDirection(targetVelocity) * inAirControl;
		
		rigidbody.AddForce(targetVelocity, ForceMode.VelocityChange);
	}
	
	// We apply gravity manually for more tuning control
	rigidbody.AddForce(Vector3 (0, -gravity * rigidbody.mass, 0));
}

function TrackGrounded (col : Collision)
{
	var minimumHeight = capsule.bounds.min.y + capsule.radius;
	for (var c : ContactPoint in col.contacts)
	{
		if (c.point.y < minimumHeight)
		{
			if (col.rigidbody)
				groundVelocity = col.rigidbody.velocity;
			else
				groundVelocity = Vector3.zero;
			grounded = true;
		}
	}	
}


function OnCollisionStay (col : Collision)
{
	TrackGrounded (col);
}

function OnCollisionEnter (col : Collision)
{
	TrackGrounded (col);
}

 
function CalculateJumpVerticalSpeed ()
{
	// From the jump height and gravity we deduce the upwards speed 
	// for the character to reach at the apex.
	return Mathf.Sqrt(2 * jumpHeight * gravity);
}

Nice. This works very well for standing on Rigidbodies. However, I’ve made the following modifications to get it working for animated colliders, too:

function TrackGrounded (col : Collision) { 
	var minimumHeight = capsule.bounds.min.y + capsule.radius; 
	for (var c : ContactPoint in col.contacts) { 
		if (c.point.y < minimumHeight) { 
			//we track velocity for rigidbodies we're standing on
			if (col.rigidbody) groundVelocity = col.rigidbody.velocity;
			//and become children of non-rigidbody colliders
			else transform.parent = col.transform;
			grounded = true; 
		} 
	}    
} 

//unparent if we are no longer standing on our parent
function OnCollisionExit (col : Collision) {
	if (col.transform == transform.parent) transform.parent = null;
}

This works really well with one obvious caveat: when you jump off an animated collider, you unparent it. I can’t think of a way to conserve momentum. Of course, lack of velocity in animated objects the underlying problem this whole script is trying to fix.

Any suggestions?

One thing you could do is to attach a script to the moving animated colliders that would keep a public Vector3 variable called velocity that would be calculated every FixedUpdate. Also in the script would be a private Vector3 that stores the object’s previous position. By looking at the difference between the current position and the previous position you can approximate the current velocity. After updating the velocity approximation variable you would then update previousPosition: previousPosition = transform.position; for next FixedUpdate.

Now in your existing script in OnCollisionExit, you can GetComponent your new script on the collider and get its approximate velocity out of it. Then you could adjust the rigidbody’s velocity accordingly.

-Jon

Ok, I’ve done this and it works pretty well. The only problem is that it’s very jumpy. Here is my speedometer script:

var averageVelocity : Vector3;
private var lastPosition : Vector3 = Vector3.zero;

function FixedUpdate () {
	averageVelocity = (transform.position - lastPosition)/Time.fixedDeltaTime;
	lastPosition = transform.position;
}

All right, I checked the Play Fixed Frame Rate box, and the jumpiness is gone. However, there is still minor sliding. I assume this is due to rounding error in the velocity calculation. Is there any way to force higher precision?

NCarter pointed out on IRC that it’s probably not a rounding error with the values involved. It’s not drag, either, because it’s still there when I reduce the player’s drag to zero. So I’ve resorted to a manually tuned compensation coefficient:

var compensation = 0.892;
var averageVelocity : Vector3;
private var lastPosition : Vector3 = Vector3.zero;

function FixedUpdate () {
	averageVelocity = (transform.position - lastPosition)/(Time.fixedDeltaTime*compensation);
	lastPosition = transform.position;
}