Этот Application.Run
вызов приводит в действие ваш насос сообщений Windows, который, в конечном счете, обеспечивает все события, которые вы можете подключить к Form
классу (и другим). Чтобы создать игровой цикл в этой экосистеме, вы хотите прослушивать, когда насос сообщений приложения пуст и, пока он остается пустым, выполните типичные шаги «обработать состояние ввода, обновить игровую логику, визуализировать сцену» в прототипическом игровом цикле. ,
В Application.Idle
событии срабатывает один раз каждый раз , когда очередь сообщений приложения будет очищена , и приложение переходит в исходное состояние. Вы можете перехватить событие в конструкторе вашей основной формы:
class MainForm : Form {
public MainForm () {
Application.Idle += HandleApplicationIdle;
}
void HandleApplicationIdle (object sender, EventArgs e) {
//TODO: Implement me.
}
}
Затем вы должны быть в состоянии определить, если приложение все еще бездействует. Idle
Событие срабатывает только один раз, когда приложение становится простаивает. Он не запускается снова, пока сообщение не попадает в очередь, а затем очередь снова очищается. Windows Forms не предоставляет метод для запроса состояния очереди сообщений, но вы можете использовать службы вызова платформы, чтобы делегировать запрос собственной функции Win32, которая может ответить на этот вопрос . Декларация import для PeekMessage
и поддерживаемых типов выглядит следующим образом:
[StructLayout(LayoutKind.Sequential)]
public struct NativeMessage
{
public IntPtr Handle;
public uint Message;
public IntPtr WParameter;
public IntPtr LParameter;
public uint Time;
public Point Location;
}
[DllImport("user32.dll")]
public static extern int PeekMessage(out NativeMessage message, IntPtr window, uint filterMin, uint filterMax, uint remove);
PeekMessage
в основном позволяет просматривать следующее сообщение в очереди; возвращает true, если он существует, в противном случае - false. Для целей этой проблемы ни один из параметров не имеет особого значения: имеет значение только возвращаемое значение. Это позволяет вам написать функцию, которая сообщает вам, если приложение все еще находится в режиме ожидания (то есть, в очереди еще нет сообщений):
bool IsApplicationIdle () {
NativeMessage result;
return PeekMessage(out result, IntPtr.Zero, (uint)0, (uint)0, (uint)0) == 0;
}
Теперь у вас есть все, что нужно для написания полного цикла игры:
class MainForm : Form {
public MainForm () {
Application.Idle += HandleApplicationIdle;
}
void HandleApplicationIdle (object sender, EventArgs e) {
while(IsApplicationIdle()) {
Update();
Render();
}
}
void Update () {
// ...
}
void Render () {
// ...
}
[StructLayout(LayoutKind.Sequential)]
public struct NativeMessage
{
public IntPtr Handle;
public uint Message;
public IntPtr WParameter;
public IntPtr LParameter;
public uint Time;
public Point Location;
}
[DllImport("user32.dll")]
public static extern int PeekMessage(out NativeMessage message, IntPtr window, uint filterMin, uint filterMax, uint remove);
}
Кроме того, этот подход максимально приближен (с минимальной зависимостью от P / Invoke) к каноническому собственному игровому циклу Windows, который выглядит следующим образом:
while (!done) {
if (PeekMessage(&message, window, 0, 0, PM_REMOVE)){
TranslateMessage(&message);
DispatchMessage(&message);
}
else {
Update();
Render();
}
}
WM_TIMER
основе)Согласился с ответом Джоша, просто хочу добавить мои 5 центов. Цикл сообщений WinForms по умолчанию (Application.Run) можно заменить следующим (без p / invoke):
Также, если вы хотите внедрить некоторый код в насос сообщений, используйте это:
источник
Я понимаю, что это старая тема, но я хотел бы предоставить две альтернативы методикам, предложенным выше. Прежде чем я углублюсь в них, вот некоторые подводные камни с предложениями, сделанными до сих пор:
PeekMessage несет значительные накладные расходы, как и методы библиотеки, которые его вызывают (SlimDX IsApplicationIdle).
Если вы хотите использовать буферизованный RawInput, вам нужно будет опросить обработчик сообщений с помощью PeekMessage в другом потоке, отличном от потока пользовательского интерфейса, чтобы не вызывать его дважды.
Application.DoEvents не предназначен для вызова в узком цикле, проблемы GC будут возникать быстро.
При использовании Application.Idle или PeekMessage, поскольку вы выполняете работу только в режиме ожидания, ваша игра или приложение не будут работать при перемещении или изменении размера окна без запахов кода.
Чтобы обойти это (кроме 2, если вы идете по дороге RawInput), вы можете:
Создайте Threading.Thread и запустите там свой игровой цикл.
Создайте Threading.Tasks.Task с флагом IsLongRunning и запустите его там. В наши дни Microsoft рекомендует использовать Задачи вместо потоков, и нетрудно понять, почему.
Оба эти метода изолируют ваш графический API от потока пользовательского интерфейса и насоса сообщений, как это рекомендуется. Обработка уничтожения ресурса / состояния и воссоздания во время изменения размера окна также упрощается и эстетически намного более профессиональна, когда делается из готового (проявляя должную осторожность, чтобы избежать взаимоблокировок с насосом сообщений) извне потока пользовательского интерфейса.
источник