Comment puis-je mettre en œuvre la gravité? Pas pour une langue particulière, juste un pseudocode ...
Comment puis-je mettre en œuvre la gravité? Pas pour une langue particulière, juste un pseudocode ...
Réponses:
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 newAcceleration
valeur calculée en utilisant la vitesse estimée. et de le réutiliser acceleration
pour le prochain pas de temps. Ils introduisent également un paramètre 0 ≤ λ ≤ 1, multiplié par acceleration
dans 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.
force(time, position, velocity)
réponse ci-dessus est juste un raccourci pour "la force agissant sur un objet qui se position
dé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.
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;
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.
position += velocity * timestep
précède par position += (velocity - acceleration * timestep / 2) * timestep
(où velocity - acceleration * timestep / 2
est 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é .
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é.
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é'
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.