简介
操作人物移动,我们一般有以下几种办法:
- 直接操作坐标
- 使用物理引擎操控
- 使用动作 Root Motion
- 使用角色控制器
关于坐标操作
坐标操作,就是通过设置transoform.position坐标 或是Vector.transform 等方法使物体移动到指定坐标。
该方式的优点,当然是简单暴力。坏处么,各种无互动无反馈,效果太楞。只适合简单的变化操作或是2d游戏。如果需要复杂点的效果,比如曲线过度,重力下降都要自己去实现。需要自己实现呀,兄弟们我们使用引擎的目的是什么,当然是使用提供的工具提升工作效率。不能什么都自己搞呀。所以这个方式只推荐应用在npc或是机关变化。
关于物理引擎操作
物理引擎操作的好处是可以使用诸多物理特性。当游戏有大量碰撞或移动特性时,使用这个比较好。比如赛车游戏。你可能需要设置赛车的重量来决定过弯的表现和撞车时力反馈的计算,又或是爆炸时被波及的力度。
使用物理引擎操作的主要方式就是添加力。由此可知,向前移动就是添加向前的作用力,跳跃就是添加向上的作用力,物理效果会让人物跳跃后自动下降。
关于Root Motion
这个方式是我认为最合理,过度最平滑,效果最好的方式。只可惜成本太高实现太难。
他所需要的技术包括:角色骨骼绑定 + Animation动作库 + Animator动画控制器 + Root Motion根坐标移动
这就需要我们不仅拥有质量高且数量庞大的动作库,以及各种对动画的精确控制才能实现。我们使用市面上已有的动作库可以实现基本的移动等操作。但总的来说,性能还是不如下面那个,而且开发成本还是比较高的,调试周期特别长,在遇到一些自适应需求的时候开发难度也会增加。
我相信,不久的将来它会成为游戏引擎中的操控最高级形态,并且在技术的推动下变得更加易用。
使用角色控制器(CharacterController)操作
这是个unity的组件。一般情况下控制人物的不二选择。那么为什么首选角色控制器呢,有以下几个理由:
- 首先他简化了刚体组件,去掉了很多不必要的计算。在人物比较多的场景中,这节省了大量的物理计算。例如物理引擎中的翻转、重力等效果,你肯定不希望你在移动过程中,会因为碰撞到物体而自动变换方向吧,一般做法就是用freezon来限制,其实这些都是没有必要的性能浪费。如果场景中人物较多时,这种优化就等于性能的巨幅提升。
- 其次,他内置了爬坡、登阶阈值设置。可以比较简单的处理多路况的移动问题。
- 第三,这个控制器,非常适合与nevmesh agent连用。即unity自带的巡路系统。当我们使用寻路系统时,如果人物身上有物理刚体,那么不仅会被沿途障碍物所影响,还会出现各种意想不到的情况比如惯性等性质。 如果使用角色控制器。则不必担心出现这种情况。一切都那么顺滑,防侧漏,夜夜安心。
- 不必担心被坐标被动作库写死。更不必担心由于动作库的坐标移动而出现意外的坐标干扰。
角色控制器(CharacterController)组件参数介绍
在目标物体的inspector窗口中 点击add components ,添加character controller 即可完成组件添加。
【Slope Limit:】:斜坡角度。允许角色在指定的坡度上行动。
比如在手游和平精英中,你可能会发现某些房顶的斜坡就能走上去,某些房顶斜坡就走不上去。这都是这个参数所致。虽然和平精英用的是ue4引擎,但使用了和unity原理相似的控制组件。他们都有这个斜坡设置。
【Step Offset】:台阶高度。允许角色自动越过多高的障碍物。 这个设置就是诸如楼梯,台阶,马路牙子之类的障碍,你总不能遇到个小东西就卡住吧,有了这个就可以越过去。有人说,我没有用character controller,用刚体也能越过比较低矮的障碍物啊,怎么回事呢? 那是因为你的碰撞盒下方并不平整,比如胶囊或圆形碰撞盒,由于下方是曲线过度的,所以会在接触物体时通过物理计算的推动下摩擦过去。这种方式不容易控制并且会可能发生意外情况。
【Skin Width】:皮肤宽度。这个就相当于第二层碰撞盒。这个值不能大于下方的碰撞盒的Radius值。否则会卡在某个地方。一般情况这个值应该在Radius的5%左右会比较好
【Min Move Distance】:最小移动范围。当你在代码中使用Move方法移动时,小于这个值的移动将被忽略。这个值可以帮你减少角色因操作产生的抖动。
【Center】:角色碰撞盒偏移。设置物理空间的位置,你需要通过调整这个来使碰撞盒与角色模型重合。
【Radius】:碰撞盒半径。角色控制器的自带碰撞盒是胶囊,这个值用来设置胶囊半径。这个设置还有另一个影响,这个半径值将影响脚本中的isGrounded属性的作用范围。这对我们实现控制角色y坐标位置,比如跳跃等功能极其重要。
【Height】:碰撞盒高度。同上,胶囊高度。
角色控制器(CharacterController)使用
在添加了CharacterController的物体上新建一个脚本。
//获取 CharacterController对象
CharacterController _characterController = gameObject.GetComponent<CharacterController> ();
移动的实现
以下是两种移动方式。分别是世界坐标和本地坐标
//移动方向
private Vector3 moveDirection = Vector3.zero;
//移动速度
float movespeed = 2f;
void FixedUpdate(){
//世界坐标移动 适用于2d游戏,有固定的方向
_characterController.Move(Vector3.up * movespeed * Time.deltaTime);
//角色坐标移动实现 基于当前角色视角的方向移动,适用于3d游戏
//根据输入的x y值获得移动方向
float vertical = Input.GetAxis ("Vertical");
float horizontal = Input.GetAxis ("Horizontal");
moveDirection = new Vector3(horizontal, 0f, vertical);
// 计算 基于当前物体坐标,要移动的方向
moveDirection = transform.TransformDirection(moveDirection) * movespeed;
// 移动
_characterController.Move(Vector3.up * movespeed * Time.deltaTime);
}
跳跃的实现
public float jumpPower = 3f;
public float speed = 10f;
public float gravity = 10f;
private Vector3 moveDirection = Vector3.zero;
void FixedUpdate() {
//isGrounded属性 用于 检测当前角色下方是否接触到地面。这个属性表现并不稳定,至少在我的unity2020中他不够稳定,经常闪烁。所以需要我们再用一个射线检测的方法来辅助。
if(_characterController.isGrounded || IsGrounded(0.2f)) {
moveDirection = new Vector3(Input.GetAxis("Horizontal"), 0f, Input.GetAxis("Vertical"));
moveDirection = transform.TransformDirection(moveDirection);
moveDirection *= speed;
if (Input.GetButton("Jump"))
moveDirection.y = jumpPower ;
}
// 角色在世界中自动下降,模拟重力
moveDirection.y -= gravity * Time.deltaTime;
controller.Move(moveDirection * Time.deltaTime);
}
// 射线检测,抛砖引玉 我这里使用五个射线。实际上不需要这么多
public bool IsGrounded(float distance){
//pointOffset: 点的偏移位置, distance:检测物体与地面的距离
float pointOffset= 0.12f;
bool b = Physics.Raycast(new Vector3(transform.position.x, transform.position.y, transform.position.z), -Vector3.up, distance);
bool b1 = Physics.Raycast(new Vector3(transform.position.x - pointOffset, transform.position.y, transform.position.z), -Vector3.up, distance);
bool b2 = Physics.Raycast(new Vector3(transform.position.x + pointOffset, transform.position.y, transform.position.z), -Vector3.up, distance);
bool b3 = Physics.Raycast(new Vector3(transform.position.x, transform.position.y, transform.position.z + pointOffset), -Vector3.up, distance);
bool b4 = Physics.Raycast(new Vector3(transform.position.x, transform.position.y, transform.position.z - pointOffset), -Vector3.up, distance);
return b || b1 || b2 || b3 || b4;
}
这里只是简单的实现,在实际的需求中,你可能要捕捉人物的跳跃状态,为人物跳跃指定起跳、浮空、落地等动作,还要设置原地起跳高度,移动跳跃距离等设定等。
注意事项
- 如果主人公身上有animator 需要看情况去掉root motion选项。该选项有可能导致坐标冲突
- CharacterController.isGrounded 这个会闪烁。所以会导致很多意外情况。可选的解决方案是:
一、碰撞盒替代。是比较理想的方案。
二、CharacterController.isGrounded + 射线辅助的方式。性能会好一些。不可以单独只使用射线。这样会存在盲点。