C#博弈中NPC的异步行为
这个问题与……有关。 上下文 当Miguel de Icaza在上首次为游戏介绍C#5异步框架时,我非常喜欢在游戏中使用C#博弈中NPC的异步行为,c#,.net,asynchronous,mono,c#-5.0,C#,.net,Asynchronous,Mono,C# 5.0,这个问题与……有关。 上下文 当Miguel de Icaza在上首次为游戏介绍C#5异步框架时,我非常喜欢在游戏中使用async和wait来处理“脚本”(可以说,因为它们是C#,在我的例子中,是编译的——及时但无论如何都要编译) 即将推出的游戏引擎似乎也能处理脚本,但从我的角度来看,在想法和实现之间确实存在差距 在中,有人使用wait让NPC在游戏其余部分仍在运行时执行一系列指令 主意 我想更进一步,允许NPC同时执行多个动作,同时以顺序方式表达这些动作。 大致如下: class NonPla
async
和wait
来处理“脚本”(可以说,因为它们是C#,在我的例子中,是编译的——及时但无论如何都要编译)
即将推出的游戏引擎似乎也能处理脚本,但从我的角度来看,在想法和实现之间确实存在差距
在中,有人使用wait
让NPC在游戏其余部分仍在运行时执行一系列指令
主意
我想更进一步,允许NPC同时执行多个动作,同时以顺序方式表达这些动作。
大致如下:
class NonPlayableCharacter
{
void Perform()
{
Task walking = Walk(destination); // Start walking
Task sleeping = FallAsleep(1000); // Start sleeping but still walks
Task speaking = Speak("I'm a sleepwalker"); // Start speaking
await walking; // Wait till we stop moving.
await sleeping; // Wait till we wake up.
await speaking; // Wait till silence falls
}
}
为了做到这一点,我使用了乔恩·斯基特的《世界杯》和以往一样精彩
实施
我的玩具实现由两个文件组成,NPC.cs和Game.cs
全国人大常委会:
使用系统;
使用System.Threading.Tasks;
命名空间异步框架
{
公营人大
{
公共NPC(国际id)
{
this.id=id;
}
公共异步void执行()
{
任务喋喋不休=说话(“我有超能力…”);
等待发言(“\t\t\t…我可以边说话边说话!”);
等待唠叨;
完成=正确;
}
公共bool Done{get{return Done;}}
受保护的异步任务(字符串消息)
{
int-previousLetters=0;
双字母=0.0;
while(字母1.0){
System.Console.Out.WriteLine(“[”+this.id.ToString()+“]”+message.Substring(0,(int)Math.Floor(Math.Min(字母,message.Length)));
previousLetters=(int)Math.Floor(字母);
}
}
}
私有int-id;
私有布尔完成=假;
私有只读双字母每毫秒=0.002*Game.Rand.Next(1,10);
}
}
Game.cs:
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
namespace asyncFramework
{
class Game
{
static public Random Rand {
get { return rand; }
}
static public Task<double> Frame {
get { return frame.Task; }
}
public static void Update (double ellapsedTime)
{
TaskCompletionSource<double> previousFrame = frame; // save the previous "frame"
frame = new TaskCompletionSource<double> (); // create the new one
previousFrame.SetResult (ellapsedTime); // consume the old one
}
public static void Main (string[] args)
{
int NPC_NUMBER = 10; // number of NPCs 10 is ok, 10000 is ko
DateTime currentTime = DateTime.Now; // Measure current time
List<NPC> npcs = new List<NPC> (); // our list of npcs
for (int i = 0; i < NPC_NUMBER; ++i) {
NPC npc = new NPC (i); // a new npc
npcs.Add (npc);
npc.Perform (); // trigger the npc actions
}
while (true) { // main loop
DateTime oldTime = currentTime;
currentTime = DateTime.Now;
double ellapsedMilliseconds = currentTime.Subtract(oldTime).TotalMilliseconds; // compute ellapsedmilliseconds
bool allDone = true;
Game.Update (ellapsedMilliseconds); // generate a new frame
for (int i = 0; i < NPC_NUMBER; ++i) {
allDone &= npcs [i].Done; // if one NPC is not done, allDone is false
}
if (allDone) // leave the main loop when all are done.
break;
}
System.Console.Out.WriteLine ("That's all folks!"); // show after main loop
}
private static TaskCompletionSource<double> frame = new TaskCompletionSource<double> ();
private static Random rand = new Random ();
}
}
使用系统;
使用System.Collections.Generic;
使用System.Threading.Tasks;
命名空间异步框架
{
班级游戏
{
静态公共随机Rand{
获取{return rand;}
}
静态公共任务框架{
获取{return frame.Task;}
}
公共静态无效更新(双ellapsedTime)
{
TaskCompletionSource previousFrame=frame;//保存上一个“frame”
frame=newtaskcompletionsource();//创建一个新的
previousFrame.SetResult(ellapsedTime);//使用旧的
}
公共静态void Main(字符串[]args)
{
int NPC_NUMBER=10;//NPC的数量10可以,10000可以
DateTime currentTime=DateTime.Now;//测量当前时间
列出NPC=新列表();//我们的NPC列表
对于(int i=0;i
这是一个非常简单的实现
问题
然而,它似乎并没有像预期的那样起作用
更准确地说,NPC_的数字是10、100或1000,我没有问题。但在10000或以上时,程序不再完成,它会写一段时间的“讲话”行,然后再也不会在控制台上运行。
虽然我不想在我的游戏中同时拥有10000个NPC,但它们也不会编写无聊的对话框,还会移动、设置动画、加载纹理等等。所以我想知道我的实现出了什么问题,以及我是否有机会修复它
我必须确定代码是在Mono下运行的。此外,“有问题”的值在您的位置可能不同,它可能是计算机特定的东西。如果这个问题在.Net下无法重现,我将在Windows下尝试
编辑
在.Net中,它最多运行1000000次,尽管初始化需要时间,但这可能是一个特定于Mono的问题。
调试人员的数据告诉我,确实有NPC没有完成。遗憾的是,目前还没有关于原因的信息
编辑2
在Monodevelop下,在不使用调试器的情况下启动应用程序似乎可以解决问题。不知道为什么
结束语
我意识到这是一个非常非常冗长的问题,我希望你能花点时间阅读,我真的很想理解我做错了什么
事先非常感谢。我不确定这是否相关,但这句话让我印象深刻:
allDone &= npcs [i].Done; // if one NPC is not done, allDone is false
我建议等待您的Perform
方法。由于您希望所有NPC异步运行,请将它们的Perform
Task
添加到列表中,并使用Task.WaitAll(…)
完成
反过来,你也可以这样做
allDone &= npcs [i].Done; // if one NPC is not done, allDone is false
var scriptList = new List<Task>(npcs.Count);
for (int i = 0; i < NPC_NUMBER; ++i)
{
var scriptTask = npcs[i].Perform();
scriptList.Add(scriptTask);
scriptTask.Start();
}
Task.WaitAll(scriptList.ToArray());
Game.Update(ellapsedMilliseconds); // generate a new frame
GC.Collect(0, GCCollectionMode.Optimized, true);
protected async Task Speak(string message)
{
int previousLetters = 0;
double letters = 0.0;
while (letters < message.Length)
{
double ellapsedTime = await Game.Frame;
await Task.Yield();
Console.WriteLine("Speak on thread: " + System.Threading.Thread.CurrentThread.ManagedThreadId);
letters += ellapsedTime * LETTERS_PER_MILLISECOND;
if (letters - previousLetters > 1.0)
{
System.Console.Out.WriteLine("[" + this.id.ToString() + "]" + message.Substring(0, (int)Math.Floor(Math.Min(letters, message.Length))));
previousLetters = (int)Math.Floor(letters);
}
}
}