- Home /
Camera Lag/Blurriness when Moving and Rotating At the Same Time
In the FPS game that I am currently working on, when moving the rigidbody attached to the player and turning the camera at the same time, a lot of lag/blurriness occurs. I am setting the position and rotation of the camera (it is not parented to the player) in Update() and adding forces to the rigidbody in FixedUpdate(). I have interpolation enabled on the rigidbody, but it doesn't seem to fix the issue. When moving the rigidbody or rotating the camera independently of one another, everything works fine. Only when doing both at the same time does the issue occur. As I increase the frequency of the physics timestep the blurriness is reduced (at 0.05 it is EXTREMELY noticeable and at 0.005 it is barely noticeable, for example). This is not a very good solution since it can tank the frame rate, so I would love to be able to avoid it. Any suggestions?
Here is my code currently:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using TMPro;
public class BackupPlayerController : MonoBehaviour
{
public static BackupPlayerController Instance;
[Header("References")]
public LayerMask groundMask;
public Transform cameraController;
public Transform playerCamera;
public TextMeshProUGUI fpsCounter;
[Header("Movement Settings")]
public float groundAccelerate;
public float airAccelerate;
public float groundSpeed;
public float airSpeed;
public float maxSpeed;
public float friction;
public Vector3 gravity;
public float jumpHeight;
[Header("Player Settings")]
public float crouchHeight;
public float crouchDuration;
public float mouseSensitivity;
public float skinWidth;
public float skinPadding;
[Header("Camera Settings")]
public float bobSpeed;
public float bobMagnitudeX;
public float bobMagnitudeY;
public float bobResetSpeed;
public float breathingSpeed;
public float breathingMagnitude;
public float swayMagnitude;
new Rigidbody rigidbody;
new CapsuleCollider collider;
RaycastHit hit;
Vector3 defaultGravity;
Vector3 wishdir;
Vector3 cameraRotation;
Vector3 cameraOffset;
Vector3 bob;
Vector3 breathing;
Vector3 sway;
Vector3 stretch;
Vector3 shakePosition;
Vector3 shakeRotation;
float defaultHeight;
float crouchOffset;
float bobTime;
float breathingTime;
float maxAirSpeed;
float airTime;
bool grounded;
bool wishjump = false;
bool wishcrouch = false;
bool crouching = false;
bool justAirborne = false;
bool justGrounded = true;
bool onSlope = false;
bool dead = false;
// ==================== UNITY METHODS ====================
void Awake()
{
Instance = this;
rigidbody = GetComponent<Rigidbody>();
collider = GetComponent<CapsuleCollider>();
defaultHeight = collider.height;
crouchOffset = crouchHeight - defaultHeight;
defaultGravity = gravity;
cameraRotation = cameraController.rotation.eulerAngles;
Cursor.lockState = CursorLockMode.Locked;
}
void Update()
{
CheckGround();
GetInput();
CameraMotion();
ShowFPSCounter();
}
void FixedUpdate()
{
if (grounded)
{
Accelerate(wishdir, crouching ? groundSpeed / 2f : groundSpeed, groundAccelerate);
if (Input.GetKey(KeyCode.Space))
{
rigidbody.velocity = new Vector3(rigidbody.velocity.x, 0f, rigidbody.velocity.z);
rigidbody.AddForce(Vector3.up * Mathf.Sqrt(jumpHeight * 2f * -defaultGravity.y), ForceMode.VelocityChange);
wishjump = false;
}
else
{
ApplyFriction();
}
}
else
{
Accelerate(wishdir, airSpeed, airAccelerate);
rigidbody.AddForce(gravity * Time.fixedDeltaTime, ForceMode.VelocityChange);
}
}
void OnTriggerEnter(Collider other)
{
SlidingDoor slidingDoor = other.GetComponent<SlidingDoor>();
if (slidingDoor != null) StartCoroutine(slidingDoor.Open());
}
// ==================== MOVEMENT METHODS ====================
void Accelerate(Vector3 wishdir, float wishspeed, float accel)
{
float addspeed, accelspeed, currentspeed;
currentspeed = Vector3.Dot(rigidbody.velocity, wishdir);
addspeed = wishspeed - currentspeed;
if (addspeed <= 0) return;
accelspeed = accel * Time.deltaTime;
if (accelspeed > addspeed) accelspeed = addspeed;
rigidbody.AddForce(accelspeed * wishdir, ForceMode.VelocityChange);
if (grounded) return;
Vector3 horizontal = new Vector3(rigidbody.velocity.x, 0f, rigidbody.velocity.z);
if (horizontal.magnitude > maxSpeed)
{
horizontal *= maxSpeed / horizontal.magnitude;
rigidbody.velocity = new Vector3(horizontal.x, rigidbody.velocity.y, horizontal.z);
}
}
void ApplyFriction()
{
float speed = rigidbody.velocity.magnitude;
if (speed == 0) return;
float drop = friction * Time.deltaTime;
float newSpeed = speed - drop;
if (newSpeed < 0) newSpeed = 0;
rigidbody.velocity *= newSpeed / speed;
}
public void Explode(Vector3 point, float force)
{
rigidbody.AddExplosionForce(force, point, 4f, 1f, ForceMode.VelocityChange);
}
IEnumerator QueueCrouch()
{
wishcrouch = true;
yield return new WaitForSeconds(0.1f);
wishcrouch = false;
}
IEnumerator Crouch()
{
wishcrouch = false;
crouching = true;
collider.height = crouchHeight;
collider.center = new Vector3(0f, (crouchHeight - defaultHeight) / 2f, 0f);
float timePassed = 0f;
while (timePassed < crouchDuration)
{
float t = timePassed / crouchDuration;
t = t * t * (3f - 2f * t);
cameraOffset.y = Mathf.Lerp(0f, crouchOffset, t);
timePassed += Time.deltaTime;
yield return null;
}
cameraOffset.y = crouchOffset;
}
IEnumerator Uncrouch()
{
crouching = false;
collider.height = defaultHeight;
collider.center = Vector3.zero;
float timePassed = 0f;
while (timePassed < crouchDuration)
{
float t = timePassed / crouchDuration;
t = t * t * (3f - 2f * t);
cameraOffset.y = Mathf.Lerp(crouchOffset, 0f, t);
timePassed += Time.deltaTime;
yield return null;
}
cameraOffset.y = 0f;
}
// ==================== MISCELLANEOUS ====================
void CheckGround()
{
grounded = Physics.SphereCast(collider.bounds.center + Vector3.down * (collider.height / 2f - collider.radius), collider.radius - skinWidth * 2f, Vector3.down, out hit, skinWidth + skinPadding, groundMask);
if (grounded)
{
float angle = Vector3.Angle(hit.normal, Vector3.up);
if (angle > 45f)
{
grounded = false;
onSlope = false;
}
else if (angle != 0f)
{
onSlope = true;
}
else
{
onSlope = false;
}
}
if (grounded)
{
justGrounded = true;
if (justAirborne)
{
justAirborne = false;
if (airTime > 0.25f)
{
StartCoroutine(StretchCamera(0.4f, Vector3.down, 0.25f));
}
}
// gravity = onSlope ? hit.normal * -6000f : defaultGravity;
}
else
{
justAirborne = true;
if (justGrounded)
{
justGrounded = false;
airTime = 0f;
}
// gravity = defaultGravity;
airTime += Time.deltaTime;
}
}
void GetInput()
{
cameraRotation += new Vector3(-Input.GetAxisRaw("Mouse Y") * mouseSensitivity * Time.deltaTime, Input.GetAxisRaw("Mouse X") * mouseSensitivity * Time.deltaTime, 0f);
cameraRotation.x = Mathf.Clamp(cameraRotation.x, -90f, 90f);
Vector3 horizontal = new Vector3(0f, cameraRotation.y, 0f);
transform.rotation = Quaternion.Euler(horizontal);
cameraController.rotation = Quaternion.Euler(cameraRotation);
wishdir = transform.right * Input.GetAxisRaw("Horizontal") + transform.forward * Input.GetAxisRaw("Vertical");
wishdir.Normalize();
if (Input.GetKeyDown(KeyCode.LeftShift) && !crouching)
{
StartCoroutine(QueueCrouch());
}
if (wishcrouch && grounded) StartCoroutine(Crouch());
if ((Input.GetKeyDown(KeyCode.Space) || Input.GetKeyUp(KeyCode.LeftShift)) && crouching)
{
StartCoroutine(Uncrouch());
}
if (Input.GetKeyDown(KeyCode.T)) StartCoroutine(ShakeCamera(1f, 10f, 1.3f, 1f));
}
// ==================== CAMERA MOTION ====================
void CameraMotion()
{
if (grounded) breathingTime += Time.deltaTime;
if (grounded && !crouching && rigidbody.velocity.magnitude > 1f)
{
bobTime += bobSpeed * Time.deltaTime;
}
else
{
int bobs = (int)(bobTime / Mathf.PI + 0.4999f);
float targetbobTime = bobs * Mathf.PI;
bobTime = Mathf.Lerp(bobTime, targetbobTime, bobResetSpeed * Time.deltaTime);
}
bob.x = -Mathf.Cos(bobTime) * bobMagnitudeX;
bob.y = Mathf.Sin(bobTime * 2f) * bobMagnitudeY;
breathing = new Vector3(Mathf.PerlinNoise(breathingTime * breathingSpeed, 0f) * 2f - 1, Mathf.PerlinNoise(0f, breathingTime * breathingSpeed) * 2f - 1, 0f) * breathingMagnitude;
sway.z = -transform.InverseTransformPoint(rigidbody.velocity).x * swayMagnitude;
playerCamera.localPosition = bob + stretch + shakePosition;
playerCamera.localRotation = Quaternion.Euler(breathing + sway + shakeRotation);
cameraController.position = transform.position + Vector3.up * 0.5f + cameraOffset;
}
IEnumerator StretchCamera(float duration, Vector3 direction, float distance)
{
float timePassed = 0f;
while (timePassed < duration)
{
stretch = direction * Mathf.Sin(timePassed / duration * Mathf.PI) * distance;
timePassed += Time.deltaTime;
yield return null;
}
stretch = Vector3.zero;
}
IEnumerator ShakeCamera(float duration, float speed, float positionalMagnitude, float rotationalMagnitude)
{
float timePassed = 0f;
float pmag = positionalMagnitude;
float rmag = rotationalMagnitude;
while (timePassed < duration)
{
shakePosition = new Vector3(Mathf.PerlinNoise(Time.time * speed, 0f) * 2f - 1, Mathf.PerlinNoise(0f, Time.time * speed) * 2f - 1, 0f) * pmag;
shakeRotation = new Vector3(Mathf.PerlinNoise(Time.time * speed, 0f) * 2f - 1, Mathf.PerlinNoise(0f, Time.time * speed) * 2f - 1, 0f) * rmag;
pmag = positionalMagnitude * (duration - timePassed);
rmag = rotationalMagnitude * (duration - timePassed);
timePassed += Time.deltaTime;
yield return null;
}
shakePosition = Vector3.zero;
shakeRotation = Vector3.zero;
}
// ==================== OTHER ====================
void ShowFPSCounter()
{
fpsCounter.text = (int)(1f / Time.deltaTime) + "";
}
}
And here are my rigidbody settings:
EDIT: After discussing with people on other forums, I have discovered that the issue was that I was setting the transform.rotation of the object with the rigidbody on it. I wanted the player's capsule to face the camera, but setting the rotation of the rigidbody directly like this interrupted its interpolation and caused the jitter. The solution I found was to create a child of the player which had the actual visible mesh, while the main object kept the script, capsule collider, and rigidbody. Then I set the camera to be a child of the mesh, and voila, everything works!
Your answer
Follow this Question
Related Questions
Gfx.waitForPresent causing massive lag spikes in editor 8 Answers
Cloth disappears (occludes?) in scene view when zoomed in too far. Is there a fix? 2 Answers
Slow Motion Physics Lag Spikes 0 Answers
Moving kinematic rigidbody causing strange physics behaviour, is this a bug? 0 Answers
Box Collider on Spring Joint Randomly Stops Working in Editor and Builds 1 Answer