using UnityEngine; [RequireComponent(typeof(CharacterController))] public class FPSController : MonoBehaviour { [Header("References")] public Camera playerCamera; public Transform weaponHolder; [Header("Movement")] public float walkSpeed = 6f; public float sprintSpeed = 8.5f; public float jumpHeight = 1.2f; public float gravity = -22f; [Header("Look")] public float mouseSensitivity = 260f; public float maxLookAngle = 85f; [Header("Recoil")] public float recoilReturnSpeed = 12f; public float recoilSnappiness = 18f; [Header("Weapon Bob")] public float bobFrequency = 9f; public float bobHorizontalAmount = 0.018f; public float bobVerticalAmount = 0.014f; public float bobSmoothing = 10f; private CharacterController _controller; private Vector2 _lookAngles; private Vector2 _recoilTarget; private Vector2 _recoilCurrent; private float _verticalVelocity; private Vector3 _weaponHolderBaseLocalPosition; private float _bobTime; private bool _lookEnabled = true; public void Initialize(Camera controlledCamera, Transform controlledWeaponHolder) { playerCamera = controlledCamera; weaponHolder = controlledWeaponHolder; } private void Awake() { _controller = GetComponent(); if (playerCamera == null) { playerCamera = GetComponentInChildren(true); } if (weaponHolder == null && playerCamera != null) { Transform foundHolder = playerCamera.transform.Find("WeaponHolder"); if (foundHolder != null) { weaponHolder = foundHolder; } } } private void Start() { if (playerCamera != null) { _lookAngles.x = transform.eulerAngles.y; _lookAngles.y = playerCamera.transform.localEulerAngles.x; if (_lookAngles.y > 180f) { _lookAngles.y -= 360f; } } if (weaponHolder != null) { _weaponHolderBaseLocalPosition = weaponHolder.localPosition; } SetCursorLocked(true); } private void Update() { if (!_lookEnabled || playerCamera == null) { return; } UpdateLook(); UpdateMovement(); UpdateWeaponBob(); } public void SetLookEnabled(bool enabled) { _lookEnabled = enabled; if (!enabled) { _recoilCurrent = Vector2.zero; _recoilTarget = Vector2.zero; } } public void SetCursorLocked(bool locked) { Cursor.lockState = locked ? CursorLockMode.Locked : CursorLockMode.None; Cursor.visible = !locked; } public void ApplyRecoil(float verticalKick, float horizontalKick) { _recoilTarget += new Vector2(verticalKick, Random.Range(-horizontalKick, horizontalKick)); } private void UpdateLook() { float mouseX = Input.GetAxis("Mouse X") * mouseSensitivity * Time.deltaTime; float mouseY = Input.GetAxis("Mouse Y") * mouseSensitivity * Time.deltaTime; _lookAngles.x += mouseX; _lookAngles.y = Mathf.Clamp(_lookAngles.y - mouseY, -maxLookAngle, maxLookAngle); _recoilTarget = Vector2.Lerp(_recoilTarget, Vector2.zero, recoilReturnSpeed * Time.deltaTime); _recoilCurrent = Vector2.Lerp(_recoilCurrent, _recoilTarget, recoilSnappiness * Time.deltaTime); transform.rotation = Quaternion.Euler(0f, _lookAngles.x + _recoilCurrent.y, 0f); playerCamera.transform.localRotation = Quaternion.Euler(_lookAngles.y - _recoilCurrent.x, 0f, 0f); } private void UpdateMovement() { Vector3 input = new Vector3(Input.GetAxisRaw("Horizontal"), 0f, Input.GetAxisRaw("Vertical")); input = Vector3.ClampMagnitude(input, 1f); float moveSpeed = Input.GetKey(KeyCode.LeftShift) ? sprintSpeed : walkSpeed; Vector3 move = transform.TransformDirection(input) * moveSpeed; if (_controller.isGrounded && _verticalVelocity < 0f) { _verticalVelocity = -2f; } if (_controller.isGrounded && Input.GetButtonDown("Jump")) { _verticalVelocity = Mathf.Sqrt(jumpHeight * -2f * gravity); } _verticalVelocity += gravity * Time.deltaTime; move.y = _verticalVelocity; _controller.Move(move * Time.deltaTime); } private void UpdateWeaponBob() { if (weaponHolder == null) { return; } Vector3 horizontalVelocity = _controller.velocity; horizontalVelocity.y = 0f; float speed = horizontalVelocity.magnitude; if (_controller.isGrounded && speed > 0.15f) { _bobTime += Time.deltaTime * bobFrequency * (speed / walkSpeed); Vector3 bobOffset = new Vector3( Mathf.Cos(_bobTime * 0.5f) * bobHorizontalAmount, Mathf.Sin(_bobTime) * bobVerticalAmount, 0f); weaponHolder.localPosition = Vector3.Lerp( weaponHolder.localPosition, _weaponHolderBaseLocalPosition + bobOffset, bobSmoothing * Time.deltaTime); } else { weaponHolder.localPosition = Vector3.Lerp( weaponHolder.localPosition, _weaponHolderBaseLocalPosition, bobSmoothing * Time.deltaTime); } } }