csharp
复制代码
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class PlayerMovement_05 : MonoBehaviour
{
private float moveSpeed; // 玩家移动速度
public float walkSpeed = 7; // 行走速度
public float sprintSpeed = 10; // 冲刺速度
public float slideSpeed = 30; // 滑动速度
public float wallrunSpeed = 8.5f;
public float climbSpeed = 3;
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
public 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 Climbing climbingScript;
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,//墙跑
climbing,
crouching, // 蹲伏
sliding, // 滑动
air // 空中
}
public bool sliding; // 是否正在滑动
public bool wallrunning;
public bool climbing;
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()
{
if (climbingScript.exitingWall) return;
// 根据朝向计算移动方向
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 (climbing)
{
state = MovementState.climbing;
desiredMoveSpeed = climbSpeed;
}
else if (wallrunning)
{
state = MovementState.wallrunning;
desiredMoveSpeed = wallrunSpeed;
}
else 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; // 最终将移动速度设置为期望移动速度
}
}
csharp
复制代码
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Climbing : MonoBehaviour
{
public Transform orientation; // 角色朝向
public Rigidbody rb; // 角色刚体
public PlayerMovement_05 pm_05; // 角色基本运动脚本
public LayerMask whatIsWall; // 定义哪些是墙的图层
public float climbSpeed = 10; // 攀爬速度
public float maxClimbTime = 0.75f; // 最大攀爬时间
private float climbTimer; // 攀爬计时器
private bool climbing; // 是否正在攀爬
public float climbJumpUpForce = 14; // 攀爬跳跃向上的力
public float climbJumpBackForce = 12; // 攀爬跳跃向后的力
public KeyCode jumpKey = KeyCode.Space; // 跳跃键
public int climbJumps = 1; // 攀爬跳跃次数
private int climbJumpsLeft; // 剩余可用的攀爬跳跃次数
public float detectionLength = 0.7f; // 检测墙的射线长度
public float sphereCastRadius = 0.25f; // 射线球的半径
public float maxWallLookAngle = 30; // 最大墙面角度
private float wallLookAngle; // 当前墙面角度
private RaycastHit frontWallHit; // 射线检测到的前方墙面信息
private bool wallFront; // 是否在墙面前方
private Transform lastWall; // 上一次攀爬的墙面
private Vector3 lastWallNormal; // 上一次攀爬的墙面法线方向
public float minWallNormalAngleChange = 5; // 最小墙面法线方向变化角度
public bool exitingWall; // 是否正在退出墙面
public float exitWallTime = 0.2f; // 退出墙面的时间
private float exitWallTimer; // 退出墙面计时器
private void Update()
{
WallCheck();
StateMachine();
if (climbing && !exitingWall)
{
ClimbingMovement();
}
}
private void StateMachine()
{
// 检查是否在墙面前方,按下攀爬键,墙面角度小于最大角度,且正在退出墙面
if (wallFront && Input.GetKey(KeyCode.W) && wallLookAngle < maxWallLookAngle && !exitingWall)
{
// 如果尚未开始攀爬且攀爬时间尚未用完
if (!climbing && climbTimer > 0)
{
Debug.Log("开始攀爬");
StartClimbing(); // 开始攀爬
}
// 如果攀爬时间尚未用完
if (climbTimer > 0)
{
climbTimer -= Time.deltaTime;
}
// 如果攀爬时间用完
if (climbTimer < 0)
{
Debug.Log("攀爬时间用完,停止攀爬!");
StopClimbing(); // 停止攀爬
}
}
// 如果正在退出墙面
else if (exitingWall)
{
// 如果正在攀爬
if (climbing)
{
StopClimbing(); // 停止攀爬
}
// 如果退出墙面计时尚未用完
if (exitWallTimer > 0)
exitWallTimer -= Time.deltaTime;
// 如果退出墙面计时用完
if (exitWallTimer < 0)
exitingWall = false;
}
else
{
// 如果正在攀爬
if (climbing)
{
StopClimbing(); // 停止攀爬
}
}
// 如果在墙面前方,按下跳跃键,且剩余可用的攀爬跳跃次数大于0
if (wallFront && Input.GetKeyDown(jumpKey) && climbJumpsLeft > 0)
{
Debug.Log("进行攀爬跳跃");
ClimbJump(); // 进行攀爬跳跃
}
}
private void WallCheck()
{
// 发出前方射线,检测是否有墙
wallFront = Physics.SphereCast(transform.position, sphereCastRadius, orientation.forward, out frontWallHit, detectionLength, whatIsWall);
// 计算墙面角度
wallLookAngle = Vector3.Angle(orientation.forward, -frontWallHit.normal);
// 判断是否是新的墙面,或者墙面法线方向变化大于最小变化角度,或者角色在地面上
bool newWall = frontWallHit.transform != lastWall || Mathf.Abs(Vector3.Angle(lastWallNormal, frontWallHit.normal)) > minWallNormalAngleChange;
// 如果在墙面前方且是新的墙面
if ((wallFront && newWall) || pm_05.grounded)
{
climbTimer = maxClimbTime; // 重置攀爬时间
climbJumpsLeft = climbJumps; // 重置可用的攀爬跳跃次数
}
}
private void StartClimbing()
{
climbing = true; // 设置正在攀爬
pm_05.climbing = true; // 触发攀爬状态
lastWall = frontWallHit.transform; // 记录上一次攀爬的墙面
lastWallNormal = frontWallHit.normal; // 记录上一次攀爬的墙面法线方向
}
private void ClimbingMovement()
{
rb.velocity = new Vector3(rb.velocity.x, climbSpeed, rb.velocity.z); // 应用攀爬速度
}
private void StopClimbing()
{
climbing = false; // 设置停止攀爬
pm_05.climbing = false; // 触发停止攀爬状态
}
private void ClimbJump()
{
exitingWall = true; // 设置正在退出墙面
exitWallTimer = exitWallTime; // 重置退出墙面计时器
// 计算施加到角色身上的力,包括向上的力和沿墙的后退力
Vector3 forceToApply = transform.up * climbJumpUpForce + frontWallHit.normal * climbJumpBackForce;
rb.velocity = new Vector3(rb.velocity.x, 0f, rb.velocity.z); // 将垂直速度置为0,确保在墙面上的水平移动
rb.AddForce(forceToApply, ForceMode.Impulse); // 应用力到刚体,实现攀爬跳跃效果
climbJumpsLeft--; // 减少可用的攀爬跳跃次数
}
}