AI中的枚举器替代

问题描述 投票:3回答:2

我正在为一款多人游戏的服务器工作,该游戏必须控制数千个生物,在世界范围内运行。每个生物都有一个具有心跳方法的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的人,而是不一定是程序员的新手,因此我要在服务器启动之前对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方法中屈服。

而且我不能使用异步/等待,因为它不适合我依赖的刻度设计(AI可能非常复杂,老实说,我不知道如何使用异步来实现它)。另外,我只是将Do()await进行交易,没有太大的改进。

所以我的问题是:谁能想到摆脱循环包装的方法?如果有某种支持的方式,我愿意使用其他.NET语言作为脚本(在服务器启动时进行编译)。

c# multiplayer enumerator game-ai behavior-tree
2个回答
0
投票

[每个生物都有一个具有心跳方法的AI,每隔数毫秒/秒就会调用一次,

为什么不充满SkyNet并使每个生物负责自己的心跳?

例如,使用计时器创建每个生物(心脏可以说是特定的心跳)。当每个计时器都跳动时,它会执行其设计的工作,还会与游戏一起检查是否需要关闭,空闲,徘徊或其他项目。

通过分散循环,您摆脱了循环,而只是向订户(生物)广播了在全局/基本级别上的操作。新手无法访问该代码,但可以理解它在概念上的作用。


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

[AI本身

这些是从通用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
    }
}
© www.soinside.com 2019 - 2024. All rights reserved.