La Application.Run
llamada impulsa su bomba de mensajes de Windows, que en última instancia es lo que alimenta todos los eventos que puede conectarForm
clase (y otros). Para crear un bucle de juego en este ecosistema, desea escuchar cuándo la bomba de mensajes de la aplicación está vacía y, mientras permanece vacía, realice los pasos típicos de "estado de entrada del proceso, actualice la lógica del juego, renderice la escena" del bucle de juego prototípico .
El Application.Idle
evento se dispara una vez cada vez que se vacía la cola de mensajes de la aplicación y la aplicación está pasando a un estado inactivo. Puede enganchar el evento en el constructor de su formulario principal:
class MainForm : Form {
public MainForm () {
Application.Idle += HandleApplicationIdle;
}
void HandleApplicationIdle (object sender, EventArgs e) {
//TODO: Implement me.
}
}
A continuación, debe poder determinar si la aplicación aún está inactiva. El Idle
evento solo se dispara una vez, cuando la aplicación queda inactiva. No se dispara nuevamente hasta que un mensaje ingresa a la cola y luego la cola se vacía nuevamente. Windows Forms no expone un método para consultar el estado de la cola de mensajes, pero puede usar los servicios de invocación de plataforma para delegar la consulta a una función nativa de Win32 que pueda responder esa pregunta . La declaración de importación PeekMessage
y sus tipos de soporte se ven así:
[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
básicamente le permite mirar el siguiente mensaje en la cola; devuelve verdadero si existe, falso de lo contrario. Para los propósitos de este problema, ninguno de los parámetros es particularmente relevante: lo que importa es solo el valor de retorno. Esto le permite escribir una función que le indica si la aplicación aún está inactiva (es decir, todavía no hay mensajes en la cola):
bool IsApplicationIdle () {
NativeMessage result;
return PeekMessage(out result, IntPtr.Zero, (uint)0, (uint)0, (uint)0) == 0;
}
Ahora tienes todo lo que necesitas para escribir tu ciclo de juego completo:
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);
}
Además, este enfoque coincide lo más cerca posible (con una dependencia mínima de P / Invoke) con el bucle canónico del juego nativo de Windows, que se ve así:
while (!done) {
if (PeekMessage(&message, window, 0, 0, PM_REMOVE)){
TranslateMessage(&message);
DispatchMessage(&message);
}
else {
Update();
Render();
}
}
WM_TIMER
basados en).De acuerdo con la respuesta de Josh, solo quiero agregar mis 5 centavos. El bucle de mensajes predeterminado de WinForms (Application.Run) podría reemplazarse con lo siguiente (sin p / invoke):
Además, si desea inyectar algún código en la bomba de mensajes, use esto:
fuente
Entiendo que este es un hilo viejo, pero me gustaría ofrecer dos alternativas a las técnicas sugeridas anteriormente. Antes de entrar en ellos, aquí están algunas de las trampas con las propuestas hechas hasta ahora:
PeekMessage lleva una sobrecarga considerable, al igual que los métodos de biblioteca que lo llaman (SlimDX IsApplicationIdle).
Si desea emplear RawInput almacenado en el búfer, deberá sondear la bomba de mensajes con PeekMessage en otro subproceso que no sea el subproceso de la interfaz de usuario, por lo que no desea llamarlo dos veces.
Application.DoEvents no está diseñado para ser llamado en un ciclo cerrado, los problemas de GC surgirán rápidamente.
Al usar Application.Idle o PeekMessage, debido a que solo está trabajando cuando está inactivo, su juego o aplicación no se ejecutará al mover o cambiar el tamaño de su ventana, sin olores de código.
Para solucionar estos problemas (excepto 2 si va por el camino de RawInput) puede:
Crea un Threading.Thread y ejecuta tu ciclo de juego allí.
Cree un Threading.Tasks.Task con el indicador IsLongRunning y ejecútelo allí. Microsoft recomienda que se usen tareas en lugar de subprocesos en estos días y no es difícil ver por qué.
Ambas técnicas aíslan su API de gráficos del hilo de la interfaz de usuario y la bomba de mensajes, como es el enfoque recomendado. El manejo de la destrucción de recursos / estado y la recreación durante el cambio de tamaño de la ventana también se simplifica y es estéticamente mucho más profesional cuando se realiza desde el final (ejerciendo la debida precaución para evitar puntos muertos con la bomba de mensajes) desde fuera del hilo de la interfaz de usuario.
fuente