替换脚本PlayerCam_01.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using DG.Tweening;
public class PlayerCam_02 : MonoBehaviour
{
// 视觉灵敏度参数
public float sensX = 400;
public float sensY = 400;
// 视角垂直旋转角度限制
public float minAngle = -90f;
public float maxAngle = 90f;
// 角色朝向的 Transform,用于水平旋转
public Transform orientation;
public Transform camHolder;
// 当前的 X 和 Y 旋转角度
private float xRotation;
private float yRotation;
private void Start()
{
// 初始时锁定鼠标光标并隐藏光标
Cursor.lockState = CursorLockMode.Locked;
Cursor.visible = false;
}
private void Update()
{
// 获取鼠标输入
float mouseX = Input.GetAxisRaw("Mouse X") * Time.deltaTime * sensX;
float mouseY = Input.GetAxisRaw("Mouse Y") * Time.deltaTime * sensY;
// 更新水平旋转角度
yRotation += mouseX;
// 更新垂直旋转角度,并限制在指定范围内
xRotation -= mouseY;
xRotation = Mathf.Clamp(xRotation, minAngle, maxAngle);
// 应用旋转到摄像机的 Transform 上,实现视角旋转
camHolder.rotation = Quaternion.Euler(xRotation, yRotation, 0);
// 应用水平旋转到角色的 Transform 上,使角色朝向与摄像机一致
orientation.rotation = Quaternion.Euler(0, yRotation, 0);
}
// 修改相机视野的动画方法
public void DoFov(float endValue)
{
GetComponent<Camera>().DOFieldOfView(endValue, 0.25f);
}
// 修改物体本地旋转的动画方法
public void DoTilt(float zTilt)
{
transform.DOLocalRotate(new Vector3(0, 0, zTilt), 0.25f);
}
}
替换脚本PlayerMovement_03.cs和Sliding.cs,并新增脚本WallRunning.cs
PlayerMovement_04.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class PlayerMovement_04 : MonoBehaviour
{
private float moveSpeed; // 玩家移动速度
public float walkSpeed = 7; // 行走速度
public float sprintSpeed = 10; // 冲刺速度
public float slideSpeed = 30; // 滑动速度
public float wallrunSpeed = 8.5f;
private float desiredMoveSpeed; // 期望的移动速度
private float lastDesiredMoveSpeed; // 上一次的期望移动速度
public float speedIncreaseMultiplier = 1.5f; // 速度增加倍数
public float slopeIncreaseMultiplier = 2.5f; // 斜坡增加倍数
public float groundDrag = 5; // 地面时的阻力
public float playerHeight = 2; // 玩家身高
public LayerMask whatIsGround; // 地面的LayerMask
private bool grounded; // 是否在地面上
public float jumpForce = 6; // 跳跃力度
public float jumpCooldown = 0.25f; // 跳跃冷却时间
public float airMultiplier = 0.4f; // 空中移动速度衰减
private bool readyToJump = true; // 是否可以跳跃
public float crouchSpeed = 3.5f; // 蹲伏时的移动速度
public float crouchYScale = 0.5f; // 蹲伏时的Y轴缩放比例
private float startYScale; // 初始Y轴缩放比例
public float maxSlopAngle = 40; // 最大坡度角度
private RaycastHit slopeHit; // 坡度检测的射线信息
private bool exitingSlope = true; // 是否正在离开坡度
public KeyCode jumpKey = KeyCode.Space; // 跳跃键
public KeyCode sprintKey = KeyCode.LeftShift; // 冲刺键
public KeyCode crouchKey = KeyCode.LeftControl; // 下蹲键
public Transform orientation; // 玩家朝向的Transform
private float h; // 水平输入
private float v; // 垂直输入
private Vector3 moveDirection; // 移动方向
private Rigidbody rb; // 玩家刚体
public MovementState state; // 当前玩家的移动状态
public enum MovementState
{
walking, // 行走
sprinting, // 冲刺
wallrunning,//墙跑
crouching, // 蹲伏
sliding, // 滑动
air // 空中
}
public bool sliding; // 是否正在滑动
public bool wallrunning;
private void Start()
{
rb = GetComponent<Rigidbody>();
rb.freezeRotation = true; // 防止刚体旋转
startYScale = transform.localScale.y; // 记录初始的Y轴缩放
}
private void Update()
{
grounded = Physics.Raycast(transform.position, Vector3.down, playerHeight * 0.5f + 0.2f, whatIsGround);
MyInput();
SpeedControl();
StateHandler();
if (grounded)
rb.drag = groundDrag;
else
rb.drag = 0;
}
private void FixedUpdate()
{
MovePlayer();
}
private void MyInput()
{
// 获取水平和垂直输入
h = Input.GetAxisRaw("Horizontal");
v = Input.GetAxisRaw("Vertical");
// 如果按下跳跃键且准备好跳,并且在地面上
if (Input.GetKey(jumpKey) && readyToJump && grounded)
{
readyToJump = false;
Jump();
Invoke(nameof(ResetJump), jumpCooldown);
}
if (Input.GetKeyDown(crouchKey))
{
// 调整玩家缩放以模拟蹲下效果
transform.localScale = new Vector3(transform.localScale.x, crouchYScale, transform.localScale.z);
rb.AddForce(Vector3.down * 5f, ForceMode.Impulse);
}
// 如果释放下蹲键
if (Input.GetKeyUp(crouchKey))
{
// 恢复到原始Y轴缩放
transform.localScale = new Vector3(transform.localScale.x, startYScale, transform.localScale.z);
}
}
private void MovePlayer()
{
// 根据朝向计算移动方向
moveDirection = orientation.forward * v + orientation.right * h;
// 如果在斜坡上并且不是即将离开斜坡
if (OnSlope() && !exitingSlope)
{
// 在斜坡上施加力,以便更好地移动
rb.AddForce(GetSlopeMoveDirection(moveDirection) * moveSpeed * 20f, ForceMode.Force);
// 如果垂直速度为正(上升),则额外施加向下的力,以克服斜坡引起的垂直速度变慢
if (rb.velocity.y > 0)
{
rb.AddForce(Vector3.down * 80f, ForceMode.Force);
}
}
else if (grounded) // 如果在地面上
{
rb.AddForce(moveDirection.normalized * moveSpeed * 10f, ForceMode.Force); // 在地面上施加移动力
}
else if (!grounded) // 如果在空中
{
// 在空中施加移动力,乘以空中移动速度衰减系数
rb.AddForce(moveDirection.normalized * moveSpeed * 10f * airMultiplier, ForceMode.Force);
}
// 根据是否在斜坡上决定是否启用重力
if (!wallrunning)
rb.useGravity = !OnSlope();
}
private void SpeedControl()
{
// 如果在斜坡上并且不是即将离开斜坡
if (OnSlope() && !exitingSlope)
{
// 如果速度的大小超过了设定的移动速度
if (rb.velocity.magnitude > moveSpeed)
{
// 将速度归一化,并乘以设定的移动速度,以限制速度在设定范围内
rb.velocity = rb.velocity.normalized * moveSpeed;
}
}
// 如果不在斜坡上
else
{
// 获取水平方向的速度
Vector3 flatVel = new Vector3(rb.velocity.x, 0f, rb.velocity.z);
// 如果水平速度的大小超过了设定的移动速度
if (flatVel.magnitude > moveSpeed)
{
// 限制水平速度在设定范围内
Vector3 limitedVel = flatVel.normalized * moveSpeed;
// 更新刚体的速度,保持垂直速度不变
rb.velocity = new Vector3(limitedVel.x, rb.velocity.y, limitedVel.z);
}
}
}
private void Jump()
{
exitingSlope = true;
//rb.velocity = new Vector3(rb.velocity.x, 0f, rb.velocity.z);
rb.velocity = Vector3.zero;
// 添加向上的力以实现跳跃
rb.AddForce(transform.up * jumpForce, ForceMode.Impulse);
}
private void ResetJump()
{
readyToJump = true;
exitingSlope = false;
}
private void StateHandler()
{
if (wallrunning)
{
state = MovementState.wallrunning;
desiredMoveSpeed = wallrunSpeed;
}
if (sliding)
{
state = MovementState.sliding; // 设置当前状态为滑动状态
if (OnSlope() && rb.velocity.y < 0.1f)
{
desiredMoveSpeed = slideSpeed; // 如果在斜坡上并且垂直速度小于0.1,则设置期望移动速度为滑动速度
}
else
{
desiredMoveSpeed = sprintSpeed; // 否则,设置期望移动速度为冲刺速度
}
}
// 如果按住蹲伏键
else if (Input.GetKey(crouchKey))
{
// 设置当前状态为蹲伏状态
state = MovementState.crouching;
// 设置移动速度为蹲伏速度
desiredMoveSpeed = crouchSpeed;
}
// 如果在地面上并且按住冲刺键
else if (grounded && Input.GetKey(sprintKey))
{
// 设置当前状态为冲刺状态
state = MovementState.sprinting;
// 设置移动速度为冲刺速度
desiredMoveSpeed = sprintSpeed;
}
// 如果在地面上但没有按住冲刺键
else if (grounded)
{
// 设置当前状态为行走状态
state = MovementState.walking;
// 设置移动速度为行走速度
desiredMoveSpeed = walkSpeed;
}
// 如果不在地面上
else
{
// 设置当前状态为空中状态
state = MovementState.air;
}
if (Mathf.Abs(desiredMoveSpeed - lastDesiredMoveSpeed) > 4f && moveSpeed != 0)
{
StopAllCoroutines(); // 停止所有协程
StartCoroutine(SmoothlyLerpMoveSpeed()); // 启动平滑插值移动速度的协程
}
else
{
moveSpeed = desiredMoveSpeed; // 否则,直接将移动速度设置为期望移动速度
}
lastDesiredMoveSpeed = desiredMoveSpeed; // 更新上一次的期望移动速度
}
public bool OnSlope()
{
// 使用射线检测当前位置向下,获取击中信息存储在slopeHit中
if (Physics.Raycast(transform.position, Vector3.down, out slopeHit, playerHeight * 0.5f + 0.3f))
{
// 计算斜坡的角度
float angle = Vector3.Angle(Vector3.up, slopeHit.normal);
// 如果角度小于最大允许斜坡角度且不等于0,表示在斜坡上
return angle < maxSlopAngle && angle != 0;
}
// 如果没有击中信息,或者角度不符合条件,表示不在斜坡上
return false;
}
public Vector3 GetSlopeMoveDirection(Vector3 direction)
{
// 使用Vector3.ProjectOnPlane将移动方向投影到斜坡法线上,然后进行归一化
return Vector3.ProjectOnPlane(direction, slopeHit.normal).normalized;
}
private IEnumerator SmoothlyLerpMoveSpeed()
{
float time = 0; // 记录经过的时间
float difference = Mathf.Abs(desiredMoveSpeed - moveSpeed); // 计算期望移动速度与当前移动速度的差值
float startValue = moveSpeed; // 记录开始时的移动速度
while (time < difference)
{
moveSpeed = Mathf.Lerp(startValue, desiredMoveSpeed, time / difference); // 使用插值平滑地改变移动速度
if (OnSlope())
{
float slopeAngle = Vector3.Angle(Vector3.up, slopeHit.normal); // 计算当前坡度的角度
float slopeAngleIncrease = 1 + (slopeAngle / 90f); // 根据坡度角度增加速度
// 根据时间、速度增加倍数、坡度增加倍数进行平滑插值
time += Time.deltaTime * speedIncreaseMultiplier * slopeIncreaseMultiplier * slopeAngleIncrease;
}
else
{
// 在平地上,只考虑时间和速度增加倍数
time += Time.deltaTime * speedIncreaseMultiplier;
}
yield return null; // 等待下一帧
}
moveSpeed = desiredMoveSpeed; // 最终将移动速度设置为期望移动速度
}
}
Sliding_01.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Sliding_01 : MonoBehaviour
{
public Transform orientation; // 玩家方向的Transform
public Transform playerObj; // 玩家对象的Transform,用于在滑动期间调整缩放
private Rigidbody rb; // 玩家的刚体组件
private PlayerMovement_04 pm_04; // PlayerMovement_03脚本的引用
public float maxSlideTime = 0.75f; // 滑动的最大持续时间
public float slideForce = 200; // 滑动期间施加的力
private float slideTimer; // 用于跟踪滑动持续时间的计时器
public float slideYScale = 0.5f; // 滑动期间玩家的Y轴缩放
private float startYScale; // 玩家的初始Y轴缩放
public KeyCode slideKey = KeyCode.F; // 启动滑动的按键
private float h; // 水平输入
private float v; // 垂直输入
private void Start()
{
rb = GetComponent<Rigidbody>();
pm_04 = GetComponent<PlayerMovement_04>();
startYScale = playerObj.localScale.y;
}
private void Update()
{
h = Input.GetAxisRaw("Horizontal");
v = Input.GetAxisRaw("Vertical");
// 检查是否按下滑动键且存在水平或垂直输入
if (Input.GetKeyDown(slideKey) && (h != 0 || v != 0))
{
StartSlide(); // 启动滑动
}
// 检查是否释放了滑动键且玩家当前正在滑动
if (Input.GetKeyUp(slideKey) && pm_04.sliding)
{
StopSlide(); // 停止滑动
}
}
private void FixedUpdate()
{
// 检查玩家当前是否在滑动
if (pm_04.sliding)
{
SlidingMovement(); // 处理滑动运动
}
}
private void StartSlide()
{
pm_04.sliding = true; // 在PlayerMovement_03脚本中设置滑动标志
// 调整玩家的缩放以创建蹲伏效果
playerObj.localScale = new Vector3(playerObj.localScale.x, slideYScale, playerObj.localScale.z);
// 应用向下的力以模拟蹲伏
rb.AddForce(Vector3.down * 5f, ForceMode.Impulse);
slideTimer = maxSlideTime; // 初始化滑动计时器
}
private void SlidingMovement()
{
Vector3 inputDirection = orientation.forward * v + orientation.right * h; // 计算输入方向
// 检查玩家是否不在斜坡上或向上移动
if (!pm_04.OnSlope() || rb.velocity.y > -0.1f)
{
// 在滑动期间在输入方向上施加力
rb.AddForce(inputDirection.normalized * slideForce, ForceMode.Force);
slideTimer -= Time.deltaTime; // 减少滑动计时器
}
else
{
// 在斜坡上滑动时根据斜坡的方向调整力
rb.AddForce(pm_04.GetSlopeMoveDirection(inputDirection) * slideForce, ForceMode.Force);
}
// 检查滑动持续时间是否已过期
if (slideTimer <= 0)
{
StopSlide(); // 停止滑动
}
}
private void StopSlide()
{
pm_04.sliding = false; // 在PlayerMovement_03脚本中重置滑动标志
// 将玩家的缩放恢复到初始大小
playerObj.localScale = new Vector3(playerObj.localScale.x, startYScale, playerObj.localScale.z);
}
}
WallRunning.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class WallRunning : MonoBehaviour
{
public LayerMask whatIsWall; // 定义墙体的LayerMask,用于射线检测
public LayerMask whatIsGround; // 定义地面的LayerMask,用于射线检测
public float wallRunForce = 200; // 墙体运动时施加的力
public float wallJumpUpForce = 7; // 墙跳时向上施加的力的大小
public float wallJumpSideForce = 12; // 墙跳时向侧面施加的力的大小
public float wallClimbSpeed = 3; // 沿墙上移动时的速度
public float maxWallRunTime = 0.7f; // 最大墙体运动时间
private float wallRunTimer; // 当前墙体运动计时器
public KeyCode upwardsRunKey = KeyCode.E; // 向上墙体运动的按键
public KeyCode downwardsRunKey = KeyCode.Q; // 向下墙体运动的按键
public KeyCode jumpKey = KeyCode.Space;
private bool upwardsRunning; // 是否正在向上墙体运动
private bool downwardsRunning; // 是否正在向下墙体运动
private float h; // 水平输入
private float v; // 垂直输入
public float wallCheckDistance = 0.7f; // 射线检测墙体的距离
public float minJumpHeight = 1; // 最小跳跃高度
private RaycastHit leftWallHit; // 射线检测到的左侧墙体信息
private RaycastHit rightWallHit; // 射线检测到的右侧墙体信息
private bool wallLeft; // 是否靠近左侧墙体
private bool wallRight; // 是否靠近右侧墙体
private bool exitingWall; // 是否正在退出墙壁状态的标志
public float exitWallTime = 0.2f; // 退出墙壁状态的时间阈值
private float exitWallTimer; // 退出墙壁状态的计时器
public bool useGravity = true; // 是否使用重力的标志,默认为 true,表示使用重力
public float gravityCounterForce = 3; // 用于控制在墙上的反向重力的力大小
public Transform orientation; // 玩家朝向的Transform
public PlayerCam_02 cam_02;
private PlayerMovement_04 pm_04; // 玩家移动脚本
private Rigidbody rb; // 玩家刚体
private void Start()
{
rb = GetComponent<Rigidbody>();
pm_04 = GetComponent<PlayerMovement_04>();
}
private void Update()
{
CheckForWall(); // 检测是否靠近墙体
StateMachine(); // 墙体运动状态机
}
private void FixedUpdate()
{
if (pm_04.wallrunning)
{
WallRunningMovement(); // 处理墙体运动
}
}
private void CheckForWall()
{
// 使用 Physics.Raycast 检测角色右侧是否有墙壁,将结果存储在 rightWallHit 中
wallRight = Physics.Raycast(transform.position, orientation.right, out rightWallHit, wallCheckDistance, whatIsWall);
// 使用 Physics.Raycast 检测角色左侧是否有墙壁,将结果存储在 leftWallHit 中
wallLeft = Physics.Raycast(transform.position, -orientation.right, out leftWallHit, wallCheckDistance, whatIsWall);
}
private bool AboveGround()
{
// 使用 Physics.Raycast 检测角色当前位置向下的射线,检测距离为 minJumpHeight,目标层级为 whatIsGround
return !Physics.Raycast(transform.position, Vector3.down, minJumpHeight, whatIsGround);
}
private void StateMachine()
{
// 获取水平和垂直输入
h = Input.GetAxisRaw("Horizontal");
v = Input.GetAxisRaw("Vertical");
// 检查是否按住上墙和下墙的按键
upwardsRunning = Input.GetKey(upwardsRunKey);
downwardsRunning = Input.GetKey(downwardsRunKey);
// 如果靠近墙,并且朝上移动,并且在地面上,并且不在退出墙体状态
if ((wallLeft || wallRight) && v > 0 && AboveGround() && !exitingWall)
{
// 如果当前不在墙体运动状态
if (!pm_04.wallrunning)
{
StartWallRun(); // 开始墙体运动
}
// 如果墙体运动计时器仍在计时
if (wallRunTimer > 0)
{
wallRunTimer -= Time.deltaTime;
}
// 如果墙体运动计时器结束,并且当前在墙体运动状态
if (wallRunTimer <= 0 && pm_04.wallrunning)
{
exitingWall = true;
exitWallTimer = exitWallTime;
}
// 如果按下跳跃键
if (Input.GetKeyDown(jumpKey))
{
WallJump();
}
}
// 如果正在退出墙体状态
else if (exitingWall)
{
// 如果当前在墙体运动状态,停止墙体运动
if (pm_04.wallrunning)
{
StopWallRun();
}
// 如果退出墙体计时器仍在计时
if (exitWallTimer > 0)
{
exitWallTimer -= Time.deltaTime;
}
// 如果退出墙体计时器结束
if (exitWallTimer <= 0)
{
exitingWall = false;
}
}
else
{
// 如果当前在墙体运动状态
if (pm_04.wallrunning)
{
StopWallRun(); // 停止墙体运动
}
}
}
private void StartWallRun()
{
pm_04.wallrunning = true; // 设置玩家正在进行墙体运动
wallRunTimer = maxWallRunTime; // 设置墙体运动计时器
rb.velocity = new Vector3(rb.velocity.x, 0f, rb.velocity.z); // 将垂直速度置为0,确保在墙体上的水平移动
cam_02.DoFov(90f); // 调用摄像机的方法,改变视野角度为90度,提高视野
// 根据墙的方向调整摄像机的倾斜角度
if (wallLeft)
{
cam_02.DoTilt(-5f); // 如果靠左墙,摄像机向左倾斜5度
Debug.Log("摄像机向左倾斜5度");
}
if (wallRight)
{
cam_02.DoTilt(5f); // 如果靠右墙,摄像机向右倾斜5度
Debug.Log("摄像机向右倾斜5度");
}
}
private void WallRunningMovement()
{
rb.useGravity = useGravity; // 关闭重力,以免影响墙体运动
Vector3 wallNormal = wallRight ? rightWallHit.normal : leftWallHit.normal; // 获取墙体法线向量
Vector3 wallForward = Vector3.Cross(wallNormal, transform.up); // 获取墙体的前方向量
if ((orientation.forward - wallForward).magnitude > (orientation.forward - -wallForward).magnitude)
{
wallForward = -wallForward; // 选择与玩家朝向更一致的前方向量
}
rb.AddForce(wallForward * wallRunForce, ForceMode.Force); // 施加墙体运动的力
if (upwardsRunning)
{
rb.velocity = new Vector3(rb.velocity.x, wallClimbSpeed, rb.velocity.z); // 向上墙体运动
}
if (downwardsRunning)
{
rb.velocity = new Vector3(rb.velocity.x, -wallClimbSpeed, rb.velocity.z); // 向下墙体运动
}
if (!(wallLeft && h > 0) && !(wallRight && h < 0))
{
rb.AddForce(-wallNormal * 100, ForceMode.Force); // 防止水平移动
}
if (useGravity)
{
rb.AddForce(transform.up * gravityCounterForce, ForceMode.Force);
}
}
private void StopWallRun()
{
pm_04.wallrunning = false; // 设置玩家停止墙体运动
cam_02.DoFov(80f); // 调用摄像机的方法,恢复视野角度为80度
cam_02.DoTilt(0f); // 调用摄像机的方法,恢复倾斜角度为0度
}
private void WallJump()
{
exitingWall = true; // 设置正在进行墙体跳跃,触发退出墙体运动的逻辑
exitWallTimer = exitWallTime; // 设置退出墙体的计时器
Vector3 wallNormal = wallRight ? rightWallHit.normal : leftWallHit.normal; // 获取墙体的法线方向
// 计算施加到玩家身上的力,包括向上的力和沿墙的侧向力
Vector3 forceToApply = transform.up * wallJumpUpForce + wallNormal * wallJumpSideForce;
rb.velocity = new Vector3(rb.velocity.x, 0f, rb.velocity.z); // 将垂直速度置为0,确保在墙体上的水平移动
rb.AddForce(forceToApply, ForceMode.Impulse); // 应用力到刚体,实现墙体跳跃效果
}
}