328 lines
9.8 KiB
C#
328 lines
9.8 KiB
C#
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<LookAtIK>();
|
|
_player = GetComponent<FPlayer>();
|
|
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();
|
|
if (_player.NowBoat)
|
|
{
|
|
_player.NowBoat.BoatInput(InputManager.GetMovementInput());
|
|
}
|
|
else
|
|
{
|
|
UpdatePlayerPosition();
|
|
}
|
|
|
|
UpdatePlayerRotation();
|
|
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<InteractiveObject>();
|
|
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 UpdatePlayerPosition()
|
|
{
|
|
// 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);
|
|
}
|
|
|
|
private void UpdatePlayerRotation()
|
|
{
|
|
// Look
|
|
Vector2 lookInput = InputManager.GetLookInput() * sensitivity;
|
|
|
|
AddControlYawInput(lookInput.x);
|
|
AddControlPitchInput(invertLook ? -lookInput.y : lookInput.y, minPitch, maxPitch);
|
|
}
|
|
|
|
/// <summary>
|
|
/// 添加输入(影响偏航)。
|
|
/// 此操作应用于角色的旋转。
|
|
/// </summary>
|
|
public virtual void AddControlYawInput(float value)
|
|
{
|
|
if (value != 0.0f)
|
|
AddYawInput(value);
|
|
}
|
|
|
|
/// <summary>
|
|
/// 添加输入(影响俯仰)。
|
|
/// 此操作应用于相机父级的本地旋转。
|
|
/// </summary>
|
|
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);
|
|
}
|
|
|
|
/// <summary>
|
|
/// 根据当前的 _cameraPitch 值更新 cameraParent 的本地旋转。
|
|
/// </summary>
|
|
protected virtual void UpdateCameraParentRotation()
|
|
{
|
|
cameraParent.transform.localRotation = Quaternion.Euler(_cameraPitch, 0.0f, 0.0f);
|
|
}
|
|
|
|
|
|
public void Sprint()
|
|
{
|
|
_sprintInputPressed = true;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Request the character to stop sprinting.
|
|
/// </summary>
|
|
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
|
|
}
|
|
} |