游戏的帧率是固定的。编写脚本的理想语法应该是:
wait(60)
while(true)
{
shootAtPlayer();
wait(30);
}
外星人。
我在C#中使用yield实现了这个解决方案:
public IEnumerable update()
{
yield return 60;
while(true)
{
shootAtPlayer();
yield return 30;
}
}
调用方保留一堆活动例程( IEnumerable计数器上的0)和休眠例程(>0)。每一帧,调用者将每个休眠例程的休眠帧计数器减1,直到1减到0。该例程被再次激活,并继续执行,直到下一次yield返回。这在大多数情况下都可以很好地工作,但令人恼火的是,子例程不能像下面这样拆分:
public IEnumerable update()
{
yield return 60;
while(true)
{
doSomeLogic()
yield return 30;
}
}
public IEnumerable doSomeLogic()
{
somethingHappening();
yield return 100;
somethingElseHappens();
}
上面的语法是不正确的,因为yield return不能嵌套在另一个方法中,这意味着执行状态只能在一个方法中维护(在本例中是update() )。
我之前问过,如何使用C#实现所需的行为,并提到await关键字在这种情况下可能工作得很好。我现在正在尝试弄清楚如何将代码更改为使用await。有没有可能像我想要做的那样,在调用的方法中嵌套等待?另外,我如何使用awaits实现一个计数器?例程的调用者将需要控制递减为每个等待例程留下的等待帧,因为它将是每帧调用一次。我该如何去实现它呢?
发布于 2013-06-05 02:27:16
我不确定async/await对此是否有好处,但它绝对是可能的。我已经创建了一个小的(好吧,至少我已经尝试使它尽可能地小)测试环境来演示可能的方法。让我们从一个概念开始:
/// <summary>A simple frame-based game engine.</summary>
interface IGameEngine
{
/// <summary>Proceed to next frame.</summary>
void NextFrame();
/// <summary>Await this to schedule action.</summary>
/// <param name="framesToWait">Number of frames to wait.</param>
/// <returns>Awaitable task.</returns>
Task Wait(int framesToWait);
}
这应该允许我们这样编写复杂的游戏脚本:
static class Scripts
{
public static async void AttackPlayer(IGameEngine g)
{
await g.Wait(60);
while(true)
{
await DoSomeLogic(g);
await g.Wait(30);
}
}
private static async Task DoSomeLogic(IGameEngine g)
{
SomethingHappening();
await g.Wait(10);
SomethingElseHappens();
}
private static void ShootAtPlayer()
{
Console.WriteLine("Pew Pew!");
}
private static void SomethingHappening()
{
Console.WriteLine("Something happening!");
}
private static void SomethingElseHappens()
{
Console.WriteLine("SomethingElseHappens!");
}
}
我将这样使用engine:
static void Main(string[] args)
{
IGameEngine engine = new GameEngine();
Scripts.AttackPlayer(engine);
while(true)
{
engine.NextFrame();
Thread.Sleep(100);
}
}
现在我们可以进入实现部分了。当然,您可以实现一个自定义的可等待对象,但我将仅依赖于Task
和TaskCompletionSource<T>
(不幸的是,没有非泛型版本,所以我将使用TaskCompletionSource<object>
):
class GameEngine : IGameEngine
{
private int _frameCounter;
private Dictionary<int, TaskCompletionSource<object>> _scheduledActions;
public GameEngine()
{
_scheduledActions = new Dictionary<int, TaskCompletionSource<object>>();
}
public void NextFrame()
{
if(_frameCounter == int.MaxValue)
{
_frameCounter = 0;
}
else
{
++_frameCounter;
}
TaskCompletionSource<object> completionSource;
if(_scheduledActions.TryGetValue(_frameCounter, out completionSource))
{
Console.WriteLine("{0}: Current frame: {1}",
Thread.CurrentThread.ManagedThreadId, _frameCounter);
_scheduledActions.Remove(_frameCounter);
completionSource.SetResult(null);
}
else
{
Console.WriteLine("{0}: Current frame: {1}, no events.",
Thread.CurrentThread.ManagedThreadId, _frameCounter);
}
}
public Task Wait(int framesToWait)
{
if(framesToWait < 0)
{
throw new ArgumentOutOfRangeException("framesToWait", "Should be non-negative.");
}
if(framesToWait == 0)
{
return Task.FromResult<object>(null);
}
long scheduledFrame = (long)_frameCounter + (long)framesToWait;
if(scheduledFrame > int.MaxValue)
{
scheduledFrame -= int.MaxValue;
}
TaskCompletionSource<object> completionSource;
if(!_scheduledActions.TryGetValue((int)scheduledFrame, out completionSource))
{
completionSource = new TaskCompletionSource<object>();
_scheduledActions.Add((int)scheduledFrame, completionSource);
}
return completionSource.Task;
}
}
主要观点:
Wait
方法创建一个任务,该任务在到达指定帧时完成。更新:通过删除不必要的List<>
简化了代码。
https://stackoverflow.com/questions/16929223
复制相似问题