Comment puis-je mettre en œuvre la gravité?

Réponses:

53

Comme d'autres l'ont noté dans les commentaires, la méthode d' intégration Euler de base décrite dans la réponse de tenpn présente quelques problèmes:

  • Même pour un mouvement simple, comme le saut balistique sous gravité constante, cela introduit une erreur systématique.

  • L'erreur dépend du pas de temps, ce qui signifie que la modification du pas de temps change les trajectoires des objets de manière systématique, ce qui peut être remarqué par les joueurs si le jeu utilise un pas de temps variable. Même pour les jeux avec un pas de temps physique fixe, le fait de modifier le pas de temps au cours du développement peut affecter de manière perceptible la physique du jeu, telle que la distance parcourue par un objet lancé avec une force donnée, susceptible de briser les niveaux précédemment conçus.

  • Il ne conserve pas d'énergie, même si la physique sous-jacente le devrait. En particulier, les objets qui doivent osciller régulièrement (pendules, ressorts, planètes en orbite, etc.) peuvent accumuler de l'énergie de façon constante jusqu'à ce que tout le système se détache.

Heureusement, il n’est pas difficile de remplacer l’intégration Euler par quelque chose qui est presque aussi simple, mais qui n’a aucun de ces problèmes - en particulier un intégrateur symplectique de second ordre tel que l’ intégration de sauts ou la méthode de vélocité étroitement corrélée Verlet . En particulier, où l’intégration Euler de base met à jour la vitesse et la position de la manière suivante:

accélération = force (temps, position) / masse;
time + = timestep;
position + = timestep * vélocité;
vélocité + = accélération *;

la méthode de vélocité Verlet le fait comme ceci:

accélération = force (temps, position) / masse;
time + = timestep;
position + = timestep * ( vélocité + timestep * accélération / 2) ;
newAcceleration = force (temps, position) / masse; 
vélocité + = pas de temps * ( accélération + nouvelle accélération ) / 2 ;

Si vous avez plusieurs objets en interaction, vous devez mettre à jour toutes leurs positions avant de recalculer les forces et de mettre à jour les vitesses. La ou les nouvelles accélérations peuvent ensuite être enregistrées et utilisées pour mettre à jour la ou les positions lors de la prochaine étape, en réduisant le nombre d'appels force()à un (par objet) par étape, comme avec la méthode Euler.

De plus, si l'accélération est normalement constante (comme la gravité lors d'un saut balistique), nous pouvons simplifier ce qui précède pour simplement:

time + = timestep;
position + = timestep * ( vélocité + timestep * accélération / 2) ;
vélocité + = accélération *;

où le terme supplémentaire en gras est le seul changement par rapport à l'intégration de base d'Euler.

Comparées à l'intégration d'Euler, les méthodes Velocity Verlet et Leapfrog ont plusieurs propriétés intéressantes:

  • Pour une accélération constante, ils donnent des résultats exacts (de toute façon jusqu'à des erreurs d'arrondis en virgule flottante), ce qui signifie que les trajectoires de sauts balistiques restent les mêmes, même si le pas de temps est modifié.

  • Ce sont des intégrateurs de second ordre, ce qui signifie que même avec des accélérations variables, l’erreur d’intégration moyenne n’est que proportionnelle au carré du pas de temps. Cela peut permettre des pas de temps plus importants sans compromettre la précision.

  • Ils sont symplectiques , ce qui signifie qu'ils conservent de l'énergie si la physique sous-jacente le fait (du moins tant que le pas de temps est constant). En particulier, cela signifie que vous n'obtiendrez pas d'éléments comme des planètes volant spontanément hors de leur orbite ou des objets reliés les uns aux autres par des ressorts qui vacillent progressivement de plus en plus jusqu'à ce que le tout explose.

Cependant, les méthodes vélocité Verlet / saute-mouton sont presque aussi simples et rapides que l’intégration Euler de base, et certainement beaucoup plus simples que des alternatives comme l’ intégration Runge-Kutta de quatrième ordre (qui, bien que généralement un très bon intégrateur, manque de la propriété symplectique et nécessite quatre évaluations. de la force()fonction par pas de temps). Ainsi, je les recommanderais fortement à quiconque écrit n'importe quel code de physique de jeu, même si c'est aussi simple que de sauter d'une plate-forme à une autre.


Edition: Bien que la dérivation formelle de la méthode de vélocité de vélocité ne soit valide que lorsque les forces sont indépendantes de la vitesse, vous pouvez l’utiliser très bien, même avec des forces dépendant de la vitesse, telles que la traînée de fluide . Pour de meilleurs résultats, vous devez utiliser la valeur d'accélération initiale pour estimer la nouvelle vitesse du second appel force(), comme ceci:

accélération = force (temps, position, vitesse) / masse;
time + = timestep;
position + = timestep * ( vélocité + timestep * accélération / 2) ;
vélocité + = accélération *;
newAcceleration = force (temps, position, vitesse) / masse; 
vélocité + = timestep * (newAcceleration - accélération) / 2 ;

Je ne sais pas si cette variante de la méthode Velocity Verlet a un nom spécifique, mais je l’ai testée et cela semble très bien fonctionner. Ce n'est pas tout à fait aussi précis que Runge-Kutta de quatrième ordre (comme on pourrait s'y attendre avec une méthode du second ordre), mais il est bien meilleur qu'Euler ou que Verlet, une vélocité naïve, sans l'estimation de la vitesse intermédiaire, tout en conservant la propriété symplectique de normale. vélocité Verlet pour les forces conservatrices non dépendantes de la vitesse.

Edit 2: Un algorithme très similaire est décrit par exemple par Groot & Warren ( J. Chem. Phys. 1997) , bien que, lisant entre les lignes, il semble qu'elles aient sacrifié une certaine précision pour une vitesse supplémentaire en sauvegardant la newAccelerationvaleur calculée en utilisant la vitesse estimée. et de le réutiliser accelerationpour le prochain pas de temps. Ils introduisent également un paramètre 0 ≤ λ ≤ 1, multiplié par accelerationdans l'estimation de la vitesse initiale; pour une raison quelconque, ils recommandent λ = 0.5, même si tous mes tests suggèrent que λ= 1 (ce qui est effectivement ce que j'utilise ci-dessus) fonctionne aussi bien ou mieux, avec ou sans la réutilisation de l'accélération. Cela tient peut-être au fait que leurs forces incluent une composante stochastique du mouvement brownien.

Ilmari Karonen
la source
Velocity Verlet, c’est bien, mais il ne peut pas y avoir de potentiel dépendant de la vitesse, donc le frottement ne peut pas être mis en oeuvre. Je pense que Runge-Kutta 2 est le meilleur pour mon but;)
Pizzirani Leonardo
1
@ PizziraniLeonardo: Vous pouvez utiliser (une variante de) la vélocité Verlet très bien, même pour les forces dépendant de la vélocité; voir mon édition ci-dessus.
Ilmari Karonen
1
La littérature ne donne pas à cette interprétation de Velocity Verlet un nom différent. Elle repose sur une stratégie de correction de prédicteur, comme indiqué également dans le présent document fire.nist.gov/bfrlpubs/build99/PDF/b99014.pdf .
Teodron
3
@ Unit978: Cela dépend du jeu, et plus particulièrement du modèle physique qu'il implémente. La force(time, position, velocity)réponse ci-dessus est juste un raccourci pour "la force agissant sur un objet qui se positiondéplace velocityà time". Typiquement, la force dépend de choses comme si l'objet est en chute libre ou assis sur une surface solide, si d'autres objets à proximité exercent une force dessus, à quelle vitesse il se déplace sur une surface (frottement) et / ou à travers un liquide. ou gaz (drag), etc.
Ilmari Karonen
1
C'est une excellente réponse, mais elle est incomplète sans parler de pas de temps fixe ( gafferongames.com/game-physics/fix-your-timestep ). J'ajouterais une réponse séparée, mais la plupart des gens s'en tiennent à la réponse acceptée, en particulier lorsqu'elle obtient le plus grand nombre de voix par une marge aussi large, comme c'est le cas ici. Je pense que la communauté est mieux servie en augmentant celle-ci.
Jibb Smart
13

Chaque boucle de mise à jour de votre jeu, procédez comme suit:

if (collidingBelow())
    gravity = 0;
else gravity = [insert gravity value here];

velocity.y += gravity;

Par exemple, dans un jeu de plateforme, une fois que vous sautez au sol, la gravité est activée (collidingBelow vous indique s'il existe ou non un sol juste en dessous de vous) et une fois que vous touchez le sol, il est désactivé.

En plus de cela, pour implémenter des sauts, alors faites ceci:

if (pressingJumpButton() && collidingBelow())
    velocity.y = [insert jump speed here]; // the jump speed should be negative

Et bien évidemment, dans la boucle de mise à jour, vous devez également mettre à jour votre position:

position += velocity;
Pecant
la source
6
Que voulez-vous dire? Il suffit de choisir votre propre valeur de gravité et comme elle change votre vitesse, pas seulement votre position, elle semble naturelle.
Pecant
1
Je n'aime pas désactiver la gravité. Je pense que la gravité devrait être constante. La chose qui devrait changer (à mon humble avis) est votre capacité à sauter.
Ultifinitus
2
Si cela vous aide, considérez-le comme une "chute" plutôt que comme une "gravité". La fonction dans son ensemble contrôle si l'objet tombe ou non en raison de la gravité. La gravité elle-même existe exactement comme cela [insérer la valeur de gravité ici]. Donc, dans ce sens, la gravité est constante, vous ne l'utilisez tout simplement que si l'objet est en suspension dans l'air.
Jason Pineo
2
Ce code dépend du débit de trame, ce qui n’est pas terrible, mais si vous avez une mise à jour constante, vous rigolez.
tenpn
1
-1, désolé. Ajouter de la vitesse et de la gravité, ou de la position et de la vitesse, n’a aucun sens. Je comprends le raccourci que vous faites, mais c'est faux. Je toucherais n'importe quel étudiant, stagiaire ou collègue qui le ferait avec le plus grand cluebat que j'ai pu trouver. La cohérence des unités est importante.
Sam Hocevar
8

Une intégration de physique newtonienne indépendante * à la cadence appropriée:

Vector forces = 0.0f;

// gravity
forces += down * m_gravityConstant; // 9.8m/s/s on earth

// left/right movement
forces += right * m_movementConstant * controlInput; // where input is scaled -1..1

// add other forces in for taste - usual suspects include air resistence
// proportional to the square of velocity, against the direction of movement. 
// this has the effect of capping max speed.

Vector acceleration = forces / m_massConstant; 
m_velocity += acceleration * timeStep;
m_position += velocity * timeStep;

Ajustez la gravitéConstant, le mouvementConstant et la masseConstant jusqu'à ce qu'il se sente bien. C'est une chose intuitive et peut prendre un certain temps pour se sentir bien.

Il est facile d'étendre le vecteur des forces pour ajouter un nouveau jeu - par exemple, en éloignant toute force d'une explosion à proximité ou des trous noirs.

* edit: ces résultats se tromperont avec le temps, mais peuvent être "assez bons" pour votre fidélité ou vos aptitudes. Voir ce lien http://lol.zoy.org/blog/2011/12/14/understanding-motion-in-games pour plus d'informations.

tenpn
la source
4
N'utilisez pas l'intégration d'Euler. Voir cet article de Glenn Fiedler qui explique les problèmes et les solutions mieux que moi. :)
Martin Sojka Le
1
Je comprends comment Euler est inexact au fil du temps, mais je pense qu'il existe des scénarios dans lesquels cela n'a pas vraiment d'importance. Tant que les règles sont cohérentes pour tout le monde et que le client "se sent" bien, ça va. Et si vous en apprenez seulement sur les phyisques, il est très facile de vous en souvenir et de les implémenter.
tenpn
... bon lien cependant. ;)
tenpn
4
Vous pouvez résoudre la plupart des problèmes liés à l'intégration d'Euler simplement en remplaçant ce qui position += velocity * timestepprécède par position += (velocity - acceleration * timestep / 2) * timestep(où velocity - acceleration * timestep / 2est simplement la moyenne de l'ancienne et de la nouvelle vélocité). En particulier, cet intégrateur donne des résultats exacts si l'accélération est constante, comme c'est généralement le cas pour la gravité. Pour une meilleure précision avec des accélérations variables, vous pouvez ajouter une correction similaire à la mise à jour de la vélocité pour obtenir l' intégration de la vélocité à la vélocité .
Ilmari Karonen
Vos arguments ont un sens, et l’inexactitude n’est souvent pas grave. Mais vous ne devriez pas prétendre qu'il s'agit d'une intégration «indépendante du débit d'images appropriée», car elle ne l'est tout simplement pas (indépendante de la fréquence de trame).
Sam Hocevar
3

Si vous souhaitez appliquer la gravité à une échelle légèrement supérieure, vous pouvez utiliser ce type de calcul à chaque boucle:

for each object in the scene
  for each other_object in the scene not equal to object
    if object.mass * other_object.mass / object.distanceSquaredBetweenCenterOfMasses(other_object) < epsilon
      abort the calculation for this pair
    if object.mass is much, much bigger than other_object.mass
      abort the calculation for this pair
    force = gravitational_constant
            * object.mass * other_object.mass
            / object.distanceSquaredBetweenCenterOfMasses(other_object)
    object.addForceAtCenterOfMass(force * object.normalizedDirectionalVectorTo(other_object))
  end for loop
end for loop

Pour des échelles encore plus grandes (galactiques), la gravité seule ne suffira pas pour créer un "vrai" mouvement. L'interaction des systèmes stellaires est dans une mesure significative et très visible dictée par les équations de Navier-Stokes pour la dynamique des fluides, et vous devrez également garder à l'esprit la vitesse finie de la lumière - et donc de la gravité.

Martin Sojka
la source
1

Le code fourni par Ilmari Karonen est presque correct, mais il y a un léger problème. En fait, vous calculez l'accélération deux fois par tick, cela ne correspond pas aux équations du manuel.

acceleration = force(time, position) / mass; // Here
time += timestep;
position += timestep * (velocity + timestep * acceleration / 2);
newAcceleration = force(time, position) / mass;
velocity += timestep * (acceleration + newAcceleration) / 2;

Le mod suivant est correct:

time += timestep;
position += timestep * (velocity + timestep * acceleration / 2);
oldAcceletation = acceleration; // Store it
acceleration = force(time, position) / mass;
velocity += timestep * (acceleration + oldAcceleration) / 2;

À votre santé'

Satanfu
la source
Je pense que vous vous trompez, car l'accélération dépend de la vitesse
super
-4

Le répondeur de Pecant a ignoré le temps de trame, ce qui rend votre comportement physique différent de temps en temps.

Si vous voulez créer un jeu très simple, vous pouvez créer votre propre petit moteur physique - attribuer une masse et toutes sortes de paramètres physiques à chaque objet en mouvement, puis détecter les collisions, puis mettre à jour leur position et leur vitesse à chaque image. Afin d'accélérer cette progression, vous devez simplifier le maillage de collision, réduire le nombre d'appels de détection de collision, etc. Dans la plupart des cas, c'est pénible.

Il est préférable d'utiliser un moteur physique comme physix, ODE et bullet. N'importe lequel d'entre eux sera suffisamment stable et efficace pour vous.

http://www.nvidia.com/object/physx_new.html

http://bulletphysics.org/wordpress/

Raymond
la source
4
-1 Réponse inutile qui ne répond pas à la question.
doppelgreener
4
Eh bien, si vous voulez l'ajuster au temps, vous pouvez simplement ajuster la vélocité en fonction du temps écoulé depuis la dernière mise à jour ().
Pecant