AI中枚举器的替代方案

时间:2015-01-14 20:19:44

标签: c# multiplayer enumerator game-ai

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

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

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

来自"状态方法",它们在foreach中被调用,在心跳中产生,所以你会在每个滴答声中回到相同的位置。

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

当然,例程也必须在循环中调用,因为它们不会以其他方式执行。但由于我不是那些编写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;

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

我无法使用async / await,因为它不适合我所依赖的刻度设计(AI可能非常复杂,老实说我不知道​​如何实现使用async)。另外,我只是Do()await进行交易,而不是那么多改进。

所以我的问题是:有没有人能想到摆脱循环包装的方法?我可以使用其他.NET语言,我可以将它们用作脚本(在服务器启动时编译它们),如果有人支持这种语言的话。

2 个答案:

答案 0 :(得分:0)

  

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

为什么不充分SkyNet让每个生物对自己的心跳负责?

例如用计时器创建每个生物(心脏可以用特定的心跳来说话)。当每个计时器击败时,它会完成它的设计目的,但也会检查游戏是否需要关闭,闲置,漂移或其他物品。

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

答案 1 :(得分:0)

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

服务器

服务器通告AI可以订阅的事件。在心跳方法中,您可以调用OnIdleOnAggro方法来引发IdleAggro事件。

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

Generic 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.
}

AIs本身

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

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