Возьмите controller.Move(motion * Time.deltaTime);
из FixedUpdate и поместите его в самый конец метода обновления.
В случае, если у вас также есть какой-то скрипт на камере, например, какая-то реализация последующей камеры, где вы работаете с компонентом преобразования этой камеры в цикле обновления, вместо этого следует использовать LateUpdate.
Смотрите это https://learn.unity.com/tutorial/update-and-fixedupdate#5c8a4242edbc2a001f47cd63
и https://docs.unity3d.com/ScriptReference/MonoBehaviour.LateUpdate.html
Эта реализация контроллера игрока для GameObject с компонентом Character Controller также может вам помочь.
void Update()
{
if (isDead) return;
isWalking = Input.GetButton("Walk");
isGrounded = characterController.isGrounded;
if (isGrounded)
{
// Move
float verticalAxis = Input.GetAxis("Vertical");
moveDirection = new Vector3(0, 0, verticalAxis);
moveDirection = transform.TransformDirection(moveDirection);
if (verticalAxis > 0 && isWalking)
{
moveDirection *= walkingSpeed;
}
else if (verticalAxis > 0)
{
moveDirection *= runningSpeed;
}
// Jump
if (Input.GetButtonDown("Jump"))
{
moveDirection.y = jumpSpeed;
//moveDirection.z = jumpDistance; in case some forward boost is required
moveDirection = transform.TransformDirection(moveDirection);
}
}
// Gravity
moveDirection.y -= gravity * Time.deltaTime;
characterController.Move(moveDirection * Time.deltaTime);
// Rotation
float horizontalAxis = Input.GetAxis("Horizontal");
transform.Rotate(0, horizontalAxis, 0);
transform.rotation = Quaternion.Slerp(transform.rotation, Quaternion.FromToRotation(transform.up, GetHitNormal()) * transform.rotation, yRotationSpeed * Time.deltaTime);
}
// To keep local up aligned with global up on a slope ground
private Vector3 GetHitNormal()
{
RaycastHit hit;
if (Physics.Raycast(transform.position, Vector3.down, out hit))
return hit.normal;
else
return Vector3.zero;
}