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!");
}
}
エンジンを次のように使用します。
static void Main(string[] args)
{
IGameEngine engine = new GameEngine();
Scripts.AttackPlayer(engine);
while(true)
{
engine.NextFrame();
Thread.Sleep(100);
}
}
これで、実装部分に到達できます。もちろん、カスタムの awaitable オブジェクトを実装することもできますが、ここでは and のみに依存します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<>
。