Comment empêcher XNA de suspendre les mises à jour pendant que la fenêtre est redimensionnée / déplacée?

15

XNA arrête d'appeler Updateet Drawpendant que la fenêtre de jeu est redimensionnée ou déplacée.

Pourquoi? Existe-t-il un moyen d'empêcher ce comportement?

(Cela provoque la désynchronisation de mon code réseau, car les messages réseau ne sont pas pompés.)

Andrew Russell
la source

Réponses:

20

Oui. Cela implique une petite quantité de dégâts avec les composants internes de XNA. J'ai une démonstration d'un correctif dans la seconde moitié de cette vidéo .

Contexte:

La raison en est que XNA suspend son horloge de jeu lorsque Gamele sous Form- jacent est redimensionné (ou déplacé, les événements sont les mêmes). Ceci, à son tour, est dû au fait qu'il entraîne sa boucle de jeu Application.Idle. Lorsqu'une fenêtre est redimensionnée, Windows fait des trucs de boucle de message win32 fous qui empêchent le Idledéclenchement.

Donc, s'il Gamen'a pas suspendu son horloge, après un redimensionnement, il aura accumulé une énorme quantité de temps qu'il devrait ensuite mettre à jour en une seule rafale - ce n'est clairement pas souhaitable.

Comment le réparer:

Il existe une longue chaîne d'événements dans XNA (si vous utilisez Game, cela ne s'applique pas aux fenêtres personnalisées) qui connecte essentiellement l'événement de redimensionnement du sous-jacent Form, Suspendet les Resumeméthodes de l'horloge de jeu.

Celles-ci sont privées, vous devrez donc utiliser la réflexion pour décrocher les événements (où thisest a Game):

this.host.Suspend = null;
this.host.Resume = null;

Voici l'incantation nécessaire:

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);

(Vous pouvez déconnecter la chaîne ailleurs, vous pouvez utiliser ILSpy pour le comprendre. Mais il n'y a rien d'autre que le redimensionnement de la fenêtre qui suspend le temporisateur - donc ces événements sont aussi bons que les autres.)

Ensuite, vous devez fournir votre propre source de tick, lorsque XNA ne fonctionne pas Application.Idle. J'ai simplement utilisé un System.Windows.Forms.Timer:

timer = new Timer();
timer.Interval = (int)(this.TargetElapsedTime.TotalMilliseconds);
timer.Tick += new EventHandler(timer_Tick);
timer.Start();

Maintenant, timer_Tickfinira par appeler Game.Tick. Maintenant que l'horloge n'est pas suspendue, nous sommes libres de continuer à utiliser le propre code temporel de XNA. Facile! Pas tout à fait: nous voulons seulement que notre ticking manuel se produise lorsque le ticking intégré de XNA ne se produit pas. Voici un code simple pour le faire:

bool manualTick;
int manualTickCount = 0;
void timer_Tick(object sender, EventArgs e)
{
    if(manualTickCount > 2)
    {
        manualTick = true;
        this.Tick();
        manualTick = false;
    }

    manualTickCount++;
}

Et puis, dans Update, mettez ce code pour réinitialiser le compteur si XNA tourne normalement.

if(!manualTick)
    manualTickCount = 0;

Notez que ce ticking est considérablement moins précis que les XNA. Il devrait cependant rester plus ou moins aligné sur le temps réel.


Il y a encore un petit défaut dans ce code. Win32, bénissez son visage laid et stupide, arrête essentiellement tous les messages pendant quelques centaines de millisecondes lorsque vous cliquez sur la barre de titre d'une fenêtre, avant que la souris ne se déplace réellement (voir à nouveau ce même lien ). Cela empêche notre Timer(qui est basé sur un message) de cocher.

Parce que nous ne suspendons plus le chronomètre de jeu de XNA, lorsque notre minuteur recommencera à fonctionner, vous obtiendrez une rafale de mises à jour. Et, malheureusement, XNA limite la durée cumulée à une durée légèrement inférieure à la durée pendant laquelle Windows arrête les messages lorsque vous cliquez sur la barre de titre. Donc, si un utilisateur clique sur la barre de titre et la maintient, sans la déplacer, vous obtiendrez une rafale de mises à jour et tomberez quelques images en moins du temps réel.

(Mais cela devrait toujours être suffisant pour la plupart des cas. Le minuteur de XNA sacrifie déjà la précision pour éviter le bégaiement, donc si vous avez besoin d' un timing parfait , vous devriez faire autre chose.)

Andrew Russell
la source
2
Belle réponse complète.
MichaelHouse
1
Pourquoi exactement avez-vous posé la question, puis y avez-vous répondu instantanément?
Alastair Pitts
4
Bonne question. C'est explicitement encouragé . L'interface utilisateur de question a même une case "Répondre à votre propre question". À l'origine, j'allais en faire une réponse à cette question , mais ce ne serait qu'une modification d'une ancienne réponse, et ma solution ne résout pas une partie de cette question. De cette façon, il obtient une meilleure visibilité (étant nouveau et étant XNA sur GDSE au lieu de SO). Et l'info est mieux adaptée ici, plutôt que mon blog.
Andrew Russell