2

私の息子は、多数の非プレイヤー キャラクター (別名 NPC) を含む単純な RPG ゲームを作成しています。各 NPC には、その動作を制御する "スクリプト" が関連付けられています。これらの動作を記述するためにミニ カスタム スクリプト言語を使用する予定でしたが、C#5/Async でこれを行う方がよいかどうか疑問に思っています。

非常に単純な例を挙げると、NPC の 1 人が 2 点間を歩いているとします。次のように書くとよいと思います。

while (true)
{
    await WalkTo(100,100);
    await WalkTo(200,200);
}

WalkTo メソッドは、2 点間の歩行に関するすべてを処理する非同期メソッドであり、これをゲーム ループの複数のフレームで実行します。バックグラウンド スレッドにオフロードできるブロッキング メソッドではありません。

そして、これが私が立ち往生している場所です...この方法で async/await を使用する例を見つけることができませんでしたが、それは完璧だと思われます。

アイデア?


私がやりたいことの非常に大まかな擬似コードを次に示します。

class NpcBase
{

    // Called from game loop
    public void onUpdate(double elapsedTime)
    {
        // Move the NPC
        .
        .
        .


        // Arrived at destination?
        if (Arrived)
        {
            // How do I trigger that the task is finished?
            _currentTask.MarkComplete();        
        }

    }


    // Async method called by NPC "script"
    public async Task WalkTo(int x, int y)
    {
        // Store new target location


        // return a task object that will be "triggered" when the walk is finished
        _currentTask = <something??>
        return _currentTask;
    }

    Task _currentTask;

}
4

2 に答える 2

4

TaskCompletionSourceゲームのフレームごとに 次に、Taskfromを待機しWalkTo、結果を に設定できOnUpdateます。

private TaskCompletionSource<double> currentFrameSource;

// Called from game loop
public void OnUpdate(double elapsedTime)
{
    ...
    var previousFrameSource = currentFrameSource;
    currentFrameSource = new TaskCompletionSource<double>();
    // This will trigger all the continuations...
    previousFrameSource.SetResult(elapsedTime);
}

// Async method called by NPC "script"
public async Task WalkTo(int x, int y)
{
    // Store new target location
    while (/* we're not there yet */)
    {
        double currentTime = await currentFrameSource.Task;
        // Move
    }
}

確かに、これがどれほど効率的かはわかりません...しかし、うまくいくはずです。

于 2013-02-19T09:59:34.863 に答える
0

私は簡単なテストプログラムでそれを理解したと思います

まず、次のようなNPCの基本クラスがあります。

編集:TaskCompletionSourceを使用するようにNpcBaseを更新しました:

public class NpcBase
{
    // Derived classes to call this when starting an async operation
    public Task BeginTask()
    {
        // Task already running?
        if (_tcs!= null)
        {
            throw new InvalidOperationException("busy");
        }

        _tcs = new TaskCompletionSource<int>();

        return _tcs.Task;
    }

    TaskCompletionSource<int> _tcs;

    // Derived class calls this when async operation complete
    public void EndTask()
    {
        if (_tcs != null)
        {
            var temp = _tcs;
            _tcs = null;
            temp.SetResult(0);
        }
    }

    // Is this NPC currently busy?
    public bool IsBusy
    {
        get
        {
            return _tcs != null;
        }
    }

}

参考までに、TaskCompletionSourceの代わりにカスタムIAsyncResult実装を使用した古いバージョンのNpcBaseを次に示します。

// DONT USE THIS, OLD VERSION FOR REFERENCE ONLY
public class NpcBase
{
    // Derived classes to call this when starting an async operation
    public Task BeginTask()
    {
        // Task already running?
        if (_result != null)
        {
            throw new InvalidOperationException("busy");
        }

        // Create the async Task
        return Task.Factory.FromAsync(
            // begin method
            (ac, o) =>
            {
                return _result = new Result(ac, o);
            },

            // End method
            (r) =>
            {

            },

            // State object
            null

            );

    }

    // Derived class calls this when async operation complete
    public void EndTask()
    {
        if (_result != null)
        {
            var temp = _result;
            _result = null;
            temp.Finish();
        }
    }

    // Is this NPC currently busy?
    public bool IsBusy
    {
        get
        {
            return _result != null;
        }
    }

    // Result object for the current task
    private Result _result;

    // Simple AsyncResult class that stores the callback and the state object
    class Result : IAsyncResult
    {
        public Result(AsyncCallback callback, object AsyncState)
        {
            _callback = callback;
            _state = AsyncState;
        }


        private AsyncCallback _callback;
        private object _state;

        public object AsyncState
        {
            get { return _state; ; }
        }

        public System.Threading.WaitHandle AsyncWaitHandle
        {
            get { throw new NotImplementedException(); }
        }

        public bool CompletedSynchronously
        {
            get { return false; }
        }

        public bool IsCompleted
        {
            get { return _finished; }
        }

        public void Finish()
        {
            _finished = true;
            if (_callback != null)
                _callback(this);
        }

        bool _finished;
    }
}

次に、一次元で動くシンプルな「NPC」があります。moveTo操作が開始されると、NpcBaseのBeginTaskが呼び出されます。宛先に到着すると、EndTask()を呼び出します。

public class NpcTest : NpcBase
{
    public NpcTest()
    {
        _position = 0;
        _target = 0;
    }

    // Async operation to count
    public Task MoveTo(int newPosition)
    {
        // Store new target
        _target = newPosition;
        return BeginTask();
    }

    public int Position
    {
        get
        {
            return _position;
        }
    }

    public void onFrame()
    {
        if (_position == _target)
        {
            EndTask();
        }
        else if (_position < _target)
        {
            _position++;
        }
        else
        {
            _position--;
        }
    }

    private int _position;
    private int _target;
}

そして最後に、それを駆動するための単純なWinFormsアプリ。ボタンと2つのラベルで構成されています。ボタンをクリックすると、NPCが起動し、その位置がラベルに表示されます。

public partial class Form1 : Form
{
    public Form1()
    {
        InitializeComponent();
    }

    private void onButtonClick(object sender, EventArgs e)
    {
        RunNpc1();
        RunNpc2();
    }

    public async void RunNpc1()
    {
        while (true)
        {
            await _npc1.MoveTo(20);
            await _npc1.MoveTo(10);
        }
    }

    public async void RunNpc2()
    {
        while (true)
        {
            await _npc2.MoveTo(80);
            await _npc2.MoveTo(70);
        }
    }


    NpcTest _npc1 = new NpcTest();
    NpcTest _npc2 = new NpcTest();

    private void timer1_Tick(object sender, EventArgs e)
    {
        _npc1.onFrame();
        _npc2.onFrame();

        label1.Text = _npc1.Position.ToString();
        label2.Text = _npc2.Position.ToString();
    }

}

そしてそれは機能し、すべてがメインUIスレッドで実行されているようです...これが私が望んでいたことです。

もちろん、操作のキャンセルや例外などを処理するために修正する必要がありますが、基本的な考え方はそこにあります。

于 2013-02-19T13:01:04.647 に答える