AI 中枚举器的替代方法
本文关键字:方法 枚举 AI | 更新日期: 2023-09-27 18:37:28
我正在为一个多人游戏开发服务器,该游戏必须控制几千种生物,在世界各地奔跑。每个生物都有一个带有心跳方法的 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;
我希望有一种方法可以在没有显式循环的情况下调用例程,但这是不可能的,因为我必须从状态方法中产生。
而且我不能使用 async/await,因为它不适合我依赖的即时报价设计(AI 可能非常复杂,老实说我不知道如何使用异步实现它)。另外,我只是用Do()
与await
交换,并没有太大的改进。
所以我的问题是:谁能想到摆脱循环包装器的方法?如果有某种语言以某种方式支持此功能,我愿意使用可以用作脚本的其他 .NET 语言(在服务器启动时编译它们)。
每个生物都有一个带有心跳方法的 AI,每隔几毫秒/秒调用一次,
为什么不去满天网,让每个生物对自己的心跳负责呢?
例如用计时器(可以说是特定的心跳)创建每个生物。当每个计时器节拍时,它会执行其设计目的,但也会与游戏检查是否需要关闭、空闲、徘徊或其他项目。
通过分散循环,你已经摆脱了循环,你只是向订阅者(生物)广播了全局/基本层面做什么。新手无法访问该代码,但可以理解它在概念层面上的作用。
您可以尝试通过使用服务器中的事件并让各个 AI 订阅它们来寻求帮助。 如果服务器正在维护检测信号,则此方法有效。
服务器
服务器通告 AI 可以订阅的事件。 在检测信号方法中,您将调用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);
}
}
}
通用生物AI
您的所有开发人员都将基于此类实现他们的生物 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
基类,并实现特定于 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
}
}