Wie verhindere ich, dass XNA Aktualisierungen anhält, während die Fenstergröße geändert / verschoben wird?


15

XNA beendet den Anruf Updateund Drawwährend die Größe des Spielfensters geändert oder verschoben wird.

Warum? Gibt es eine Möglichkeit, dieses Verhalten zu verhindern?

(Mein Netzwerkcode wird dadurch desynchronisiert, da keine Netzwerknachrichten gepumpt werden.)

Antworten:


20

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 Gamedie FormGröß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 Gamedie 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 Suspendund ResumeMethoden für das Spiel Uhr.

Da diese privat sind, müssen Sie Reflection verwenden , um die Ereignisse aufzuheben (wo thisist 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_Tickwerde 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 Updatediesen 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.)


2
Schöne umfassende Antwort.
MichaelHouse

1
Warum genau hast du die Frage gestellt und sie dann sofort beantwortet?
Alastair Pitts

4
Faire Frage. Es wird ausdrücklich empfohlen . In der Benutzeroberfläche der Frage befindet sich sogar ein Feld "Beantworten Sie Ihre eigene Frage". Ursprünglich wollte ich diese Frage beantworten , aber das wäre nur eine Überarbeitung einer alten Antwort, und meine Lösung löst einen Teil dieser Frage nicht. Auf diese Weise wird die Sichtbarkeit verbessert (neu und XNA auf GDSE anstelle von SO). Und die Info passt hier besser als mein Blog.
Andrew Russell
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.