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 Game
el subyacente Form
cambia 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 Idle
active.
Entonces, si Game
no 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 Suspend
y Resume
métodos para el reloj de juego.
Estos son privados, por lo que deberá utilizar la reflexión para desenganchar los eventos (donde this
es 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_Tick
eventualmente 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, Update
ingrese 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)