【问题标题】:Alternative to enumerators in AIAI中枚举器的替代方案
【发布时间】:2015-01-14 20:19:44
【问题描述】:

我正在开发一个多人游戏服务器,该游戏必须控制数千个在世界各地奔跑的生物。每个生物都有一个带有心跳方法的 AI,如果玩家在附近,每隔几毫秒/秒就会调用一次,这样他们就可以做出反应。

目前 AI 使用枚举器作为“例程”,例如

IEnumerable WanderAround(int radius)
{
    // Do something
}

从“状态方法”中调用,这些方法在 foreachs 中调用,在心跳中产生,因此您在每次滴答时都回到同一个位置。

void OnHeartbeat()
{
    // Do checks, maybe select a new state method...
    // Then continue the current sequence
    currentState.MoveNext();
}

当然,例程也必须在循环中调用,因为否则它们不会执行。但由于我不是编写这些 AI 的人,而是不一定是程序员的新手,因此我在服务器启动时编译它们之前预编译 AI(简单的 .cs 文件)。这给了我这样的 AI 脚本:

override IEnumerable Idle()
{
    Do(WanderAround(400));
    Do(Wait(3000));
}

override IEnumerable Aggro()
{
    Do(Attack());
    Do(Wait(3000));
}

Do 被一个迭代例程调用的 foreach 替换。

我真的很喜欢这种设计,因为 AI 易于理解,但功能强大。这不是简单的状态,但也不难理解/编写行为树。

现在我的实际“问题”,我不喜欢Do 包装器,我不喜欢预编译我的脚本。但是我想不出没有循环的任何其他方法来实现它,由于冗长和编写这些脚本的人的技能水平,我想隐藏它。

foreach(var r in Attack()) yield return r;

我希望有一种方法可以在没有显式循环的情况下调用例程,但这是不可能的,因为我必须从 state 方法中让步。

而且我不能使用 async/await,因为它不符合我所依赖的 tick 设计(AI 可能非常复杂,老实说我不知道​​如何使用 async 来实现它)。另外,我只是将Do() 与await 进行交易,并没有太大的改进。

所以我的问题是:任何人都可以想办法摆脱那个循环包装器吗?我愿意使用其他可以用作脚本的 .NET 语言(在服务器启动时编译它们),如果有一种以某种方式支持的话。

【问题讨论】:

  • 你能改用events,让每个AI实现一个事件处理程序吗?然后你所做的就是引发事件(空闲、仇恨等),每个 AI 都会按照脚本进行响应。 .NET 框架将处理所有订阅 AI 事件处理程序的循环。
  • 不起作用,因为脚本编写者定义的动作不会立即执行 =/ 例如,一个 AI 可能应该走 100 个单位,然后说点什么,稍等片刻,然后攻击, 都是为了响应一个事件。而且由于 AI 太多,我不想给每个 AI 分配自己的线程/任务。

标签: c# multiplayer enumerator game-ai behavior-tree


【解决方案1】:

每个生物都有一个带有心跳方法的 AI,每隔几毫秒/秒调用一次,

为什么不全力以赴 SkyNet 并让每个生物对自己的心跳负责?

例如用计时器创建每个生物(可以说是心脏具有特定的心跳)。当每个计时器跳动时,它会按照设计的方式进行操作,但还会检查游戏是否需要关闭、空闲、徘徊或其他项目。

通过分散循环,您已经摆脱了循环,您只需向订阅者(生物)广播在全局/基本级别上做什么。新手无法访问该代码,但可以理解它在概念层面上的作用。

【讨论】:

  • 这将如何改变脚本的编写方式? o.o 毕竟,您仍然必须指定它应该做什么。循环围绕着对返回 IEnumerators 的方法的调用,因此在下一个滴答声中有一个要返回的状态。
【解决方案2】:

您可以尝试通过使用服务器中的事件并让各个 AI 订阅它们来寻求 .NET 框架的帮助。如果服务器正在维护心跳,则此方法有效。

服务器

服务器通告 AI 可以订阅的事件。在 heartbeat 方法中,您将调用 OnIdle 和 OnAggro 方法来引发 Idle 和 Aggro 事件。

public class GameServer
{
    // You can change the type of these if you need to pass arguments to the handlers.
    public event EventHandler Idle;
    public event EventHandler Aggro;

    void OnIdle()
    {
        EventHandler RaiseIdleEvent = Idle;
        if (null != RaiseIdleEvent)
        {
            // Change the EventArgs.Empty to an appropriate value to pass arguments to the handlers
            RaiseIdleEvent(this, EventArgs.Empty);
        }
    }

    void OnAggro()
    {
        EventHandler RaiseAggroEvent = Aggro;
        if (null != RaiseAggroEvent)
        {
            // Change the EventArgs.Empty to an appropriate value to pass arguments to the handlers
            RaiseAggroEvent(this, EventArgs.Empty);
        }
    }
}

通用 CreatureAI

您的所有开发人员都将基于此类实现他们的生物 AI。构造函数采用GameServer 引用参数来允许挂钩事件。这是一个未保存引用的简化示例。在实践中,您可以保存引用并允许 AI 实现者根据他们的 AI 所处的状态订阅和取消订阅事件。例如,仅当玩家试图窃取您的鸡蛋时才订阅 Aggro 事件。

public abstract class CreatureAI
{
    // For the specific derived class AI to implement
    protected abstract void IdleEventHandler(object theServer, EventArgs args);
    protected abstract void AggroEventHandler(object theServer, EventArgs args);

    // Prevent default construction
    private CreatureAI() { }

    // The derived classes should call this
    protected CreatureAI(GameServer theServer)
    {
        // Subscribe to the Idle AND Aggro events.
        // You probably won't want to do this, but it shows how.
        theServer.Idle += this.IdleEventHandler;
        theServer.Aggro += this.AggroEventHandler;
    }

    // You might put in methods to subscribe to the event handlers to prevent a 
    //single instance of a creature from being subscribe to more than one event at once.
}

人工智能本身

这些派生自通用CreatureAI 基类并实现特定于生物的事件处理程序。

public class ChickenAI : CreatureAI
{
    public ChickenAI(GameServer theServer) :
        base(theServer)
    {
        // Do ChickenAI construction
    }

    protected override void IdleEventHandler(object theServer, EventArgs args)
    {
        // Do ChickenAI Idle actions
    }

    protected override void AggroEventHandler(object theServer, EventArgs args)
    {
        // Do ChickenAI Aggro actions
    }
}

【讨论】:

  • 不知怎的,我感觉你们错过了我的问题^^" 这可能需要一段时间才能执行.这就是为什么我目前有刻度和枚举器,我一次又一次地进入当前动作直到它完成.我只是想知道如果不使用循环调用动作(枚举器)或前面的等待,这是否可能它(如果我使用异步)。
  • 也许我的回答不清楚。 .NET event 是一种框架机制,您的类中的 event 不必与游戏事件相对应。因此,您的 AI 可能会通过向敌人迈出一步来响应 Aggro 事件,下一次呼叫它可能会啄食敌人,或者敌人可能已经逃跑并且它会取消订阅进一步的 Aggro 事件。
  • 但是你会如何编码这种行为呢?有状态?这对用户不友好 =/ 我正在寻找一种方法来实现不需要额外代码的无状态行为/操作,如果没有更复杂的预编译,这实际上可能是不可能的。
  • @Mars 与您期望他们对“枚举器”行为进行编码的方式相同,只是他们不是在MoveNext 方法中对其进行编码,而是在事件处理程序中实现它。我怀疑你是对的,我错过了这个问题。或许你能澄清一下服务器和 AI 的架构、接口和职责?
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2016-12-15
  • 2010-10-28
  • 1970-01-01
  • 1970-01-01
  • 2011-06-26
  • 1970-01-01
相关资源
最近更新 更多