L'extrapolation rompt la détection des collisions


10

Avant d'appliquer l'extrapolation au mouvement de mon sprite, ma collision a parfaitement fonctionné. Cependant, après avoir appliqué une extrapolation au mouvement de mon sprite (pour lisser les choses), la collision ne fonctionne plus.

Voici comment les choses fonctionnaient avant l'extrapolation:

entrez la description de l'image ici

Cependant, après avoir implémenté mon extrapolation, la routine de collision se rompt. Je suppose que c'est parce qu'il agit sur la nouvelle coordonnée qui a été produite par la routine d'extrapolation (qui est située dans mon appel de rendu).

Après avoir appliqué mon extrapolation

entrez la description de l'image ici

Comment corriger ce comportement?

J'ai essayé de mettre un contrôle de collision supplémentaire juste après l'extrapolation - cela semble résoudre beaucoup de problèmes, mais j'ai écarté cela car il n'est pas question de mettre de la logique dans mon rendu.

J'ai également essayé de faire une copie de la position de spritesX, d'extrapoler et de dessiner en utilisant cela plutôt que l'original, laissant ainsi l'original intact pour que la logique reprenne - cela semble une meilleure option, mais cela produit toujours des effets étranges en entrant en collision avec des murs. Je suis presque sûr que ce n'est pas non plus la bonne façon de gérer cela.

J'ai trouvé quelques questions similaires ici, mais les réponses ne m'ont pas aidé.

Voici mon code d'extrapolation:

public void onDrawFrame(GL10 gl) {


        //Set/Re-set loop back to 0 to start counting again
        loops=0;

        while(System.currentTimeMillis() > nextGameTick && loops < maxFrameskip){

        SceneManager.getInstance().getCurrentScene().updateLogic();
        nextGameTick+=skipTicks;
        timeCorrection += (1000d/ticksPerSecond) % 1;
        nextGameTick+=timeCorrection;
        timeCorrection %=1;
        loops++;
        tics++;

     }

        extrapolation = (float)(System.currentTimeMillis() + skipTicks - nextGameTick) / (float)skipTicks; 

        render(extrapolation);
}

Appliquer l'extrapolation

            render(float extrapolation){

            //This example shows extrapolation for X axis only.  Y position (spriteScreenY is assumed to be valid)
            extrapolatedPosX = spriteGridX+(SpriteXVelocity*dt)*extrapolation;
            spriteScreenPosX = extrapolationPosX * screenWidth;

            drawSprite(spriteScreenX, spriteScreenY);           


        }

Éditer

Comme je l'ai mentionné ci-dessus, j'ai essayé de faire une copie des coordonnées du sprite spécifiquement pour dessiner avec ... cela a ses propres problèmes.

Tout d'abord, quelle que soit la copie, lorsque le sprite se déplace, il est super lisse, lorsqu'il s'arrête, il oscille légèrement à gauche / à droite - car il extrapole toujours sa position en fonction du temps. Est-ce un comportement normal et pouvons-nous le «désactiver» lorsque le sprite s'arrête?

J'ai essayé d'avoir des drapeaux pour gauche / droite et d'extrapoler uniquement si l'un d'eux est activé. J'ai également essayé de copier les dernières positions et les positions actuelles pour voir s'il y a une différence. Cependant, en ce qui concerne la collision, cela n'aide pas.

Si l'utilisateur appuie sur le bouton droit et que le sprite se déplace vers la droite, lorsqu'il touche un mur, si l'utilisateur continue de maintenir le bouton droit enfoncé, le sprite continuera à s'animer vers la droite, tout en étant arrêté par le mur ( donc pas réellement en mouvement), mais parce que le bon drapeau est toujours positionné et aussi parce que la routine de collision déplace constamment le sprite hors du mur, il semble toujours au code (pas au joueur) que le sprite est toujours en mouvement, et donc l'extrapolation se poursuit. Donc, ce que le joueur verrait, c'est le sprite 'statique' (oui, il est animé, mais il ne se déplace pas réellement sur l'écran), et de temps en temps, il tremble violemment alors que l'extrapolation tente de faire sa chose ..... .. J'espère que cette aide


1
Cela prendra un certain digérant pour bien comprendre ( « interpolation » a apparemment une douzaine de significations différentes et il est pas tout à fait clair à première vue tout ce que vous entendez par ici), mais mon premier instinct est « vous ne devriez pas faire quoi que ce soit à affecter votre position de l'objet dans votre routine de rendu ». Votre moteur de rendu doit dessiner votre objet à la position donnée de l'objet, et en manipulant l'objet, il existe une recette pour des problèmes, car il couple intrinsèquement le moteur de rendu à la physique du jeu. Dans un monde idéal, votre moteur de rendu devrait pouvoir prendre des pointeurs const vers des objets de jeu.
Steven Stadnicki

Bonjour @StevenStadnicki, merci beaucoup pour votre commentaire, il existe une multitude d'exemples montrant une valeur d'interpolation transmise dans le moteur de rendu, veuillez voir ceci: mysecretroom.com/www/programming-and-software/… qui est d'où j'ai adapté mon code. Ma compréhension limitée est que cela interpole la position entre la dernière et la prochaine mise à jour en fonction du temps nécessaire depuis la dernière mise à jour - je suis d'accord que c'est un peu un cauchemar! Je serais reconnaissant si vous pouviez proposer une alternative pour que je sois plus facile à travailler - cheers :-)
BungleBonce

6
Eh bien, je dirai «simplement parce que vous pouvez trouver du code pour quelque chose, ce n'est pas une bonne pratique» :-) Dans ce cas, cependant, je soupçonne que la page à laquelle vous avez lié utilise la valeur d'interpolation pour déterminer où afficher ses objets, mais elle ne met pas à jour la position des objets avec eux; vous pouvez le faire aussi, simplement en ayant une position spécifique au dessin qui est calculée à chaque image, mais en gardant cette position distincte de la position «physique» réelle de l'objet.
Steven Stadnicki

Bonjour @StevenStadnicki, comme indiqué dans ma question (le paragraphe commençant par "J'ai également essayé de faire une copie"), j'ai en fait déjà essayé d'utiliser une position "dessiner uniquement" :-) Pouvez-vous proposer une méthode d'interpolation où je vous n'avez pas besoin d'ajuster la position de l'image-objet dans la routine de rendu? Merci!
BungleBonce

En regardant votre code, il semble que vous faites une extrapolation au lieu d'une interpolation.
Durza007

Réponses:


1

Je ne peux pas encore poster de commentaire, je vais donc poster ceci comme réponse.

Si je comprends bien le problème, cela ressemble à ceci:

  1. vous avez d'abord une collision
  2. alors la position de l'objet est corrigée (vraisemblablement par la routine de détection de collision)
  3. la position mise à jour de l'objet est envoyée à la fonction de rendu
  4. la fonction de rendu met ensuite à jour l'emplacement de l'objet à l'aide d'extrapolation
  5. la position de l'objet extrapolé rompt désormais la routine de détection de collision

Je peux penser à 3 solutions possibles. Je les énumérerai dans l'ordre le plus souhaitable pour le moins à mon humble avis.

  1. Déplacez l'extrapolation hors de la fonction de rendu. Extrapolez la position de l'objet, puis testez la collision.
  2. ou si vous souhaitez conserver l'extrapolation dans la fonction de rendu, définissez un indicateur pour indiquer qu'une collision s'est produite. Demandez à la détection de collision de corriger la position de l'objet comme vous le faites déjà, mais avant de calculer la valeur d'extrapolation, vérifiez d'abord l'indicateur de collision. Étant donné que l'objet est déjà là où il doit être, il n'est pas nécessaire de le déplacer.
  3. La dernière possibilité, qui pour moi est plus une solution de contournement qu'un correctif serait de surcompenser la détection de collision. Après une collision, éloignez l'objet du mur, de sorte qu'après extrapolation, l'objet soit de retour au mur.

Exemple de code pour # 2.

if (collisionHasOccured)
    extrpolation = 0.0f;
else
    extrapolation = (float)(System.currentTimeMillis() + skipTicks - nextGameTick) / (float)skipTicks;

Je pense que le n ° 2 serait probablement le plus rapide et le plus facile à démarrer, bien que le n ° 1 semble être une solution plus précise sur le plan logique. Selon la façon dont vous gérez votre delta time, la solution # 1 peut être cassée de la même manière par un grand delta, auquel cas vous devrez peut-être utiliser à la fois # 1 et # 2 ensemble.

EDIT: J'ai mal compris votre code plus tôt. Les boucles sont censées être rendues aussi rapidement que possible et mises à jour à un intervalle défini. C'est pourquoi vous interpoleriez la position du sprite, pour gérer le cas où vous dessinez plus que la mise à jour. Cependant, si la boucle prend du retard, vous interrogez sur la mise à jour jusqu'à ce que vous soyez rattrapé ou que vous ayez ignoré le nombre maximal de tirages d'image.

Cela étant dit, le seul problème est que l'objet se déplace après une collision. En cas de collision, l'objet doit cesser de se déplacer dans cette direction. Donc, s'il y a une collision, définissez sa vitesse sur 0. Cela devrait empêcher la fonction de rendu de déplacer l'objet plus loin.


Salut @Aholio J'ai essayé l'option 2 et cela fonctionne mais provoque quelques défauts peut-être que je l'ai mal fait, je vais y revenir. Cependant, je suis très intéressé par l'option 1 bien que je ne trouve aucune information sur la façon de procéder. Comment puis-je extrapoler dire dans ma routine logique? BTW mon delta est fixe est donc 1/60 ou 0,01667 (c'est plutôt le chiffre que j'utilise pour intégrer bien que le temps que prend chaque itération de mon jeu puisse évidemment varier en fonction de ce qui se passe mais ne devrait jamais vraiment prendre plus de 0,01667 ) donc toute aide à ce sujet serait un grand merci.
BungleBonce

Peut-être que je ne comprends pas bien ce que vous faites. Quelque chose qui me semble un peu étrange, c'est que vous ne faites pas seulement des mouvements de position dans votre fonction de dessin; vous faites également des calculs de temps. Est-ce fait pour chaque objet? L'ordre correct doit être le suivant: calcul du temps effectué dans le code de boucle de jeu. La boucle de jeu transmet le delta à une fonction de mise à jour (dt). Update (dt) mettra à jour tous les objets du jeu. Il doit gérer tout mouvement, extrapolation et détection de collision. Après que update () soit revenu à la boucle de jeu, il appelle alors une fonction render () qui dessine tous les objets de jeu fraîchement mis à jour.
Aholio

Hmmmmm actuellement ma fonction de mise à jour fait tout. Est-ce que le mouvement (par mouvement, je veux dire calcule la nouvelle position des sprites - cela est calculé à partir de mon temps delta). La seule chose que je fais dans ma fonction de rendu (en dehors du dessin réel) est d'extrapoler la `` nouvelle '' position, mais bien sûr, cela utilise du temps car il doit prendre en compte le temps entre la dernière mise à jour logique et l'appel de rendu. C'est ma compréhension de toute façon :-) Comment feriez-vous cela? Je ne comprends pas comment utiliser l'extrapolation uniquement dans la mise à jour logique. Merci!
BungleBonce le

Mis à jour ma réponse. J'espère que cela aide
Aholio

Merci, voyez dans mon Q original "quand il s'arrête, il vacille légèrement à gauche / droite" - donc même si j'arrête mon sprite, l'extrapolation maintient toujours le sprite "en mouvement" (wobbling) - cela se produit parce que je n'utilise pas le les positions anciennes et actuelles du sprite pour calculer mon extrapolation, donc même si le sprite est statique, il a toujours cet effet d'oscillation basé sur la valeur d'extrapolation qui est calculée à chaque itération de trame. J'ai même essayé de dire «si les positions anciennes et actuelles sont les mêmes, alors n'extrapolez pas», mais cela ne semble toujours pas fonctionner, je vais revenir en arrière et jeter un autre coup d'œil!
BungleBonce

0

Il semble que vous ayez besoin de séparer entièrement le rendu et la mise à jour de la physique. Habituellement, la simulation sous-jacente s'exécutera à des pas de temps discrets et la fréquence ne changera jamais. Par exemple, vous pouvez simuler le mouvement de votre balle tous les 1 / 60ème de seconde, et c'est tout.

Afin de permettre une fréquence d'images variable, le code de rendu doit fonctionner sur une fréquence variable, mais toute simulation doit toujours être à un pas de temps fixe. Cela permet aux graphiques de lire la mémoire de la simulation en lecture seule et de configurer l'interpolation au lieu de l'extrapolation.

Étant donné que l'extrapolation tente de prédire où seront les valeurs futures, des changements soudains de mouvement peuvent vous donner d'énormes erreurs d'extrapolation. Il est préférable de rendre votre scène à la place d'une image derrière la simulation et d'interpoler entre des positions discrètes connues.

Si vous voulez voir quelques détails de mise en œuvre, j'ai déjà écrit une courte section sur ce sujet dans un article ici . Veuillez consulter la section intitulée "Timeste".

Voici le code psuedo important de l'article:

const float fps = 100
const float dt = 1 / fps
float accumulator = 0

// In units seconds
float frameStart = GetCurrentTime( )

// main loop
while(true)
  const float currentTime = GetCurrentTime( )

  // Store the time elapsed since the last frame began
  accumulator += currentTime - frameStart( )

  // Record the starting of this frame
  frameStart = currentTime

  // Avoid spiral of death and clamp dt, thus clamping
  // how many times the UpdatePhysics can be called in
  // a single game loop.
  if(accumulator > 0.2f)
    accumulator = 0.2f

  while(accumulator > dt)
    UpdatePhysics( dt )
    accumulator -= dt

  const float alpha = accumulator / dt;

  RenderGame( alpha )

void RenderGame( float alpha )
  for shape in game do
    // calculate an interpolated transform for rendering
    Transform i = shape.previous * alpha + shape.current * (1.0f - alpha)
    shape.previous = shape.current
    shape.Render( i )

La RenderGamefonction présente le plus d'intérêt. L'idée est d'utiliser l'interpolation entre des positions de simulation discrètes. Le code de rendu peut créer ses propres copies des données de simulation en lecture seule et utiliser une valeur interpolée temporaire pour le rendu. Cela vous donnera un mouvement très fluide sans aucun problème stupide comme ce que vous semblez avoir!

En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.