前言
其实一开始我是不打算写的,我感觉这种简单的功能,网上应该随便一搜一大堆,但是实际去搜会发现网上很多都是复制粘贴
,要么没有实操过
,要么就是功能不全
,或者毫无解释的把代码丢出来,所以特地花时间去研究了研究,下面是记录我的一些思路过程,希望对你有帮助。
其实之前实战有做过FPS移动控制,只是没有说的很全面,感兴趣可以查看之前的文章:
【用unity实现100个游戏之18】从零开始制作一个类CSGO/CS2、CF第一人称FPS射击游戏——基础篇1
为什么使用CharacterController
Unity中常用的三种角色移动方式如下:
- 使用刚体(Rigidbody)组件:这种方式将角色对象添加刚体组件,并通过力(Force)或者速度(Velocity)来控制移动。你可以使用输入控制器(例如键盘、手柄)获取移动输入,然后将对应的力或速度施加给角色刚体,从而实现移动。
- 使用Transform组件的Translate方法:这种方式直接使用Transform组件的Translate方法来移动角色。你可以通过获取输入控制器的输入,计算出移动方向和距离,然后调用Translate方法将角色移动到指定位置。
- 使用Character Controller组件:这种方式需要将角色对象添加Character Controller组件,并使用Character Controller提供的Move方法来移动角色。你可以通过输入控制器获取移动输入,然后将输入转换为移动向量,并传递给Character Controller的Move方法来实现移动。
刚体自带重力和物理效果,但是对于爬坡,走楼梯要单独处理,比较麻烦。(ps:当然,后面有机会我在研究Rigidbody如何控制人物,可以关注期待一下
)
Transform呢,不带重力又不带碰撞,移动不受物理引擎控制,可能导致穿透墙壁或其他对象。不支持碰撞和重力等物理效果。所以我是直接pass的
CharacterController主要是不适用于需要处理复杂物理交互的情况,例如推动物体等。但是对于爬坡,楼梯自带了处理方式,完美解决了刚体的痛点,而如果你想推动物体也可以直接给物体一个推力即可解决(文章最后面我分享解决方案
)
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); } }
实现物理碰撞效果(2024/01/02补充)
这段代码的作用是在角色控制器与其他碰撞体发生碰撞时,给碰撞体施加一个基于角色控制器速度的冲量,以模拟碰撞的物理效果。
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); // 在碰撞点施加冲量 }