角色控制

This commit is contained in:
2025-05-29 21:10:30 +08:00
parent cdcb007d6d
commit 8d3cfe170c
6 changed files with 463 additions and 401 deletions

View File

@@ -4,7 +4,7 @@ namespace EasyPeasyFirstPersonController
using System.Collections; using System.Collections;
using UnityEngine; using UnityEngine;
public partial class FirstPersonController : MonoBehaviour public class FirstPersonController : MonoBehaviour
{ {
[Range(0, 100)] public float mouseSensitivity = 25f; [Range(0, 100)] public float mouseSensitivity = 25f;
[Range(0f, 200f)] private float snappiness = 100f; [Range(0f, 200f)] private float snappiness = 100f;
@@ -55,7 +55,6 @@ namespace EasyPeasyFirstPersonController
private Camera cam; private Camera cam;
private AudioSource slideAudioSource; private AudioSource slideAudioSource;
private float bobTimer; private float bobTimer;
private float defaultPosY;
private Vector3 recoil = Vector3.zero; private Vector3 recoil = Vector3.zero;
private bool isLook = true, isMove = true; private bool isLook = true, isMove = true;
private float currentCameraHeight; private float currentCameraHeight;
@@ -67,15 +66,13 @@ namespace EasyPeasyFirstPersonController
private float currentTiltAngle; private float currentTiltAngle;
private float tiltVelocity; private float tiltVelocity;
public float CurrentCameraHeight => isCrouching || isSliding ? crouchCameraHeight : originalCameraParentHeight;
private void Awake() private void Awake()
{ {
characterController = GetComponent<CharacterController>(); characterController = GetComponent<CharacterController>();
cam = playerCamera.GetComponent<Camera>(); cam = playerCamera.GetComponent<Camera>();
originalHeight = characterController.height; originalHeight = characterController.height;
originalCameraParentHeight = cameraParent.localPosition.y; originalCameraParentHeight = cameraParent.localPosition.y;
defaultPosY = cameraParent.localPosition.y; // defaultPosY = cameraParent.localPosition.y;
slideAudioSource = gameObject.AddComponent<AudioSource>(); slideAudioSource = gameObject.AddComponent<AudioSource>();
slideAudioSource.playOnAwake = false; slideAudioSource.playOnAwake = false;
slideAudioSource.loop = false; slideAudioSource.loop = false;
@@ -118,10 +115,11 @@ namespace EasyPeasyFirstPersonController
transform.rotation = Quaternion.Euler(0f, xVelocity, 0f); transform.rotation = Quaternion.Euler(0f, xVelocity, 0f);
} }
HandleHeadBob(); // HandleHeadBob();
bool wantsToCrouch = canCrouch && Input.GetKey(KeyCode.LeftControl) && !isSliding; bool wantsToCrouch = canCrouch && Input.GetKey(KeyCode.LeftControl) && !isSliding;
Vector3 point1 = transform.position + characterController.center - Vector3.up * (characterController.height * 0.5f); Vector3 point1 = transform.position + characterController.center -
Vector3.up * (characterController.height * 0.5f);
Vector3 point2 = point1 + Vector3.up * characterController.height * 0.6f; Vector3 point2 = point1 + Vector3.up * characterController.height * 0.6f;
float capsuleRadius = characterController.radius * 0.95f; float capsuleRadius = characterController.radius * 0.95f;
float castDistance = isSliding ? originalHeight + 0.2f : originalHeight - crouchHeight + 0.2f; float castDistance = isSliding ? originalHeight + 0.2f : originalHeight - crouchHeight + 0.2f;
@@ -130,6 +128,7 @@ namespace EasyPeasyFirstPersonController
{ {
postSlideCrouchTimer = 0.3f; postSlideCrouchTimer = 0.3f;
} }
if (postSlideCrouchTimer > 0) if (postSlideCrouchTimer > 0)
{ {
postSlideCrouchTimer -= Time.deltaTime; postSlideCrouchTimer -= Time.deltaTime;
@@ -144,7 +143,9 @@ namespace EasyPeasyFirstPersonController
{ {
isSliding = true; isSliding = true;
slideTimer = slideDuration; slideTimer = slideDuration;
slideDirection = moveInput.magnitude > 0.1f ? (transform.right * moveInput.x + transform.forward * moveInput.y).normalized : transform.forward; slideDirection = moveInput.magnitude > 0.1f
? (transform.right * moveInput.x + transform.forward * moveInput.y).normalized
: transform.forward;
currentSlideSpeed = sprintSpeed; currentSlideSpeed = sprintSpeed;
} }
@@ -156,6 +157,7 @@ namespace EasyPeasyFirstPersonController
{ {
isSliding = false; isSliding = false;
} }
float targetSlideSpeed = slideSpeed * Mathf.Lerp(0.7f, 1f, slideProgress); float targetSlideSpeed = slideSpeed * Mathf.Lerp(0.7f, 1f, slideProgress);
currentSlideSpeed = Mathf.SmoothDamp(currentSlideSpeed, targetSlideSpeed, ref slideSpeedVelocity, 0.2f); currentSlideSpeed = Mathf.SmoothDamp(currentSlideSpeed, targetSlideSpeed, ref slideSpeedVelocity, 0.2f);
characterController.Move(slideDirection * currentSlideSpeed * Time.deltaTime); characterController.Move(slideDirection * currentSlideSpeed * Time.deltaTime);
@@ -165,7 +167,9 @@ namespace EasyPeasyFirstPersonController
characterController.height = Mathf.Lerp(characterController.height, targetHeight, Time.deltaTime * 10f); characterController.height = Mathf.Lerp(characterController.height, targetHeight, Time.deltaTime * 10f);
characterController.center = new Vector3(0f, characterController.height * 0.5f, 0f); characterController.center = new Vector3(0f, characterController.height * 0.5f, 0f);
float targetFov = isSprinting ? sprintFov : (isSliding ? sprintFov + (slideFovBoost * Mathf.Lerp(0f, 1f, 1f - slideProgress)) : normalFov); float targetFov = isSprinting
? sprintFov
: (isSliding ? sprintFov + (slideFovBoost * Mathf.Lerp(0f, 1f, 1f - slideProgress)) : normalFov);
currentFov = Mathf.SmoothDamp(currentFov, targetFov, ref fovVelocity, 1f / fovChangeSpeed); currentFov = Mathf.SmoothDamp(currentFov, targetFov, ref fovVelocity, 1f / fovChangeSpeed);
cam.fieldOfView = currentFov; cam.fieldOfView = currentFov;
@@ -174,7 +178,8 @@ namespace EasyPeasyFirstPersonController
private void HandleHeadBob() private void HandleHeadBob()
{ {
Vector3 horizontalVelocity = new Vector3(characterController.velocity.x, 0f, characterController.velocity.z); Vector3 horizontalVelocity =
new Vector3(characterController.velocity.x, 0f, characterController.velocity.z);
bool isMovingEnough = horizontalVelocity.magnitude > 0.1f; bool isMovingEnough = horizontalVelocity.magnitude > 0.1f;
float targetBobOffset = isMovingEnough ? Mathf.Sin(bobTimer) * bobbingAmount : 0f; float targetBobOffset = isMovingEnough ? Mathf.Sin(bobTimer) * bobbingAmount : 0f;
@@ -190,7 +195,8 @@ namespace EasyPeasyFirstPersonController
currentCameraHeight + currentBobOffset, currentCameraHeight + currentBobOffset,
cameraParent.localPosition.z); cameraParent.localPosition.z);
recoil = Vector3.zero; recoil = Vector3.zero;
cameraParent.localRotation = Quaternion.RotateTowards(cameraParent.localRotation, Quaternion.Euler(recoil), recoilReturnSpeed * Time.deltaTime); cameraParent.localRotation = Quaternion.RotateTowards(cameraParent.localRotation,
Quaternion.Euler(recoil), recoilReturnSpeed * Time.deltaTime);
return; return;
} }
@@ -218,14 +224,16 @@ namespace EasyPeasyFirstPersonController
recoil = Vector3.zero; recoil = Vector3.zero;
} }
cameraParent.localRotation = Quaternion.RotateTowards(cameraParent.localRotation, Quaternion.Euler(recoil), recoilReturnSpeed * Time.deltaTime); cameraParent.localRotation = Quaternion.RotateTowards(cameraParent.localRotation, Quaternion.Euler(recoil),
recoilReturnSpeed * Time.deltaTime);
} }
private void HandleMovement() private void HandleMovement()
{ {
moveInput.x = Input.GetAxis("Horizontal"); moveInput.x = Input.GetAxis("Horizontal");
moveInput.y = Input.GetAxis("Vertical"); moveInput.y = Input.GetAxis("Vertical");
isSprinting = canSprint && Input.GetKey(KeyCode.LeftShift) && moveInput.y > 0.1f && isGrounded && !isCrouching && !isSliding; isSprinting = canSprint && Input.GetKey(KeyCode.LeftShift) && moveInput.y > 0.1f && isGrounded &&
!isCrouching && !isSliding;
float currentSpeed = isCrouching ? crouchSpeed : (isSprinting ? sprintSpeed : walkSpeed); float currentSpeed = isCrouching ? crouchSpeed : (isSprinting ? sprintSpeed : walkSpeed);
if (!isMove) currentSpeed = 0f; if (!isMove) currentSpeed = 0f;
@@ -256,27 +264,5 @@ namespace EasyPeasyFirstPersonController
characterController.Move(moveDirection * Time.deltaTime); characterController.Move(moveDirection * Time.deltaTime);
} }
} }
public void SetControl(bool newState)
{
SetLookControl(newState);
SetMoveControl(newState);
}
public void SetLookControl(bool newState)
{
isLook = newState;
}
public void SetMoveControl(bool newState)
{
isMove = newState;
}
public void SetCursorVisibility(bool newVisibility)
{
Cursor.lockState = newVisibility ? CursorLockMode.None : CursorLockMode.Locked;
Cursor.visible = newVisibility;
}
} }
} }

View File

@@ -4635,7 +4635,7 @@ MonoBehaviour:
m_PrefabInstance: {fileID: 0} m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0} m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 5559475303044247694} m_GameObject: {fileID: 5559475303044247694}
m_Enabled: 0 m_Enabled: 1
m_EditorHideFlags: 0 m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: 776adaaacdc5c4e8ab0395120a6e972b, type: 3} m_Script: {fileID: 11500000, guid: 776adaaacdc5c4e8ab0395120a6e972b, type: 3}
m_Name: m_Name:

View File

@@ -1,4 +1,6 @@
using NBC; using System;
using NBC;
using RootMotion.FinalIK;
using UnityEngine; using UnityEngine;
namespace NBF namespace NBF
@@ -16,9 +18,20 @@ namespace NBF
private FPlayer _player; private FPlayer _player;
private bool _isRun; private bool _isRun;
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;
private void Start() private void Start()
{ {
firstPersonController = GetComponent<FirstPersonController>(); firstPersonController = GetComponent<FirstPersonController>();
@@ -41,6 +54,11 @@ namespace NBF
InputManager.OnPlayerCanceled += OnPlayerCanceled; InputManager.OnPlayerCanceled += OnPlayerCanceled;
InputManager.OnPlayerPerformed += OnPlayerPerformed; InputManager.OnPlayerPerformed += OnPlayerPerformed;
lookAtIK = GetComponent<LookAtIK>();
lookTarget = new GameObject("SpineLookTarget").transform;
lookTarget.SetParent(_player.transform);
interactableLayer = LayerMask.GetMask("Interactive");
} }
private void OnDestroy() private void OnDestroy()
@@ -87,7 +105,7 @@ namespace NBF
{ {
firstPersonController.enabled = true; firstPersonController.enabled = true;
} }
private void Update() private void Update()
{ {
var movementAxis = InputManager.GetMovementInput(); var movementAxis = InputManager.GetMovementInput();
@@ -130,8 +148,11 @@ namespace NBF
nextShowSlotIndex = -1; nextShowSlotIndex = -1;
} }
} }
UpdatePlayerHandView();
} }
private void FixedUpdate() private void FixedUpdate()
{ {
if (_player.MainArm) if (_player.MainArm)
@@ -139,5 +160,31 @@ namespace NBF
// _player.MainArm.Shoulder.SetCameraEulerAngleX(BaseCamera.Main.transform.localEulerAngles.x); // _player.MainArm.Shoulder.SetCameraEulerAngleX(BaseCamera.Main.transform.localEulerAngles.x);
} }
} }
#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
} }
} }

File diff suppressed because one or more lines are too long

View File

@@ -30,10 +30,10 @@ EditorUserSettings:
value: 550007565c005e0858575d23497a5c441516417d7a7171347c794931b6b1313c value: 550007565c005e0858575d23497a5c441516417d7a7171347c794931b6b1313c
flags: 0 flags: 0
RecentlyUsedSceneGuid-7: RecentlyUsedSceneGuid-7:
value: 500606050702510d0e570876137a09441516197b782925632e2a4d64b3b16169 value: 515250075c0c595e5f5a5e71122159444e4e4a2f7a7d7f602f284d66b4b76661
flags: 0 flags: 0
RecentlyUsedSceneGuid-8: RecentlyUsedSceneGuid-8:
value: 515250075c0c595e5f5a5e71122159444e4e4a2f7a7d7f602f284d66b4b76661 value: 500606050702510d0e570876137a09441516197b782925632e2a4d64b3b16169
flags: 0 flags: 0
RecentlyUsedSceneGuid-9: RecentlyUsedSceneGuid-9:
value: 5505015f5c515a085f5b092149760f441716407a787d7564287b1b36e7e1366e value: 5505015f5c515a085f5b092149760f441716407a787d7564287b1b36e7e1366e