XNA smette di chiamare Update
e Draw
mentre 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.)
XNA smette di chiamare Update
e Draw
mentre 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:
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 Game
il 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 Idle
scatto.
Quindi, se Game
non 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 Suspend
e ai Resume
metodi 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_Tick
alla 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.)