Come posso impedire a XNA di mettere in pausa gli aggiornamenti mentre la finestra viene ridimensionata / spostata?


15

XNA smette di chiamare Updatee Drawmentre la finestra di gioco viene ridimensionata o spostata.

Perché? C'è un modo per prevenire questo comportamento?

(Fa desincronizzare il mio codice di rete, perché i messaggi di rete non vengono pompati.)

Risposte:


20

Sì. Implica una piccola quantità di problemi con gli interni di XNA. Ho una dimostrazione di una correzione nella seconda metà di questo video .

Sfondo:

La ragione per cui ciò accade è perché XNA sospende il suo clock di gioco quando Gameil sottostante Formè ridimensionato (o spostato, gli eventi sono gli stessi). Questo, a sua volta, è perché sta guidando il suo loop di gioco da Application.Idle. Quando una finestra viene ridimensionata, Windows fa alcune cose pazze del ciclo di messaggi win32 che impediscono lo Idlescatto.

Quindi, se Gamenon sospendere il suo orologio, dopo un ridimensionamento avrà accumulato una quantità enorme di tempo che avrebbe poi dovuto aggiornamento attraverso in un'unica sequenza - chiaramente non è auspicabile.

Come sistemarlo:

Esiste una lunga catena di eventi in XNA (se si utilizza Game, ciò non si applica alle finestre personalizzate) che fondamentalmente collega l'evento di ridimensionamento del sottostante Form, a Suspende ai Resumemetodi per l'orologio di gioco.

Questi sono privati, quindi dovrai usare la riflessione per sganciare gli eventi (dove thisè un Game):

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

Ecco l'incantesimo necessario:

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);

(Potresti disconnettere la catena altrove, puoi usare ILSpy per capirlo. Ma non c'è nient'altro oltre al ridimensionamento della finestra che sospende il timer - quindi questi eventi sono buoni come tutti.)

Quindi è necessario fornire la propria fonte di spunta, per quando XNA non sta spuntando Application.Idle. Ho semplicemente usato un System.Windows.Forms.Timer:

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

Ora, timer_Tickalla fine chiamerà Game.Tick. Ora che l'orologio non è sospeso, siamo liberi di continuare a utilizzare il codice di temporizzazione di XNA. Facile! Non proprio: vogliamo che il nostro ticking manuale avvenga solo quando il ticking integrato di XNA non si verifica. Ecco un semplice codice per farlo:

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

    manualTickCount++;
}

E poi, in Update, inserisci questo codice per resettare il contatore se XNA sta spuntando normalmente.

if(!manualTick)
    manualTickCount = 0;

Si noti che questo ticking è notevolmente meno preciso di quello degli XNA. Tuttavia, dovrebbe rimanere più o meno allineato con il tempo reale.


C'è ancora un piccolo difetto in questo codice. Win32, benedici la sua stupida faccia brutta, interrompe essenzialmente tutti i messaggi per alcune centinaia di millisecondi quando fai clic sulla barra del titolo di una finestra, prima che il mouse si sposti effettivamente (vedi di nuovo lo stesso link ). Questo impedisce al nostro Timer(che è basato sui messaggi) di spuntare.

Poiché ora non sospendiamo l'orologio di gioco di XNA, quando il nostro timer ricomincia a ticchettare, otterrai una raffica di aggiornamenti. E, purtroppo, XNA limita la quantità di tempo accumulabile a un importo leggermente inferiore alla durata del tempo in cui Windows interrompe i messaggi quando si fa clic sulla barra del titolo. Quindi, se un utente fa clic e tieni premuta la barra del titolo, senza spostarla, otterrai una raffica di aggiornamenti e perderai alcuni fotogrammi in tempo reale.

(Ma questo dovrebbe essere ancora abbastanza buono per la maggior parte dei casi. Il timer di XNA sacrifica già la precisione per evitare la balbuzie, quindi se hai bisogno di un tempismo perfetto , dovresti fare qualcos'altro.)


2
Bella risposta globale.
MichaelHouse

1
Perché esattamente hai posto la domanda e poi hai risposto immediatamente?
Alastair Pitts

4
Domanda equa. È esplicitamente incoraggiato . L'interfaccia utente della domanda ha persino una casella "Rispondi alla tua domanda". Inizialmente avevo intenzione di renderlo una risposta a questa domanda , ma sarebbe solo una modifica di una vecchia risposta, e la mia soluzione non risolve parte di quella domanda. In questo modo ottiene una migliore visibilità (essere nuovo ed essere XNA su GDSE anziché SO). E le informazioni si adattano meglio qui, piuttosto che al mio blog.
Andrew Russell,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.