using System; using RootMotion.FinalIK; using UnityEngine; namespace NBF { using UnityEngine; using UnityEngine.InputSystem; [RequireComponent(typeof(CharacterController))] public class SmoothFPSController : MonoBehaviour { public enum ControlMode { LocalPlayer, // 本地玩家,接受输入控制 NetworkPlayer // 网络玩家,接受数据驱动 } [Header("Control Settings")] public ControlMode controlMode = ControlMode.LocalPlayer; [Range(0.1f, 10f)] public float lookSensitivity = 2f; [Range(0f, 0.5f)] public float lookSmoothing = 0.1f; [Range(0.1f, 10f)] public float moveSpeed = 5f; public float jumpHeight = 2f; public float gravity = -9.81f; [Header("Components")] public CharacterController characterController; public Transform cameraTransform; // 物理状态 private Vector3 velocity; private bool isGrounded; // 视角控制变量 private Vector2 currentLookInput; private Vector2 smoothedLookInput; private Vector2 lookVelocity; private float xRotation = 0f; // 移动控制变量 private Vector2 currentMoveInput; private bool currentJumpInput; // 网络同步数据 // private Vector3 targetPosition; // private Quaternion targetRotation; private float positionLerpSpeed = 10f; private float rotationLerpSpeed = 5f; public bool IsSelf; private FPlayer _player; public LookAtIK lookAtIK; // 挂在背部上的 LookAtIK 脚本 private Transform lookTarget; // 实际目标点 [Header("限制角度(单位:度)")] public float maxUpAngle = 20f; // 相机抬头最多20° public float maxDownAngle = 40f; // 相机低头最多40° public float aimDistance = 1.5f; // 目标点离相机多远 [HideInInspector] public int nextShowSlotIndex = -1; public LayerMask interactableLayer; public Rigidbody physicsBody; // 用于物理同步的Rigidbody private void Awake() { if (characterController == null) characterController = GetComponent(); interactableLayer = LayerMask.GetMask("Interactive"); physicsBody = GetComponent(); } private void Start() { InputManager.OnPlayerCanceled += OnPlayerCanceled; InputManager.OnPlayerPerformed += OnPlayerPerformed; lookAtIK = GetComponent(); _player = GetComponent(); if (IsSelf) { // camera = BaseCamera.Main; } lookTarget = new GameObject("SpineLookTarget").transform; lookTarget.SetParent(_player.transform); transform.position = _player.Data.position; transform.rotation = _player.Data.rotation; } private void OnDestroy() { InputManager.OnPlayerCanceled -= OnPlayerCanceled; InputManager.OnPlayerPerformed -= OnPlayerPerformed; } private void Update() { if (controlMode == ControlMode.LocalPlayer) { UpdateLocalPlayer(); } else { UpdateNetworkPlayer(); } UpdateGear(); UpdatePlayerHandView(); } private void GetInputs() { currentMoveInput = InputManager.GetMovementInput(); currentLookInput = InputManager.GetLookInput(); currentJumpInput = Keyboard.current.spaceKey.wasPressedThisFrame; } private void UpdatePlayerHandView() { Vector3 cameraForward = cameraTransform.forward; Vector3 flatForward = Vector3.ProjectOnPlane(cameraForward, Vector3.up).normalized; // 获取相机 pitch 角度(负值是上看,正值是下看) float pitchAngle = Vector3.SignedAngle(flatForward, cameraForward, cameraTransform.right); // 限制 pitch 角度 pitchAngle = Mathf.Clamp(pitchAngle, -maxUpAngle, maxDownAngle); // 重新构造限制后的目标方向 Quaternion limitedPitch = Quaternion.AngleAxis(pitchAngle, cameraTransform.right); Vector3 limitedDirection = limitedPitch * flatForward; // 设置目标点 lookTarget.position = cameraTransform.position + limitedDirection * aimDistance; lookAtIK.solver.target = lookTarget; } #region 钓组控制 private void UpdateGear() { if (_player.CanChangeGear()) { if (nextShowSlotIndex > 0) { Debug.LogError("切换钓组========="); var data = Fishing.Inst.Datasource; data.SetSelfTestGear(nextShowSlotIndex); nextShowSlotIndex = -1; } } } #endregion #region 按键输入事件 private void OnPlayerPerformed(string action) { if (action == "Run") { Sprint(); } } private void OnPlayerCanceled(string action) { if (action == "Run") { StopSprinting(); } else if (action.StartsWith("Quick")) { nextShowSlotIndex = int.Parse(action.Substring("Quick".Length)); } else if (action == "UseTorch") { _player.Data.openLight = !_player.Data.openLight; } else if (action == "UseTelescope") { _player.Data.openTelescope = !_player.Data.openTelescope; _player.ToggleTelescope(); } } #endregion #region 角色位移和旋转控制 private bool _isSprinting; private bool _sprintInputPressed; [Space(10.0f)] public float maxSprintSpeed = 10.0f; private void UpdateNetworkPlayer() { } private void UpdateLocalPlayer() { // 获取输入 GetInputs(); // 处理地面检测 GroundCheck(); // 处理移动 HandleMovement(); // 处理视角旋转(现在更加平滑) HandleLookRotation(); // 处理跳跃 // HandleJump(); // 应用重力 ApplyGravity(); } private void GroundCheck() { isGrounded = characterController.isGrounded; if (isGrounded && velocity.y < 0) { velocity.y = -2f; // 轻微下压确保贴地 } } private void HandleMovement() { Vector3 move = (transform.right * currentMoveInput.x + transform.forward * currentMoveInput.y) * GetMaxSpeed(); characterController.Move(move * Time.deltaTime); // physicsBody.MovePosition(move * Time.deltaTime); // physicsBody.Move(move * Time.deltaTime, transform.rotation); // 手动同步CharacterController和物理位置 if (physicsBody != null) { physicsBody.position = transform.position; physicsBody.rotation = transform.rotation; } } private void HandleLookRotation() { // 应用输入平滑(使用平滑阻尼) smoothedLookInput = Vector2.SmoothDamp( smoothedLookInput, currentLookInput, ref lookVelocity, lookSmoothing); // 计算旋转量(应用灵敏度) float lookX = smoothedLookInput.x * lookSensitivity * Time.deltaTime; float lookY = smoothedLookInput.y * lookSensitivity * Time.deltaTime; // 水平旋转(左右看) // transform.Rotate(Vector3.up * lookX); physicsBody.MoveRotation(physicsBody.rotation * Quaternion.Euler(0, lookX, 0)); // 垂直旋转(上下看) - 带有角度限制 xRotation -= lookY; xRotation = Mathf.Clamp(xRotation, -45f, 80f); // 限制上下视角范围 // 应用摄像机旋转 cameraTransform.localRotation = Quaternion.Euler(xRotation, 0f, 0f); } private void HandleJump() { if (currentJumpInput && isGrounded) { velocity.y = Mathf.Sqrt(jumpHeight * -2f * gravity); } } private void ApplyGravity() { velocity.y += gravity * Time.deltaTime; characterController.Move(velocity * Time.deltaTime); } public void Sprint() { _sprintInputPressed = true; } /// /// Request the character to stop sprinting. /// public void StopSprinting() { _sprintInputPressed = false; } private bool CanSprint() { return true; // return IsWalking() && !IsCrouched(); } private void CheckSprintInput() { if (!_isSprinting && _sprintInputPressed && CanSprint()) { _isSprinting = true; } else if (_isSprinting && (!_sprintInputPressed || !CanSprint())) { _isSprinting = false; } } public float GetMaxSpeed() { return _isSprinting ? maxSprintSpeed : moveSpeed; } #endregion } }