Cette question est une question "de suivi" de ma précédente, concernant la détection et la résolution des collisions, que vous pouvez trouver ici .
Si vous ne voulez pas lire la question précédente, voici une brève description du fonctionnement de mon moteur physique:
Chaque entité physique est stockée dans une classe appelée SSSPBody.
Seuls les AABB sont pris en charge.
Chaque SSSPBody est stocké dans une classe appelée SSSPWorld, qui met à jour chaque corps et gère la gravité.
À chaque image, SSSPWorld met à jour chaque corps.
Chaque corps mis à jour recherche les corps à proximité dans un hachage spatial, vérifie s'ils doivent détecter les collisions avec eux. Si oui, ils invoquent un événement "collision" et vérifient s'ils doivent résoudre les collisions avec eux. Si oui, ils calculent le vecteur de pénétration et le chevauchement directionnel, puis modifient leur position afin de résoudre la pénétration.
Lorsqu'un corps entre en collision avec un autre, il transfère sa vitesse à l'autre en réglant simplement la vitesse du corps à la sienne.
La vitesse d'un corps est définie sur 0 lorsqu'il n'a pas changé de position depuis la dernière image. S'il entre également en collision avec un corps en mouvement (comme un ascenseur ou une plate-forme mobile), il calcule la différence de mouvement de l'ascenseur pour voir si le corps n'a pas bougé de sa dernière position.
En outre, un corps invoque un événement "écrasé" lorsque tous ses coins AABB chevauchaient quelque chose dans un cadre.
Ceci est le code source COMPLET de mon jeu. Il est divisé en trois projets. SFMLStart est une bibliothèque simple qui gère la saisie, le dessin et la mise à jour des entités. SFMLStartPhysics est le plus important, où se trouvent les classes SSSPBody et SSSPWorld. PlatformerPhysicsTest est le projet de jeu, contenant toute la logique du jeu.
Et c'est la méthode « mise à jour » dans la classe SSSPBody, a commenté et simplifié. Vous ne pouvez y jeter un œil que si vous n'avez pas envie de regarder l'ensemble du projet SFMLStartSimplePhysics. (Et même si vous le faites, vous devriez toujours y jeter un œil car il est commenté.)
Le .gif montre deux problèmes.
- Si les corps sont placés dans un ordre différent, des résultats différents se produisent. Les caisses de gauche sont identiques aux caisses de droite, placées uniquement dans l'ordre inverse (dans l'éditeur).
- Les deux caisses doivent être propulsées vers le haut de l'écran. Dans la situation de gauche, aucune caisse n'est propulsée. À droite, un seul d'entre eux l'est. Les deux situations sont involontaires.
Premier problème: ordre de mise à jour
C'est assez simple à comprendre. Dans la situation de gauche, la caisse la plus haute est mise à jour avant l'autre. Même si la caisse du bas "transfère" la vitesse à l'autre, elle doit attendre la prochaine image pour se déplacer. Comme il n'a pas bougé, la vitesse de la caisse inférieure est réglée sur 0.
Je n'ai aucune idée de comment résoudre ce problème. Je préférerais que la solution ne dépende pas du "tri" de la liste de mise à jour, car je pense que je fais quelque chose de mal dans la conception du moteur physique.
Comment les principaux moteurs physiques (Box2D, Bullet, Chipmunk) gèrent-ils l'ordre de mise à jour?
Deuxième problème: une seule caisse est propulsée vers le plafond
Je ne comprends pas encore pourquoi cela se produit. Ce que fait l'entité "ressort" est de régler la vitesse du corps à -4000 et de la repositionner au-dessus du ressort lui-même. Même si je désactive le code de repositionnement, le problème persiste.
Mon idée est que lorsque la caisse inférieure entre en collision avec la caisse supérieure, sa vitesse est fixée à 0. Je ne sais pas pourquoi cela se produit.
Malgré la chance de ressembler à quelqu'un qui abandonne au premier problème, j'ai posté tout le code source du projet ci-dessus. Je n'ai rien pour le prouver, mais croyez-moi, j'ai essayé de résoudre ce problème, mais je n'ai tout simplement pas trouvé de solution et je n'ai aucune expérience préalable de la physique et des collisions. J'essaie de résoudre ces deux problèmes depuis plus d'une semaine et maintenant je suis désespéré.
Je ne pense pas que je puisse trouver une solution par moi-même sans retirer de nombreuses fonctionnalités du jeu (transfert de vitesse et ressorts, par exemple).
Merci beaucoup pour le temps passé à lire cette question, et merci encore plus si vous essayez de trouver une solution ou une suggestion.
Réponses:
En fait, les problèmes d'ordre de mise à jour sont assez courants pour les moteurs de physique des impulsions normales, vous ne pouvez pas simplement retarder l'application de la force comme le suggère Vigil, vous finiriez par briser la conservation de l'énergie lorsqu'un objet entre simultanément en collision avec 2 autres. Habituellement, ils parviennent à créer quelque chose qui semble assez réel, même si un ordre de mise à jour différent aurait produit un résultat sensiblement différent.
Dans tous les cas, pour votre objectif, il y a suffisamment de hoquets dans un système d'impulsions que je vous suggérerais plutôt de construire un modèle de masse-ressort.
L'idée de base est qu'au lieu d'essayer de résoudre une collision en une seule étape, vous appliquez une force aux objets qui entrent en collision, cette force devrait être équivalente à la quantité de chevauchement entre les objets, ce qui est comparable à la façon dont les objets réels lors d'une collision transforment leur l'énergie de mouvement en déformation, puis de nouveau en mouvement, la grande chose à propos de ce système est qu'il permet à la force de voyager à travers un objet sans que cet objet ait à rebondir d'avant en arrière, et cela peut raisonnablement être fait complètement indépendamment de la mise à jour.
Pour que les objets s'arrêtent plutôt que de rebondir indéfiniment, vous devrez appliquer une certaine forme d'amortissement, vous pouvez grandement affecter le style et la sensation de votre jeu en fonction de la façon dont vous le faites, mais une approche très basique serait de appliquer une force à deux objets en contact équivalente à leur mouvement interne, vous pouvez choisir de l'appliquer uniquement lorsqu'ils se rapprochent l'un de l'autre, ou lorsqu'ils s'éloignent l'un de l'autre, ce dernier peut être utilisé pour empêcher complètement les objets de rebondir quand ils touchent le sol, mais les rendront également un peu collants.
Vous pouvez également créer un effet de friction en freinant un objet dans la direction perpendiculaire à une collision, la quantité de freinage doit être équivalente à la quantité de chevauchement.
Vous pouvez contourner assez facilement le concept de masse en faisant en sorte que tous les objets aient la même masse, et les objets immobiles fonctionneront comme ayant une masse infinie si vous négligez simplement de les accélérer.
Un pseudo-code, juste au cas où ce qui précède n'était pas assez clair:
Le point des propriétés addXvelocity et addYvelocity est que celles-ci sont ajoutées à la vitesse de leur objet une fois la gestion des collisions terminée.
Modifier:
vous pouvez faire des choses dans l'ordre suivant, où chaque puce doit être effectuée sur tous les éléments avant que la suivante ne soit effectuée:
De plus, je me rends compte que ce qui suit peut ne pas être complètement clair dans mon message initial, sous l'influence des objets de gravité qui se chevaucheront lorsqu'ils se poseront les uns sur les autres, cela suggère que leur boîte de collision devrait être légèrement plus élevée que leur représentation graphique pour éviter le chevauchement visuellement. Ce problème sera moindre si la physique est exécutée à un taux de mise à jour plus élevé. Je vous suggère d'essayer de fonctionner à 120 Hz pour un compromis raisonnable entre le temps CPU et la précision physique.
Edit2:
Flux de moteur physique très basique:
acceleration = [Complicated formulas]
velocity += acceleration
position += velocity
la source
Eh bien, vous n'êtes évidemment pas quelqu'un qui abandonne facilement, vous êtes un vrai homme de fer, j'aurais jeté les mains en l'air beaucoup plus tôt, car ce projet ressemble beaucoup à une forêt de varech :)
Tout d'abord, les positions et les vitesses sont définies partout, du point de vue du sous-système physique, c'est la recette d'une catastrophe. En outre, lorsque vous modifiez des éléments intégraux par différents sous-systèmes, créez des méthodes privées telles que "ChangeVelocityByPhysicsEngine", "ChangeVelocityBySpring", "LimitVelocity", "TransferVelocity" ou quelque chose du genre. Il ajoutera une capacité de vérifier les changements apportés par une partie spécifique de la logique et fournira une signification supplémentaire à ces changements de vitesse. De cette façon, le débogage serait plus facile.
Premier problème.
Sur la question elle-même. Maintenant, vous appliquez simplement les corrections de position et de vitesse "au fur et à mesure" par ordre d'apparence et de logique de jeu. Cela ne fonctionnera pas pour des interactions complexes sans coder en dur la physique de chaque chose complexe. Un moteur physique séparé n'est alors pas nécessaire.
Afin de faire des interactions complexes sans hacks, vous devez ajouter une étape supplémentaire entre la détection des collisions en fonction des positions qui ont été modifiées par les vitesses initiales et les changements finaux des positions en fonction de la "post-vitesse". J'imagine que ça se passerait comme ceci:
D'autres choses peuvent apparaître, comme traiter les secousses, le refus de s'empiler lorsque le FPS est petit, ou d'autres choses comme ça, soyez prêt :)
Deuxième problème
La vitesse verticale de ces deux caisses "à port en lourd" ne change jamais de zéro. Étrangement, dans la boucle de mise à jour de PhysSpring, vous affectez la vitesse, mais dans la boucle de mise à jour de PhysCrate, elle est déjà nulle. Il est possible de trouver une ligne où la vélocité va mal, mais j'ai arrêté le débogage ici car c'est la situation "Reap What You Sew". Il est temps d'arrêter le codage et de tout repenser lorsque le débogage devient difficile. Mais s'il arrive à un point où il est impossible, même pour l'auteur du code, de comprendre ce qui se passe dans le code, votre base de code est déjà morte sans que vous vous en rendiez compte :)
Troisième problème
Je pense que quelque chose ne fonctionne pas lorsque vous devez recréer une partie de Farseer pour faire un simple jeu de plateforme basé sur des tuiles. Personnellement, je considérerais votre moteur actuel comme une expérience formidable, puis je l'abandonnerais complètement pour une physique basée sur des carreaux plus simple et plus directe. Ce faisant, il serait sage de reprendre des choses comme Debug.Assert et peut-être même, oh l'horreur, des tests unitaires, car il serait possible de détecter des choses inattendues plus tôt.
la source
Votre problème est que ce sont des hypothèses fondamentalement erronées sur le mouvement, donc ce que vous obtenez ne ressemble pas au mouvement car vous le connaissez.
Lorsqu'un corps entre en collision avec un autre, l'élan est conservé. Penser à ceci comme "A frappe B" contre "B frappe A" revient à appliquer un verbe transitif à une situation intransitive. A et B entrent en collision; l'impulsion résultante doit être égale à l'impulsion initiale. Autrement dit, si A et B sont de masse égale, ils voyagent maintenant tous les deux avec la moyenne de leurs vitesses d'origine.
Vous aurez également probablement besoin d'un peu de collision et d'un solveur itératif, ou vous rencontrerez des problèmes de stabilité. Vous devriez probablement lire certaines des présentations d'Erin Catto sur GDC.
la source
Je pense que vous avez fait un effort vraiment noble, mais il semble que la structure du code pose des problèmes fondamentaux. Comme d'autres l'ont suggéré, il peut être utile de séparer les opérations en parties discrètes, par exemple:
En séparant les phases, tous les objets sont mis à jour progressivement en synchronisation et vous n'aurez pas les dépendances d'ordre avec lesquelles vous vous débattez actuellement. Le code s'avère également généralement plus simple et plus facile à modifier. Chacune de ces phases est assez générique, et il est souvent possible de remplacer de meilleurs algorithmes après avoir un système fonctionnel.
Cela dit, chacune de ces parties est une science en soi et peut prendre beaucoup de temps à essayer de trouver la solution optimale. Il peut être préférable de commencer avec certains des algorithmes les plus couramment utilisés:
Un bon (et évident) point de départ est les lois du mouvement de Newton .
la source