Как предотвратить приостановку обновлений 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). И информация здесь лучше подходит, чем мой блог.
Эндрю Рассел