using Unity.Netcode; using UnityEngine; public sealed class FirstPersonCameraController : NetworkBehaviour, IPlayerSystem { [SerializeField, Range(1f, 89f)] private float verticalLookLimit = 80f; private readonly NetworkVariable synchronizedPitch = new( 0f, NetworkVariableReadPermission.Everyone, NetworkVariableWritePermission.Owner); private Camera playerCamera; private AudioListener audioListener; private MeshRenderer playerRenderer; private float pitch; private bool isInitialized; public Transform AimTransform => playerCamera != null ? playerCamera.transform : transform; public Vector3 AimForward => AimTransform.forward; public void Initialize(PlayerFacade facade) { playerCamera = GetComponentInChildren(true); audioListener = playerCamera != null ? playerCamera.GetComponent() : null; playerRenderer = GetComponentInChildren(); isInitialized = playerCamera != null; if (!isInitialized) return; bool isLocalPlayer = IsOwner; playerCamera.gameObject.SetActive(isLocalPlayer); if (!isLocalPlayer) ApplyCameraPitch(synchronizedPitch.Value); if (!isLocalPlayer) return; if (playerRenderer != null) playerRenderer.enabled = false; foreach (Camera camera in FindObjectsByType(FindObjectsSortMode.None)) { if (camera != playerCamera) camera.enabled = false; } if (audioListener != null) audioListener.enabled = true; Cursor.lockState = CursorLockMode.Locked; Cursor.visible = false; } public override void OnNetworkSpawn() { synchronizedPitch.OnValueChanged += HandleSynchronizedPitchChanged; if (isInitialized && !IsOwner) ApplyCameraPitch(synchronizedPitch.Value); if (isInitialized && IsOwner) CaptureCursor(); } private void LateUpdate() { if (!isInitialized || !IsOwner || !Application.isFocused) return; if (Cursor.lockState != CursorLockMode.Locked || Cursor.visible) CaptureCursor(); } private void OnApplicationFocus(bool hasFocus) { if (hasFocus && isInitialized && IsOwner) CaptureCursor(); } public void ApplyPitch(float pitchDelta) { if (!isInitialized || !IsOwner) return; pitch = Mathf.Clamp(pitch - pitchDelta, -verticalLookLimit, verticalLookLimit); ApplyCameraPitch(pitch); if (IsSpawned) synchronizedPitch.Value = pitch; } public override void OnNetworkDespawn() { synchronizedPitch.OnValueChanged -= HandleSynchronizedPitchChanged; if (IsOwner) { Cursor.lockState = CursorLockMode.None; Cursor.visible = true; } } private void HandleSynchronizedPitchChanged(float _, float currentPitch) { if (!IsOwner) ApplyCameraPitch(currentPitch); } private void ApplyCameraPitch(float currentPitch) { pitch = currentPitch; if (playerCamera != null) playerCamera.transform.localRotation = Quaternion.Euler(pitch, 0f, 0f); } private static void CaptureCursor() { Cursor.lockState = CursorLockMode.Locked; Cursor.visible = false; } }