using RootMotion.FinalIK; using UnityEngine; namespace NBF { /// /// /// public class PlayerLocalMover : PlayerDriveBase { public LayerMask interactableLayer; public LookAtIK lookAtIK; // 挂在背部上的 LookAtIK 脚本 public float aimDistance = 1.5f; // 目标点离相机多远 private Transform lookTarget; // 实际目标点 [Header("限制角度(单位:度)")] public float maxUpAngle = 20f; // 相机抬头最多20° public float maxDownAngle = 40f; // 相机低头最多40° /// /// 基础移动速度 /// private const float MoveSpeed = 3f; /// /// 冲刺速度 /// private const float SprintSpeed = 5f; private float acceleration = 10f; // 加速度 /// /// 重力加速度 /// private float gravity = 9.81f; private float groundedGravity = -0.5f; // 小的向下力确保角色保持在地面 private float airControl = 0.5f; // 空中控制系数 private float verticalVelocity = 0f; private Vector3 currentVelocity = Vector3.zero; private float currentSpeed = 0f; [Tooltip("视角旋转敏感度")] public Vector2 sensitivity = new Vector2(0.015f, 0.015f); public float minPitch = -40.0f; public float maxPitch = 75.0f; public float boatMaxPitch = 60; private float _cameraPitch; private Vector3 _rotationInput = Vector3.zero; private bool _isRun; [HideInInspector] public int nextShowSlotIndex = -1; protected override void OnStart() { lookAtIK = Character.gameObject.GetComponent(); lookTarget = new GameObject("SpineLookTarget").transform; lookTarget.SetParent(Character.transform); interactableLayer = LayerMask.GetMask("Interactive"); InputManager.OnPlayerCanceled += OnPlayerCanceled; InputManager.OnPlayerPerformed += OnPlayerPerformed; } protected override void OnUpdate() { UpdateGear(); InteractiveCheck(); } protected override void OnLateUpdate() { UpdatePlayerHandView(); } protected override void OnFixedUpdate() { UpdatePlayerPosition(); UpdatePlayerRotation(); } public override void OnDestroy() { InputManager.OnPlayerCanceled -= OnPlayerCanceled; InputManager.OnPlayerPerformed -= OnPlayerPerformed; } #region 钓组控制 private void UpdateGear() { if (Player.CanChangeGear()) { if (nextShowSlotIndex > 0) { var data = Fishing.Inst.Datasource; data.SetSelfTestGear(nextShowSlotIndex); nextShowSlotIndex = -1; } } } #endregion #region 事件输入 private void OnPlayerPerformed(string action) { if (action == "Run") { _isRun = true; } } private void OnPlayerCanceled(string action) { if (action == "Run") { _isRun = false; } 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 RaycastHit hitInfo; private bool isHit; private readonly float _interactionDistance = 8f; private InteractiveObject _lastInteractiveObject; private void InteractiveCheck() { if (Player.NowBoat) { SetInteractiveObject(null); return; } if (Player && Player.Fsm.CurrentStateId == States.Player.Idle) { // 从屏幕中心发射射线 Ray ray = BaseCamera.Main.ViewportPointToRay(new Vector3(0.5f, 0.5f, 0)); isHit = Physics.Raycast(ray, out hitInfo, _interactionDistance, interactableLayer); // 检测到可交互物体时 if (isHit) { var interactiveObject = hitInfo.transform.gameObject.GetComponent(); if (interactiveObject != null) { SetInteractiveObject(interactiveObject); return; } } } SetInteractiveObject(null); } private void SetInteractiveObject(InteractiveObject interactiveObject) { if (_lastInteractiveObject != interactiveObject) { _lastInteractiveObject = interactiveObject; InputManager.Instance.OnInteractiveObject(_lastInteractiveObject); } } #endregion #region 肩膀控制 private void UpdatePlayerHandView() { var cameraTransform = BaseCamera.Main.transform; 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; } #endregion #region 位移和旋转控制 private void UpdatePlayerPosition() { Vector2 movementInput = InputManager.GetMovementInput(); if (Player.NowBoat) { Player.NowBoat.BoatInput(movementInput); return; } Vector3 movementDirection = Vector3.zero; movementDirection += Vector3.forward * movementInput.y; movementDirection += Vector3.right * movementInput.x; movementDirection = movementDirection.relativeTo(BaseCamera.Main.transform, Character.transform.up); // 计算目标速度 float targetSpeed = _isRun ? SprintSpeed : MoveSpeed; // 平滑加速 if (movementDirection.magnitude > 0.1f) { currentSpeed = Mathf.Lerp(currentSpeed, targetSpeed, acceleration * Time.deltaTime); } else { currentSpeed = Mathf.Lerp(currentSpeed, 0f, acceleration * 2f * Time.deltaTime); } // 处理重力 if (Character.CharacterController.isGrounded) { verticalVelocity = groundedGravity; // 地面移动 - 完全控制 currentVelocity = Vector3.Lerp(currentVelocity, movementDirection.normalized * currentSpeed, acceleration * Time.deltaTime); } else { verticalVelocity -= gravity * Time.deltaTime; // 空中移动 - 有限控制 Vector3 targetAirVelocity = movementDirection.normalized * currentSpeed * airControl; currentVelocity = Vector3.Lerp(currentVelocity, new Vector3(targetAirVelocity.x, currentVelocity.y, targetAirVelocity.z), acceleration * Time.deltaTime); } // 组合移动 Vector3 totalMovement = currentVelocity * Time.deltaTime; totalMovement.y = verticalVelocity * Time.deltaTime; Character.CollisionFlags = Character.CharacterController.Move(totalMovement); } private void UpdatePlayerRotation() { // Look Vector2 lookInput = InputManager.GetLookInput() * sensitivity; if (lookInput.x != 0.0f) { _rotationInput.y += lookInput.x; } AddControlPitchInput(-lookInput.y, minPitch, Player.NowBoat ? boatMaxPitch : maxPitch); Player.CameraRoot.transform.localRotation = Quaternion.Euler(_cameraPitch, 0.0f, 0.0f); if (_rotationInput != Vector3.zero) { Character.CharacterController.transform.rotation *= Quaternion.Euler(_rotationInput); _rotationInput = Vector3.zero; } } /// /// 添加输入(影响俯仰)。 /// 此操作应用于相机父级的本地旋转。 /// private void AddControlPitchInput(float value, float minPitch = -40.0f, float maxPitch = 70.0f) { if (value != 0.0f) _cameraPitch = VectorUtil.ClampAngle(_cameraPitch + value, minPitch, maxPitch); } #endregion } }