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


15

XNA停止调用UpdateDraw并且在调整游戏窗口大小或移动游戏窗口时。

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

(这会导致我的网络代码不同步,因为没有发送网络消息。)

Answers:


20

是。它涉及到XNA内部的一些混乱。在此视频的后半部分中,我已演示了修复方法。

背景:

发生这种情况的原因是因为XNA在调整Game底层Form的大小(或移动的事件是相同的)时暂停了其游戏时钟。反过来,这是因为它从驱动了游戏循环Application.Idle。调整窗口大小时,Windows会执行一些疯狂的win32消息循环操作,以防止Idle触发事件。

因此,如果Game不暂停时钟,则在调整大小后将积累大量时间,然后必须在单个突发中进行更新-这显然是不希望的。

解决方法:

还有很长的XNA一连串事件(如果你使用Game,这并不适用于自定义窗口),基本上连接的基本的调整大小事件Form,以SuspendResume方法的比赛计时钟。

这些是私有的,因此您需要使用反射来取消挂接事件(在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内置的滴答声时发生手动滴答声。这是一些简单的代码可以做到这一点:

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的准确。但是,它应该或多或少与实时保持一致。


此代码中仍然存在一个小缺陷。Win32祝福它愚蠢的丑陋面孔,当您单击窗口的标题栏时,实际上在鼠标实际移动之前,它会停止所有消息几百毫秒(请再次查看同一链接)。这样可以防止我们的Timer(基于消息的)滴答答答。

由于我们现在不暂停XNA的游戏时钟,因此当计时器最终再次开始计时时,您将获得大量更新。而且,可悲的是,XNA将可累积时间的上限限制为小于Windows单击标题栏时停止消息的时间长度。因此,如果用户恰好单击并按住标题栏而不移动它,您将获得大量的更新,并且比实时时间少了几帧。

(但是对于大多数情况,这仍然足够好。XNA的计时器已经牺牲了准确性来防止卡顿,因此,如果需要完美的计时,则应该执行其他操作。)


2
不错的综合答案。
MichaelHouse

1
您为什么要问这个问题,然后立即回答呢?
Alastair Pitts 2013年

4
公平的问题。它明确鼓励。问题用户界面甚至有一个“回答自己的问题”框。本来我打算将其作为该问题的答案,但这只是对旧答案的修改,而我的解决方案不能解决该问题的一部分。这样,它可以获得更好的可见性(是新的并且在GDSE上成为XNA而不是SO)。而且信息比我的博客更适合此处。
Andrew Russell
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.