Como impedir que o XNA pause as atualizações enquanto a janela está sendo redimensionada / movida?

15

O XNA para de chamar Updatee Drawenquanto a janela do jogo está sendo redimensionada ou movida.

Por quê? Existe uma maneira de evitar esse comportamento?

(Faz com que meu código de rede seja dessincronizado, porque as mensagens de rede não estão sendo bombeadas.)

Andrew Russell
fonte

Respostas:

20

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 Gameo 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 Idledisparo.

Portanto, se Gamenã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 Forma métodos Suspende Resumepara o relógio do jogo.

Como são particulares, você precisará usar a reflexão para soltar os eventos (onde thisestá 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_Tickacabará 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, Updateinsira 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.)

Andrew Russell
fonte
2
Boa resposta abrangente.
Michaelhouse
11
Por que exatamente você fez a pergunta e respondeu instantaneamente?
Alastair Pitts
4
Questão justa. É explicitamente encorajado . A interface do usuário da pergunta ainda tem uma caixa "Responda sua própria pergunta". Originalmente, eu ia dar uma resposta a esta pergunta , mas isso seria apenas uma edição de uma resposta antiga, e minha solução não resolve parte dessa pergunta. Dessa forma, obtém melhor visibilidade (sendo novo e sendo XNA no GDSE em vez de SO). E a informação é mais adequada aqui, do que no meu blog.
Andrew Russell