I figured I’d start a new thread since the scope of the problem has changed enough that it merits a new thread. As with the last thread, I’ll include the full source code at the bottom of this post.
I’m working on an first-person shooter character motor using Unity’s character controller. To keep the character grounded, I’m using “playerVelocity.y = -controller.stepOffset / Time.deltaTime;” on line 274 (with “controller” being a reference to the default character controller included in Unity).
Unfortunately, this has a side effect that I just can’t seem to overcome. When the player walks off a ledge, they’re briefly (for one frame) travelling at a value equal to “-controller.stepOffset / Time.deltaTime;”. I’ve tried numerous ways to get around this, but nothing seems to work. It’s this one-frame hiccup that’s causing me a lot of grief.
It seems like this should be a simple fix, but I just can’t find the problem at all.
edit: whoops, hit post instead of preview.
I’ve tried assigning a variable “wasGrounded” in my update loop, but if I put it at the bottom, after the character controller has moved, it doesn’t seem to assign properly. See:
void Update () {
if (!isDead) {
cameraRotX -= player.GetAxisRaw ("LookVertical") * mouseXSensitivity * Time.deltaTime;
cameraRotY += player.GetAxisRaw ("LookHorizontal") * mouseYSensitivity * Time.deltaTime;
}
if (cameraRotX < -90f) {
cameraRotX = -90f;
} else if (cameraRotX > 90f) {
cameraRotX = 90f;
}
transform.rotation = Quaternion.Euler (0f, cameraRotY, 0f);
FPSCamera.transform.rotation = Quaternion.Euler (cameraRotX, cameraRotY, 0f);
QueueJump ();
if (controller.isGrounded && !isGrappling && !isDead) {
GroundMove ();
} else if (!controller.isGrounded && !isGrappling && !isDead) {
AirMove ();
}
if (isDead) {
playerVelocity = new Vector3 (0f,0f,0f);
}
if (!controller.isGrounded && wasGrounded && playerVelocity.y <= 0f) {
playerVelocity.y = 0f;
}
wasGrounded = controller.isGrounded;
controller.Move (playerVelocity * Time.deltaTime);
}
If I keep it above controller.Move, it gives me the downward hiccup. If I put it below, it doesn’t trigger at all.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using Rewired;
using System;
[RequireComponent(typeof(CharacterController))]
public class FPSController : MonoBehaviour {
[HideInInspector]
public int playerID;
private Player player;
private CharacterController controller;
[HideInInspector]
public Camera FPSCamera;
[Header("Input Settings")]
public float mouseXSensitivity = 30f;
public float mouseYSensitivity = 30f;
[Header("Movement Settings")]
public float friction = 6f;
public float moveSpeed = 7f;
public float runAcceleration = 14f;
public float runDecceleration = 10f;
public float airAcceleration = 2f;
public float airDecceleration = 2f;
public float airControl = 0.3f;
public float strafeSpeed = 1f;
public float strafeAcceleration = 50f;
public float jumpSpeed = 8.0f;
public float moveScale = 1.0f;
[HideInInspector]
public float wishSpeedGlobal = 2f;
[HideInInspector]
public bool isGrappling = false;
[HideInInspector]
public bool isDead = false;
[Header("Audio Settings")]
public AudioClip[] jumpSounds;
AudioSource audio;
private float cameraRotX = 0f;
private float cameraRotY = 0f;
private Vector3 moveDirection = Vector3.zero;
private Vector3 moveDirectionNorm = Vector3.zero;
[HideInInspector]
public Vector3 playerVelocity = Vector3.zero;
private float playerFriction = 0f;
private bool wishJump = false;
private bool wasGrounded = false;
private Vector3 contactPoint;
public class Cmd {
public float forwardMove;
public float rightMove;
public float upMove;
}
protected Cmd cmd;
void Start () {
player = ReInput.players.GetPlayer (playerID);
controller = GetComponent<CharacterController> ();
FPSCamera = GameObject.FindGameObjectWithTag ("PlayerCamera").GetComponent<Camera> ();
Cursor.visible = false;
Cursor.lockState = CursorLockMode.Locked;
cmd = new Cmd();
audio = GetComponent<AudioSource> ();
}
public float cmdScale() {
float max;
float total;
max = Mathf.Abs(cmd.forwardMove);
if (Mathf.Abs(cmd.rightMove) > max)
max = Mathf.Abs(cmd.rightMove);
if (max <= 0f)
return 0f;
total = Mathf.Sqrt(cmd.forwardMove * cmd.forwardMove + cmd.rightMove * cmd.rightMove);
return moveSpeed * max / (moveScale * total);
}
void PlayJumpSound() {
if (audio.isPlaying) {
return;
}
audio.clip = jumpSounds [UnityEngine.Random.Range (0, jumpSounds.Length)];
audio.Play();
}
//Movement
void SetMovementDir() {
cmd.forwardMove = player.GetAxisRaw ("MoveForward");
cmd.rightMove = player.GetAxisRaw ("Strafe");
}
void QueueJump() {
if (player.GetButtonDown ("Jump") && !wishJump) {
wishJump = true;
}
if (player.GetButtonUp ("Jump")) {
wishJump = false;
}
}
void Accelerate(Vector3 wishDir, float wishSpeed, float accel) {
float addSpeed;
float accelSpeed;
float currentSpeed;
currentSpeed = Vector3.Dot (playerVelocity, wishDir);
addSpeed = wishSpeed - currentSpeed;
if (addSpeed <= 0f) {
return;
}
accelSpeed = accel * Time.deltaTime * wishSpeed;
if (accelSpeed > addSpeed) {
accelSpeed = addSpeed;
}
playerVelocity.x += accelSpeed * wishDir.x;
playerVelocity.z += accelSpeed * wishDir.z;
}
void AirControl(Vector3 wishDir, float wishSpeed) {
float zSpeed;
float speed;
float dot;
float k;
if (cmd.forwardMove == 0f || wishSpeed == 0f) {
return;
}
zSpeed = playerVelocity.y;
playerVelocity.y = 0f;
speed = playerVelocity.magnitude;
playerVelocity.Normalize ();
dot = Vector3.Dot (playerVelocity, wishDir);
k = 32f;
k *= airControl * dot * dot * Time.deltaTime;
if (dot > 0f) {
playerVelocity = new Vector3 (playerVelocity.x * speed * wishDir.x * k,
playerVelocity.y * speed * wishDir.y * k,
playerVelocity.z * speed * wishDir.z * k);
playerVelocity.Normalize ();
}
playerVelocity *= speed;
playerVelocity.y = zSpeed;
}
void AirMove() {
Vector3 wishDir;
float wishVelocity = airAcceleration;
float accel;
float scale = cmdScale ();
SetMovementDir ();
wishDir = new Vector3 (cmd.rightMove, 0f, cmd.forwardMove);
wishDir = transform.TransformDirection (wishDir);
wishDir.Normalize ();
moveDirection = wishDir;
float wishSpeed = wishDir.magnitude;
wishSpeed *= moveSpeed;
float wishSpeed2 = wishSpeed;
if (Vector3.Dot (playerVelocity, wishDir) < 0f) {
accel = airDecceleration;
} else {
accel = airAcceleration;
}
if (cmd.forwardMove == 0f && cmd.rightMove != 0f) {
if (wishSpeed > wishSpeedGlobal) {
wishSpeed = wishSpeedGlobal;
}
accel = strafeAcceleration;
Accelerate (wishDir, wishSpeed, accel);
if (airControl != 0f) {
AirControl (wishDir, wishSpeed2);
}
} else {
Accelerate (wishDir, wishSpeed, accel);
}
if ((controller.collisionFlags == CollisionFlags.Above) == true) {
if (playerVelocity.y > 0) {
playerVelocity.y = 0f;
}
}
playerVelocity.y += Physics.gravity.y * Time.deltaTime;
}
void ApplyFriction (){
Vector3 vec = playerVelocity;
float speed;
float newSpeed;
float control;
float drop;
vec.y = 0f;
speed = vec.magnitude;
drop = 0f;
if (controller.isGrounded) {
control = speed < runDecceleration ? runDecceleration : speed;
drop = control * friction * Time.deltaTime;
}
newSpeed = speed - drop;
playerFriction = newSpeed;
if (newSpeed < 0f) {
newSpeed = 0f;
}
if (speed > 0f) {
newSpeed /= speed;
}
playerVelocity.x *= newSpeed;
playerVelocity.z *= newSpeed;
}
void GroundMove() {
Vector3 wishDir;
if (!wishJump) {
ApplyFriction ();
}
float scale = cmdScale ();
SetMovementDir ();
wishDir = new Vector3 (cmd.rightMove, 0f, cmd.forwardMove);
wishDir = transform.TransformDirection (wishDir);
wishDir.Normalize ();
moveDirection = wishDir;
float wishSpeed = wishDir.magnitude;
wishSpeed *= moveSpeed;
Accelerate (wishDir, wishSpeed, runAcceleration);
playerVelocity.y = -controller.stepOffset / Time.deltaTime;
if (wishJump) {
playerVelocity.y = jumpSpeed;
wishJump = false;
PlayJumpSound();
}
}
void Update () {
if (!isDead) {
cameraRotX -= player.GetAxisRaw ("LookVertical") * mouseXSensitivity * Time.deltaTime;
cameraRotY += player.GetAxisRaw ("LookHorizontal") * mouseYSensitivity * Time.deltaTime;
}
if (cameraRotX < -90f) {
cameraRotX = -90f;
} else if (cameraRotX > 90f) {
cameraRotX = 90f;
}
transform.rotation = Quaternion.Euler (0f, cameraRotY, 0f);
FPSCamera.transform.rotation = Quaternion.Euler (cameraRotX, cameraRotY, 0f);
QueueJump ();
if (controller.isGrounded && !isGrappling && !isDead) {
GroundMove ();
} else if (!controller.isGrounded && !isGrappling && !isDead) {
AirMove ();
}
if (isDead) {
playerVelocity = new Vector3 (0f,0f,0f);
}
if (!controller.isGrounded && wasGrounded && playerVelocity.y <= 0f) {
playerVelocity.y = 0f;
}
wasGrounded = controller.isGrounded;
controller.Move (playerVelocity * Time.deltaTime);
}
}