当前位置: 首页 > news >正文

【unity小技巧】unity最完美的CharacterController 3d角色控制器,实现移动、跳跃、下蹲、奔跑、上下坡、物理碰撞效果,复制粘贴即用

最终效果

在这里插入图片描述

文章目录

  • 最终效果
  • 前言
  • 为什么使用CharacterController
  • SimpleMove和Move如何选择?
    • 1. SimpleMove
    • 2. Move
  • 配置CharacterController参数
  • 控制相机
  • 移动
  • 跳跃
    • 方式一
    • 方式二
  • 下蹲
  • 处理下坡抖动问题
  • 实现奔跑和不同移速控制
  • 完整代码
    • 补充,简单版本
  • 实现物理碰撞效果(2024/01/02补充)
  • 完结

前言

其实一开始我是不打算写的,我感觉这种简单的功能,网上应该随便一搜一大堆,但是实际去搜会发现网上很多都是复制粘贴,要么没有实操过,要么就是功能不全,或者毫无解释的把代码丢出来,所以特地花时间去研究了研究,下面是记录我的一些思路过程,希望对你有帮助。

其实之前实战有做过FPS移动控制,只是没有说的很全面,感兴趣可以查看之前的文章:
【用unity实现100个游戏之18】从零开始制作一个类CSGO/CS2、CF第一人称FPS射击游戏——基础篇1

为什么使用CharacterController

Unity中常用的三种角色移动方式如下:

  1. 使用刚体(Rigidbody)组件:这种方式将角色对象添加刚体组件,并通过力(Force)或者速度(Velocity)来控制移动。你可以使用输入控制器(例如键盘、手柄)获取移动输入,然后将对应的力或速度施加给角色刚体,从而实现移动。

  2. 使用Transform组件的Translate方法:这种方式直接使用Transform组件的Translate方法来移动角色。你可以通过获取输入控制器的输入,计算出移动方向和距离,然后调用Translate方法将角色移动到指定位置。

  3. 使用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;// 累计x轴上的旋转量xRotation -= mouseY;//限制旋转角度在-90到90度之间,防止过度翻转xRotation = Mathf.Clamp(xRotation, -90f, 90f);// 应用摄像头的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); // 在碰撞点施加冲量
}

完结

赠人玫瑰,手有余香!如果文章内容对你有所帮助,请不要吝啬你的点赞评论和关注,以便我第一时间收到反馈,你的每一次支持都是我不断创作的最大动力。当然如果你发现了文章中存在错误或者有更好的解决方法,也欢迎评论私信告诉我哦!

好了,我是向宇,https://xiangyu.blog.csdn.net

一位在小公司默默奋斗的开发者,出于兴趣爱好,最近开始自学unity,闲暇之余,边学习边记录分享,站在巨人的肩膀上,通过学习前辈们的经验总是会给我很多帮助和启发!php是工作,unity是生活!如果你遇到任何问题,也欢迎你评论私信找我, 虽然有些问题我也不一定会,但是我会查阅各方资料,争取给出最好的建议,希望可以帮助更多想学编程的人,共勉~

在这里插入图片描述

相关文章:

【unity小技巧】unity最完美的CharacterController 3d角色控制器,实现移动、跳跃、下蹲、奔跑、上下坡、物理碰撞效果,复制粘贴即用

最终效果 文章目录 最终效果前言为什么使用CharacterControllerSimpleMove和Move如何选择&#xff1f;1. SimpleMove2. Move 配置CharacterController参数控制相机移动跳跃方式一方式二 下蹲处理下坡抖动问题实现奔跑和不同移速控制完整代码补充&#xff0c;简单版本 实现物理碰…...

66 mysql 的 表自增长锁

前言 mysql 的表锁之 AUTO_INC, 是我们自增长的时候做并发控制的锁 主要是用于 自增长生成新的 id 的时候的控制 在前面的文档中, 我们又看到 mysql 这边自增长的处理的相关的大概脉络 但是 对于一些 并发控制的细节, 我们当时 应该是直接忽略掉了 我们这里就来看一下…...

神经网络问题之一:梯度消失(Vanishing Gradient)

梯度消失&#xff08;Vanishing Gradient&#xff09;问题是深度神经网络训练中的一个关键问题&#xff0c;它主要发生在反向传播过程中&#xff0c;导致靠近输入层的权重更新变得非常缓慢甚至几乎停滞&#xff0c;严重影响网络的训练效果和性能。 图1 在深度神经网络中容易出现…...

企业网页设计的安全与数据保护

企业网页设计不仅要考虑美观和功能性&#xff0c;安全与数据保护也是重中之重。在这个信息爆炸的时代&#xff0c;用户的数据隐私和安全问题日益凸显&#xff0c;企业必须采取多种措施来保障用户的信息安全。 首先&#xff0c;**SSL加密**是基础中的基础。通过使用SSL证书&…...

对 TypeScript 中类是怎么理解的?都有哪些应用场景?

在 TypeScript 中&#xff0c;类&#xff08;class&#xff09;是面向对象编程的核心构造之一&#xff0c;它允许你创建具有特定属性和方法的对象模板。TypeScript 的类概念和 JavaScript 中的类基本相同&#xff0c;但它提供了额外的类型检查和静态类型系统&#xff0c;从而增…...

2024“龙信杯“电子数据取证竞赛-服务器取证题目Writeup

服务器检材-分析 前置 提示&#xff1a;该服务器做了登录密码校验配置&#xff0c;如果没有拿到服务器的密码而直接仿真服务器&#xff0c;输入密码进入系统后&#xff0c;服务器会将部分数据给自动删除 前提&#xff1a;无 因为我们仿真进入服务器会自动删除文件&#xff0…...

Label-studio-ml-backend 和YOLOV8 YOLO11自动化标注,目标检测,实例分割,图像分类,关键点估计,视频跟踪

这里写目录标题 1.目标检测 Detection2.实例分割 segment3.图像分类 classify4.关键点估计 Keypoint detection5.视频帧检测 video detect6.视频帧分类 video classify7.旋转目标检测 obb detect8.替换yolo11模型 给我点个赞吧&#xff0c;谢谢了附录coco80类名称 笔记本 华为m…...

Elasticsearch Windows版的安装及启动

一、下载 https://www.elastic.co/cn/downloads/past-releases#elasticsearch 如下图 选择版本 我用的是7.17.5 你换成你需要的版本 二 使用 1.解压 解压完如图 2.启动 进入 bin 文件目录&#xff0c;双击运行 elasticsearch.bat 文件启动 ES 服务 出现报错 Cause…...

解决 VMware 嵌套虚拟化提示 关闭“侧通道缓解“

最近给电脑做了新版的 Windows 11 LTSC操作系统&#xff0c;在启动VMware Workstation时&#xff0c;提示"此虚拟机已启用侧通道缓解&#xff0c;可增强安全性&#xff0c;但也会降低性能"&#xff0c;但是我没有启用 Hyper-V 相关的任何功能以及 WSL&#xff0c; 从…...

基于Redis实现的手机短信登入功能

目录 开发准备 注册阿里短信服务 依赖坐标 阿里短信 依赖 mybatis-plus 依赖 redis 依赖 配置文件 导入数据库表 短信发送工具类 生成随机验证码的工具类 校验合法手机号的工具类 ThreadLocal 线程工具类 消息工具类 基于 session 的短信登录的问题 开发教程 Redis 结构设计 …...

C# NetworkStream用法

一、注意事项&#xff1a; NetworkStream 是稳定的&#xff0c;面向连接的&#xff0c;所以它只适合 TCP 协议的环境下工作所以一旦在 UDP环境中&#xff0c;虽然编译不会报错&#xff0c;但是会跳出异常。如果用构造产生NetworkStream的实例&#xff0c;则必须使用连接的Socke…...

华三预赛从零开始学习笔记(每日编辑,复习完为止)

知识点分布 路由交换技术基础 计算机网络基本概念 计算机网络基本概念&#xff1a; 很多电脑和设备通过电线或无线信号连在一起&#xff0c;可以互相“说话”和“分享东西” 网络的主要形式和发展历程&#xff1a; 诞生阶段-最早的计算机网络是以单个计算机为中心的联机系统-终…...

MySQL基础大全(看这一篇足够!!!)

文章目录 前言一、初识MySQL1.1 数据库基础1.2 数据库技术构成1.2.1 数据库系统1.2.2 SQL语言1.2.3 数据库访问接口 1.3 什么是MySQL 二、数据库的基本操作2.1 数据库创建和删除2.2 数据库存储引擎2.2.1 MySQL存储引擎简介2.2.2 InnoDB存储引擎2.2.3 MyISAM存储引擎2.2.4 存储引…...

[ 应急响应进阶篇-2 ] Linux创建后门并进行应急处置-1:超级用户帐号后门

&#x1f36c; 博主介绍 &#x1f468;‍&#x1f393; 博主介绍&#xff1a;大家好&#xff0c;我是 _PowerShell &#xff0c;很高兴认识大家~ ✨主攻领域&#xff1a;【渗透领域】【数据通信】 【通讯安全】 【web安全】【面试分析】 &#x1f389;点赞➕评论➕收藏 养成习…...

【无人机/平衡车/机器人】详解STM32+MPU6050姿态解算—卡尔曼滤波+四元数法+互补滤波

详解STM32+MPU6050姿态解算—卡尔曼滤波+四元数法+互补滤波 效果: 更多单片机项目,单片机项目合集列表目录与专栏说明: 单片机项目合集列表与专栏说明——Excel合集列表目录查阅(持续更新)-CSDN博客​编辑https://archie.blog.csdn.net/article/details/142381401https:/…...

数据结构-8.Java. 七大排序算法(上篇)

本篇博客给大家带来的是排序的知识点, 由于时间有限, 分两天来写, 上篇主要实现 前四种排序算法: 直接插入, 希尔, 选择, 堆排。 文章专栏: Java-数据结构 若有问题 评论区见 欢迎大家点赞 评论 收藏 分享 如果你不知道分享给谁,那就分享给薯条. 你们的支持是我不断创作的动力 …...

YOLOV5/rknn生成可执行文件部署在RK3568上

接上一篇文章best-sim.rknn模型生成好后&#xff0c;我们要将其转换成可执行文件运行在RK3568上&#xff0c;这一步需要在rknpu上进行&#xff0c;在强调一遍&#xff01;&#xff01;rknpu的作用是可以直接生成在开发板上运行的程序 退出上一步的docker环境 exit1.复制best-…...

java http body的格式 ‌application/x-www-form-urlencoded‌不支持文件上传

在Java中&#xff0c;HTTP请求的body部分可以包含多种格式的数据&#xff0c;主要包括以下几种‌&#xff1a; ‌application/x-www-form-urlencoded‌&#xff1a;这种格式将数据编码成键值对的形式&#xff0c;键和值都进行了URL编码&#xff0c;键值对之间用&符号连接。…...

GPU服务器厂家:为什么要选择 GPU 服务器?

文章来源于百家号&#xff1a;GPU服务器厂家 嘿&#xff0c;各位小伙伴们&#xff01;今天咱来聊聊为啥要选择 GPU 服务器&#xff0c;特别是定制化的那种哦。 你们知道吗&#xff1f;现在定制化 GPU 服务器那可是超火的&#xff0c;简直就是科研项目的超强 “外挂”&#x…...

Python操作neo4j库py2neo使用之py2neo 删除及事务相关操作(三)

Python操作neo4j库py2neo使用之py2neo 删除及事务相关操作&#xff08;三&#xff09; py2neo 删除 1、连接数据库 from py2neo import Graph graph Graph("bolt://xx.xx.xx.xx:7687", auth(user, pwd), nameneo4j)2、删除节点 # 删除单个节点 node graph.node…...

Idea忽略提交文件、Idea设置文件隐藏、Idea提交时隐藏部分文件、git提交时忽略文件

文章目录 一、在idea中commit文件时隐藏文件方式一&#xff1a;创建.gitignore文件&#xff08;推荐&#xff09;方式二&#xff1a;‌通过File Types设置隐藏文件方式三&#xff1a;通过Git配置忽略文件‌&#xff08;不推荐&#xff09;总结 二、可能遇到的问题2.1、.gitigno…...

python如何使用spark操作hive

文章目录 1、服务启动2、修改配置3、验证4、开发环境编写代码操作hive 1、服务启动 # 启动hdfs和yarn start-all.sh # 日志服务也需要启动一下 mapred --daemon start historyserver # 启动spark的日志服务 /opt/installs/spark/sbin/start-history-server.sh #启动hive的meta…...

观察者模式和订阅模式

观察者模式和订阅模式在概念上是相似的&#xff0c;它们都涉及到一个对象&#xff08;通常称为“主题”或“发布者”&#xff09;和多个依赖对象&#xff08;称为“观察者”或“订阅者”&#xff09;之间的关系。然而&#xff0c;尽管它们有相似之处&#xff0c;但在某些方面也…...

基于ToLua的C#和Lua内存共享方案保姆级教程

C#和Lua内存共享方案保姆级教程 前言 在介绍C#和Lua内存共享方案之前,先介绍下面两个点来支撑这个方案的必要性 跨语言交互很费 Lua和C#交互最早是基于反射的方式实现的,后来为了提升性能发展成Luajit+C#静态方法导出注入到lua虚拟机的方式至此Lua+Unity的性能才达到了实…...

OpenCV与AI深度学习|16个含源码和数据集的计算机视觉实战项目(建议收藏!)

本文来源公众号“OpenCV与AI深度学习”&#xff0c;仅用于学术分享&#xff0c;侵权删&#xff0c;干货满满。 原文链接&#xff1a;分享&#xff5c;16个含源码和数据集的计算机视觉实战项目 本文将分享16个含源码和数据集的计算机视觉实战项目。具体包括&#xff1a; 1. 人…...

Vue 如何简单更快的对 TypeScript 中接口的理解?应用场景?

TypeScript 中接口&#xff08;Interface&#xff09;的理解与应用 在 TypeScript 中&#xff0c;接口&#xff08;Interface&#xff09; 是一种用来定义对象的结构或形状的方式。接口可以指定对象中应该包含哪些属性、这些属性的类型以及它们的函数签名。接口帮助我们在代码…...

R语言绘图过程中遇到图例的图块中出现字符“a“的解决方法

R语言绘图过程中遇到图例的图块中出现字符的解决方法 因为我遇到这个问题的时候没在网上找到合适的方法&#xff0c;找到个需要付费的&#xff0c;算了。也许是因为问的方式不同&#xff0c;问了半天AI也回答出来&#xff0c;莫名有些烦躁&#xff0c;打算对代码做个分析&…...

视图合并机制解析 | OceanBase查询优化

背景 在默认配置下&#xff0c;若查询语句中嵌入了视图&#xff0c;系统会先等待视图内部所包含的查询完全执行完成后&#xff0c;再继续执行父查询。这种方式造成优化器无法将视图查询与外层查询视为一个整体来进行优化处理&#xff0c;从而限制了优化效果。因此&#xff0c;…...

sql注入报错分享(mssql+mysql)

mysql mysql的报错内容比较多 网上也有比较多的 这里重复的就不多介绍了。一笔带过 溢出类 bigint 当超过mysql的整形的时候&#xff0c;就会导致溢出&#xff0c;mysql可能会将错误信息带出。这里user()是字母默认为0 取反以后1可能就会导致异常。 报错特征 BIGINT UNSIG…...

PHP 高并发解决方案

PHP作为一种脚本语言&#xff0c;在处理高并发请求时可能面临一些挑战。但通过合理的设计和优化&#xff0c;可以有效提升PHP应用程序的性能和并发处理的能力。 一、缓存 页面缓存&#xff1a;将生成的页面缓存起来&#xff0c;减少对数据库的查询&#xff0c;提高响应速度。…...