Ja. Es ist mit einigem Durcheinander mit den XNA-Interna verbunden. Ich habe in der zweiten Hälfte dieses Videos eine Demonstration eines Fixes .
Hintergrund:
Der Grund dafür ist, dass XNA seine Spieluhr anhält, wenn Game
die Form
Größe des zugrunde liegenden Objekts geändert wird (oder verschoben wird, da die Ereignisse gleich sind). Dies liegt wiederum daran, dass es seine Spielrunde ab fährt Application.Idle
. Wenn die Größe eines Fensters geändert wird, führt Windows einige verrückte Win32-Nachrichtenschleifen aus, die das Auslösen verhindern Idle
.
Wenn Game
die Uhr also nicht angehalten wurde, hat sie nach einer Größenänderung eine enorme Menge an Zeit angesammelt, die sie dann in einem einzigen Burst aktualisieren müsste - eindeutig nicht wünschenswert.
Wie man es repariert:
Es gibt eine lange Kette von Ereignissen in XNA (wenn Sie Game
, dies gilt nicht für benutzerdefinierte Fenster), die im Grunde das Resize - Ereignis der zugrunde liegenden verbindet Form
, zu Suspend
und Resume
Methoden für das Spiel Uhr.
Da diese privat sind, müssen Sie Reflection verwenden , um die Ereignisse aufzuheben (wo this
ist a Game
):
this.host.Suspend = null;
this.host.Resume = null;
Hier ist die notwendige Beschwörung:
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);
(Sie können die Kette auch an anderer Stelle trennen. Sie können ILSpy verwenden, um dies herauszufinden. Es gibt jedoch nichts anderes als die Fenstergröße, die den Timer anhält. Diese Ereignisse sind also so gut wie alle anderen.)
Dann müssen Sie Ihre eigene Tick-Quelle angeben, wenn XNA nicht aktiviert ist Application.Idle
. Ich habe einfach ein System.Windows.Forms.Timer
:
timer = new Timer();
timer.Interval = (int)(this.TargetElapsedTime.TotalMilliseconds);
timer.Tick += new EventHandler(timer_Tick);
timer.Start();
Nun timer_Tick
werde irgendwann anrufen Game.Tick
. Jetzt, da die Uhr nicht angehalten ist, können wir weiterhin den eigenen Timing-Code von XNA verwenden. Einfach! Nicht ganz: Wir möchten, dass unser manuelles Ticking nur ausgeführt wird, wenn das in XNA integrierte Ticking nicht ausgeführt wird. Hier ist ein einfacher Code, um das zu tun:
bool manualTick;
int manualTickCount = 0;
void timer_Tick(object sender, EventArgs e)
{
if(manualTickCount > 2)
{
manualTick = true;
this.Tick();
manualTick = false;
}
manualTickCount++;
}
Setzen Sie dann Update
diesen Code ein, um den Zähler zurückzusetzen, wenn XNA normal tickt.
if(!manualTick)
manualTickCount = 0;
Beachten Sie, dass dieses Ticking wesentlich ungenauer ist als das von XNA. Es sollte jedoch mehr oder weniger in Echtzeit sein.
Es gibt noch einen kleinen Fehler in diesem Code. Win32 stoppt für ein paar hundert Millisekunden im Wesentlichen alle Nachrichten, wenn Sie auf die Titelleiste eines Fensters klicken, bevor sich die Maus tatsächlich bewegt (siehe denselben Link erneut ). Dies verhindert, dass unser Timer
(was nachrichtenbasiert ist) tickt.
Da wir jetzt die Spieluhr von XNA nicht aussetzen, werden Sie eine Reihe von Updates erhalten, wenn unser Timer irgendwann wieder zu ticken beginnt. Und leider begrenzt XNA die akkumulierbare Zeit auf einen Wert, der etwas kürzer ist als die Zeit, die Windows zum Beenden von Nachrichten benötigt, wenn Sie auf die Titelleiste klicken. Wenn ein Benutzer also auf die Titelleiste klickt und sie hält, ohne sie zu verschieben, werden zahlreiche Aktualisierungen angezeigt und die Echtzeit wird um einige Frames verkürzt.
(In den meisten Fällen sollte dies jedoch noch ausreichen . Der Timer von XNA beeinträchtigt bereits die Genauigkeit, um ein Stottern zu verhindern. Wenn Sie also ein perfektes Timing benötigen , sollten Sie etwas anderes tun.)