- Home /
Help with fixing character's physics
So I have this custom rigidbody-based character controller I'm making, and I need a bit of help tweaking the physics. Pretty much what's wring with it is A) player can stop and turn on a dime, B) walking uphill is slower than the regular walking speed, C) walking downhill is very jittery, D) for some reason my character moves faster diagonally than horizontally or vertically, and E) jumping is entirely momentum-based which is not very good for a platformer.
Here's the script:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
[AddComponentMenu("Controllers/Player Controller")]
public class PlayerController : MonoBehaviour {
public Animator playerAnimator;
Transform cameraPos;
CapsuleCollider col; //Collider
Rigidbody phys; //Rigidbody
float BaseHeight;//The default height of the character controller
public float CrouchingHeight; //The height of the character controller while crouching
public float CrawlSpeed = 1.0f;
public float RunSpeed = 5.0f;
public float SprintSpeed = 10.0f;
float CurrentSpeed = 0.0f;
bool Crouching = false;
bool Sprinting = false;
bool CanSprint = true;
bool Climbing = false;
bool CanClimb = false;
public float MaxStamina = 8.0f;
public float CurrentStamina = 8.0f; //How long the player can sprint or climb
public LayerMask ClimbingMask;
//Jump variables
public float MaxJumpHeight = 1.5f;
//State variables
public float GroundCheckDistance = 0.1f; //How close to the ground is the player to be considered grounded?
public float WallCheckDistance = 0.3f; //How close to a wall can the player be to start climbing?
bool Grounded ()
{
return Physics.Raycast (transform.position, -Vector3.up, GroundCheckDistance);
}
bool WallDetected ()
{
return Physics.Raycast (transform.position, transform.forward, WallCheckDistance, ClimbingMask);
}
//Stealth Elements
public Transform SunLight, FeetAnchor, HeadAnchor;
bool IsHidden;
// Use this for initialization
void Start () {
cameraPos = Camera.main.transform;
col = GetComponent<CapsuleCollider> ();
phys = GetComponent<Rigidbody> ();
BaseHeight = col.height;
}
// Update is called once per frame
void Update () {
//Set IsHidden
if (Physics.Raycast (FeetAnchor.position, SunLight.forward * -1f, 100) && Physics.Raycast (HeadAnchor.position, SunLight.forward * -1f, 100)) {
IsHidden = true;
print ("Hidden");
} else {
IsHidden = false;
print ("Visible");
}
//Movement
///Set Crouching
if (Input.GetButtonDown ("Crouch")) {
Crouching = !Crouching;
playerAnimator.SetBool ("IsCrouching", Crouching);
}
if (Crouching) {
col.height = CrouchingHeight;
} else {
col.height = BaseHeight;
}
////////Set Sprinting
if (Input.GetButton ("Sprint") && !Crouching) {
if (CanSprint && phys.velocity != Vector3.zero) {
Sprinting = true;
CurrentStamina -= Time.deltaTime;
}
} else {
Sprinting = false;
}
if (CurrentStamina <= 0) {
CanSprint = false;
Sprinting = false;
}
if (CurrentStamina > 4) {
CanSprint = true;
}
if (CurrentStamina >= MaxStamina) {
CurrentStamina = MaxStamina;
}
if (!Sprinting) {
CurrentStamina += Time.deltaTime * 0.7f;
}
float h = Input.GetAxis ("Horizontal");
float v = Input.GetAxis ("Vertical");
Vector3 movementDir = new Vector3 (h, 0f, v);
movementDir = cameraPos.TransformDirection (movementDir);
if (movementDir != Vector3.zero)
transform.forward = new Vector3 (movementDir.x, 0f, movementDir.z);
if (Grounded()) {
//Movement
if (!Crouching) {
if (!Sprinting)
CurrentSpeed = RunSpeed * movementDir.magnitude;
else
CurrentSpeed = SprintSpeed * movementDir.magnitude;
} else {
CurrentSpeed = CrawlSpeed * movementDir.magnitude;
}
phys.velocity = new Vector3 (movementDir.x * CurrentSpeed, 0f, movementDir.z * CurrentSpeed);
//Jumping
if (Input.GetButtonDown ("Jump") && !Crouching) {
phys.AddForce (Vector3.up * MaxJumpHeight, ForceMode.Impulse);
}
}
//Wall Detection (Grounded)
if (Grounded ()) {
if (Physics.Raycast (transform.position, transform.forward, WallCheckDistance)) {
CurrentSpeed = 0;
}
}
//Climbing
if (!Grounded () && WallDetected () && Input.GetButtonDown ("Sprint")) {
Climbing = true;
if (Input.GetButtonDown ("Jump") && Climbing) {
Climbing = false;
}
}
playerAnimator.SetFloat ("GroundSpeed", CurrentSpeed);
playerAnimator.SetBool ("Grounded", Grounded());
playerAnimator.SetFloat ("AirSpeed", phys.velocity.y);
}
}
Any help is appreciated. Thanks in advance.
Answer by Eno-Khaon · Apr 25, 2017 at 07:56 PM
To address all major points (roughly) in order:
A) (player can stop and turn on a dime) Your movement is defined by forcibly defining your velocity every frame rather than by adding a force, and that movement is defined based on the current input. There's nothing to accelerate and decelerate aside from Input.GetAxis() easing in and out for the controls. If you want strict control over the character's movement, you'll probably want to directly control acceleration and maximum speed yourself by monitoring a 2D movement axis.
Incidentally, this also means you're winding up somewhat lucky right now -- If you weren't restricting controls to when Grounded only, you would be cancelling out vertical movement every frame.
B, C, and D) (walking uphill is slower than the regular walking speed) -- (walking downhill is very jittery) -- (player can stop and turn on a dime) This also fits in with the means of directing character input. As long as you know that your character is Grounded, you can observe the traits of the ground to know how to walk along it.
Problem D can also be covered here -- By using both axis inputs in full, you reach a maximum of √2 (square root of 2) speed, or approximately 1.4x your intended speed.
Vector3 movementDir = new Vector3 (h, 0f, v);
// Solve problem D by normalizing when necessary
if(movementDir.sqrMagnitude > 1.0f)
{
movementDir = movementDir.normalized;
}
movementDir = cameraPos.TransformDirection (movementDir);
// Locate the ground
// distanceToFeet should be slightly longer distance (10% at most) than from the center of your character to the bottom of its collider
if(grounded)
{
RaycastHit hit;
if(Physics.Raycast(transform.position, Vector3.down, out hit, distanceToFeet))
{
// If the raycast hits, you now know the angle of the ground.
Quaternion rotateToGround = Quaternion.FromToRotation(transform.up, hit.normal);
// rotate your movement direction relative to the ground
movementDir = rotateToGround * movementDir;
}
}
Why not normalize the initial movement vector all the time? If you're using analog control (e.g. a gamepad), for example, you'll still have the granularity such controls should offer.
E) (jumping is entirely momentum-based which is not very good for a platformer) Contrary to your current basis for jumping, If you want an absolute jumping force applied, without influence from current momentum, you actually WANT to apply this manually.
movementDir.y = maxJumpHeight;
Your answer
Follow this Question
Related Questions
Having trouble turning a Transform movement into a Rigidbody force. Code included 1 Answer
Rigidbody Controller Jittery 1 Answer
My Rigidbody FPS controller is falling very slow, please help! 2 Answers
Help with rigidbody character controller 0 Answers
Rigidbody character controller issues 0 Answers