using System; using ECM2; using RootMotion.FinalIK; using UnityEngine; namespace NBF { public class PlayerCharacter : Character { private float _cameraPitch; private FPlayer _player; [Space(10.0f)] public float maxSprintSpeed = 10.0f; public GameObject cameraParent; [Space(15.0f)] public bool invertLook = true; [Tooltip("视角旋转敏感度")] public Vector2 sensitivity = new Vector2(0.05f, 0.05f); [Space(15.0f)] public float minPitch = -80.0f; public float maxPitch = 80.0f; [Tooltip("手上下最大角度")] public Vector2 handMaxAngle = new Vector2(0, 0); [HideInInspector] public int nextShowSlotIndex = -1; public bool IsSelf; public LookAtIK lookAtIK; // 挂在背部上的 LookAtIK 脚本 public float aimDistance = 1.5f; // 目标点离相机多远 private Transform lookTarget; // 实际目标点 [Header("限制角度(单位:度)")] public float maxUpAngle = 20f; // 相机抬头最多20° public float maxDownAngle = 40f; // 相机低头最多40° public LayerMask interactableLayer; protected override void Start() { base.Start(); // 创建一个动态目标点对象,避免你每次动态生成 lookAtIK = GetComponent(); _player = GetComponent(); IsSelf = _player.Data.PlayerID == GameModel.RoleID; cameraParent = _player.CameraRoot.gameObject; if (IsSelf) { camera = BaseCamera.Main; } lookTarget = new GameObject("SpineLookTarget").transform; lookTarget.SetParent(_player.transform); transform.position = _player.Data.position; transform.rotation = _player.Data.rotation; InputManager.OnPlayerCanceled += OnPlayerCanceled; InputManager.OnPlayerPerformed += OnPlayerPerformed; // InputManager.OnRunAction += OnRunAction; // InputManager.OnQuickIndexAction += OnQuickIndexAction; // InputManager.OnUseTorchAction += OnUseTorchAction; // InputManager.OnUseTelescopeAction += OnUseTelescopeAction; interactableLayer = LayerMask.GetMask("Interactive"); } private void Update() { InteractiveCheck(); UpdatePlayerPositionAndRotation(); UpdateGear(); } protected override void OnBeforeSimulationUpdate(float deltaTime) { base.OnBeforeSimulationUpdate(deltaTime); CheckSprintInput(); } protected virtual void LateUpdate() { UpdateCameraParentRotation(); UpdatePlayerHandView(); } protected override void Reset() { base.Reset(); SetRotationMode(RotationMode.None); StopSprinting(); } private void OnDestroy() { InputManager.OnPlayerCanceled -= OnPlayerCanceled; InputManager.OnPlayerPerformed -= OnPlayerPerformed; } #region 交互检测 private RaycastHit hitInfo; private bool isHit; private readonly float _interactionDistance = 8f; private InteractiveObject _lastInteractiveObject; private void InteractiveCheck() { if (IsSelf && _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() { 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 UpdateGear() { if (_player.CanChangeGear()) { if (nextShowSlotIndex > 0) { Debug.LogError("切换钓组========="); var data = Fishing.Inst.Datasource; data.SetSelfTestGear(nextShowSlotIndex); nextShowSlotIndex = -1; } } } #endregion #region 角色位移和旋转控制 private bool _isSprinting; private bool _sprintInputPressed; private void UpdatePlayerPositionAndRotation() { // Move Vector2 movementInput = InputManager.GetMovementInput(); Vector3 movementDirection = Vector3.zero; movementDirection += Vector3.forward * movementInput.y; movementDirection += Vector3.right * movementInput.x; movementDirection = movementDirection.relativeTo(cameraTransform, GetUpVector()); SetMovementDirection(movementDirection); // Look Vector2 lookInput = InputManager.GetLookInput() * sensitivity; AddControlYawInput(lookInput.x); AddControlPitchInput(invertLook ? -lookInput.y : lookInput.y, minPitch, maxPitch); } /// /// 添加输入(影响偏航)。 /// 此操作应用于角色的旋转。 /// public virtual void AddControlYawInput(float value) { if (value != 0.0f) AddYawInput(value); } /// /// 添加输入(影响俯仰)。 /// 此操作应用于相机父级的本地旋转。 /// public virtual void AddControlPitchInput(float value, float minPitch = -80.0f, float maxPitch = 80.0f) { if (value != 0.0f) _cameraPitch = MathLib.ClampAngle(_cameraPitch + value, minPitch, maxPitch); } /// /// 根据当前的 _cameraPitch 值更新 cameraParent 的本地旋转。 /// protected virtual void UpdateCameraParentRotation() { cameraParent.transform.localRotation = Quaternion.Euler(_cameraPitch, 0.0f, 0.0f); } public void Sprint() { _sprintInputPressed = true; } /// /// Request the character to stop sprinting. /// public void StopSprinting() { _sprintInputPressed = false; } public bool IsSprinting() { return _isSprinting; } private bool CanSprint() { return IsWalking() && !IsCrouched(); } private void CheckSprintInput() { if (!_isSprinting && _sprintInputPressed && CanSprint()) { _isSprinting = true; } else if (_isSprinting && (!_sprintInputPressed || !CanSprint())) { _isSprinting = false; } } public override float GetMaxSpeed() { return _isSprinting ? maxSprintSpeed : base.GetMaxSpeed(); } #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 } }