Come posso implementare la gravità? Non per una lingua particolare, solo pseudocodice ...
Come posso implementare la gravità? Non per una lingua particolare, solo pseudocodice ...
Risposte:
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 newAcceleration
valore calcolato usando la velocità stimata e riutilizzandolo come acceleration
per il prossimo timestep. Introducono anche un parametro 0 ≤ λ ≤ 1 che viene moltiplicato per acceleration
nella 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.
force(time, position, velocity)
mia risposta sopra è solo una scorciatoia per "la forza che agisce su un oggetto in position
movimento a velocity
a 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.
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;
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.
position += velocity * timestep
sopra 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à .
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à.
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'
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.