使用C#5异步来等待在多个游戏帧上执行的东西
本文关键字:执行 游戏 异步 C#5 等待 使用 | 更新日期: 2023-09-27 17:59:57
我儿子正在写一款简单的RPG游戏,游戏中有许多非玩家角色(又名NPC)。每个NPC都有一个相关的"脚本"来控制其行为。我们本来打算使用一种小型自定义脚本语言来编写这些行为,但我现在想知道在C#5/Async中这样做是否更好。
举一个非常简单的例子,假设一个NPC只是在两点之间徘徊,我想写这样的东西会很好:
while (true)
{
await WalkTo(100,100);
await WalkTo(200,200);
}
WalkTo方法将是一种异步方法,它处理与在两点之间行走有关的一切,并在游戏循环中的多个帧上执行此操作。它不是一个可以卸载到后台线程的阻塞方法。
这就是我被困的地方。。。我还没有找到任何以这种方式使用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;
}
好吧,听起来有一个选项是为游戏的每一帧设置一个TaskCompletionSource
。然后,您可以等待来自WalkTo
的Task
,并在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
}
}
无可否认,我不确定这会有多有效。。。但它应该起作用。
我想我已经在一个简单的测试程序中找到了它
首先,我有一个NPC的基类:
编辑:更新NpcBase以使用TaskCompletionSource:
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;
}
}
}
作为参考,这里是NpcBase的旧版本,带有自定义IAsyncResult实现,而不是TaskCompletionSource:
// 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应用程序来驱动它。它由一个按钮和两个标签组成。点击按钮启动两个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线程上运行。。。这正是我想要的。
当然,它需要修复以处理操作的取消、异常等……但基本思想是存在的。