Sim. Envolve uma pequena quantidade de mexer com os internos do XNA. Eu tenho uma demonstração de uma correção na segunda metade deste vídeo .
Fundo:
A razão pela qual isso acontece é porque o XNA suspende o relógio do jogo quando Game
o subjacente Form
é redimensionado (ou movido, os eventos são os mesmos). Isso, por sua vez, é porque está direcionando seu ciclo de jogo Application.Idle
. Quando uma janela é redimensionada, o Windows faz algumas coisas malucas do loop de mensagens win32 que impedem o Idle
disparo.
Portanto, se Game
não suspendesse o relógio, após um redimensionamento, ele acumularia uma quantidade enorme de tempo que seria necessário atualizar em uma única explosão - claramente não desejável.
Como corrigi-lo:
Há uma longa cadeia de eventos no XNA (se você usar Game
, isso não se aplica a janelas personalizadas) que basicamente conecta o evento de redimensionamento do subjacente Form
a métodos Suspend
e Resume
para o relógio do jogo.
Como são particulares, você precisará usar a reflexão para soltar os eventos (onde this
está um Game
):
this.host.Suspend = null;
this.host.Resume = null;
Aqui está o encantamento necessário:
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);
(Você pode desconectar a cadeia em outro lugar, pode usar o ILSpy para descobrir isso. Mas não há mais nada além do redimensionamento da janela que suspende o timer - para que esses eventos sejam tão bons quanto os outros.)
Então, você precisa fornecer sua própria fonte de ticks, para quando o XNA não estiver passando Application.Idle
. Eu simplesmente usei um System.Windows.Forms.Timer
:
timer = new Timer();
timer.Interval = (int)(this.TargetElapsedTime.TotalMilliseconds);
timer.Tick += new EventHandler(timer_Tick);
timer.Start();
Agora, timer_Tick
acabará por ligar Game.Tick
. Agora que o relógio não está suspenso, estamos livres para continuar usando o código de tempo do XNA. Fácil! Não é bem assim: queremos que o nosso ticking manual aconteça apenas quando o ticking interno do XNA não acontecer. Aqui está um código simples para fazer isso:
bool manualTick;
int manualTickCount = 0;
void timer_Tick(object sender, EventArgs e)
{
if(manualTickCount > 2)
{
manualTick = true;
this.Tick();
manualTick = false;
}
manualTickCount++;
}
E, em seguida, Update
insira esse código para redefinir o contador se o XNA estiver funcionando normalmente.
if(!manualTick)
manualTickCount = 0;
Observe que essa marcação é consideravelmente menos precisa que os XNAs. No entanto, deve permanecer mais ou menos alinhado com o tempo real.
Ainda existe uma pequena falha nesse código. O Win32, abençoe sua cara feia e estúpida, para essencialmente todas as mensagens por algumas centenas de milissegundos quando você clica na barra de título de uma janela, antes que o mouse se mova (consulte o mesmo link novamente ). Isso impede que o nosso Timer
(que é baseado em mensagens) passe.
Como agora não suspendemos o relógio do jogo da XNA, quando o temporizador começar a funcionar novamente, você receberá uma explosão de atualizações. E, infelizmente, o XNA limita a quantidade de tempo acumulável a uma quantidade um pouco menor que a quantidade de tempo que o Windows interrompe as mensagens quando você clica na barra de título. Portanto, se um usuário clicar e segurar sua barra de título sem movê-la, você receberá uma explosão de atualizações e ficará alguns quadros aquém do tempo real.
(Mas isso ainda deve ser bom o suficiente para a maioria dos casos. O timer do XNA já sacrifica a precisão para evitar a gagueira; portanto, se você precisar de um timing perfeito , faça outra coisa.)