Come posso implementare la gravità?


Risposte:


53

Come altri hanno notato nei commenti, il metodo di integrazione di Eulero di base descritto nella risposta di tenpn soffre di alcuni problemi:

  • Anche per un semplice movimento, come il salto balistico sotto gravità costante, introduce un errore sistematico.

  • L'errore dipende dal timestep, nel senso che cambiando il timestep cambia le traiettorie degli oggetti in modo sistematico che può essere notato dai giocatori se il gioco utilizza un timestep variabile. Anche per i giochi con un timestep di fisica fisso, la modifica del timestep durante lo sviluppo può influire notevolmente sulla fisica del gioco, come la distanza che un oggetto lanciato con una data forza volerà, potenzialmente rompendo i livelli precedentemente progettati.

  • Non conserva energia, anche se la fisica sottostante dovrebbe. In particolare, gli oggetti che dovrebbero oscillare costantemente (ad es. Pendoli, molle, pianeti orbitanti, ecc.) Possono accumulare costantemente energia fino a quando l'intero sistema non si rompe.

Fortunatamente, non è difficile da sostituire l'integrazione di Eulero con qualcosa che è quasi il più semplice, ma ha nessuno di questi problemi - in particolare, un secondo ordine integratore simplettica come ad esempio l'integrazione cavallina o strettamente correlato velocità metodo Verlet . In particolare, dove l'integrazione di base di Euler aggiorna la velocità e la posizione come:

accelerazione = forza (tempo, posizione) / massa;
tempo + = timestep;
posizione + = timestep * velocità;
velocità + = timestep * accelerazione;

il metodo Velocity Verlet fa così:

accelerazione = forza (tempo, posizione) / massa;
tempo + = timestep;
posizione + = timestep * ( velocità + timestep * accelerazione / 2) ;
newAcceleration = force (time, position) / mass; 
velocity + = timestep * ( acceleration + newAcceleration) / 2 ;

Se si hanno più oggetti interagenti, è necessario aggiornare tutte le loro posizioni prima di ricalcolare le forze e aggiornare le velocità. Le nuove accelerazioni possono quindi essere salvate e utilizzate per aggiornare le posizioni al successivo timestep, riducendo il numero di chiamate force()a uno (per oggetto) per timestep, proprio come con il metodo Euler.

Inoltre, se l'accelerazione è normalmente costante (come la gravità durante il salto balistico), possiamo semplificare quanto sopra solo a:

tempo + = timestep;
posizione + = timestep * ( velocità + timestep * accelerazione / 2) ;
velocità + = timestep * accelerazione;

dove il termine aggiuntivo in grassetto è l'unica modifica rispetto all'integrazione di base di Euler.

Rispetto all'integrazione di Eulero, i metodi velocity Verlet e leapfrog hanno diverse belle proprietà:

  • Per un'accelerazione costante, forniscono risultati esatti (fino a errori di arrotondamento in virgola mobile, comunque), il che significa che le traiettorie del salto balistico rimangono invariate anche se il timestep viene modificato.

  • Sono integratori del secondo ordine, il che significa che, anche con un'accelerazione variabile, l'errore di integrazione medio è solo proporzionale al quadrato del timestep. Ciò può consentire tempi più lunghi senza compromettere la precisione.

  • Sono simpatici , nel senso che conservano energia se la fisica sottostante lo fa (almeno fino a quando il tempo è costante). In particolare, ciò significa che non riuscirai a far volare spontaneamente cose come i pianeti dalle loro orbite, o gli oggetti attaccati l'uno all'altro con molle che oscillano gradualmente sempre di più fino a quando tutto esplode.

Tuttavia, il metodo Velocity Verlet / leapfrog è quasi semplice e veloce come l'integrazione di base di Euler, e sicuramente molto più semplice delle alternative come l' integrazione di Runge-Kutta del quarto ordine (che, sebbene generalmente un integratore molto carino, manca della proprietà simpatica e richiede quattro valutazioni della force()funzione per fase temporale). Pertanto, li consiglio vivamente a chiunque scriva qualsiasi tipo di codice di fisica del gioco, anche se è semplice come saltare da una piattaforma all'altra.


Modifica: Mentre la derivazione formale del metodo Verlet di velocità è valida solo quando le forze sono indipendenti dalla velocità, in pratica puoi usarla bene anche con forze dipendenti dalla velocità come la resistenza del fluido . Per risultati ottimali, è necessario utilizzare il valore di accelerazione iniziale per stimare la nuova velocità per la seconda chiamata force(), in questo modo:

accelerazione = forza (tempo, posizione, velocità) / massa;
tempo + = timestep;
posizione + = timestep * ( velocità + timestep * accelerazione / 2) ;
velocità + = timestep * accelerazione;
newAcceleration = forza (tempo, posizione, velocità) / massa; 
velocity + = timestep * (newAcceleration - acceleration) / 2 ;

Non sono sicuro che questa particolare variante del metodo Velocity Verlet abbia un nome specifico, ma l'ho testato e sembra funzionare molto bene. Non è così accurato come Runge-Kutta di ordine di bocca (come ci si aspetterebbe da un metodo del secondo ordine), ma è molto meglio di Euler o del Verlet di velocità ingenuo senza la stima della velocità intermedia, e mantiene ancora la proprietà simplettica del normale Verlet di velocità per forze conservative, non dipendenti dalla velocità.

Modifica 2: Un algoritmo molto simile è descritto ad esempio da Groot & Warren ( J. Chem. Phys. 1997) , sebbene, leggendo tra le righe, sembra che abbiano sacrificato una certa precisione per una maggiore velocità salvando il newAccelerationvalore calcolato usando la velocità stimata e riutilizzandolo come accelerationper il prossimo timestep. Introducono anche un parametro 0 ≤ λ ≤ 1 che viene moltiplicato per accelerationnella stima della velocità iniziale; per qualche ragione, raccomandano λ = 0,5, anche se tutti i miei test suggeriscono che λ= 1 (che è effettivamente quello che uso sopra) funziona bene o meglio, con o senza il riutilizzo dell'accelerazione. Forse ha qualcosa a che fare con il fatto che le loro forze includono una componente stocastica del movimento browniano.


Velocity Verlet è bello ma non può avere un potenziale dipendente dalla velocità, quindi l'attrito non può essere implementato. Penso che Runge-Kutta 2 sia il migliore per il mio scopo;)
Pizzirani Leonardo

1
@PizziraniLeonardo: puoi usare (una variante di) il Velocity Verlet bene anche per le forze dipendenti dalla velocità; vedi la mia modifica sopra.
Ilmari Karonen,

1
La letteratura non dà a questa interpretazione di Velocity Verlet un nome diverso. Si basa su una strategia predittore-correttore, come indicato anche in questo documento fire.nist.gov/bfrlpubs/build99/PDF/b99014.pdf .
teodron,

3
@ Unit978: dipende dal gioco e in particolare dal modello fisico che implementa. La force(time, position, velocity)mia risposta sopra è solo una scorciatoia per "la forza che agisce su un oggetto in positionmovimento a velocitya time". In genere, la forza dipende da cose come se l'oggetto è in caduta libera o seduto su una superficie solida, se altri oggetti vicini esercitano una forza su di esso, quanto velocemente si muove su una superficie (attrito) e / o attraverso un liquido o gas (trascinamento), ecc.
Ilmari Karonen,

1
Questa è un'ottima risposta, ma è incompleta senza parlare della fase temporale fissa ( gafferongames.com/game-physics/fix-your-timestep ). Aggiungerei una risposta separata, ma la maggior parte delle persone si ferma alla risposta accettata, soprattutto quando ha il maggior numero di voti con un margine così ampio, come è il caso qui. Penso che la comunità sia meglio servita aumentando questa.
Jibb Smart,

13

Ogni ciclo di aggiornamento del tuo gioco, procedi come segue:

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

velocity.y += gravity;

Ad esempio, in un platform, una volta che salti la gravità sarebbe abilitata (collidingBelow ti dice se c'è o meno un terreno proprio sotto di te) e una volta che colpisci il terreno sarebbe disabilitato.

Oltre a questo, per implementare i salti, quindi fai questo:

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

E ovviamente, nel ciclo di aggiornamento devi anche aggiornare la tua posizione:

position += velocity;

6
Cosa intendi? Basta scegliere il proprio valore di gravità e poiché cambia la velocità, non solo la posizione, sembra naturale.
Pecant,

1
Non mi piace spegnere mai la gravità. Penso che la gravità dovrebbe essere costante. La cosa che dovrebbe cambiare (imho) è la tua capacità di saltare.
ultifinito

2
Se aiuta, pensalo come "caduta" piuttosto che "gravità". La funzione nel suo insieme controlla se l'oggetto cade o meno a causa della gravità. La stessa gravità esiste esattamente come [inserire qui il valore di gravità]. Quindi, in questo senso, la gravità è costante, non la usi per nulla a meno che l'oggetto non sia in volo.
Jason Pineo,

2
Questo codice dipende dalla frequenza dei fotogrammi, il che non è eccezionale, ma se hai un aggiornamento costante, ridi.
tenpn,

1
-1, scusa. Aggiungere velocità e gravità, o posizione e velocità, non ha proprio senso. Capisco la scorciatoia che stai facendo, ma è sbagliata. Colpirei qualsiasi studente, apprendista o collega facendolo con il più grande indizio che potessi trovare. La coerenza delle unità è importante.
Sam Hocevar,

8

Una corretta integrazione della fisica newtoniana indipendente dalla frequenza dei fotogrammi:

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;

Modifica Gravità Costante, movimento Costante e massa Costante fino a quando non ti senti bene. È una cosa intuitiva e può richiedere del tempo per sentirsi bene.

È facile estendere il vettore delle forze per aggiungere un nuovo gameplay, ad esempio aggiungere una forza lontano da qualsiasi esplosione nelle vicinanze o verso i buchi neri.

* modifica: questi risultati saranno errati nel tempo, ma potrebbero essere "abbastanza buoni" per la tua fedeltà o attitudine. Vedi questo link http://lol.zoy.org/blog/2011/12/14/understanding-motion-in-games per maggiori informazioni.


4
Non utilizzare l'integrazione di Euler. Vedi questo articolo di Glenn Fiedler che spiega i problemi e le soluzioni meglio di quanto potessi. :)
Martin Sojka,

1
Capisco come Euler sia inaccurato nel tempo, ma penso che ci siano scenari in cui non importa davvero. Finché le regole sono coerenti per tutti, e "sembra" giusto, va bene. E se stai solo imparando a conoscere i phyisc, è molto facile da ricordare e implmenet.
tenpn,

... buon collegamento però. ;)
tenpn

4
Puoi risolvere la maggior parte dei problemi con l'integrazione di Euler semplicemente sostituendo position += velocity * timestepsopra con position += (velocity - acceleration * timestep / 2) * timestep(dove velocity - acceleration * timestep / 2è semplicemente la media delle vecchie e nuove velocità). In particolare, questo integratore fornisce risultati esatti se l'accelerazione è costante, come in genere per gravità. Per una migliore precisione in caso di accelerazione variabile, è possibile aggiungere una correzione simile all'aggiornamento della velocità per ottenere l' integrazione del Verlet di velocità .
Ilmari Karonen,

Le tue argomentazioni hanno un senso e l'imprecisione spesso non è un grosso problema. Ma non dovresti affermare che si tratta di un'integrazione "indipendente dalla frequenza fotogrammi corretta", perché semplicemente non lo è (framerate indipendente).
Sam Hocevar,

3

Se vuoi implementare la gravità su una scala leggermente più grande, puoi usare questo tipo di calcolo per ogni ciclo:

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

Per scale ancora più grandi (galattiche), la gravità da sola non sarà sufficiente per creare un movimento "reale". L'interazione dei sistemi stellari è in misura significativa e molto visibile dettata dalle equazioni di Navier-Stokes per la fluidodinamica, e dovrai tenere presente anche la velocità finita della luce - e quindi la gravità.


1

Il codice fornito da Ilmari Karonen è quasi corretto, ma c'è un piccolo difetto. In realtà si calcola l'accelerazione 2 volte per tick, questo non segue le equazioni del libro di testo.

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

La seguente mod è corretta:

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

Saluti'


Penso che ti sbagli, poiché l'accelerazione dipende dalla velocità
super

-4

La risposta di Pecant ha ignorato il tempo di frame, e questo rende il tuo comportamento fisico diverso di volta in volta.

Se hai intenzione di creare un gioco molto semplice, puoi creare il tuo piccolo motore fisico: assegnare la massa e tutti i tipi di parametri fisici per ogni oggetto in movimento e fare il rilevamento delle collisioni, quindi aggiornare la loro posizione e la velocità di ogni fotogramma. Per accelerare questo progresso, è necessario semplificare la mesh di collisione, ridurre le chiamate di rilevamento delle collisioni, ecc. Nella maggior parte dei casi, questo è un dolore.

È meglio usare un motore fisico come Physix, ODE e bullet. Ognuno di loro sarà abbastanza stabile ed efficiente per te.

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

http://bulletphysics.org/wordpress/


4
-1 Risposta inutile che non risponde alla domanda.
doppelgreener,

4
bene, se vuoi regolarlo per tempo, puoi semplicemente ridimensionare la velocità per il tempo trascorso dall'ultimo aggiornamento ().
Pecant
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.