【unity小技巧】unity最完美的CharacterController 3d角色控制器,实现移动、跳跃、下蹲、奔跑、上下坡、物理碰撞效果,复制粘贴即用
本文详细介绍了在Unity中使用CharacterController实现角色移动的完整方案。对比了三种移动方式(Rigidbody、Transform.Translate和CharacterController)的优缺点,重点讲解了CharacterController的Move方法实现,包括移动、跳跃、下蹲、奔跑等功能。文章提供了两种地面检测方案(CharacterController.isG
目录
前言
其实一开始我是不打算写的,我感觉这种简单的功能,网上应该随便一搜一大堆,但是实际去搜会发现网上很多都是复制粘贴
,要么没有实操过
,要么就是功能不全
,或者毫无解释的把代码丢出来,所以特地花时间去研究了研究,下面是记录我的一些思路过程,希望对你有帮助。
为什么使用CharacterController
Unity中常用的三种角色移动方式如下:
-
刚体(Rigidbody)组件 为角色对象添加刚体组件后,可通过施加力(Force)或设置速度(Velocity)实现移动。此方法需要获取输入设备(如键盘、手柄)的输入值,转换为相应的力或速度作用于刚体。刚体自带重力与物理效果,但需额外处理斜坡、楼梯等特殊地形(后续会专门探讨Rigidbody的人物控制技巧)。
-
Transform组件的Translate方法 直接调用Transform组件的Translate方法进行位移。通过输入设备获取移动输入,计算移动方向和距离后执行位移。该方法不受物理引擎控制,缺乏重力与碰撞检测功能,可能导致穿墙等问题,因此实际开发中较少采用。
-
Character Controller组件 为角色添加Character Controller组件后,使用其Move方法实现移动。将输入设备的输入转换为移动向量后传递给Move方法即可。该组件已内置斜坡和楼梯处理方案,解决了刚体的地形适应问题。虽然不适用于复杂物理交互(如推动物体),但可通过直接施加作用力解决(文末将提供具体实现方案)。
SimpleMove和Move如何选择?
而对于CharacterController有用两种移动实现方式,SimpleMove和Move。
1. SimpleMove
- 不受Y轴速度影响,自带重力效果,
无法实现跳跃
功能 - 返回值为Bool,当角色接触地面返回True,反之为False。
- SimpleMove方法是CharacterController组件提供的一个用于处理角色平面移动的简化方法,它自动处理了角色与地面的碰撞* 检测和摩擦,但它不支持跳跃等垂直方向的动作
2. Move
- 无重力效果,自行实现重力,
可做跳跃
功能 - 返回值(CollisionFlags对象),返回角色与物体碰撞的信息
可以看到SimpleMove看着虽好,但是最大的痛点是无法实现跳跃
,所以我们只能忍痛Pass掉
配置CharacterController参数
新增一个胶囊体,代表角色,在角色身上新增CharacterController组件,参数配置如下
控制相机
将相机拖入为玩家的子物体,放置在角色的头部位置,修改
新增MouseLook脚本,挂载在相机上,控制相机视角
public class MouseLook : MonoBehaviour
{
// 鼠标灵敏度
public float mouseSensitivity = 1000f;
// 玩家的身体Transform组件,用于旋转
public Transform playerBody;
// x轴的旋转角度
float xRotation = 0f;
void Start()
{
// 锁定光标到屏幕中心,并隐藏光标
Cursor.lockState = CursorLockMode.Locked;
}
// Update在每一帧调用
void Update()
{
// 执行自由视角查看功能
FreeLook();
}
// 自由视角查看功能的实现
void FreeLook()
{
// 获取鼠标X轴和Y轴的移动量,乘以灵敏度和时间,得到平滑的移动速率
float mouseX = Input.GetAxis("Mouse X") * mouseSensitivity * Time.deltaTime;
float mouseY = Input.GetAxis("Mouse Y") * mouseSensitivity * Time.deltaTime;
//限制旋转角度在-90到90度之间,防止过度翻转
xRotation = Mathf.Clamp(xRotation, -90f, 90f);
// 累计x轴上的旋转量
xRotation -= mouseY;
// 应用摄像头的x轴旋转
transform.localRotation = Quaternion.Euler(xRotation, 0f, 0f);
// 应用玩家身体的y轴旋转
playerBody.Rotate(Vector3.up * mouseX);
}
}
效果
移动
经过上面的分享,我们使用Move实现一下人物的移动
新增MovementScript脚本,挂载在角色身上
public class MovementScript : MonoBehaviour
{
[Tooltip("角色控制器")] public CharacterController characterController;
private float horizontal;
private float vertical;
[Header("移动")]
[Tooltip("角色行走的速度")] public float walkSpeed = 6f;
[Tooltip("当前速度")] private float speed;
[Tooltip("角色移动的方向")] private Vector3 moveDirection;
void Start()
{
speed = walkSpeed;
}
void Update()
{
horizontal = Input.GetAxis("Horizontal");
vertical = Input.GetAxis("Vertical");
moveDirection = transform.right * horizontal + transform.forward * vertical; // 计算移动方向
//将该向量从局部坐标系转换为世界坐标系,得到最终的移动方向,效果和上面的一样
// moveDirection = transform.TransformDirection(new Vector3(h, 0, v));
moveDirection = moveDirection.normalized; // 归一化移动方向,避免斜向移动速度过快
characterController.Move(moveDirection * Time.deltaTime * speed);
}
}
效果
跳跃
方式一
补充:使用 CharaterController.IsGrounded 进行地面检测更为简便。不过这种方法存在一个缺点:在下坡时可能出现检测异常,导致无法跳跃(也可能是我的使用方法不当)。如果这个影响对你来说可以接受,可以考虑采用此方法,毕竟它的实现确实非常简单。
void Update(){
//地面检测
isGround = characterController.isGrounded;
SetJump();
}
//控制跳跃
void SetJump()
{
bool jump = Input.GetButtonDown("Jump");
if (isGround)
{
// 在着地时阻止垂直速度无限下降
if (_verticalVelocity < 0.0f)
{
_verticalVelocity = -2f;
}
if (jump)
{
_verticalVelocity = jumpHeight;
}
}
else
{
//随时间施加重力
_verticalVelocity += Gravity * Time.deltaTime;
}
characterController.Move(moveDirection * speed * Time.deltaTime + new Vector3(0.0f, _verticalVelocity, 0.0f) * Time.deltaTime);
}
方式二
地面检测我们就用圆形球体把,因为人物是胶囊体,这种方法最合适
[Header("地面检测")]
[Tooltip("地面检测位置")] public Transform groundCheck;
[Tooltip("地面检测半径")] public float sphereRadius = 0.5f;
[Tooltip("是否在地面")] private bool isGround;
[Header("跳跃")]
[Tooltip("角色跳跃的高度")] public float jumpHeight = 1.5f;
[Tooltip("判断是否在跳跃")] private bool isJumping;
private float _verticalVelocity;
void Update()
{
//地面检测
isGround = IsGrounded();
SetJump();
}
//控制跳跃
void SetJump()
{
bool jump = Input.GetButtonDown("Jump");
if (isGround)
{
isJumping = false;
// 在着地时阻止垂直速度无限下降
if (_verticalVelocity < 0.0f)
{
_verticalVelocity = -2f;
}
// 跳跃
if (jump)
{
// H * -2 * G 的平方根 = 达到期望高度所需的速度
_verticalVelocity = Mathf.Sqrt(jumpHeight * -2f * Gravity);
}
}
else
{
isJumping = true;
}
// 随时间施加重力
_verticalVelocity += Gravity * Time.deltaTime;
}
//是否在地面
bool IsGrounded()
{
Collider[] colliders = Physics.OverlapSphere(groundCheck.position, sphereRadius);
foreach (Collider collider in colliders)
{
if (collider.gameObject != gameObject && !IsChildOf(collider.transform, transform)) // 忽略角色自身和所有子集碰撞体
{
return true;
}
}
return false;
}
//判断child是否是parent的子集
bool IsChildOf(Transform child, Transform parent)
{
while (child != null)
{
if (child == parent)
{
return true;
}
child = child.parent;
}
return false;
}
//在场景视图显示检测,方便调试
private void OnDrawGizmos()
{
Gizmos.color = Color.red;
//地面检测可视化
Gizmos.DrawWireSphere(groundCheck.position, sphereRadius);
}
配置地面检测点位置
效果
下蹲
下蹲的实现原理是让CharacterController的高度和中心点位置同时减半,同时调整摄像机高度。需要注意的是,当角色头顶有障碍物时,应禁止站立动作以避免穿模问题。为此需要使用盒子碰撞检测来覆盖整个头部区域进行检测。
[Header("相机")]
[Tooltip("摄像机相机")] public Transform mainCamera;
[Tooltip("摄像机高度变化的平滑值")] public float interpolationSpeed = 10f;
[Tooltip("当前摄像机的位置")] private Vector3 cameraLocalPosition;
[Tooltip("当前摄像机的高度")] private float height;
[Header("头顶检测")]
[Tooltip("头顶检测位置")] public Transform headCheck;
[Tooltip("盒子半长、半宽、半高")] public Vector3 halfExtents = new Vector3(0.4f, 0.5f, 0.4f);
[Tooltip("判断玩家是否可以站立")] private bool isCanStand;
[Header("下蹲")]
[Tooltip("下蹲时候的玩家高度")] private float crouchHeight;
[Tooltip("判断玩家是否在下蹲")] private bool isCrouching;
[Tooltip("正常站立时玩家高度")] private float standHeight;
void Start()
{
standHeight = characterController.height;
crouchHeight = standHeight / 2;
cameraLocalPosition = mainCamera.localPosition;
speed = walkSpeed;
}
void Update()
{
//头顶检测
isCanStand = CanStand();
SetCrouch();
}
//控制下蹲
void SetCrouch()
{
if (Input.GetKey(KeyCode.LeftControl))
{
Crouch(true);
}
else
{
Crouch(false);
}
}
//newCrouching控制下蹲起立
public void Crouch(bool newCrouching)
{
if (!newCrouching && !isCanStand) return; //准备起立时且头顶有东西,不能进行站立
isCrouching = newCrouching;
float targetHeight = isCrouching ? crouchHeight : standHeight;
characterController.height = targetHeight; //根据下蹲状态设置下蹲时候的高度和站立的高度
characterController.center = new Vector3(0, targetHeight / 2, 0); //将角色控制器的中心位置Y,从头顶往下减少1半的高度
// 设置下蹲站立时候的摄像机高度
float heightTarget = isCrouching ? cameraLocalPosition.y / 2 + characterController.center.y : cameraLocalPosition.y;
height = Mathf.Lerp(height, heightTarget, interpolationSpeed * Time.deltaTime);//平滑过渡
mainCamera.localPosition = new Vector3(cameraLocalPosition.x, height, cameraLocalPosition.z);
}
//是否可以起立,及头顶是否有物品
bool CanStand()
{
Collider[] colliders = Physics.OverlapBox(headCheck.position, halfExtents);
foreach (Collider collider in colliders)
{
//忽略角色自身和所有子集碰撞体
if (collider.gameObject != gameObject && !IsChildOf(collider.transform, transform))
{
return false;
}
}
return true;
}
//在场景视图显示检测,方便调试
private void OnDrawGizmos()
{
Gizmos.color = Color.red;
//头顶检测可视化
Gizmos.DrawWireCube(headCheck.position, halfExtents * 2f);
}
配置头顶检测
效果
处理下坡抖动问题
在下坡移动时,角色可能会出现抖动现象。虽然抖动本身是正常物理现象,但会影响地面检测的准确性,导致角色下坡时无法正常跳跃,这显然不符合预期。解决方案是:当检测到角色处于斜面时,施加一个向下的压力来增加角色与地面的接触稳定性。具体实现方法是:从角色位置向下发射射线,通过检测射线与地面法线的夹角,若夹角不等于90度即可判定为斜面状态。
[Header("斜坡检测")]
[Tooltip("斜坡射线长度")] public float slopeForceRayLength = 0.2f;
[Tooltip("是否在斜坡")] private bool isSlope;
[Header("斜坡")]
[Tooltip("走斜坡时向下施加的力度")] public float slopeForce = 6.0f;
void Update()
{
//斜坡检测
isSlope = OnSlope();
SetJump();
}
//控制跳跃
void SetJump()
{
//。。。
//为了不影响跳跃,一定要在isJumping = false之前加力
SetSlope();
isJumping = false;
}
//控制斜坡
public void SetSlope()
{
//如果处于斜坡
if (isSlope && !isJumping)
{
//向下增加力
moveDirection.y = characterController.height / 2 * slopeForceRayLength;
characterController.Move(Vector3.down * characterController.height / 2 * slopeForce * Time.deltaTime);
}
}
//是否在斜面
public bool OnSlope()
{
RaycastHit hit;
// 向下打出射线(检测是否在斜坡上)
if (Physics.Raycast(transform.position + characterController.height / 2 * Vector3.down, Vector3.down, out hit, characterController.height / 2 * slopeForceRayLength))
{
// 如果接触到的点的法线,不在(0,1,0)的方向上,那么人物就在斜坡上
if (hit.normal != Vector3.up)
return true;
}
return false;
}
//在场景视图显示检测,方便调试
private void OnDrawGizmos()
{
Gizmos.color = Color.red;
//斜坡检测可视化
Debug.DrawRay(transform.position + characterController.height / 2 * Vector3.down, Vector3.down * characterController.height / 2 * slopeForceRayLength, Color.blue);
}
斜坡检测线
效果
实现奔跑和不同移速控制
[Header("移动")]
[Tooltip("角色行走的速度")] public float walkSpeed = 6f;
[Tooltip("角色奔跑的速度")] public float runSpeed = 9f;
[Tooltip("角色下蹲的速度")] public float crouchSpeed = 3f;
[Tooltip("角色移动的方向")] private Vector3 moveDirection;
[Tooltip("当前速度")] private float speed;
[Tooltip("是否奔跑")] private bool isRun;
//速度设置
void SetSpeed()
{
if (isRun)
{
speed = runSpeed;
}
else if (isCrouching)
{
speed = crouchSpeed;
}
else
{
speed = walkSpeed;
}
}
//控制奔跑
void SetRun()
{
if (Input.GetKey(KeyCode.LeftShift) && !isCrouching)
{
isRun = true;
}
else
{
isRun = false;
}
}
效果
完整代码
注意
:代码的参数都是经过我测试过的,复制即可使用,并不推荐大家修改,除非你知道自己在干什么
[RequireComponent(typeof(CharacterController))]
public class MovementScript : MonoBehaviour
{
[Tooltip("角色控制器")] public CharacterController characterController;
[Tooltip("重力加速度")] private float Gravity = 9.8f;
private float horizontal;
private float vertical;
[Header("相机")]
[Tooltip("摄像机相机")] public Transform mainCamera;
[Tooltip("摄像机高度变化的平滑值")] public float interpolationSpeed = 10f;
[Tooltip("当前摄像机的位置")] private Vector3 cameraLocalPosition;
[Tooltip("当前摄像机的高度")] private float height;
[Header("移动")]
[Tooltip("角色行走的速度")] public float walkSpeed = 6f;
[Tooltip("角色奔跑的速度")] public float runSpeed = 9f;
[Tooltip("角色下蹲的速度")] public float crouchSpeed = 3f;
[Tooltip("角色移动的方向")] private Vector3 moveDirection;
[Tooltip("当前速度")] private float speed;
[Tooltip("是否奔跑")] private bool isRun;
[Header("地面检测")]
[Tooltip("地面检测位置")] public Transform groundCheck;
[Tooltip("地面检测半径")] public float sphereRadius = 0.4f;
[Tooltip("是否在地面")] private bool isGround;
[Header("头顶检测")]
[Tooltip("头顶检测位置")] public Transform headCheck;
[Tooltip("盒子半长、半宽、半高")] public Vector3 halfExtents = new Vector3(0.4f, 0.5f, 0.4f);
[Tooltip("判断玩家是否可以站立")] private bool isCanStand;
[Header("斜坡检测")]
[Tooltip("斜坡射线长度")] public float slopeForceRayLength = 0.2f;
[Tooltip("是否在斜坡")] private bool isSlope;
[Header("跳跃")]
[Tooltip("角色跳跃的高度")] public float jumpHeight = 2.5f;
[Tooltip("判断是否在跳跃")] private bool isJumping;
[Header("下蹲")]
[Tooltip("下蹲时候的玩家高度")] private float crouchHeight;
[Tooltip("判断玩家是否在下蹲")] private bool isCrouching;
[Tooltip("正常站立时玩家高度")] private float standHeight;
[Header("斜坡")]
[Tooltip("走斜坡时施加的力度")] public float slopeForce = 6.0f;
void Start()
{
standHeight = characterController.height;
crouchHeight = standHeight / 2;
cameraLocalPosition = mainCamera.localPosition;
speed = walkSpeed;
}
void Update()
{
horizontal = Input.GetAxis("Horizontal");
vertical = Input.GetAxis("Vertical");
//地面检测
isGround = IsGrounded();
//头顶检测
isCanStand = CanStand();
//斜坡检测
isSlope = OnSlope();
SetSpeed();
SetRun();
SetCrouch();
SetMove();
SetJump();
}
//速度设置
void SetSpeed()
{
if (isRun)
{
speed = runSpeed;
}
else if (isCrouching)
{
speed = crouchSpeed;
}
else
{
speed = walkSpeed;
}
}
//控制奔跑
void SetRun()
{
if (Input.GetKey(KeyCode.LeftShift) && !isCrouching)
{
isRun = true;
}
else
{
isRun = false;
}
}
//控制下蹲
void SetCrouch()
{
if (Input.GetKey(KeyCode.LeftControl))
{
Crouch(true);
}
else
{
Crouch(false);
}
}
//控制移动
void SetMove()
{
if (isGround)
{
moveDirection = transform.right * horizontal + transform.forward * vertical; // 计算移动方向
//将该向量从局部坐标系转换为世界坐标系,得到最终的移动方向
// moveDirection = transform.TransformDirection(new Vector3(h, 0, v));
moveDirection = moveDirection.normalized; // 归一化移动方向,避免斜向移动速度过快
}
}
//控制跳跃
void SetJump()
{
if (Input.GetButtonDown("Jump") && isGround)
{
isJumping = true;
moveDirection.y = jumpHeight;
}
moveDirection.y -= Gravity * Time.deltaTime;
characterController.Move(moveDirection * Time.deltaTime * speed);
//为了不影响跳跃,一定要在isJumping = false之前加力
SetSlope();
isJumping = false;
}
//控制斜坡
public void SetSlope()
{
//如果处于斜坡
if (isSlope && !isJumping)
{
//向下增加力
moveDirection.y = characterController.height / 2 * slopeForceRayLength;
characterController.Move(Vector3.down * characterController.height / 2 * slopeForce * Time.deltaTime);
}
}
//newCrouching控制下蹲起立
public void Crouch(bool newCrouching)
{
if (!newCrouching && !isCanStand) return; //准备起立时且头顶有东西,不能进行站立
isCrouching = newCrouching;
float targetHeight = isCrouching ? crouchHeight : standHeight;
float heightChange = targetHeight - characterController.height; //计算高度变化
characterController.height = targetHeight; //根据下蹲状态设置下蹲时候的高度和站立的高度
characterController.center += new Vector3(0, heightChange / 2, 0); //根据高度变化调整中心位置
// 设置下蹲站立时候的摄像机高度
float heightTarget = isCrouching ? cameraLocalPosition.y / 2 + characterController.center.y : cameraLocalPosition.y;
height = Mathf.Lerp(height, heightTarget, interpolationSpeed * Time.deltaTime);
mainCamera.localPosition = new Vector3(cameraLocalPosition.x, height, cameraLocalPosition.z);
}
//是否可以起立,及头顶是否有物品
bool CanStand()
{
Collider[] colliders = Physics.OverlapBox(headCheck.position, halfExtents);
foreach (Collider collider in colliders)
{
//忽略角色自身和所有子集碰撞体
if (collider.gameObject != gameObject && !IsChildOf(collider.transform, transform))
{
return false;
}
}
return true;
}
//是否在地面
bool IsGrounded()
{
Collider[] colliders = Physics.OverlapSphere(groundCheck.position, sphereRadius);
foreach (Collider collider in colliders)
{
if (collider.gameObject != gameObject && !IsChildOf(collider.transform, transform)) // 忽略角色自身和所有子集碰撞体
{
return true;
}
}
return false;
}
//是否在斜面
public bool OnSlope()
{
RaycastHit hit;
// 向下打出射线(检测是否在斜坡上)
if (Physics.Raycast(transform.position + characterController.height / 2 * Vector3.down, Vector3.down, out hit, characterController.height / 2 * slopeForceRayLength))
{
// 如果接触到的点的法线,不在(0,1,0)的方向上,那么人物就在斜坡上
if (hit.normal != Vector3.up)
return true;
}
return false;
}
//判断child是否是parent的子集
bool IsChildOf(Transform child, Transform parent)
{
while (child != null)
{
if (child == parent)
{
return true;
}
child = child.parent;
}
return false;
}
//在场景视图显示检测,方便调试
private void OnDrawGizmos()
{
Gizmos.color = Color.red;
//头顶检测可视化
Gizmos.DrawWireCube(headCheck.position, halfExtents * 2f);
//地面检测可视化
Gizmos.DrawWireSphere(groundCheck.position, sphereRadius);
//斜坡检测可视化
Debug.DrawRay(transform.position + characterController.height / 2 * Vector3.down, Vector3.down * characterController.height / 2 * slopeForceRayLength, Color.blue);
}
}
补充,简单版本
using UnityEngine;
[RequireComponent(typeof(CharacterController))]
public class MovementScript : MonoBehaviour
{
[Tooltip("角色控制器")] public CharacterController characterController;
[Tooltip("重力加速度")] private float Gravity = -19.8f;
private float horizontal;
private float vertical;
[Header("相机")]
[Tooltip("摄像机相机")] public Transform mainCamera;
[Tooltip("摄像机高度变化的平滑值")] public float interpolationSpeed = 10f;
[Tooltip("当前摄像机的位置")] private Vector3 cameraLocalPosition;
[Tooltip("当前摄像机的高度")] private float height;
[Header("移动")]
[Tooltip("角色行走的速度")] public float walkSpeed = 6f;
[Tooltip("角色奔跑的速度")] public float runSpeed = 9f;
[Tooltip("角色下蹲的速度")] public float crouchSpeed = 3f;
[Tooltip("角色移动的方向")] private Vector3 moveDirection;
[Tooltip("当前速度")] private float speed;
[Tooltip("是否奔跑")] private bool isRun;
[Header("地面检测")]
[Tooltip("是否在地面")] private bool isGround;
[Header("头顶检测")]
[Tooltip("头顶检测位置")] public Transform headCheck;
[Tooltip("盒子半长、半宽、半高")] public Vector3 halfExtents = new Vector3(0.4f, 0.5f, 0.4f);
[Tooltip("判断玩家是否可以站立")] private bool isCanStand;
[Header("跳跃")]
[Tooltip("角色跳跃的高度")] public float jumpHeight = 2.5f;
private float _verticalVelocity;
[Header("下蹲")]
[Tooltip("下蹲时候的玩家高度")] private float crouchHeight;
[Tooltip("判断玩家是否在下蹲")] private bool isCrouching;
[Tooltip("正常站立时玩家高度")] private float standHeight;
void Start()
{
standHeight = characterController.height;
crouchHeight = standHeight / 2;
cameraLocalPosition = mainCamera.localPosition;
speed = walkSpeed;
}
void Update()
{
horizontal = Input.GetAxis("Horizontal");
vertical = Input.GetAxis("Vertical");
//地面检测
isGround = characterController.isGrounded;
//头顶检测
isCanStand = CanStand();
SetSpeed();
SetRun();
SetCrouch();
SetMove();
SetJump();
}
//速度设置
void SetSpeed()
{
if (isRun)
{
speed = runSpeed;
}
else if (isCrouching)
{
speed = crouchSpeed;
}
else
{
speed = walkSpeed;
}
}
//控制奔跑
void SetRun()
{
if (Input.GetKey(KeyCode.LeftShift) && !isCrouching)
{
isRun = true;
}
else
{
isRun = false;
}
}
//控制下蹲
void SetCrouch()
{
if (Input.GetKey(KeyCode.LeftControl))
{
Crouch(true);
}
else
{
Crouch(false);
}
}
//控制移动
void SetMove()
{
moveDirection = transform.right * horizontal + transform.forward * vertical; // 计算移动方向
//将该向量从局部坐标系转换为世界坐标系,得到最终的移动方向
// moveDirection = transform.TransformDirection(new Vector3(h, 0, v));
moveDirection = moveDirection.normalized; // 归一化移动方向,避免斜向移动速度过快
}
//控制跳跃
void SetJump()
{
bool jump = Input.GetButtonDown("Jump");
if (isGround)
{
// 在着地时阻止垂直速度无限下降
if (_verticalVelocity < 0.0f)
{
_verticalVelocity = -2f;
}
if (jump)
{
_verticalVelocity = jumpHeight;
}
}
else
{
//随时间施加重力
_verticalVelocity += Gravity * Time.deltaTime;
}
characterController.Move(moveDirection * speed * Time.deltaTime + new Vector3(0.0f, _verticalVelocity, 0.0f) * Time.deltaTime);
}
//newCrouching控制下蹲起立
public void Crouch(bool newCrouching)
{
if (!newCrouching && !isCanStand) return; //准备起立时且头顶有东西,不能进行站立
isCrouching = newCrouching;
float targetHeight = isCrouching ? crouchHeight : standHeight;
float heightChange = targetHeight - characterController.height; //计算高度变化
characterController.height = targetHeight; //根据下蹲状态设置下蹲时候的高度和站立的高度
characterController.center += new Vector3(0, heightChange / 2, 0); //根据高度变化调整中心位置
// 设置下蹲站立时候的摄像机高度
float heightTarget = isCrouching ? cameraLocalPosition.y / 2 + characterController.center.y : cameraLocalPosition.y;
height = Mathf.Lerp(height, heightTarget, interpolationSpeed * Time.deltaTime);
mainCamera.localPosition = new Vector3(cameraLocalPosition.x, height, cameraLocalPosition.z);
}
//是否可以起立,及头顶是否有物品
bool CanStand()
{
Collider[] colliders = Physics.OverlapBox(headCheck.position, halfExtents);
foreach (Collider collider in colliders)
{
//忽略角色自身和所有子集碰撞体
if (collider.gameObject != gameObject && !IsChildOf(collider.transform, transform))
{
return false;
}
}
return true;
}
//判断child是否是parent的子集
bool IsChildOf(Transform child, Transform parent)
{
while (child != null)
{
if (child == parent)
{
return true;
}
child = child.parent;
}
return false;
}
//在场景视图显示检测,方便调试
private void OnDrawGizmos()
{
Gizmos.color = Color.red;
//头顶检测可视化
Gizmos.DrawWireCube(headCheck.position, halfExtents * 2f);
}
}
实现物理碰撞效果(2025/08/06补充)
这段代码的作用是在角色控制器与其他碰撞体发生碰撞时,给碰撞体施加一个基于角色控制器速度的冲量,以模拟碰撞的物理效果。
private CollisionFlags m_CollisionFlags; // 碰撞标记
m_CollisionFlags = characterController.Move(...); // 移动角色控制器
private void OnControllerColliderHit(ControllerColliderHit hit)
{
Debug.Log("与其他碰撞体发生碰撞");
//获取碰撞体上的 Rigidbody 组件。
Rigidbody body = hit.collider.attachedRigidbody;
//CollisionFlags.Below 表示角色控制器与碰撞体之间是底部接触
if (m_CollisionFlags == CollisionFlags.Below)
{
return;
}
//然后,再次检查获取到的 Rigidbody 是否为空或者是否为静态物体(isKinematic),如果是,则同样不进行任何处理,直接返回。
if (body == null || body.isKinematic)
{
return;
}
body.AddForceAtPosition(characterController.velocity * 0.1f, hit.point, ForceMode.Impulse); // 在碰撞点施加冲量
}
更多推荐
所有评论(0)