I have a simple project where I’ve set up an FPS Controller. My problem is that there’s some stutter when I move the player’s body and rotate the camera at the same time. I did some research and found out that you needed to put both the player’s body and the camera either in Update() or in FixedUpdate(). I put both in Update() but it doesn’t seem to work.
Here’s the code for both the player body and the camera :
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Movement : MonoBehaviour
{
public float playerSpeed;
public float maxSpeed = 10;
public float minSpeed = 5f;
private Rigidbody rb;
[SerializeField] AudioSource steps;
private void Awake()
{
rb = GetComponent<Rigidbody>();
}
private void Update()
{
//speed limit
if (rb.velocity.magnitude >= maxSpeed)
{
return;
}
//walk
if (Input.GetKey(KeyCode.LeftShift))
{
if (rb.velocity.magnitude >= minSpeed)
{
return;
}
}
else
{
if (rb.velocity.magnitude >= maxSpeed)
{
return;
}
}
//vertical and horizontal movement
if (Input.GetKey(KeyCode.Z))
{
rb.AddForce(transform.forward * playerSpeed);
}
if (Input.GetKey(KeyCode.S))
{
rb.AddForce(-transform.forward * playerSpeed);
}
if (Input.GetKey(KeyCode.Q))
{
rb.AddForce(-transform.right * playerSpeed);
}
if (Input.GetKey(KeyCode.D))
{
rb.AddForce(transform.right * playerSpeed);
}
Steps();
}
void Steps()
{
if (Input.GetKey(KeyCode.Z) || Input.GetKey(KeyCode.S) || Input.GetKey(KeyCode.Q) || Input.GetKey(KeyCode.D))
{
if (!steps.isPlaying)
{
steps.Play();
}
}
else
{
steps.Pause();
}
}
}
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class View : MonoBehaviour
{
public float sensitivity;
public Transform playerCamera;
float limit;
float random;
void Start()
{
Cursor.lockState = CursorLockMode.Locked;
}
// Update is called once per frame
void Update()
{
float movementX = Input.GetAxis("Mouse X") * sensitivity;
float movementY = Input.GetAxis("Mouse Y") * -sensitivity;
limit -= movementY;
limit = Mathf.Clamp(limit, -90f, 90f);
transform.Rotate(0, movementX, 0);
playerCamera.transform.localRotation = Quaternion.Euler(-limit, 0, 0);
}
}
Any help is greatly appreciated,
Thanks in advance,
Draav.
I just fixed it by changing the Fixed Timestep to 0.005. In case anyone else has this problem you can go to Edit > Project Settings > Time > Fixed Timestep.
Unfortunately I can’t help with your problem but I’ll flag the advice to lower the physics time step to 0.005 as misleading: that’s 200 physics steps per second that are usually not necessary and will consume 4x the usual CPU time for physics.
another hint:
AddForce and AddTorque belong into the FixedUpdate function. Otherwise they’re frame rate dependent. You can read the player input inside the Update() method and store it in a vector2 member variable and use that in FixedUpdate.
Are the View and Movement components on the same GameObject? If so, we have the same character controller setups in our game: The body (with a rigidbody), that rotates around the y axis for the direction and a head that’s only for looking up and down. I also had stuttering in the beginning and what made it go away was to use a CinemachineVirtualCamera on the head game object and set the Camera (extra GameObject outside the player GameObject hierarchy) with the CinemachineBrain to “Late Update” for the update method.
I’m pretty sure that Cinemachine is not needed in this case, but clearly I was doing things wrong that Cinemachine is doing right that I didn’t completely understand at the time.
I will try setting the input in Update() and the force applied in FixedUpdate() and let you know if it solves the problem. I have high hopes for it though. Thank you very much!
So, I followed a tutorial on Youtube and I think I found a fix. The solution was to separate the camera from the player’s body and make the camera follow the body on its own. However I’m not entirely sure the issue is solved since now my player won’t rotate with the camera. Here’s the updated script and a picture of the different GameObjects making the player :
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Movement : MonoBehaviour
{
//movement related variables
public float playerSpeed;
public float maxSpeed = 10;
public float minSpeed = 5f;
private Rigidbody rb;
[SerializeField] AudioSource steps;
float x, y, movementX, movementY;
public Transform playerPos;
//camera related variables
public float sensitivity;
public Transform playerCamera;
float limit;
//jumping related variables
public float jumpPower;
private bool shouldJump;
public Rigidbody body;
public bool isGrounded;
private void Awake()
{
rb = GetComponent<Rigidbody>();
}
private void Start()
{
Cursor.lockState = CursorLockMode.Locked;
}
private void Update()
{
Steps();
PlayerInput();
Camera();
JumpCheck();
}
private void FixedUpdate()
{
//speed limit
if (rb.velocity.magnitude >= maxSpeed)
{
return;
}
//walk
if (Input.GetKey(KeyCode.LeftShift))
{
if (rb.velocity.magnitude >= minSpeed)
{
return;
}
}
else
{
if (rb.velocity.magnitude >= maxSpeed)
{
return;
}
}
//vertical and horizontal movement
rb.AddForce(transform.right * x * playerSpeed);
rb.AddForce(transform.forward * y * playerSpeed);
//jumping
if (shouldJump && isGrounded)
{
body.AddForce(jumpPower * Vector3.up);
shouldJump = false;
}
}
void PlayerInput()
{
//movement input
x = Input.GetAxisRaw("Horizontal");
y = Input.GetAxisRaw("Vertical");
//camera input
movementX = Input.GetAxis("Mouse X") * sensitivity;
movementY = Input.GetAxis("Mouse Y") * -sensitivity;
}
void Steps()
{
if (Input.GetKey(KeyCode.Z) || Input.GetKey(KeyCode.S) || Input.GetKey(KeyCode.Q) || Input.GetKey(KeyCode.D))
{
if (!steps.isPlaying)
{
steps.Play();
}
}
else
{
steps.Pause();
}
}
void Camera()
{
limit -= movementY;
limit = Mathf.Clamp(limit, -90f, 90f);
transform.Rotate(0, movementX, 0);
playerCamera.transform.localRotation = Quaternion.Euler(-limit, 0, 0);
}
void JumpCheck()
{
Ray ray = new Ray(transform.position, -transform.up);
if (Physics.Raycast(transform.position, -transform.up, out RaycastHit hitInfo, 1f))
{
isGrounded = true;
Debug.DrawRay(transform.position, -transform.up, Color.green);
}
else
{
isGrounded = false;
Debug.DrawRay(transform.position, -transform.up, Color.red);
}
if (shouldJump == false)
{
shouldJump = Input.GetKeyDown(KeyCode.Space);
}
}
}