Як запобігти XNA призупиняти оновлення під час зміни розміру / переміщення вікна?


15

XNA припиняє дзвінки Updateі Drawпід час зміни вікна і переміщення вікна гри.

Чому? Чи є спосіб запобігти такій поведінці?

(Це спричиняє десинхронізацію мого мережевого коду, оскільки мережеві повідомлення не перекачуються.)

Відповіді:


20

Так. Він передбачає невелику кількість возитися з внутрішніми органами XNA. Я демонстрував виправлення у другій половині цього відео .

Фон:

Причина цього відбувається в тому, що XNA призупиняє свої ігрові годинники, коли розмір Gameоснови Formзнижується (або переміщується, події однакові). Це, в свою чергу, тому, що він веде свій ігровий цикл Application.Idle. Коли розмір вікна змінюється, Windows виконує деякі шалені повідомлення з циклу повідомлень win32, що запобігає Idleзапусканню.

Отже, якщо Gameне призупинити його годинник, після зміни розміру він накопичить величезну кількість часу, яке йому доведеться потім оновлювати за один вибух - явно не бажано.

Як це виправити:

У XNA існує довгий ланцюжок подій (якщо ви використовуєте Game, це не стосується спеціальних вікон), які в основному пов'язують подію зміни розміру основної Form, Suspendта Resumeметоди ігрового годинника.

Вони є приватними, тому вам потрібно використовувати роздуми, щоб скасувати події (де thisце Game):

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

Ось необхідний заклик:

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

(Ви можете від'єднати ланцюг в іншому місці, ви можете використовувати ILSpy, щоб розібратися в цьому. Але немає нічого іншого, крім розміру вікна, який призупиняє таймер - тому ці події такі ж хороші, як і будь-які.)

Тоді вам потрібно надати власне джерело галочок, коли XNA не тикне Application.Idle. Я просто використав System.Windows.Forms.Timer:

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

Тепер, timer_Tickзрештою, подзвонить Game.Tick. Тепер, коли годинник не припинено, ми можемо просто використовувати власний синхронізуючий код XNA. Легко! Не зовсім так: ми хочемо, щоб наші ручні відмітки відбувалися лише тоді, коли вбудованого галочки XNA не відбувається. Ось простий код для цього:

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

    manualTickCount++;
}

А потім, введіть Updateцей код, щоб скинути лічильник, якщо XNA тикає нормально.

if(!manualTick)
    manualTickCount = 0;

Зауважте, що цей відмітка значно менш точний, ніж XNA. Однак він повинен залишатися більш-менш вишикуваним у режимі реального часу.


У цьому коді є ще один невеликий недолік. Win32, благословляючи його дурне потворне обличчя, зупиняє фактично всі повідомлення на кілька сотень мілісекунд, коли ви клацаєте рядок заголовка вікна, перш ніж миша дійсно переміститься (див. Те саме посилання ще раз ). Це не дозволяє нашим Timer(на основі повідомлень) відмітити.

Оскільки ми зараз не призупиняємо ігровий годинник XNA, коли наш таймер врешті-решт знову почне тикати, ви отримаєте безліч оновлень. І, на жаль, XNA обмежує кількість накопичуваного часу на суму, трохи меншу, ніж тривалість часу, коли Windows зупиняє повідомлення при натисканні рядка заголовка. Тож якщо користувач натисне та утримує ваш рядок заголовка, не переміщуючи його, ви отримаєте сплеск оновлень і втратите кілька кадрів у режимі реального часу.

(Але це все-таки має бути досить хорошим для більшості випадків. Таймер XNA вже жертвує точністю, щоб запобігти заїканню, тому, якщо вам потрібні ідеальні терміни, вам слід зробити щось інше.)


2
Приємна вичерпна відповідь.
MichaelHouse

1
Чому саме ви задали питання, а потім миттєво відповіли на нього?
Аластер Піттс

4
Справедливе запитання. Це прямо рекомендується . У інтерфейсі запитання навіть є поле "Відповісти на власне запитання". Спочатку я збирався зробити це відповіддю на це питання , але це було б просто редагуванням старої відповіді, і моє рішення не вирішує частину цього питання. Таким чином, вона отримує кращу видимість (будучи новою та XNA на GDSE замість SO). І інформація тут краще підходить, ніж мій блог.
Ендрю Рассел
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.