Jak zapobiec wstrzymywaniu aktualizacji przez XNA podczas zmiany rozmiaru / przenoszenia okna?


15

XNA przestaje dzwonić Updatei Drawpodczas zmiany rozmiaru lub przenoszenia okna gry.

Dlaczego? Czy istnieje sposób, aby temu zapobiec?

(Powoduje desynchronizację mojego kodu sieciowego, ponieważ wiadomości sieciowe nie są pompowane).

Odpowiedzi:


20

Tak. Wymaga to niewielkiego bałaganu z elementami wewnętrznymi XNA. Mam demonstrację poprawki w drugiej połowie tego filmu .

Tło:

Dzieje się tak dlatego, że XNA zawiesza swój zegar gry, gdy rozmiar Gamepodstawy Formjest zmieniany (lub przenoszone, zdarzenia są takie same). Wynika to z tego, że napędza pętlę gry Application.Idle. Po zmianie rozmiaru okna system Windows wykonuje szalone pętle komunikatów win32, które uniemożliwiają Idleuruchomienie.

Tak więc, jeśli Gamenie zawiesiłby swojego zegara, po zmianie rozmiaru zgromadziłby ogromną ilość czasu, który musiałby następnie zaktualizować w jednej serii - wyraźnie nie jest to pożądane.

Jak to naprawić:

Jest to długi łańcuch wydarzeń w XNA (jeśli używasz Game, to nie stosuje się do niestandardowych okien), które w zasadzie łączy zdarzenia zmiany rozmiaru z bazowych Form, do Suspendi Resumemetod zegara gry.

Są prywatne, więc musisz użyć refleksji, aby odblokować wydarzenia (gdzie thisjest a Game):

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

Oto niezbędne zaklęcie:

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

(Możesz odłączyć łańcuch gdzie indziej, możesz użyć ILSpy, aby to rozgryźć. Ale nie ma nic innego oprócz zmiany rozmiaru okna, która wstrzymuje stoper - więc te zdarzenia są tak dobre, jak inne.)

Następnie musisz podać własne źródło tykania, ponieważ gdy XNA nie tyka Application.Idle. Po prostu użyłem System.Windows.Forms.Timer:

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

Teraz w timer_Tickkońcu zadzwoni Game.Tick. Teraz, gdy zegar nie jest zawieszony, możemy nadal używać własnego kodu czasowego XNA. Łatwo! Niezupełnie: chcemy, aby nasze ręczne tykanie odbywało się tylko wtedy, gdy wbudowane tykanie XNA nie ma miejsca. Oto prosty kod, aby to zrobić:

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

    manualTickCount++;
}

A następnie Updatewstaw ten kod, aby zresetować licznik, jeśli XNA tyka normalnie.

if(!manualTick)
    manualTickCount = 0;

Zauważ, że to tykanie jest znacznie mniej dokładne niż XNA. Powinno jednak pozostać mniej więcej w linii z czasem rzeczywistym.


W tym kodzie jest jeszcze jedna mała wada. Win32, pobłogosław swoją głupią, brzydką twarz, zatrzymuje zasadniczo wszystkie wiadomości na kilkaset milisekund po kliknięciu paska tytułu okna, zanim mysz faktycznie się poruszy (zobacz ten sam link ponownie ). Zapobiega to tykaniu naszego Timer(opartego na wiadomościach).

Ponieważ nie zawieszamy teraz zegara gry XNA, kiedy nasz zegar zacznie w końcu znowu tykać, otrzymasz serię aktualizacji. I niestety XNA ogranicza czas do akumulacji do kwoty nieco mniejszej niż czas, w którym system Windows zatrzymuje wiadomości po kliknięciu paska tytułu. Jeśli więc użytkownik kliknie i przytrzyma pasek tytułu, nie przesuwając go, dostaniesz serię aktualizacji i spadnie kilka klatek w czasie rzeczywistym.

(Ale w większości przypadków powinno to być wystarczające . Zegar XNA już poświęca dokładność, aby zapobiec jąkaniu, więc jeśli potrzebujesz idealnego czasu, powinieneś zrobić coś innego.)


2
Ładna kompleksowa odpowiedź.
MichaelHouse

1
Dlaczego dokładnie zadałeś pytanie, a następnie natychmiast na nie odpowiedziałeś?
Alastair Pitts

4
Uczciwe pytanie Jest to wyraźnie zalecane . Interfejs pytania ma nawet pole „Odpowiedz na swoje pytanie”. Początkowo zamierzałem udzielić odpowiedzi na to pytanie , ale byłaby to tylko edycja starej odpowiedzi, a moje rozwiązanie nie rozwiązuje części tego pytania. W ten sposób zyskuje lepszą widoczność (będąc nowym i będąc XNA na GDSE zamiast SO). Informacje lepiej pasują tutaj, niż do mojego bloga.
Andrew Russell
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.