XNA przestaje dzwonić Update
i Draw
podczas 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).
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 Game
podstawy Form
jest 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ą Idle
uruchomienie.
Tak więc, jeśli Game
nie 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 Suspend
i Resume
metod zegara gry.
Są prywatne, więc musisz użyć refleksji, aby odblokować wydarzenia (gdzie this
jest 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_Tick
koń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 Update
wstaw 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.)