2017-08-31 1 views
1

Il y a une erreur étrange avec le minuteur et les formulaires.La minuterie ne coche pas jusqu'à la fermeture du formulaire

Je fais un éditeur pour le jeu. L'éditeur a deux formes - MainForm et PreviewForm. PreviewForm contient uniquement le contrôle pour la sortie OpenGL (contrôle personnalisé basé sur GLControl d'OpenTK), nommé glSurface. GlSurface a deux temporisateurs en ligne (Windows.Forms.Timer) - un pour le rendu, et un pour mettre à jour l'état du jeu. Timers déclenche dans glSurface la méthode Run(double updateRate, double frameRate). Donc, je veux montrer PreviewForm et exécuter la mise à jour et le rendu de MainForm. Mon code est:

PreviewForm = new PreviewForm(); 
PreviewForm.glSurface.Run(60d, 60d); 
PreviewForm.Show(this); //Form is "modal" 

corps de la méthode Run:

if (Running) 
    throw new Exception("Already run"); 
_updateRate = updateRate; 
_renderRate = frameRate; 

var renderFrames = Convert.ToInt32(1000/frameRate); 
var updateFrames = Convert.ToInt32(1000/updateRate); 
RenderTimer.Interval = renderFrames; 
UpdateTimer.Interval = updateFrames; 
RenderTimer.Start(); 
UpdateTimer.Start(); 
Running = true; 

Timers est en cours d'initialisation en cas OnVisibleChanged:

protected override void OnVisibleChanged(EventArgs e) 
{ 
    ... 
    RenderTimer = new Timer(); 
    UpdateTimer = new Timer(); 
    RenderTimer.Tick += RenderTick; 
    UpdateTimer.Tick += UpdateTick; 
    ... 
} 

choses étranges commencent ici.

Lorsque PreviewForm est affiché, rien ne se passe. MAIS quand je ferme ce formulaire, les deux minuteurs tirent leurs événements! J'ai testé pour l'interaction transversale possible, mais PreviewForm.InvokeRequired et glSurface.InvokeRequired sont tous les deux false.

Aidez-moi à comprendre ce que vous pensez.

+0

'PreviewForm.Show (this); // Le formulaire est "modal" 'La forme est" non "modale. ShowDialog le rendrait modal. – LarsTech

+3

Il semble que vous démarrez les minuteurs avant de les créer. Et puis, quand vous les créez, vous ne les lancez pas. – LarsTech

+0

'J'ai testé l'interaction possible entre les threads': utilisez-vous le multithreading? Windows.Forms.Timers sont conçus pour des applications à un seul thread. –

Répondre

0

Dans ce cas Accroche et commencer vos et initialiser minuteries tous dans le bloc un code:

{ 
    .../... 

    RenderTimer = new Timer(); 
    UpdateTimer = new Timer(); 
    RenderTimer.Tick += RenderTick; 
    UpdateTimer.Tick += UpdateTick; 
    var renderFrames = Convert.ToInt32(1000/frameRate); 
    var updateFrames = Convert.ToInt32(1000/updateRate); 
    RenderTimer.Interval = renderFrames; 
    UpdateTimer.Interval = updateFrames; 
    RenderTimer.Start(); 
    UpdateTimer.Start(); 

    .../... 
} 

Sans voir le déroulement du programme c'est la plus sûre. Il semble que les variables sont locales à l'événement OnVisibleChanged, donc je ne suis pas sûr de savoir comment vous n'obtiendrez pas une exception de référence nulle lorsque vous les appelez à partir de votre if (Running).

L'autre chose que vous pourriez faire est de leur faire des variables de classe et s'assurer qu'elles sont initialisées avant de les utiliser. Ensuite, appelez start dans l'instruction if. En ce qui concerne le problème de leur démarrage à la fermeture du formulaire, il est impossible de déterminer à partir du code que vous avez montré.

+1

Si vous avez besoin d'autres réponses - vous devrez fournir un [mcve] –

0

Modifier: Il existe un problème plus profond.

Vous ne devriez pas utiliser les minuteurs système pour conduire votre jeu mises à jour et le rendu.

Les temporisateurs système sur la plupart des plates-formes ont une faible précision qui est inadéquate pour les performances multimédia telles que l'audio et la plupart des jeux. Sous Windows System.Windows.Forms.Timer, les minuteurs Win32 ont une précision particulièrement faible, ce qui entraîne généralement des intervalles d'au moins 15 ms (voir this answer). Voir this technical breakdown et this overview pour plus d'informations. Fondamentalement, même si votre code fonctionnait correctement, vos images bégayeraient.

La plupart des jeux « tiques » en exécutant une boucle infinie dans le thread principal, procédez comme suit à chaque fois (pas nécessairement dans cet ordre):

  • Appel de nouveau dans le cadre pour gérer l'attente des événements OS.
  • Effectue le suivi de la différence de temps depuis la dernière "coche" pour que la synchronisation indépendante de la trame fonctionne.
  • Mettez à jour l'état du jeu (comme la physique et la logique du jeu, éventuellement dans un autre thread).
  • Rendre la scène basée sur une mise à jour précédente (éventuellement dans un autre thread).

Comme indiqué par les intervenants, le problème principal dans votre code de minuterie est que l'initialisation est divisé entre Run et OnVisibleChanged. Je n'ai pas pu reproduire le cas où la minuterie se déclenche après la fermeture d'un sous-formulaire. Je soupçonne qu'un autre code que vous n'avez pas posté est la cause. Vous vous épargnerez beaucoup de problèmes si vous utilisez OpenTK.GameWindow. Il gère la boucle repulpant pour vous, semblable à XNA. This est un exemple d'une façon de l'intégrer avec WinForms. Voir the manual pour plus d'informations. En Run, vous définissez Interval et démarrez chaque temporisateur. Non Tick les rappels sont définis. Dans OnVisibleChanged, vous recréez les temporisations et affectez des rappels Tick. Aucun intervalle n'est défini et la minuterie n'a pas été démarrée.

Le code d'initialisation de la minuterie dans Run est essentiellement ignoré car aucun rappel de coche n'est défini et OnVisibleChanged recrée les minuteurs. OnVisibleChanged se déclenche presque immédiatement après Run, peu de temps après avoir appelé PreviewForm.Show(this).

Si vous êtes mort sur l'utilisation ensemble des minuteries du système, cela devrait fonctionner:

// somewhere before Run(ideally in the initialization of the main form). 
RenderTimer.Interval = Convert.ToInt32(1000/frameRate); 
RenderTimer.Tick += RenderTick; 
UpdateTimer.Interval = Convert.ToInt32(1000/updateRate); 
UpdateTimer.Tick += UpdateTick; 

void Run(double frameRate, double updateRate) 
{ 
    // ... 
    RenderTimer.Start(); 
    UpdateTimer.Start(); 
    // ... 
    Running = true; 
} 
// ... 
protected override void OnVisibleChanged(EventArgs e) 
{ 
    // ... 
    // Don't initialize timers here. 
    // ... 
} 
+0

Veuillez noter mon commentaire sous OP. –