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?
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 1 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
}
You might want to take a look at the RigidbodyFPSWalker script on the Unity Wiki.
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
It may not be the Character Controller see
directly controling Velocity
var speed:float=4;
function FixedUpdate(){
rigidbody.velocity=(Input.GetAxis("Horizontal")*transform.right+Input.GetAxis("Vertical")*transform.forward).normalized*speed;
}
The code you wrote is fantastic and works perfectly but sadly it was a little outdated, not too bad just some changes between unity 4 and 5 did some damage so i tried to fix that and reupload it
p.s (I added camera control to it, you just have to set the sensitivity and the camera gameobject, hopefully not too bad)
'using UnityEngine;
using System.Collections;
[RequireComponent(typeof(Rigidbody))]
[RequireComponent(typeof(CapsuleCollider))]
public class Movement : 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;
Transform cam;
public GameObject eyes;
float rotX;
float rotY;
public float sensitivity;
#endregion
#region Unity event functions
/// <summary>
/// Use for initialization
/// </summary>
void Awake()
{
capsule = GetComponent<CapsuleCollider>();
this.GetComponent<Rigidbody>().freezeRotation = true;
this.GetComponent<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;
//Mouse Input & fps camera movement
cam = GetComponentInChildren<Camera> ().transform;
Vector3 myPos = GetComponent<Transform> ().position;
cam.GetComponent<Transform> ().position = new Vector3 (myPos.x, myPos.y, myPos.z);
rotX = Input.GetAxis ("Mouse X");
rotY = Input.GetAxis ("Mouse Y");
this.GetComponent<Transform> ().Rotate (0, rotX * sensitivity, 0);
eyes.GetComponent<Transform> ().Rotate (-rotY * sensitivity, 0, 0);
}
/// <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);
this.GetComponent<Rigidbody>().AddForce(velocityChange, ForceMode.VelocityChange);
// Jump
if (canJump && jumpFlag)
{
Rigidbody rig = this.GetComponent<Rigidbody> ();
jumpFlag = false;
this.GetComponent<Rigidbody>().velocity = new Vector3(rig.velocity.x, rig.velocity.y + CalculateJumpVerticalSpeed(), rig.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;
this.GetComponent<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 = this.GetComponent<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(GetComponent<Collider>().transform);
}
private bool isKinematic(Transform transform)
{
return transform.GetComponent<Rigidbody>() && transform.GetComponent<Rigidbody>().isKinematic;
}
private bool isStatic(Collision collision)
{
return isStatic(collision.transform);
}
private bool isStatic(Transform transform)
{
return transform.gameObject.isStatic;
}
#endregion
}
`
code kinda got screwed up a bit, just make sure to grab the top bit as well, sorry im new to this