A Application.Run
chamada aciona sua bomba de mensagens do Windows, que é o que alimenta todos os eventos que você pode conectar à Form
classe (e outros). Para criar um loop de jogo nesse ecossistema, você deseja escutar quando a bomba de mensagens do aplicativo estiver vazia e, enquanto estiver vazia, execute as etapas típicas do "estado de entrada do processo, atualize a lógica do jogo, renderize a cena" do loop de protótipo do jogo .
O Application.Idle
evento é acionado uma vez toda vez que a fila de mensagens do aplicativo é esvaziada e o aplicativo está em transição para um estado inativo. Você pode conectar o evento no construtor do seu formulário principal:
class MainForm : Form {
public MainForm () {
Application.Idle += HandleApplicationIdle;
}
void HandleApplicationIdle (object sender, EventArgs e) {
//TODO: Implement me.
}
}
Em seguida, você precisa determinar se o aplicativo ainda está ocioso. O Idle
evento é acionado apenas uma vez, quando o aplicativo fica inativo. Ele não é acionado novamente até que uma mensagem entre na fila e, em seguida, a fila esvazia novamente. O Windows Forms não expõe um método para consultar o estado da fila de mensagens, mas você pode usar os serviços de chamada de plataforma para delegar a consulta a uma função nativa do Win32 que possa responder a essa pergunta . A declaração de importação PeekMessage
e seus tipos de suporte são semelhantes a:
[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
basicamente permite que você veja a próxima mensagem na fila; retorna true se houver, false caso contrário. Para os propósitos desse problema, nenhum dos parâmetros é particularmente relevante: é apenas o valor de retorno que importa. Isso permite que você escreva uma função que informa se o aplicativo ainda está ocioso (ou seja, ainda não há mensagens na fila):
bool IsApplicationIdle () {
NativeMessage result;
return PeekMessage(out result, IntPtr.Zero, (uint)0, (uint)0, (uint)0) == 0;
}
Agora você tem tudo o que precisa para escrever seu ciclo completo do jogo:
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);
}
Além disso, essa abordagem se aproxima o mais próximo possível (com dependência mínima de P / Invoke) do loop de jogo nativo canônico do Windows, que se parece com:
while (!done) {
if (PeekMessage(&message, window, 0, 0, PM_REMOVE)){
TranslateMessage(&message);
DispatchMessage(&message);
}
else {
Update();
Render();
}
}
WM_TIMER
baseados).Concordo com a resposta de Josh, só quero adicionar meus 5 centavos. O loop de mensagem padrão do WinForms (Application.Run) pode ser substituído pelo seguinte (sem p / invoke):
Além disso, se você deseja injetar algum código na bomba de mensagens, use o seguinte:
fonte
Entendo que esse é um tópico antigo, mas gostaria de fornecer duas alternativas para as técnicas sugeridas acima. Antes de entrar nelas, aqui estão algumas das armadilhas das propostas feitas até agora:
PeekMessage carrega uma sobrecarga considerável, assim como os métodos da biblioteca que o chamam (SlimDX IsApplicationIdle).
Se você deseja empregar RawInput em buffer, precisará pesquisar a bomba de mensagens com PeekMessage em outro thread que não seja o thread da UI, para que não queira chamá-lo duas vezes.
O Application.DoEvents não foi projetado para ser chamado em um circuito fechado, os problemas do GC surgirão rapidamente.
Ao usar Application.Idle ou PeekMessage, porque você só está trabalhando quando Idle, seu jogo ou aplicativo não será executado ao mover ou redimensionar sua janela, sem odores de código.
Para contornar esses problemas (exceto 2, se você estiver indo pela estrada RawInput), você pode:
Crie um Threading.Thread e execute o loop do jogo lá.
Crie um Threading.Tasks.Task com o sinalizador IsLongRunning e execute-o lá. A Microsoft recomenda que as tarefas sejam usadas no lugar dos threads hoje em dia e não é difícil entender o porquê.
Ambas as técnicas isolam sua API de gráficos do thread da interface do usuário e da bomba de mensagens, como é a abordagem recomendada. O manuseio da destruição e recreação de recursos / estado durante o redimensionamento do Windows também é simplificado e é esteticamente muito mais profissional quando concluído (exercitando a devida cautela para evitar bloqueios com a bomba de mensagens) de fora do thread da interface do usuário.
fonte