XNA перестает звонить Update
и Draw
пока игровое окно изменяется или перемещается.
Почему? Есть ли способ предотвратить такое поведение?
(Это приводит к десинхронизации моего сетевого кода, потому что сетевые сообщения не перекачиваются.)
Да. Это вовлекает небольшое количество возни с внутренностями 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 уже жертвует точностью, чтобы предотвратить заикание, поэтому, если вам нужно идеальное время, вам следует заняться чем-то другим.)