- Home /
Make rigidbody walk like character controller
I am using a rigidbody to model my character. I have to use it because I need full physics. But the problem is that it accelerates/decelerates when moving. Is there a way to avoid this, so it has constant velocity?
Answer by Celtc · Aug 09, 2014 at 02:54 AM
I know I'm a bit late but this may help someone.
The RigidbodyFPSWalker script from the Unity Wiki don't work because it adds the velocity of the object the character is standing on to its own velocity, without checking the nature of this object which may cause a sudden increase of velocity in an unrealistic way. Also, it has an error where it does not make it zero when it's standing over an object which doesn't have a rigid body.
Well I put here the code with some fixes (C#). I think it works quite well now. And here is a LINK to download a sample project. Hope it helps.
using UnityEngine;
using System.Collections;
[RequireComponent(typeof(Rigidbody))]
[RequireComponent(typeof(CapsuleCollider))]
public class RigidbodyFPS : MonoBehaviour
{
#region Variables (private)
private bool grounded = false;
private Vector3 groundVelocity;
private CapsuleCollider capsule;
// Inputs Cache
private bool jumpFlag = false;
#endregion
#region Properties (public)
// Speeds
public float walkSpeed = 8.0f;
public float walkBackwardSpeed = 4.0f;
public float runSpeed = 14.0f;
public float runBackwardSpeed = 6.0f;
public float sidestepSpeed = 8.0f;
public float runSidestepSpeed = 12.0f;
public float maxVelocityChange = 10.0f;
// Air
public float inAirControl = 0.1f;
public float jumpHeight = 2.0f;
// Can Flags
public bool canRunSidestep = true;
public bool canJump = true;
public bool canRun = true;
#endregion
#region Unity event functions
/// <summary>
/// Use for initialization
/// </summary>
void Awake()
{
capsule = GetComponent<CapsuleCollider>();
rigidbody.freezeRotation = true;
rigidbody.useGravity = true;
}
/// <summary>
/// Use this for initialization
/// </summary>
void Start()
{
}
/// <summary>
/// Update is called once per frame
/// </summary>
void Update()
{
// Cache the input
if (Input.GetButtonDown("Jump"))
jumpFlag = true;
}
/// <summary>
/// Update for physics
/// </summary>
void FixedUpdate()
{
// Cache de input
var inputVector = new Vector3(Input.GetAxis("Horizontal"), 0, Input.GetAxis("Vertical"));
// On the ground
if (grounded)
{
// Apply a force that attempts to reach our target velocity
var velocityChange = CalculateVelocityChange(inputVector);
rigidbody.AddForce(velocityChange, ForceMode.VelocityChange);
// Jump
if (canJump && jumpFlag)
{
jumpFlag = false;
rigidbody.velocity = new Vector3(rigidbody.velocity.x, rigidbody.velocity.y + CalculateJumpVerticalSpeed(), rigidbody.velocity.z);
}
// By setting the grounded to false in every FixedUpdate we avoid
// checking if the character is not grounded on OnCollisionExit()
grounded = false;
}
// In mid-air
else
{
// Uses the input vector to affect the mid air direction
var velocityChange = transform.TransformDirection(inputVector) * inAirControl;
rigidbody.AddForce(velocityChange, ForceMode.VelocityChange);
}
}
// Unparent if we are no longer standing on our parent
void OnCollisionExit(Collision collision)
{
if (collision.transform == transform.parent)
transform.parent = null;
}
// If there are collisions check if the character is grounded
void OnCollisionStay(Collision col)
{
TrackGrounded(col);
}
void OnCollisionEnter(Collision col)
{
TrackGrounded(col);
}
#endregion
#region Methods
// From the user input calculate using the set up speeds the velocity change
private Vector3 CalculateVelocityChange(Vector3 inputVector)
{
// Calculate how fast we should be moving
var relativeVelocity = transform.TransformDirection(inputVector);
if (inputVector.z > 0)
{
relativeVelocity.z *= (canRun && Input.GetButton("Sprint")) ? runSpeed : walkSpeed;
}
else
{
relativeVelocity.z *= (canRun && Input.GetButton("Sprint")) ? runBackwardSpeed : walkBackwardSpeed;
}
relativeVelocity.x *= (canRunSidestep && Input.GetButton("Sprint")) ? runSidestepSpeed : sidestepSpeed;
// Calcualte the delta velocity
var currRelativeVelocity = rigidbody.velocity - groundVelocity;
var velocityChange = relativeVelocity - currRelativeVelocity;
velocityChange.x = Mathf.Clamp(velocityChange.x, -maxVelocityChange, maxVelocityChange);
velocityChange.z = Mathf.Clamp(velocityChange.z, -maxVelocityChange, maxVelocityChange);
velocityChange.y = 0;
return velocityChange;
}
// From the jump height and gravity we deduce the upwards speed for the character to reach at the apex.
private float CalculateJumpVerticalSpeed()
{
return Mathf.Sqrt(2f * jumpHeight * Mathf.Abs(Physics.gravity.y));
}
// Check if the base of the capsule is colliding to track if it's grounded
private void TrackGrounded(Collision collision)
{
var maxHeight = capsule.bounds.min.y + capsule.radius * .9f;
foreach (var contact in collision.contacts)
{
if (contact.point.y < maxHeight)
{
if (isKinematic(collision))
{
// Get the ground velocity and we parent to it
groundVelocity = collision.rigidbody.velocity;
transform.parent = collision.transform;
}
else if (isStatic(collision))
{
// Just parent to it since it's static
transform.parent = collision.transform;
}
else
{
// We are standing over a dinamic object,
// set the groundVelocity to Zero to avoid jiggers and extreme accelerations
groundVelocity = Vector3.zero;
}
// Esta en el suelo
grounded = true;
}
break;
}
}
private bool isKinematic(Collision collision)
{
return isKinematic(collider.transform);
}
private bool isKinematic(Transform transform)
{
return transform.rigidbody && transform.rigidbody.isKinematic;
}
private bool isStatic(Collision collision)
{
return isStatic(collision.transform);
}
private bool isStatic(Transform transform)
{
return transform.gameObject.isStatic;
}
#endregion
}
"it adds the velocity of the object the character is standing on to its own velocity" This must be why if you run over the cubes in the demo scene, the player flies forward incredibly fast. Thank you so much for your script, it avoids that problem very well!
Answer by rocket5tim · Dec 23, 2009 at 10:23 PM
You might want to take a look at the RigidbodyFPSWalker script on the Unity Wiki.
Tried the project on this wiki. Unity had to rebuild it as it was made on a higher version than my 2.6. Seems to work, partially. FPSWalker does not stop moving after a certain amount of 'walking' it... and the direction keyboard keys no longer match the direction the FPS takes on... FPS rapidly falls off the platform to infinity. Will make platform huge to see if I can get a feel for the behavior of the FPS.
Was losing the capsule(FPS) in the hierarchy? ...renamed all the cubes 00,01,02,... 10. When hit 'play' the capsule becomes child to cube10(non-terrain-floor)... while using arrow keys to move around, the FPS changes shape and also builds up large speed in any one direction that cannot be compensated by the arrows action... and poof we go to infinity. Don't know if it is due to Unity (versions)conversion or bad C# script... I use javascript, can't comment further until I have had a chance to translate the C# to javascript
Answer by Ashkan_gc · Dec 05, 2009 at 03:57 AM
you can have constant velocity by disabling friction and drag and setting the velocity of the rigidbody directly to a value. you should not aply forces and you should just use the velocity property and set it to the value that you want. generally it's not a good idea to make your character move in this way. you should use ragdolls when you need physics and use character controllers when you don't need it. simulating human movements with physics is not an easy task and it will become very heavy. there is a great video about physics in unite 08. one of the problem that is described there is "character simulation using ragdolls or rigidbodies".
hope this helps
Answer by Techsuport · Aug 28, 2011 at 07:50 PM
directly controling Velocity ;)
var speed:float=4;
function FixedUpdate(){
rigidbody.velocity=(Input.GetAxis("Horizontal")*transform.right+Input.GetAxis("Vertical")*transform.forward).normalized*speed;
}