¿Cómo evito que XNA pause las actualizaciones mientras la ventana se redimensiona / mueve?

15

XNA deja de llamar Updatey Drawmientras la ventana del juego se cambia de tamaño o se mueve.

¿Por qué? ¿Hay alguna manera de prevenir este comportamiento?

(Hace que mi código de red se desincronice, porque los mensajes de red no se están bombeando).

Andrew Russell
fuente

Respuestas:

20

Si. Implica una pequeña cantidad de juegos con los elementos internos de XNA. Tengo una demostración de una solución en la segunda mitad de este video .

Antecedentes:

La razón por la que esto sucede es porque XNA suspende su reloj de juego cuando Gameel subyacente Formcambia de tamaño (o se mueve, los eventos son los mismos). Esto, a su vez, se debe a que está impulsando su ciclo de juego Application.Idle. Cuando se cambia el tamaño de una ventana, Windows hace algunas locuras de bucle de mensajes win32 que evitan que se Idleactive.

Entonces, si Gameno suspendió su reloj, después de un cambio de tamaño, habrá acumulado una enorme cantidad de tiempo que luego debería actualizar en una sola ráfaga, claramente no es deseable.

Como arreglarlo:

Hay una larga cadena de eventos en XNA (si se utiliza Game, esto no se aplica a las ventanas personalizados) que, básicamente, se conecta el evento de cambio de tamaño de los subyacentes Form, a Suspendy Resumemétodos para el reloj de juego.

Estos son privados, por lo que deberá utilizar la reflexión para desenganchar los eventos (donde thises a Game):

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

Aquí está el encantamiento necesario:

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

(Puede desconectar la cadena en otro lugar, puede usar ILSpy para resolverlo. Pero no hay nada más que el cambio de tamaño de la ventana que suspenda el temporizador, por lo que estos eventos son tan buenos como cualquier otro).

Luego, debe proporcionar su propia fuente de ticks, para cuando XNA no está funcionando Application.Idle. Simplemente usé un System.Windows.Forms.Timer:

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

Ahora, timer_Tickeventualmente llamará Game.Tick. Ahora que el reloj no está suspendido, somos libres de seguir usando el propio código de sincronización de XNA. ¡Fácil! No del todo: solo queremos que nuestro tictac manual suceda cuando el tictac incorporado de XNA no ocurre. Aquí hay un código simple para hacer eso:

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

    manualTickCount++;
}

Y luego, Updateingrese este código para restablecer el contador si XNA está funcionando normalmente.

if(!manualTick)
    manualTickCount = 0;

Tenga en cuenta que este tictac es considerablemente menos preciso que el de XNA. Sin embargo, debería permanecer más o menos alineado con el tiempo real.


Todavía hay una pequeña falla en este código. Win32, bendiga su estúpida y fea cara, detiene esencialmente todos los mensajes durante unos cientos de milisegundos cuando hace clic en la barra de título de una ventana, antes de que el mouse se mueva (vea ese mismo enlace nuevamente ). Esto evita que nuestro Timer(que está basado en mensajes) haga tictac.

Debido a que ahora no suspendemos el reloj del juego de XNA, cuando nuestro temporizador finalmente comience a funcionar nuevamente, obtendrá una explosión de actualizaciones. Y, lamentablemente, XNA limita la cantidad de tiempo acumulable a una cantidad ligeramente menor que la cantidad de tiempo que Windows detiene los mensajes cuando hace clic en la barra de título. Entonces, si un usuario hace clic y mantiene presionada su barra de título, sin moverla, obtendrá una explosión de actualizaciones y se quedará unos pocos cuadros por debajo del tiempo real.

(Pero esto debería ser lo suficientemente bueno para la mayoría de los casos. El temporizador de XNA ya sacrifica la precisión para evitar la tartamudez, por lo que si necesita un tiempo perfecto , debe hacer otra cosa)

Andrew Russell
fuente
2
Buena respuesta integral.
MichaelHouse
1
¿Por qué exactamente hiciste la pregunta y luego la respondiste al instante?
Alastair Pitts
44
Buena pregunta. Es explícitamente alentado . La interfaz de usuario de la pregunta incluso tiene un cuadro de "Responde tu propia pregunta". Originalmente iba a hacer una respuesta a esta pregunta , pero eso sería solo una edición de una respuesta anterior, y mi solución no resuelve parte de esa pregunta. De esta forma, obtiene una mejor visibilidad (ser nuevo y ser XNA en GDSE en lugar de SO). Y la información encaja mejor aquí, en lugar de mi blog.
Andrew Russell