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

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

最终效果

在这里插入图片描述

文章目录

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

更好的方式(2024/12/12补充)

你好,这里是来自未来的我,未来的我发现CharacterController可以有更加简单且高效的使用方法,比如使用CharacterController.isGrounded进行地面检测,但是我又不想直接修改这篇文章,毕竟实现方法不同,于是我重新写了一篇文章介绍如何更加简单合理的使用它,并重新书写了代码。

新文章传送门:【unity实战】Cinemachine虚拟相机+Character Controller实现俯视角、第三人称角色控制(附项目源码)

当然,这篇文章也还是存在价值,通过对比你才能发现新旧的区别,并真正理解它,包括人物下蹲的操作我没有再重新实现,因为他们实现的逻辑其实是一样的。

前言

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

其实之前实战有做过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;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;
}//在场景视图显示检测,方便调试
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角色控制器,实现移动、跳跃、下蹲、奔跑、上下坡、物理碰撞效果,复制粘贴即用(2024/12/12补充)

最终效果 文章目录 最终效果更好的方式&#xff08;2024/12/12补充&#xff09;前言为什么使用CharacterControllerSimpleMove和Move如何选择&#xff1f;1. SimpleMove2. Move 配置CharacterController参数控制相机移动跳跃方式一方式二 下蹲处理下坡抖动问题实现奔跑和不同移…...

6_Sass 选择器函数 --[CSS预处理]

Sass 提供了一系列的选择器函数&#xff0c;用于操作和组合CSS选择器。这些函数可以帮助你更灵活地创建样式规则&#xff0c;并且可以减少重复代码。以下是几个常用的选择器函数及其用法&#xff1a; 1. selector-append($selector1, $selector2...) selector-append($select…...

系列2:基于Centos-8.6Kubernetes 集成GPU资源信息

每日禅语 自省&#xff0c;就是自我反省、自我检查&#xff0c;自知己短&#xff0c;从而弥补短处、纠正过失。佛陀强调自觉觉他&#xff0c;强调以达到觉行圆满为修行的最高境界。要改正错误&#xff0c;除了虚心接受他人意见之外&#xff0c;还要不忘时时观照己身。自省自悟之…...

C# 探险之旅:第三十五节 - 类型class之抽象类 (Abstract Class) 和 抽象方法 (Abstract Method)

&#x1f44b; 嗨&#xff0c;勇敢的探险家们&#xff01;欢迎再次踏上C#的神秘之旅。今天&#xff0c;我们要进入一片既神秘又充满无限可能的领域——抽象类与抽象函数的奇幻森林。想象一下&#xff0c;你是一名勇敢的骑士&#xff0c;要在这片森林里寻找传说中的“编程之宝”…...

npm内存溢出

项目过大运行项目内存溢出 报错代码 运行内存溢出 increase-memory-limit ‘“node --max-old-space-size8192”’ 不是内部或外部命令&#xff0c;也不是可运行的程序 FATAL ERROR: Ineffective mark-compacts near heap limit Allocation failed - JavaScript heap out of m…...

CSS|08 浮动清除浮动

浮动 需求: 能够实现让多个元素排在同一行&#xff0c;并且给这些元素设置宽度与高度! 让多个元素排在同一行:行内元素的特性 给这些元素设置宽高:块级元素的特性 在标准文档流中的元素只有两种:块级元素和行内元素。如果想让一些元素既要有块级元素的特点也要有行内元素的特…...

【学习笔记】反向传播到底是如何进行的?

文章目录 一、写在前面二、举个例子三、混合损失函数如何进行呢&#xff1f; 一、写在前面 不知道小伙伴们有没有考虑过这种感觉&#xff0c;在最开始学习深度学习的时候&#xff0c;一定都了解过前向传播&#xff0c;反向传播等等&#xff0c;但是在实际的操作过程中却“几乎…...

利用cnocr库完成中文扫描pdf文件的文字识别

很多pdf文件文字识别软件都会收费&#xff0c;免费的网页版可能会带来信息泄露&#xff0c;还有一些类似于腾讯AI和百度AI的接口都有调用次数限制&#xff0c;因此&#xff0c;利用识别正确率极高且免费的cnocr库来自己动手做个pdf文件文字识别程序就是一个很不错的选择。以下程…...

el-table ToggleRowSelection实现取消选中没效果(virtual-scroll)

场景&#xff1a; 就是在虚拟列表el-table选中之后 点击查询 默认之前选中的 现象&#xff1a; 就是实现选中&#xff0c; 但是无法去除勾选等等 问题发现&#xff1a; 看定位的数据 有多个一样的&#xff0c;我想着勾选之前 先去掉勾选 &#xff0c;但是没效果或者说“相同的…...

Vue入门到精通:运行环境

Vue入门到精通&#xff1a;运行环境 Vue3的运行环境搭建主要有两种方法&#xff1a;一种是直接在页面中引入Vue库&#xff0c;另一种是通过脚手架工具创建Vue项目。 &#xff08;一&#xff09;页面直接引入Vue库 页面直接引入Vue库的方法&#xff0c;是指在HTML网页中通过s…...

LNK2001: virtual struct QMetaObject const 错误的解决方法和原因

目录 1.现象 2.原因分析 3.解决方法 3.1.方法1 3.2.方法2 1.现象 今天调整了下工程目录结构(环境是VS2019Qt5.12.12)&#xff0c;重新编译突然出现以下错误&#xff1a; 没有修改代码&#xff0c;怎么就出现这个错误了呢&#xff1f;从上面的错误来看&#xff0c;其实就是…...

电脑win11家庭版升级专业版和企业版相关事项

我的是零刻ser9&#xff0c;自带win11家庭版&#xff0c;但是我有远程操控需求&#xff0c;想用windows系统自带的远程连接功能&#xff0c;所以需要升级为专业版。然后在系统激活页面通过更改序列号方式&#xff0c;淘宝几块钱买了个序列号升级成功专业版了。但是&#xff0c;…...

用户认证系统登录界面

下面是使用HTML和JavaScript实现的一个中文版登录界面&#xff0c;包含登录、注册和修改密码功能。注册成功后会显示提示信息&#xff0c;在登录成功后进入一个大大的欢迎页面。 1.代码展示 <!DOCTYPE html> <html lang"zh-CN"> <head><meta …...

深圳国威HB1910数字IP程控交换机 generate.php 远程命令执行漏洞复现

0x01 产品描述: 深圳国威主营国威模拟、数字、IP 交换机、语音网关、IP 电话机及各种电话机。深圳国威电子有限公司HB1910是一款功能强大的网络通信设备,适用于各种企业通信需求。 0x02 漏洞描述: 深圳国威电子有限公司HB1910数字IP程控交换机generate.php存在远程命令执行…...

客户端(浏览器)vue3本地预览txt,doc,docx,pptx,pdf,xlsx,csv,

预览文件 1、入口文件preview/index.vue2、预览txt3、预览doc4、预览pdf5、预览pptx6、预览xlsx7、预览csv 1、入口文件preview/index.vue 预览样式&#xff0c;如pdf 文件目录如图所示&#xff1a; 代码如下 <template><div class"preview-wrap" ref&…...

(八)机器学习 - 线性回归

线性回归&#xff08;Linear Regression&#xff09;是一种统计学方法&#xff0c;用于建立一个或多个自变量&#xff08;解释变量&#xff09;与因变量&#xff08;响应变量&#xff09;之间的线性关系。线性回归的目的是通过最小化预测误差来找到最佳的线性拟合模型&#xff…...

Springboot 整合 Java DL4J 打造金融风险评估系统

🧑 博主简介:CSDN博客专家,历代文学网(PC端可以访问:https://literature.sinhy.com/#/literature?__c=1000,移动端可微信小程序搜索“历代文学”)总架构师,15年工作经验,精通Java编程,高并发设计,Springboot和微服务,熟悉Linux,ESXI虚拟化以及云原生Docker和K8s…...

CSDN博客:如何使用Python的`datasets`库转换音频采样率

CSDN博客&#xff1a;如何使用Python的datasets库转换音频采样率 什么是采样率&#xff1f;代码用途&#xff1a;调整音频数据的采样率完整代码示例代码详解运行结果&#xff08;示例&#xff09;总结 在这篇文章中&#xff0c;我们将学习如何使用Python的datasets库对音频数据…...

geoserver(1) 发布sql 图层 支持自定义参数

前提使用postgis 数据库支持关联 join 支持 in,not in,like,及其他sql原生函数 新增sql图层 编写自定义sql 编辑sql语句必须输出带有geom数据 正则表达式去除 设置id以及坐标参考系 预览sql图层效果 拼接sql参数 http://xxx.com/geoserver/weather/wms?SERVICEWMS&VERSI…...

从 Router 到 Navigation:HarmonyOS 路由框架的全面升级与迁移指南

在本教程中&#xff0c;我们深入探讨了 Router 和 Navigation 在 HarmonyOS 中的用法差异及如何从 Router 切换到 Navigation 的方法。重点涵盖了页面跳转、转场动画、生命周期管理以及跨包路由的实现。 页面结构对比 Router 页面结构 每个页面需要使用 Entry 注解。 页面需要…...

http1.1 vs http2.0 速度对比实测

首先对比一下http1.1 vs http2.0 区别&#xff1a; 1. 连接管理&#xff1a; HTTP/1.1: 每个请求/响应都需要一个独立的 TCP 连接&#xff0c;虽然可以使用持久连接&#xff08;keep-alive&#xff09;来复用连接&#xff0c;但仍然存在请求队头阻塞&#xff08;Head-of-Line…...

uniappp配置导航栏自定义按钮(解决首次加载图标失败问题)

1.引入iconfont的图标&#xff0c;只保留这两个文件 2.App.vue引入到全局中 import "./static/fonts/iconfont.css"3.pages.json中配置text为图标对应的unicode {"path": "pages/invite/invite","style": {"h5": {"…...

vue运行项目时local有显示 但是network却显示unavailable

问题描述 日常开发中 和后端本地调试时 后端需要使用你的本地去访问页面 可运行项目时会出现network显示unavailable的情况 解决方式 1.其实这只是vue脚手架对于ip地址获取的方式兼容上有一些问题 但其实是不影响ip访问本地的 你可以直接cmd内ipconfig去查看自己的ip然后…...

【Java学习笔记】JUnit

一、为什么需要 JUnit 二、基本介绍 三、实现方法 第一次添加&#xff1a; 在需要测试的方法处输入 Test注解&#xff0c;快捷键AltInsert选择添加版本&#xff08;常用JUnit5.4&#xff09; 出现绿色箭头可进行测试和编译...

Next.js配置教程:构建自定义服务器

更多有关Next.js教程&#xff0c;请查阅&#xff1a; 【目录】Next.js 独立开发系列教程-CSDN博客 目录 前言 1. 什么是自定义服务器&#xff1f; 2. 配置自定义服务器 2.1 基础配置 2.2 集成不同的服务器框架 使用Fastify 使用Koa 3. 自定义服务器的高级功能 3.1 路…...

el-table 动态计算合并行

原始表格及代码 <el-table:data"tableData"class"myTable"header-row-class-name"tableHead" ><el-table-column prop"date" label"日期"> </el-table-column><el-table-column prop"name" …...

【杭州电商商城系统开发建设】

杭州电商商城系统开发建设是一项综合性的工程&#xff0c;它涉及到多个方面的内容。以下是对杭州电商商城系统开发建设的详细分析&#xff1a; 需求分析&#xff1a;深入了解用户需求&#xff0c;包括用户群体特征、购物习惯、支付偏好等&#xff0c;为系统设计提供基础。明确…...

架构实践05-互联网架构模板

零、文章目录 架构实践05-互联网架构模板 1、技术演进的方向 &#xff08;1&#xff09;技术演进的方向判断 潮流派&#xff1a;热衷于新技术&#xff0c;紧跟技术潮流&#xff0c;但可能面临技术不成熟的风险和学习成本。保守派&#xff1a;强调稳定&#xff0c;对新技术持…...

家校通小程序实战教程10部门管理前后端连接

目录 1 加载后端的数据2 为什么不直接给变量赋值3 保存部门信息4 最终的效果5 总结 现在部门管理已经完成了后端功能和前端开发&#xff0c;就需要在前端调用后端的数据完成界面的展示&#xff0c;而且在录入部门信息后需要提交到数据库里&#xff0c;本篇我们介绍一下前后端如…...

【前端面试题】书、定位问题、困难

看过什么书 《JavaScript 高级程序设计&#xff08;第 4 版&#xff09;》&#xff08;作者&#xff1a;Matt Frisbie&#xff09; 这是一本深入学习 JavaScript 语言的经典书籍。它详细地涵盖了 JavaScript 的高级特性&#xff0c;包括原型链、闭包、异步编程等复杂概念。以闭…...

VSCode设置字体

参考文章&#xff1a;【面向小白】vscode最佳实践&#xff08;2&#xff09;—— 字体设置&#xff08;fira code更纱黑体&#xff09;&#xff0c;这篇文章末尾给了安装字体的链接。 配置的字体还是很好看的。 ‘Fira Code Retina’, ‘Sarasa Mono Sc’ 需要注意的一个点&am…...

《机器学习》2.4假设检验 t分布 F分布

目录 t发布 注意是这个东西服从t分布 数据服从t分布通常是在以下情况下&#xff1a; 以下是一些具体的例子&#xff0c;说明在何种情况下数据会服从t分布&#xff1a; t检验 交叉验证t检验 样本方差​编辑 F分布&#xff08;fisher Friedman检验是一种非参数统计方法&a…...

Mysql之视图

MySQL 视图&#xff08;View&#xff09; 1. 概念 视图是一个虚拟的表&#xff0c;它是基于 SELECT 查询的结果集。视图不存储实际数据&#xff0c;而是动态地从基表中提取数据。视图可以简化复杂查询、提高数据安全性&#xff08;限制访问特定列或行&#xff09;以及提供数据…...

kafka学习笔记

kafka消息中间件精讲 - B站动力节点 JDK17在Windows安装及环境变量配置超详细的教程 Windows 多版本java 装多个版本jdk Windows同时安装多个JDK jdk17下载与安装教程&#xff08;win10&#xff09;&#xff0c;超详细 jdk17-archive-downloads 如何在IDEA中配置指定JDK版…...

【保姆级教程】基于OpenCV+Python的人脸识别上课签到系统

【保姆级教程】基于OpenCVPython的人脸识别上课签到系统 一、软件安装及环境配置1. 安装IDE&#xff1a;PyCharm2. 搭建Python的环境3. 新建项目、安装插件、库 二、源文件编写1. 采集人脸.py2. 训练模型.py3. 生成表格.py4. 识别签到.py5. 创建图形界面.py 三、相关函数分析1.…...

LVS能否实现两台服务器的负载均衡

LVS能否实现两台服务器的负载均衡 是的&#xff0c;LVS&#xff08;Linux Virtual Server&#xff09;可以实现两台服务器的负载均衡&#xff0c;并且它非常适合这种场景。 LVS&#xff08;Linux Virtual Server&#xff09;简介&#xff1a; LVS 是一种基于 Linux 的负载均…...

智能人体安全防护:3D 视觉技术原理、系统架构与代码实现剖析

随着工业化程度的提高&#xff0c;生产安全已成为企业关注的重点。尤其是在一些存在禁区的工业厂区和车间&#xff0c;人员误入或违规进入将带来严重的安全隐患。为了解决这一问题&#xff0c;迈尔微视推出了智能人体安全检测解决方案&#xff0c;为企业提供全方位的人员安全监…...

JAVA后端实现全国区县下拉选择--树形结构

设计图如图&#xff1a; 直接上代码 数据库中的格式&#xff1a; JAVA实体类&#xff1a; Data public class SysAreaZoningDO {private Long districtId;private Long parentId;private String districtName;private List<SysAreaZoningDO> children; } MapperSQL语句…...

DVWA及其他常见网络靶场

常见网络靶场 Metasploitable2 介绍&#xff1a; Metasploitable2 是一个用于安全培训和测试渗透测试工具的虚拟靶机。它故意配置了许多已知的安全漏洞&#xff0c;涵盖了操作系统、网络服务等多个方面。基于 Ubuntu Linux 操作系统构建&#xff0c;包含了如 Apache、MySQL、FT…...

API接口安全:电商数据保护的坚固防线

随着电子商务的蓬勃发展&#xff0c;电商平台的数据安全和隐私保护成为了至关重要的议题。API&#xff08;应用程序编程接口&#xff09;作为电商平台与外部系统交互的桥梁&#xff0c;其安全性直接关系到整个平台的数据保护能力。本文将从API接口安全的重要性、面临的安全威胁…...

springboot437校园悬赏任务平台(论文+源码)_kaic

摘 要 使用旧方法对校园悬赏任务平台的信息进行系统化管理已经不再让人们信赖了&#xff0c;把现在的网络信息技术运用在校园悬赏任务平台的管理上面可以解决许多信息管理上面的难题&#xff0c;比如处理数据时间很长&#xff0c;数据存在错误不能及时纠正等问题。这次开发的校…...

可视化报表如何制作?一文详解如何用报表工具开发可视化报表

在如今这个数据驱动的商业时代&#xff0c;众多企业正如火如荼地推进数字化转型&#xff0c;力求在激烈的市场竞争中占据先机。然而&#xff0c;随着业务规模的扩大和运营复杂度的提升&#xff0c;企业的数据量爆炸式增长&#xff0c;传统报表格式单一、信息呈现密集且不易解读…...

STM32 HAL库之SDIO例程 Micro SD卡 - 2

1、硬件图 2、示例代码 根据提示配置SDCLK为72/3 24MHz。 static void MX_SDIO_SD_Init(void) {/* USER CODE BEGIN SDIO_Init 0 */SD_InitTypeDef Init;Init.ClockEdge SDIO_CLOCK_EDGE_RISING;Init.ClockBypass SDIO_CLOCK_BYPASS_DISABLE;Init.ClockPo…...

架构实践02-高性能架构模式

零、文章目录 架构实践02-高性能架构模式 1、 高性能数据库集群&#xff1a;读写分离 &#xff08;1&#xff09;引言 背景&#xff1a;随着业务的发展和数据的增长&#xff0c;单个数据库服务器难以满足需求&#xff0c;必须考虑数据库集群。目的&#xff1a;介绍高性能数…...

leetcode-73.矩阵置零-day5

class Solution {public void setZeroes(int[][] mat) {int m mat.length, n mat[0].length;// 1. 扫描「首行」和「首列」记录「首行」和「首列」是否该被置零boolean r0 false, c0 false;for (int i 0; i < m; i) {if (mat[i][0] 0) {r0 true;break;}}for (int j …...

Docker与虚拟机:虚拟化技术的差异解析

在信息技术飞速发展的今天&#xff0c;虚拟化技术已成为现代IT架构不可或缺的一部分。而虚拟化从技术层面划分则分为以下几种&#xff1a; 完全虚拟化&#xff1a;虚拟机能够完全模拟底层硬件的特权指令的执行过程&#xff0c;客户操作系统无须进行修改。 硬件辅助虚拟化&#…...

数据结构——ST表

ST表的定义 ST表&#xff0c;又名稀疏表&#xff0c;是一种基于倍增思想&#xff0c;用于解决可重复贡献问题的数据结构 倍增思想 这里列举一个去寻找一个区间内的最大值的例子 因为每次会将将区间增大一倍&#xff0c;所以才被称之为倍增思想 &#xff0c;这种思想十分好用…...

flutter命令行直接指定设备

> flutter driver Found 3 connected devices:sdk gphone16k x86 64 (mobile) • emulator-5554 • android-x64 • Android 15 (API 35) (emulator)Linux (desktop) • linux • linux-x64 • Ubuntu 22.04.5 LTS 6.8.0-49-genericChrome (…...

【STM32】RTT-Studio中HAL库开发教程九:FLASH中的OPT

文章目录 一、概要二、内部FLASH排布三、内部FLASH主要特色四、OTP函数介绍五、测试验证 一、概要 STM32系列是一款强大而灵活的微控制器&#xff0c;它的片内Flash存储器可以用来存储有关代码和数据&#xff0c;在实际应用中&#xff0c;我们也需要对这个存储器进行读写操作。…...

COLA学习之代码规范(二)

小伙伴们&#xff0c;你们好&#xff0c;我是老寇&#xff0c;上一节&#xff0c;我们学习了DDD相关术语&#xff0c;继续跟老寇学习COLA代码规范 代码规范 包命名 层次包名功能必选Adapter层web处理页面请求Controller否Adapter层wireless处理无线端适配否Adapter层wap处理…...