如何防止XNA在窗口大小/移动时暂停更新?

当游戏窗口被调整或移动时,XNA停止调用UpdateDraw

为什么? 有没有办法来防止这种行为?

(这会导致我的networking代码不同步,因为networking消息没有被抽取。)

是。 这涉及到XNA内部的一些小问题。 我在这个video的后半部分有一个修复的演示。

背景:

发生这种情况的原因是,当Game的底层Form被resize(或移动,事件相同)时,XNA暂停游戏时钟。 这又是因为它正在从Application.Idle驱动它的游戏循环。 当一个窗口被resize,Windows会做一些疯狂的win32消息循环的东西 ,防止Idle

所以,如果Game没有暂停它的时钟,那么在resize之后,它将积累大量的时间,然后它将不得不通过一次突发更新 – 显然是不可取的。

如何解决它:

在XNA(如果使用Game ,这不适用于自定义窗口)中有很长的事件链,它基本上将基础Form的resize事件连接到游戏时钟的SuspendResume方法。

这些是私人的,所以你需要使用reflection来解开事件( this是一个Game ):

 this.host.Suspend = null; this.host.Resume = null; 

这是必要的咒语:

 object host = typeof(Game).GetField("host", BindingFlags.NonPublic | BindingFlags.Instance).GetValue(this); host.GetType().BaseType.GetField("Suspend", BindingFlags.NonPublic | BindingFlags.Instance).SetValue(host, null); host.GetType().BaseType.GetField("Resume", BindingFlags.NonPublic | BindingFlags.Instance).SetValue(host, null); 

(你可以在其他地方断开连接,你可以使用ILSpy来解决这个问题,除了调整窗口大小的时候,除了定时器外,没有别的东西了 – 所以这些事情和任何事情一样好。

那么你需要提供你自己的刻度源,因为当XNA没有从Application.Idle滴答。 我只是使用了一个System.Windows.Forms.Timer

 timer = new Timer(); timer.Interval = (int)(this.TargetElapsedTime.TotalMilliseconds); timer.Tick += new EventHandler(timer_Tick); timer.Start(); 

现在, timer_Tick最终会调用Game.Tick 。 现在时钟没有被暂停,我们可以继续使用XNA自己的时间码。 简单! 不完全是这样的:我们只希望在XNA的内置滴答function没有发生的时候手动滴答滴答地发生。 这是一些简单的代码来做到这一点:

 bool manualTick; int manualTickCount = 0; void timer_Tick(object sender, EventArgs e) { if(manualTickCount > 2) { manualTick = true; this.Tick(); manualTick = false; } manualTickCount++; } 

然后,在Update ,如果XNA正常运行,则将此代码重置为计数器。

 if(!manualTick) manualTickCount = 0; 

请注意,这个滴答是相当不准确的XNA的。 但是,它应该保持或多或less的实时排队。


这个代码还有一个小缺陷。 Win32,祝福它的愚蠢丑陋的面孔,当你点击窗口的标题栏时,在鼠标实际移动( 再次看到相同的链接 )之前,基本上停止了几百毫秒的所有消息。 这可以防止我们的Timer (这是基于消息)滴答作响。

因为我们现在不会暂停XNA的游戏时钟,当我们的计时器最终再次开始滴答时,你会得到一连串的更新。 而且,遗憾的是,XNA将可累积的时间量限制在稍微低于Windows单击标题栏时停止消息的时间长度。 因此,如果用户碰巧点击并按住标题栏,而不移动标题栏,则会得到一连串的更新, 并会在几秒内实时下降。

(但对大多数情况来说 ,这应该还是够用的 ,XNA的计时器已经牺牲了准确性来防止口吃,所以如果你需要完美的时间,你应该做其他的事情。)