游戏AI:实现Sense-Think-Act组件的模式?

时间:2010-04-20 01:38:45

标签: model-view-controller design-patterns game-ai

我正在开发一款游戏。游戏中的每个实体都是GameObject。每个GameObjectGameObjectControllerGameObjectModelGameObjectView组成。 (或其遗传。)

对于NPC,GameObjectController分为:

IThinkNPC:读取当前状态并决定要做什么

IActNPC:根据需要做什么来更新状态

ISenseNPC:读取当前状态以回答世界查询(例如“我是否在阴影中?”)

我的问题:这对ISenseNPC界面是否正常?

public interface ISenseNPC
    {
        // ...

        /// <summary>
        /// True if `dest` is a safe point to which to retreat.
        /// </summary>
        /// <param name="dest"></param>
        /// <param name="angleToThreat"></param>
        /// <param name="range"></param>
        /// <returns></returns>
        bool IsSafeToRetreat(Vector2 dest, float angleToThreat, float range);

        /// <summary>
        /// Finds a new location to which to retreat.
        /// </summary>
        /// <param name="angleToThreat"></param>
        /// <returns></returns>
        Vector2 newRetreatDest(float angleToThreat);

        /// <summary>
        /// Returns the closest LightSource that illuminates the NPC.
        /// Null if the NPC is not illuminated.
        /// </summary>
        /// <returns></returns>
        ILightSource ClosestIlluminatingLight();

        /// <summary>
        /// True if the NPC is sufficiently far away from target.
        /// Assumes that target is the only entity it could ever run from.
        /// </summary>
        /// <returns></returns>
        bool IsSafeFromTarget();
    }

这些方法都不采用任何参数。相反,该实现应该保持对相关GameObjectController的引用并阅读该内容。

但是,我现在正在尝试为此编写单元测试。显然,有必要使用mocking,因为我无法直接传递参数。我这样做的方式感觉非常脆弱 - 如果另一个实现以不同的方式使用世界查询实用程序会怎样?真的,我没有测试界面,我正在测试实现。差。

我首先使用此模式的原因是保持IThinkNPC实现代码清洁:

    public BehaviorState RetreatTransition(BehaviorState currentBehavior)
    {
        if (sense.IsCollidingWithTarget())
        {
            NPCUtils.TraceTransitionIfNeeded(ToString(), BehaviorState.ATTACK.ToString(), "is colliding with target");
            return BehaviorState.ATTACK;
        }

        if (sense.IsSafeFromTarget() && sense.ClosestIlluminatingLight() == null)
        {
            return BehaviorState.WANDER;
        }

        if (sense.ClosestIlluminatingLight() != null && sense.SeesTarget())
        {
            NPCUtils.TraceTransitionIfNeeded(ToString(), BehaviorState.ATTACK.ToString(), "collides with target");
            return BehaviorState.CHASE;
        }
        return currentBehavior;
    }

然而,清洁度可能不值得。

所以,如果ISenseNPC每次都需要它所需的所有参数,我可以将其设为静态。这有什么问题吗?

1 个答案:

答案 0 :(得分:2)

NO。不不不。您在AI中创建了大量隐藏(而非隐藏)的依赖项。首先,MVC在这里使用并不是一个好的模式,因为实际上没有你需要关注的“视图”,只有动作。此外,这里的“模型”实际上是当时人工智能已知的世界状态,它完全独立于人工智能本身,尽管这可以被认为是游戏世界的“视角”。你对象的位置和属性的快照(我这样做,非常有效)。

然而,核心问题是你的retreatTransition代码与行为和状态高度耦合。如果你不得不做出改变会发生什么?如果你需要200种不同类型的人工智能,那么你会怎样保持这种状态呢?答案是,你不能,这将是一个烂摊子。你在这里有效地创建了一个状态机,并且状态机不能很好地扩展。此外,如果不编辑代码,则无法在计算机上添加/更改/删除状态。

我建议的是,考虑转移到不同的架构。您的TDD方法非常棒,但是在做出选择之前,您需要退后一步,查看不同的AI架构并了解核心概念。我首先看看Jeff Orkin的优秀文章“3个州和一个计划”,该文章是关于F.E.A.R的基于目标的架构。 (http://web.media.mit.edu/~jorkin/goap.html)。我之前已经实现了它,它非常有效且易于设计和维护。它的核心设计也可以很好地促进TDD(实际上BDD是一个更好的选择)。

另一件事:您的ISenseNPC看起来与世界状态紧密耦合。你的AI(它可以从世界中观察到的东西)的感知应该是完全独立的,所以这告诉我你应该有一个类WorldModel或传递给ISenseNPC对象的东西,然后检查WorldModel的相关性信息通过其感知(将感知视为AI可以感知世界的方式,如传感器,视觉半径,声纳等),然后您甚至可以创建单独的感知并将其添加到您的ISenseNPC,这将解耦世界国家,人工智能感知世界的方式,以及人工智能对世界本身的理解。从那里,你的AI可以决定它应该做什么。

您正在建模一个简单的反射代理,它只是一组响应给定知觉序列的规则,这对于简单的AI来说很好。它基本上是一个美化的状态机,但是你可以在Think对象中创建一个感知到行为的映射,可以单独维护,改变映射或扩展它不需要改变代码(工作中的单一责任原则)。此外,您可以创建一个游戏编辑器,可以枚举所有感知,决策和操作,并将它们链接在一起用于任何给定的AI,这将有助于您维护AI而无需进入游戏甚至重建代码(可能) 。我想你会发现这比你在这里尝试做的更加灵活和可维护。抛弃MVC这个特殊的东西,MVC非常适合图形和较小程度的物理,但它真的不适合AI非常好,因为AI没有真正的“视图”。

如果您对此有任何其他疑问,请告诉我。我在为游戏实施基于目标的架构以及其他一些事情方面有一些经验,我很乐意帮助您。< / p>