Unity Apex寻路插件与Mecanim动画结合

发表于2018-03-03
评论1 4.2k浏览
最近寻找合适的寻路算法。目前主流的有三种选择。

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));  
        }  
    }  
}  

如社区发表内容存在侵权行为,您可以点击这里查看侵权投诉指引