I'm working on a server for a multi-player game that has to control a few thousand creatures, running around in the world. Every creature has an AI with a heartbeat method that is called every few ms/s, if a player is nearby, so they can react.
Currently the AI uses enumerators as "routines", e.g.
IEnumerable WanderAround(int radius)
{
// Do something
}
which are called from "state methods", which are called in foreachs, yielding in the heartbeat so you get back to the same spot on every tick.
void OnHeartbeat()
{
// Do checks, maybe select a new state method...
// Then continue the current sequence
currentState.MoveNext();
}
Naturally the routines have to be called in a loop as well, because they wouldn't execute otherwise. But since I'm not the one writing those AIs, but newbies who aren't necessarily programmers, I'm pre-compiling the AIs (simple .cs files) before compiling them on server start. This gives me AI scripts that look like this:
override IEnumerable Idle()
{
Do(WanderAround(400));
Do(Wait(3000));
}
override IEnumerable Aggro()
{
Do(Attack());
Do(Wait(3000));
}
with Do
being replaced by a foreach that iterates over the routine call.
I really like this design because the AIs are easy to understand, yet powerful. It's not simple states but it's not a hard to understand/write behavior tree either.
Now to my actual "problem", I don't like the Do
wrapper, I don't like having to pre-compile my scripts. But I just can't think of any other way to implement this without the loops, that I want to hide because of verbosity and the skill level of the people who're gonna write these scripts.
foreach(var r in Attack()) yield return r;
I'd wish there'd be a way to call the routines without an explicit loop, but that's not possible because I have to yield from the state method.
And I can't use async/await because it doesn't fit the tick design that I depend on (the AIs can be quite complex and I honestly don't know how I would implement that using async). Also I'd just trade Do()
against await
, not that much of an improvement.
So my question is: Can anyone think of a way to get rid of that loop wrapper? I'd be open to using other .NET languages that I can use as scripts (compiling them on server start) if there's one that supports this somehow.