Unity Apex寻路插件与Mecanim动画结合
发表于2018-03-03
最近寻找合适的寻路算法。目前主流的有三种选择。
Unity自带的NavMesh
优点:原生寻路,整合性好,基于多边形网格的寻路,更精确,适合制作人数不是很多,但需求精度高,功能庞大的动作类游戏。
缺点:慢,不适合做RTS,不能在运行时重新烘焙。
A Star Project寻路算法
一开始我也打算用这个,不过,看了一下网上的评价自己也简单体验了一下,功能强大没的说,但是的确易用性上不是很好。
在寻路算法上甚至优于Apex,但是学习曲线明显比Apex长,代码结构也没有Apex好,在单位很多的时候,负载均衡没有Apex强大
支持Runtime更新,但是更新的时候对帧率没有保障,对于需要频繁Runtime Bake的生存游戏,明显就不合适了。他似乎只能以网格为单位进行烘焙,但是没必要把所有的网格都重新烘焙一遍。
适合做RTS,MMORPG之类的游戏,当然动作游戏也OK.不适合做生存类游戏。
最后是Apex Path 寻路算法
缺点:
(1)基于Grid的寻路算法,不像NavMesh那样有非常高的精度。不支持单Grid多层,多层的时候需要Portal(传送门,可以实现等价于Off mesh Link 的功能)
(2)贵:有三个插件 Apex Path基本包,95刀,还有两个DLC,啊,不对,是Add on。一个是Apex Steering 支持更优秀的导航和阵型,不过据说没有BehaviorDesigner那家出的Formation Pack好用。还有一个是Apex Dynamic Obstacle,支持更强大的动态障碍功能,据说优化的非常好,不过目前没有祖国版。可以等打折的时候入手(妈的,现在Asset Store 已经超过Sbeam成为我的第一剁手eshop了)
优点:
代码结构合理,非常适合学习
支持一个网格内的局部动态更新。比如新建了一个台阶,那么可以直接针对台阶附近10*10*10Unity单位的网格进行再烘焙。不必重新烘焙整张网格。
负载均衡做的非常好,几百个单位也不卡
支持超大世界,不过需要同样需要做网格分页,可以结合WorldStreamer插件一起做分页。
多线程烘焙,并且支持逐帧延迟烘焙,以保证不掉帧。
非常易于扩展,这得益于良好的代码结构
所以最后我选择了Apex
基本的例子都很好用,不过当我开始做导航和Mecanim动画结合的时候,遇到了一些小问题。 就是人物像得了帕金森一样,一上楼梯就抖个不停。
最后找到原因:
后来发现官网提供的例子,没有使用碰撞体,单纯演示了如何实现IMoveUnit接口。我傻傻的直接用了一个胶囊碰撞体做为Player的碰撞体。胶囊碰撞体在上楼梯的时候,会不停的改变速度。于是乎Unity 的BlendTree自然也就会不停的来回切换造成抖动了。
之后无意中发现了官网还有一个例子,是使用Apex结合CharacterController来实现导航移动的,于是我想是不是把这两个例子结合起来能解决我的问题呢。
结合后的代码如下:
namespace Apex.Mecanim { using UnityEngine; using System.Collections; using Apex.Steering; public class ApexMecanimMover : MonoBehaviour, IMoveUnits { public float animatorSpeed = 1f; private CharacterController _controller; private Transform _transform; private Animator _animator; private int _speedId; private int _angleId; private Rigidbody _rigidbody; private void Start() { _animator = GetComponent<Animator>(); _controller = GetComponent<CharacterController>(); _transform = transform; _animator.speed = animatorSpeed; _speedId = Animator.StringToHash("Speed"); _angleId = Animator.StringToHash("Angle"); } public void Move(Vector3 velocity, float deltaTime) { //_rigidbody.velocity = velocity; float speed = Mathf.Sqrt(velocity.x * velocity.x + velocity.z + velocity.z); _animator.SetFloat(_speedId, speed); if (speed < 0.2f) { _animator.SetFloat(_speedId, 0f); _animator.SetFloat(_angleId, 0f); return; } _animator.SetFloat(_speedId, speed); float angleDirection = TurnDir(_transform.forward, velocity); float angle = Vector3.Angle(_transform.forward, velocity) * angleDirection; _animator.SetFloat(_angleId, angle); _controller.Move(velocity * deltaTime); } public void Rotate(Vector3 targetOrientation, float angularSpeed, float deltaTime) { /* NOT USED */ _transform.forward = Vector3.RotateTowards(_transform.forward, targetOrientation, angularSpeed * deltaTime, 0f); } public void Stop() { _animator.SetFloat(_speedId, 0f); } private static float TurnDir(Vector3 p1, Vector3 p2) { return Mathf.Sign((p1.z * p2.x) - (p1.x * p2.z)); } } }