最终效果
文章目录
- 最终效果
- 一、前言
- 二、Character Controller参数介绍
- 三、添加虚拟相机
- 四、2.5D俯视角人物操作
- 五、自带重力的SimpleMove 移动
- 六、第三人称角色控制
- 1、移动
- 2、添加重力
- 3、 加地面检测,限制在地面重力不要累加
- 3.1、自定义球形区域检测
- 3.2、使用isGrounded判断是否接触地面
- 4、跳跃
- 5、物理碰撞效果
- 6、最终代码
- 七、下蹲
- 完结
一、前言
前面其实已经做过使用CharacterController实现过第一人称的角色控制器:
【unity小技巧】unity最完美的CharacterController 3d角色控制器,实现移动、跳跃、下蹲、奔跑、上下坡、物理碰撞效果,复制粘贴即用
但是我发现很多人还是不理解,都跑来私信问我,所以决定重新做一个更加详细且简单控制器,主要是实现俯视角和第三人称角色控制。
二、Character Controller参数介绍
添加一个胶囊体,添加Character Controller组件,记得删除Capsule Collider组件,因为Character Controller自带了碰撞,所以不需要
Character Controller参数介绍
Slope Limit
可以爬坡的角度
Step Offset
能上的楼梯阶梯高度
skin width
相当于在CharacterController自己的碰撞体外再添加了一个与碰撞角度相关的碰撞体,skinwith可以消除抖动并且防止角色卡住。这里的建议是最好让你的skinwith的值至少大于0.01
并且大于控制器半径的10%
,但是调整了这个值可能会导致角色的脚无法触及地面,记得同时把碰撞体重心的位置向上调整一点点就可以了
Min Move Distance
最小的移动距离是指的是低于这个数值的移动会被系统忽略掉,我们一般设置为0
Center、Radius、Height
其实就是定义碰撞体范围,中心位置、半径、高
三、添加虚拟相机
参考:【推荐100个unity插件之10】Unity最全的最详细的Cinemachine(虚拟相机系统)介绍,详细案例讲解,快速上手
四、2.5D俯视角人物操作
代码
using UnityEngine;
public class PlayerMovement : MonoBehaviour
{
private CharacterController controller; // 角色控制器组件的引用
public float Speed = 0.1f; // 玩家移动的速度
float horizontal;
float vertical;
void Start()
{
controller = GetComponent<CharacterController>(); // 初始化角色控制器
}
void Update()
{
// 获取水平和垂直轴的输入
horizontal = Input.GetAxis("Horizontal");
vertical = Input.GetAxis("Vertical");
MoveLikeTopDown();
}
private void MoveLikeTopDown()
{
// 获取水平和垂直轴的输入
float horizontal = Input.GetAxis("Horizontal");
float vertical = Input.GetAxis("Vertical");
// 根据输入创建移动方向向量,并标准化
Vector3 direction = new Vector3(horizontal, 0, vertical).normalized;
// 计算移动向量并根据速度和时间更新位置
Vector3 move = direction * Speed * Time.deltaTime;
controller.Move(move);
// 将玩家的位置从世界坐标转换为屏幕坐标
Vector3 playerScreenPoint = Camera.main.WorldToScreenPoint(transform.position);
// 计算鼠标相对于玩家的偏移量
Vector3 point = Input.mousePosition - playerScreenPoint;
// 计算与鼠标位置的角度
float angle = Mathf.Atan2(point.x, point.y) * Mathf.Rad2Deg;
// 更新玩家的朝向,使其面向鼠标
transform.eulerAngles = new Vector3(transform.eulerAngles.x, angle, transform.eulerAngles.z);
}
}
配置
相机参数改一下,改成俯视角,拉高相机
效果
五、自带重力的SimpleMove 移动
SimpleMove 方法是 CharacterController 组件提供的一个简化移动方式,它会自动处理重力
,并且更适合不需要手动处理重力的场景。使用 SimpleMove 方法可以让代码更简洁,因为它自动处理了重力的应用。相比之下,Move 方法则要求你手动计算和应用重力。因此,如果你的角色只需要基本的移动而不需要额外的物理处理,SimpleMove 是一个更方便的选择。
但是SimpleMove 方法本身不支持跳跃
。SimpleMove 自动处理重力,但不提供直接的方式来控制跳跃或其他复杂的运动行为。如果你需要实现跳跃功能,你应该使用 Move 方法,并手动管理重力和跳跃逻辑。
void MoveLikeWow()
{
float horizontal = Input.GetAxis("Horizontal");
float vertical = Input.GetAxis("Vertical");
Vector3 move = transform.forward * Speed * vertical;
// 使用 SimpleMove 方法,它会自动处理重力
controller.SimpleMove(move);
// 旋转角色
transform.Rotate(Vector3.up, horizontal * RotateSpeed);
}
效果,可以看到角色已经有了重力
六、第三人称角色控制
如前面所说,如果你的游戏不需要角色进行跳跃且对重力定制性不高功能,则使用前面的SimpleMove确实是一种很好的方法。
如果我们角色需要跳跃等复杂的功能时,
1、移动
ws控制人物前后移动,用A和D来操作人物的旋转
using UnityEngine;
public class PlayerMovement : MonoBehaviour
{
private CharacterController controller; // 角色控制器组件的引用
public float Speed = 0.1f; // 玩家移动的速度
public float RotateSpeed = 1f; // 玩家旋转的速度
float horizontal;
float vertical;
void Start()
{
controller = GetComponent<CharacterController>(); // 初始化角色控制器
}
void Update()
{
// 获取水平和垂直轴的输入
horizontal = Input.GetAxis("Horizontal");
vertical = Input.GetAxis("Vertical");
MoveLikeWow();
}
private void MoveLikeWow()
{
// 根据输入和速度计算移动方向
Vector3 move = transform.forward * Speed * vertical;
// 使用角色控制器移动角色
controller.Move(move);
// 根据水平输入旋转角色
transform.Rotate(Vector3.up, horizontal * RotateSpeed);
}
}
绑定相机跟随看向主角,并修改相机视角看向角色的头部
效果
相机拐弯有点抖动问题,把这个水平方向的这个震荡值调成零
效果
2、添加重力
注意controller.move调用多次的话,这里面的位移是可以互相叠加的,所以就是最终就是水平位移加上重力,不用担心下面move的会覆盖前面的move
public float Gravity = 19.8f;//重力
private Vector3 Velocity = Vector3.zero; // 角色的当前速度
// 更新重力影响的速度
Velocity.y += Gravity * Time.deltaTime;
// 使用角色控制器应用重力的移动
controller.Move(Velocity * Time.deltaTime);
效果
3、 加地面检测,限制在地面重力不要累加
可以看到,目前角色下落很快,那是因为目前游戏,角色重力就是一直累加,我们先加地面检测,地面检测其实有两种办法
3.1、自定义球形区域检测
你可以选择自己在角色脚下放置一个球形检测区域做地面检测,就像下面这样
[Header("地面检测")]
public bool IsGround;
public Transform GroundCheck;
public float CheckRadius = 0.2f;
public LayerMask layerMask;
//Physics.CheckSphere 用于检测一个球形区域是否与指定的碰撞层发生接触
IsGround = Physics.CheckSphere(GroundCheck.position, CheckRadius, layerMask);
但是这样通常会有一个问题,就是当角色处于悬崖边缘时,检测很容易不准确,导致跳不起来。
当然你可以旋转不使用球形检测,改用其他的检测进行优化,但是这无疑会增加很多的工作量。
3.2、使用isGrounded判断是否接触地面
CharactorController自带有isGrounded函数可以判断是否接触地面
但是它可不像官方文档说的这么简单,如果不懂如何正常使用的话,它存在很多坑,比如明明角色在地面上但是"CharacterController.isGrounded的值总是为false
",或者"CharacterController.isGrounded的值在true和false反复横跳
"。
如果你查看CharacterController.isGrounded会发现有一个段描述如下
其实际意思是上一次调用CharacterController.Move时CharacterController是否接触到了地面?
所以我们可以总结出isGrounded正确进行地面检测的两个条件:
CharacterController.isGrounded的地面检测判断之前一定要先执行CharacterController.Move方法
你必须一直为它施加一个向下的速度,且速度不能为0
代码实现如下,注意当isGrounded为true时,将Y轴方向的速度设为了一个较小的负值(这里我设置的-2),这是为了确保能稳定触碰地面,避免isGrounded误判为false
// 应用重力
controller.Move(velocity * Time.fixedDeltaTime);
//地面检测
isGround = controller.isGrounded;
if (isGround)
{
velocity.y = -2f;
}else{
// 重力累加
velocity.y += Gravity * Time.fixedDeltaTime;
}
4、跳跃
其实也就是在垂直方向施加一个力,这里有一个通用的近似物理效果的一个运动公式
// 跳跃处理
if (isGround && Input.GetButtonDown("Jump"))
{
velocity.y = Mathf.Sqrt(JumpHeight * -2 * Gravity);
}
效果
5、物理碰撞效果
Character Controller本身不会对力作出反应,也不会自动推开刚体。
如果要通过角色控制器来推动刚体或对象,可以编写脚本通过 OnControllerColliderHit() 函数对与控制器碰撞的任何对象施力。
另一方面,如果希望玩家角色受到物理组件的影响,那么可能更适合使用rigidbody,而不是Character Controller
//物理碰撞
private void OnControllerColliderHit(ControllerColliderHit hit)
{
//获取碰撞体上的 Rigidbody 组件。
Rigidbody body = hit.rigidbody;
//检查获取到的 Rigidbody 是否为空或者是否为静态物体(isKinematic)
if (body == null || body.isKinematic) return;
// 我们不想把物体推到我们下面
if (hit.moveDirection.y < -0.3) return;
//根据移动方向计算推动方向,
//我们只将物体推向两侧,从不上下
Vector3 pushDir = new Vector3(hit.moveDirection.x, 0, hit.moveDirection.z);
// 在碰撞点施加冲量 ForceMode.Impulse:添加一个瞬间的冲击力到刚体,且自动应用它的质量
body.AddForceAtPosition(pushDir * 0.1f, hit.point, ForceMode.Impulse);
}
效果
6、最终代码
using UnityEngine;
public class PlayerMovement : MonoBehaviour
{
private CharacterController characterController;
float horizontal;
float vertical;
[Header("移动")]
public float Speed = 10f; // 玩家移动的速度
public Vector3 velocity = Vector3.zero; // 角色的当前速度
[Header("旋转")]
public float RotateSpeed = 30f; // 玩家旋转的速度
[Header("跳跃")]
public float JumpHeight = 3f;//跳跃高度
public float Gravity = -39.8f;//重力
[Header("地面检测")]
public bool isGround;
void Start()
{
characterController = GetComponent<CharacterController>(); // 初始化角色控制器
}
void Update()
{
// 获取输入
horizontal = Input.GetAxis("Horizontal");
vertical = Input.GetAxis("Vertical");
// 处理角色旋转
if (horizontal != 0)
{
transform.Rotate(Vector3.up * horizontal * RotateSpeed * Time.deltaTime);
}
// 处理角色移动
Vector3 move = transform.forward * Speed * vertical;
m_CollisionFlags = characterController.Move(move * Time.deltaTime);
// 跳跃处理
if (isGround && Input.GetButtonDown("Jump"))
{
velocity.y = Mathf.Sqrt(JumpHeight * -2 * Gravity);
}
// 应用重力
characterController.Move(velocity * Time.deltaTime);
//地面检测
isGround = characterController.isGrounded;
if (isGround)
{
// 当isGrounded为true时,将Y轴方向的速度设为了一个较小的负值,这是为了确保能稳定触碰地面,避免isGrounded误判为false
velocity.y = -2f;
}
else
{
// 更新重力影响的速度
velocity.y += Gravity * Time.deltaTime;
}
}
//物理碰撞
private void OnControllerColliderHit(ControllerColliderHit hit)
{
//获取碰撞体上的 Rigidbody 组件。
Rigidbody body = hit.rigidbody;
//检查获取到的 Rigidbody 是否为空或者是否为静态物体(isKinematic)
if (body == null || body.isKinematic) return;
// 我们不想把物体推到我们下面
if (hit.moveDirection.y < -0.3) return;
//根据移动方向计算推动方向,
//我们只将物体推向两侧,从不上下
Vector3 pushDir = new Vector3(hit.moveDirection.x, 0, hit.moveDirection.z);
// 在碰撞点施加冲量 ForceMode.Impulse:添加一个瞬间的冲击力到刚体,且自动应用它的质量
body.AddForceAtPosition(pushDir * 0.1f, hit.point, ForceMode.Impulse);
}
}
七、下蹲
下蹲的逻辑就是让CharacterController 的高度减半,还有中心点的位置也跟着减半,当然还有摄像机的高度,还需要注意的是人物如果头顶有东西的时候我们是不允许他起立的,不然会穿模,所以还需要一个头顶检测,头顶我们使用盒子检测最好,可以覆盖整个头部
参考:https://blog.csdn.net/qq_36303853/article/details/134984516
完结
赠人玫瑰,手有余香!如果文章内容对你有所帮助,请不要吝啬你的点赞评论和关注
,你的每一次支持
都是我不断创作的最大动力。当然如果你发现了文章中存在错误
或者有更好的解决方法
,也欢迎评论私信告诉我哦!
好了,我是向宇
,https://xiangyu.blog.csdn.net
一位在小公司默默奋斗的开发者,闲暇之余,边学习边记录分享,站在巨人的肩膀上,通过学习前辈们的经验总是会给我很多帮助和启发!如果你遇到任何问题,也欢迎你评论私信或者加群找我, 虽然有些问题我也不一定会,但是我会查阅各方资料,争取给出最好的建议,希望可以帮助更多想学编程的人,共勉~