Unity3D 插件之 Behavior Designer Movement Pack For Apex Path 的巨坑

在目前的项目中,我们有使用到一个叫 Apex Path 的插件来做自动寻路的事情,这个插件整体来说还是棒棒哒,完全对得起 $65 的价格,我在写这篇文章的时候,刚好这货又在做半价促销了,链接在此

今天先不展开聊这个 Apex Path 插件了,今天主要是要吐槽一下 Behavior Designer 插件的增强包 Movement Pack,在初次接触到 Behavior Designer 插件之时,顿时觉得这货能帮忙解决 AI 行为树的大部分问题,立马买回来尝试了几把,发现真心不错,可以解决很多问题。然后在他们网站上发现还有一个额外的用于处理移动的增强包,果断再次入手。拿回来简单地测试了一番,感觉还不错。但是在后续的开发中,总是会发现一些奇怪的问题,大体的表现就是 NPC 完全在没有移动到应该移动到的攻击位置就开始攻击了以及类似的问题,而且这个问题是在有多个使用了 Behavior Designer 控制的 NPC 出现在场景中的时候才会出现。

这个真心奇了怪了,作为一个朝内程序猿,我一直都奔着崇洋媚外的态度来看待国外程序员大大们,一直认为他们好牛逼好牛逼,然后先入为主地认为是自己用得肯定有问题,多次检查了自己的代码,依然没能找到问题。最终我就只能搞了一个空场景,就放了两个 NPC,让 NPC 做最简单的行为,把 Log 加上,尼玛最后知道真相的我,眼泪掉下来啊,有木有!

我们来看看 Behavior Designer Movement Pack For Apex Path 中 Patrol 脚本中关于 Apex 中 UnitNavigationEventMessage 回调的处理。

 // Add the waypoint back on the stack when the destination is reached
public override void Handle(UnitNavigationEventMessage message)
{
    switch (message.eventCode) {
        case UnitNavigationEventMessage.Event.WaypointReached:
            movableAgent.MoveTo(waypoints[waypointIndex].position, true);
            waypointIndex = (waypointIndex + 1) % waypoints.Length;
        break;
    }
}

然后我们对比一下 Apex Path 插件中自带的一个 PatrolBehaviour 脚本是如何处理这个 UnitNavigationEventMessage 的回调的。

        
void IHandleMessage.Handle(UnitNavigationEventMessage message)
{
    if (message.entity != this.gameObject || message.isHandled)
    {
        return;
    }

    if (message.eventCode == UnitNavigationEventMessage.Event.WaypointReached)
    {
        message.isHandled = true;
        MoveNext(true);
    } else if (message.eventCode == UnitNavigationEventMessage.Event.DestinationReached)
    {
        message.isHandled = true;
        StartCoroutine(DelayedMove());
    }
}

细心对比一下,我们就会发现,在后者的处理逻辑中,加入了一个关于这个消息的 entity 对象是否为当前脚本所绑定的 GameObject 对象的判断,如果当前接收到的消息不是当前这个 GameObject 发出的,是不做任何逻辑,直接 return 的。然后再回过头来看看 Behavior Designer Movement Pack For Apex Path 中的处理,你就知道问题出在哪儿了。

因为 Apex Path 的 GameServices 中的 MessageBus 实现机制是任何对象都可以通过 GameServices.messageBus 访问到这个全局静态的 MessageBus 对象,并且可以通过 subscribe 和 unsubscribe 方法来进行消息的订阅和取消订阅。也就因为这个任何 MessageBus 的全局机制,任意注册了的对象都会收到回调,所以我们需要在处理消息回调的时候自行判断这个消息是否是我们需要的,或者说需要判断这个消息是否是发给自己的。MessageBus 的机制就是一个广播,只要有任何一个对象触发了事件,注册了消息的所有对象都会收到广播,所以需要收到广播消息的对象自行甄别这货是否是发给自己的。

Behavior Designer Movement Pack For Apex Path 脚本中对于回调的处理显然是忽略了这个甄别的过程,所以就会出现一些完全不在意料之中的事情,一切都能解释得通了。

Opsive 的这个团队在 Behavior Designer 的工作确实非常惊艳,让人竖大拇哥,但是在这种问题上犯错误只能让我觉得这是实习生做的,或者就是完全没有真正地使用过 Apex Path,然后就直接推出了一个似是而非的 Movement 增强包,我是很不以为然的。今天看到这个 Movement 的插件还升级了,Update 需要花费 $10,鉴于这个问题,我想暂时还是不升级了,虽然我很想看看他们升级的版本中是否已经修复了这个低级错误,等我今天闲下来了,我得给他们发个邮件问问。


给 Opsive 团队发过邮件了,邮件回复说是新的版本中已经加入这个判断了,问题已经修复,回复内容如下:

Hello,

Thank you for letting me know. Have you imported the most recent version of the Apex Path integration off of the integrations page? This integration doesn’t include an ApexPathSteeringBase file. It only includes ApexPathMovement as the base class. Within that class there is a check for the GameObject:

        public virtual void Handle(UnitNavigationEventMessage message)

        {

            if (!message.entity.Equals(gameObject)) {

                return;

            }

            switch (message.eventCode) {

                case UnitNavigationEventMessage.Event.DestinationReached:

                    arrived = true;

                    break;

            }

        }

I am not setting message.isHandled to true because all I am doing when destination is reached is setting a flag so a new destination hasn’t been set yet.

Thank you,

Justin