XNA припиняє дзвінки Update
і Draw
під час зміни вікна і переміщення вікна гри.
Чому? Чи є спосіб запобігти такій поведінці?
(Це спричиняє десинхронізацію мого мережевого коду, оскільки мережеві повідомлення не перекачуються.)
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 вже жертвує точністю, щоб запобігти заїканню, тому, якщо вам потрібні ідеальні терміни, вам слід зробити щось інше.)