Det finns många anledningar att du kanske vill skapa en anpassad fysikmotor. Först lär du dig och lär dig dina färdigheter i matematik, fysik och programmering. Det är bra skäl att försöka med ett sådant projekt. För det andra kan en anpassad fysikmotor ta itu med någon form av teknisk effekt som skaparen har färdighet att skapa. I denna artikel vill jag ge en solid introduktion om hur man skapar en anpassad fysikmotor helt från början.
Fysik ger ett underbart sätt att låta en spelare fördjupa sig i ett spel. Det är vettigt att en behärskning av en fysikmotor skulle vara en kraftfull tillgång för vilken programmerare som helst har till sitt förfogande. Optimeringar och specialiseringar kan göras när som helst på grund av en djup förståelse för fysikmotorns inre verkningar.
Vid slutet av denna handledning har följande ämnen tagits upp i två dimensioner:
Här är en snabb demo:
Notera: Även om denna handledning skrivs med C ++, borde du kunna använda samma tekniker och begrepp i nästan vilken spelutvecklingsmiljö som helst.
Denna artikel innehåller en hel del matematik och geometri, och i mycket mindre utsträckning faktisk kodning. Ett par förutsättningar för denna artikel är:
Det finns en hel del artiklar och handledning på internet, inklusive här på Tuts +, som täcker kollisionsdetektering. Jag vet att jag skulle vilja springa igenom ämnet väldigt snabbt, eftersom det här avsnittet inte är fokus för denna artikel.
En Axis Aligned Bounding Box (AABB) är en låda som har sina fyra axlar i linje med det koordinatsystem där det ligger. Det betyder att det är en låda som inte kan rotera och kvadreras alltid 90 grader (vanligtvis anpassad till skärmen). I allmänhet kallas det en "begränsningsbox" eftersom AABB används för att binda andra mer komplexa former.
Ett exempel AABB.AABB med en komplex form kan användas som ett enkelt test för att se om mer komplexa former inom AABB: erna kan vara korsande. Men för de flesta spel används AABB som en grundläggande form och förbinder inte faktiskt något annat. Strukturen i din AABB är viktig. Det finns några olika sätt att representera en AABB, men det här är min favorit:
struct AABB Vec2 min; Vec2 max; ;
Denna blankett gör det möjligt för en AABB att representeras av två punkter. Minpunkten representerar de nedre gränserna för x- och y-axeln, och max representerar de högre gränserna - med andra ord representerar de de övre vänstra och nedre högra hörnen. För att kunna berätta om två AABB-former skärs måste du ha en grundläggande förståelse för Separat Axis Theorem (SAT).
Här är ett snabbtest taget från Real-Time Collision Detection av Christer Ericson, som använder sig av SAT:
bool AABBvsAABB (AABB a, AABB b) // Avsluta utan korsning om det finns separerat längs en axel om (a.max.x < b.min.x or a.min.x > b.max.x) returnera falskt om (a.max.y < b.min.y or a.min.y > b.max.y) returnera false // Ingen separeringsaxel hittades, därför finns det åtminstone en överlappande axelavkastning sant
En cirkel representeras av en radie och en punkt. Här är vad din cirkelstruktur borde se ut:
struct Circle floatradie Vec position;
Testning av huruvida två cirklar korsar eller inte är mycket enkla: Ta radierna i de två cirklarna och lägg dem ihop och kontrollera om denna summa är större än avståndet mellan de två cirklarna.
En viktig optimering att göra här är att bli av med att behöva använda kvadratrotsoperatören:
float Avstånd (Vec2 a, Vec2 b) retur sqrt ((ax-bx) ^ 2 + (ay-by) ^ 2 boll CirclevsCircleUnoptimized (Circle a, Circle b) float r = a.radius + b.radius returnera r < Distance( a.position, b.position ) bool CirclevsCircleOptimized( Circle a, Circle b ) float r = a.radius + b.radius r *= r return r < (a.x + b.x)^2 + (a.y + b.y)^2
Generellt är multiplikation en mycket billigare operation än att ta kvadratroten av ett värde.
Impulsupplösning är en speciell typ av kollisionsupplösningsstrategi. Kollisionsupplösning är en handling att ta två objekt som befinner sig i korsning och modifiering av dem på ett sådant sätt att de inte kan förbli korsande.
I allmänhet har ett objekt inom en fysikmotor tre huvudgrader av frihet (i två dimensioner): rörelse i xy-planet och rotation. I denna artikel begränsar vi implikt rotation och använder bara AABB och cirklar, så den enda frihetsgraden vi verkligen behöver tänka på är rörelse längs xyplanet.
Genom att lösa upptäckta kollisioner ställer vi in en begränsning av rörelse så att objekt inte kan förbli korsande varandra. Tanken bakom impulsupplösningen är att använda en impuls (momentan hastighetsförändring) för att separera objekt som hittades kolliderande. För att göra detta måste massan, positionen och hastigheten för varje objekt beaktas på något sätt: vi vill att stora föremål kolliderar med mindre för att flytta lite under kollision och att skicka små föremål som flyger bort. Vi vill också ha objekt med oändlig massa för att inte flytta alls.
Enkelt exempel på vilken impulsupplösning som kan uppnås.För att uppnå sådana effekter och följa med naturliga intuitioner om hur föremål beter sig använder vi styva kroppar och en rättvis matbit. En stel kropp är bara en form som definieras av användaren (det vill säga av dig, utvecklaren) som implicit definieras vara icke-deformerbar. Både AABB och cirklar i den här artikeln är inte deformerbara och kommer alltid att vara antingen en AABB eller Circle. Ingen squashing eller stretching tillåts.
Att arbeta med styva kroppar möjliggör en hel del förenkling av mat och derivat. Det är därför som stela kroppar ofta används i spelsimuleringar, och varför använder vi dem i den här artikeln.
Förutsatt att vi har två former som befinner sig att korsa, hur skiljer man faktiskt de två? Låt oss anta att vår kollisionsdetektering gav oss två viktiga uppgifter:
För att kunna tillämpa en impuls på båda objekten och flytta dem, måste vi veta vilken riktning de ska driva och hur mycket. Kollisionsnormal är den riktning i vilken impulsen kommer att appliceras. Penningdjupet (tillsammans med några andra saker) bestämmer hur stor en impuls ska användas. Det betyder att det enda värdet som behöver lösas är storleken på vår impuls.
Låt oss nu gå på en lång vandring för att upptäcka hur vi kan lösa denna impulsstorlek. Vi börjar med våra två objekt som har visat sig vara korsande:
Ekvation 1\ [V ^ AB = V ^ B - V ^ A \] Observera att för att skapa en vektor från position A till position B måste du göra: slutpunkt - startpunkt
. \ (V ^ AB \) är den relativa hastigheten från A till B. Denna ekvation borde uttryckas med avseende på kollisionsnormal \ (n \) - det vill säga vi skulle vilja veta relativhastigheten från A till B längs kollisionens normala riktning:
\ [V ^ AB \ cdot n = (V ^ B - V ^ A) \ cdot n \]
Vi använder nu primärprodukten. Dotprodukten är enkel; det är summan av komponentvisa produkter:
Ekvation 3\ [V_1 = \ start bmatrix x_1 \\ y_1 \ end bmatrix, V_2 = \ start bmatrix x_2 \\ y_2 \ end bmatrix \\ V_1 \ cdot V_2 = x_1 * x_2 + y_2 * y_2 \ ]
Nästa steg är att införa vad som kallas återbetalningskoefficient. Återbetalning är ett begrepp som betyder elasticitet eller bounciness. Varje objekt i din fysikmotor kommer att få en återbetalning representerad som ett decimalvärde. Men endast en decimalvärde kommer att användas under impulberäkningen.
För att bestämma vilken återbetalning som ska användas (betecknad av \ (e \) för epsilon) ska du alltid använda den lägsta återbetalningen som är inblandad i kollisionen för intuitiva resultat:
// Med tanke på två objekt A och B e = min (A.restitution, B.restitution)
När \ (e \) är förvärvat kan vi placera den i vår ekvationslösning för impulsstyrkan.
Newtons Restitution Law säger följande:
Ekvation 4\ [V '= e * V \]
Allt detta säger är att hastigheten efter en kollision är lika med hastigheten före den, multiplicerad med en viss konstant. Denna konstant representerar en "studsfaktor". Att veta detta blir ganska enkelt att integrera återbetalning i vår nuvarande avledning:
Ekvation 5\ [V ^ AB \ cdot n = -e * (V ^ B - V ^ A) \ cdot n \]
Lägg märke till hur vi introducerade ett negativt tecken här. I Newtons Restitution Law, \ (V '\), den resulterande vektorn efter studsen, går faktiskt i motsatt riktning mot V. Så hur representerar vi motsatta riktningar i vår avledning? Presentera ett negativt tecken.
Än så länge är allt bra. Nu måste vi kunna uttrycka dessa hastigheter medan de påverkas av en impuls. Här är en enkel ekvation för att modifiera en vektor med viss impulskalär \ (j \) längs en specifik riktning \ (n \):
Ekvation 6\ [V '= V + j * n \]
Förhoppningsvis är ovanstående ekvation meningsfull, eftersom det är väldigt viktigt att förstå. Vi har en enhedsvektor \ (n \) som representerar en riktning. Vi har en skalär \ (j \) som representerar hur länge vår \ (n \) vektor kommer att vara. Vi lägger sedan till vår skalade \ (n \) vektor till \ (V \) för att resultera i \ (V '\). Detta lägger bara till en vektor på en annan, och vi kan använda den här lilla ekvationen för att applicera en impuls från en vektor till en annan.
Det finns lite mer arbete att göra här. Formellt definieras en impuls som en förändring i momentum. Momentum är massa * hastighet
. Att veta detta kan vi representera en impuls, eftersom den formellt definieras som så:
\ [Impulse = massa * Hastighet \\ Velocity = \ frac Impulse mass \ därför V '= V + \ frac j * n mass \]
De tre prickarna i en liten triangel (\ (\ därför \)) kan läsas som "därför". Det är vanligt att visa att saken i förväg kan användas för att dra slutsatsen att det som kommer nästa är sant.Goda framsteg har gjorts hittills! Vi måste dock kunna uttrycka en impuls med hjälp av \ (j \) när det gäller två olika objekt. Under en kollision med objekt A och B kommer A att tryckas i motsatt riktning av B:
Ekvation 8Mass ^ A \\ V '^ B = V ^ B - \ frac j * n mass ^ B \]
Dessa två ekvationer kommer att trycka A bort från B längs riktningsenhetsvektorn \ (n \) genom impulsskalär (magnitud av \ (n \)) \ (j \).
Allt som nu krävs är att slå samman ekvationerna 8 och 5. Vår resulterande ekvation kommer att se ut så här:
Ekvation 9Mass ^ A + \ frac j * n mass ^ B) * n = -e * (V ^ B-V ^ A) \ cdot n \\ \ därför \\ (V ^ A - V ^ V + \ frac j * n mass ^ A + \ frac j * n mass ^ B) * n + e * (V ^ B - V ^ A) \ cdot n = 0 \]
Om du kommer ihåg var det ursprungliga målet att isolera vår storlek. Detta beror på att vi vet vilken riktning som ska lösas i kollisionen (antagen av kollisionsdetektering) och har bara lämnat för att lösa storleken på denna riktning. Storleken som är okänd i vårt fall är \ (j \); vi måste isolera \ (j \) och lösa det.
Ekvation 10\ [(V ^ B - V ^ A) \ cdot n + j * \ frac j * n mass ^ A + \ frac j * n mass ^ B) * n + e * V ^ B - V ^ A) \ cdot n = 0 \\ \ därför \\ (1 + e) ((V ^ B - V ^ A) \ cdot n) + j * \ frac j * n massa ^ A + \ frac j * n mass ^ B) * n = 0 \\ \ därför \\ j = \ frac - (1 + e) ((V ^ B - V ^ A) \ cdot n) \ frac 1 mass ^ A + \ frac 1 mass ^ B \]
Puh! Det var en bra bit av matte! Det är dock överallt för nu. Det är viktigt att märka att i den slutliga versionen av ekvation 10 har vi \ (j \) till vänster (vår storlek) och allt till höger är alla kända. Det betyder att vi kan skriva några rader kod för att lösa för vår impulskalar \ (j \). Och pojken är koden mycket mer läsbar än matematisk notation!
void ResolveCollision (Objekt A, Objekt B) // Beräkna relativ hastighet Vec2 rv = B.hastocity - A.velocity // Beräkna relativ hastighet med avseende på normal riktning float velAlongNormal = DotProduct (rv, normal) // Lös inte om hastigheter separeras om (velAlongNormal> 0) återvänder; // Beräkna återbetalningsflott e = min (A.restitution, B.restitution) // Beräkna impulscalarflödet j = - (1 + e) * velAlongNormal j / = 1 / A.mass + 1 / B.mass // Applicera impuls Vec2 impuls = j * normal A.hastighet - = 1 / A.mass * impuls B.hastighet + = 1 / B.mass * impuls
Det finns några viktiga saker att notera i ovanstående kodprov. Det första är kontrollen på Linje 10, om (VelAlongNormal> 0)
. Denna kontroll är mycket viktig; Det säkerställer att du bara löser en kollision om objekten rör sig mot varandra.
Om objekt rör sig bort från varandra vill vi inte göra någonting. Detta kommer att förhindra objekt som faktiskt inte bör anses vara kolliderande från att lösa sig ifrån varandra. Detta är viktigt för att skapa en simulering som följer mänsklig intuition om vad som ska hända under objektinteraktion.
Den andra sak att notera är att invers massa beräknas flera gånger utan anledning. Det är bäst att bara lagra din inversa massa inom varje objekt och förberäkna det en gång:
A.inv_mass = 1 / A.massMånga fysikmotorer lagrar faktiskt inte rå massa. Fysikmotorer lagrar ofta inverterad massa och invers massa i sig. Det händer bara att de flesta matematik som involverar massa är i form av
1 / massa
. Det sista att notera är att vi intelligent distribuerar vår impulskalär \ (j \) över de två objekten. Vi vill att små objekt ska studsa av stora objekt med en stor del av \ (j \), och de stora objekten har sina hastigheter modifierade av en mycket liten del av \ (j \).
För att göra detta kan du göra:
float mass_sum = A.mass + B.massflödesförhållande = A.mass / mass_sum A.hastighet - = förhållande * impulsförhållande = B.mass / mass_sum B.hastighet + = förhållande * impuls
Det är viktigt att inse att ovanstående kod motsvarar ResolveCollision ()
provfunktion från tidigare. Som sagt tidigare är inversa massor ganska användbara i en fysikmotor.
Om vi fortsätter och använder koden som vi har hittills, kommer objekt att springa in i varandra och studsa av. Det här är bra, men vad händer om en av föremålen har oändlig massa? Tja, vi behöver ett bra sätt att representera oändlig massa inom vår simulering.
Jag föreslår att du använder noll som oändlig massa - men om vi försöker beräkna invers massa av ett objekt med noll kommer vi att ha en uppdelning med noll. Lösningen för detta är att göra följande vid beräkning av invers massa:
om (A.mass == 0) A.inv_mass = 0 annars A.inv_mass = 1 / A.mass
Ett värde på noll kommer att resultera i korrekta beräkningar under impulsupplösning. Detta är fortfarande okej. Problemet med att sänka föremål uppstår när något börjar sjunka in i ett annat objekt på grund av gravitationen. Kanske kan något med låg återställning träffa en vägg med oändlig massa och börjar sjunka.
Denna sänkning beror på flytande punktfel. Under varje flytpunktsberäkning införs ett litet flödesfel på grund av maskinvaran. (För mer information, Google [Floating point error IEEE754].) Med tiden ackumuleras detta fel i positionsfel, vilket orsakar att objekt sjunker i varandra.
För att korrigera detta fel måste det redovisas. För att korrigera detta positionsfel ska jag visa en metod som kallas linjär projektion. Linjär projektion minskar penetrering av två objekt med en liten procentandel, och detta utförs efter impulsen appliceras. Positionskorrigering är väldigt enkel: Flytta varje objekt längs kollisionen normal \ (n \) med en procentandel av penetrationsdjupet:
void PositionalCorrection (Objekt A, Objekt B) const floatprocent = 0.2 // vanligtvis 20% till 80% Vec2-korrigering = penetrationDepth / (A.inv_mass + B.inv_mass)) * procent * n A.position - = A.inv_mass * korrigering B.position + = B.inv_mass * korrigering
Observera att vi skala på Penetrationsdjup
av systemets totala massa. Detta kommer att ge en positionskorrigering proportionell mot hur mycket massa vi har att göra med. Små föremål skjuter snabbare än tyngre föremål.
Det finns ett litet problem med denna implementering: om vi alltid löser vårt positionsfel kommer objekten att jitter fram och tillbaka medan de vilar på varandra. För att förhindra detta måste viss släckning ges. Vi utför endast positionskorrigering om penetrationen ligger över en godtycklig tröskel, kallad "slop":
void PositionalCorrection (Objekt A, Objekt B) const floatprocent = 0.2 // Vanligtvis 20% till 80% const float slop = 0,01 // Vanligtvis 0,01 till 0,1 Vec2 korrigering = max (penetration - k_slop, 0,0f) / (A. inv_mass + B.inv_mass)) * procent * n A.position - = A.inv_mass * korrigering B.position + = B.inv_mass * korrigering
Detta gör det möjligt för föremål att penetrera någonsin så lite utan att positionskorrigeringen sparkar in.
Det sista ämnet att täcka i den här artikeln är enkel manifoldgenerering. en grenrör i matematiska termer är något i linje med "en samling punkter som representerar ett område i rymden". Men när jag hänvisar till termen manifold hänvisar jag till ett litet objekt som innehåller information om en kollision mellan två objekt.
Här är en typisk manifolduppsättning:
struct Manifold Objekt * A; Objekt * B; flytande penetration; Vec2 normal; ;
Under kollisionsdetektering ska både penetrationen och kollisionsnormalen beräknas. För att hitta denna information måste de ursprungliga kollisionsdetekteringsalgoritmerna från början av den här artikeln förlängas.
Lets börja med den enklaste kollisionsalgoritmen: Circle vs Circle. Detta test är mestadels trivialt. Kan du föreställa dig vilken väg att lösa kollisionen kommer att vara i? Det är vektorn från cirkel A till cirkel B. Detta kan erhållas genom att subtrahera B: s position från A: s.
Penetrationsdjupet är relaterat till cirklarna och avståndet från varandra. Cirkelarnas överlappning kan beräknas genom att subtrahera den summerade radien med avståndet från varje objekt.
Här är en fullständig provalgoritm för att generera grenröret för en Circle vs Circle collision:
bool CirclevsCircle (Manifold * m) // Ange ett par pekare till varje objekt Objekt * A = m-> A; Objekt * B = m-> B; // Vektor från A till B Vec2 n = B-> pos - A-> pos float r = A-> radie + B-> radie r * = r om (n.LengthSquared ()> r) returnera false // Cirklar ha kolliderat, beräkna nu manifoldflöde d = n.Längd () // utföra faktiskt kvadrat // Om avståndet mellan cirklarna är inte noll om (d! = 0) // Avståndet är skillnaden mellan radie och avstånd m-> penetration = r - d // Använd vår d eftersom vi utförde sqrt på den redan inom Längd () // Poäng från A till B, och en enhetsvektor c-> normal = t / d return true // Cirklarna är i samma position annars // Välj slumpmässiga (men konsekventa) värden c-> penetration = A-> radie c-> normal = Vec (1, 0) returnera true
De mest anmärkningsvärda sakerna här är: Vi utför inte några rötter tills det är nödvändigt (objekt hittas kollideras), och vi kontrollerar att cirklarna inte befinner sig i samma exakta position. Om de befinner sig i samma position skulle vårt avstånd vara noll, och vi måste undvika uppdelning med noll när vi beräknar t / d
.
AABB till AABB-testet är lite mer komplicerat än Circle vs Circle. Kollisionsnormalen kommer inte att vara vektorn från A till B, men kommer att vara ett ansikte normalt. En AABB är en låda med fyra ansikten. Varje ansikte har en normal. Denna normala representerar en enhetsvektor som är vinkelrätt mot ansiktet.
Undersök den allmänna ekvationen för en rad i 2D:
\ [ax + by + c = 0 \\ normal = \ start bmatrix a \\ b \ slutet bmatrix \]
I ovanstående ekvation, en
och b
är den normala vektorn för en linje och vektorn (a, b)
antas normaliseras (längden på vektorn är noll). Återigen kommer vår kollisions normala (riktning för att lösa kollisionen) att vara i riktning mot en av ansiktsnormalerna.
c
representerar i den allmänna ekvationen för en linje? c
är avståndet från ursprunget. Detta är mycket användbart för testning för att se om en punkt är på ena sidan av en linje eller en annan, som du kommer att se i nästa artikel. Nu är allt som behövs för att ta reda på vilket ansikte som kolliderar på ett av föremålen med det andra objektet, och vi har vårt normala. Men ibland kan flera ansikten av två AABB skär, såsom när två hörn skär varandra. Det betyder att vi måste hitta axel med minst penetration.
Två penetrationsaxlar; Den horisontella x-axeln är axel med minst penetration och denna kollision bör lösas längs x-axeln.Här är en fullständig algoritm för AABB till AABB-manifoldgenerering och kollisionsdetektering:
bool AABBvsAABB (Manifold * m) // Ange ett par pekare till varje objekt Objekt * A = m-> Ett objekt * B = m-> B // Vektor från A till B Vec2 n = B-> pos - A- > pos AABB abox = A-> aabb AABB bbox = B-> aabb // Beräkna halva längden längs x-axeln för varje objekt float a_extent = (abox.max.x - abox.min.x) / 2 float b_extent = (bbox .max.x - bbox.min.x) / 2 // Beräkna överlappning på x-axelflott x_overlap = a_extent + b_extent - abs (nx) // SAT-test på x-axeln om (x_overlap> 0) // Beräkna halva omfång längs x-axeln för varje objekt float a_extent = (abox.max.y - abox.min.y) / 2 float b_extent = (bbox.max.y - bbox.min.y) / 2 // Beräkna överlappning på y-axelflotta y_overlap = a_extent + b_extent - abs (ny) // SAT-test på y-axeln om (y_overlap> 0) // Ta reda på vilken axel som är minst axelaxel om (x_overlap> y_overlap) // Peka mot B som vet att n pekar från A till B om (nx < 0) m->normal = Vec2 (-1, 0) annars m-> normal = Vec2 (0, 0) m-> penetration = x_overlap return true else // Peka mot B som vet att n poäng från A till B om (n.y < 0) m->normal = Vec2 (0, -1) annars m-> normal = Vec2 (0, 1) m-> penetration = y_overlap return true
Det sista testet jag kommer att täcka är Circle vs AABB-testet. Tanken här är att beräkna den närmaste punkten på AABB till cirkeln; därifrån går testet till något som liknar Circle vs Circle-testet. När den närmaste punkten är beräknad och en kollision detekteras är normal den riktning som ligger närmast till cirkelns centrum. Penetrationsdjupet är skillnaden mellan avståndet till närmsta punkt till cirkeln och cirkelns radie.
Det finns ett knepigt speciellt fall; Om cirkelns centrum ligger inom AABB måste centrumets mittpunkt klippas till AABB: s närmaste kant, och det normala måste vändas.
bool AABBvsCircle (Manifold * m) // Ange ett par pekare till varje objekt Objekt * A = m-> Ett Objekt * B = m-> B // Vektor från A till B Vec2 n = B-> pos - A- > pos // Närmaste punkt på A till mitten av B Vec2 närmast = n // Beräkna halva längder längs varje axelflott x_extent = (A-> aabb.max.x - A-> aabb.min.x) / 2 float y_extent = (A-> aabb.max.y - A-> aabb.min.y) / 2 // Klämpunkt på kanterna på AABB närmaste.x = Klämma (-x_extent, x_extent, närmaste.x) closest.y = Clamp (-y_extent, y_extent, closest.y) bool inuti = false // Cirkel är inne i AABB, så vi måste klämma cirkelns centrum // till närmaste kanten om (n == närmast) inside = true // Hitta närmaste axel om (abs (nx)> abs (ny)) // Kläm i närmaste grad om (närmaste.x> 0) närmaste.x = x_extent annat närmaste.x = -x_extent // y-axeln är kortare // Klämma i närmaste grad om (närmaste.y> 0) närmaste.y = y_extent annat närmaste.y = -y_extent Vec2 normal = n - närmaste verkliga d = normal.LengthSquared () real r = B-> radius // Ea rly utanför radien är kortare än avståndet till närmsta punkt och // Circle inte inuti AABB om (d> r * r &&! inuti) returnerar false // Avoided sqrt tills vi behövde d = sqrt (d) // Kollisions normal måste vändas för att peka utåt om cirkeln var // inuti AABB om (inuti) m-> normal = -n m-> penetration = r - d annars m-> normal = n m-> penetration = r - d returnera true
Förhoppningsvis har du lärt dig en eller två om fysiksimulering. Denna handledning är tillräcklig för att låta dig ställa in en enkel anpassad fysikmotor helt från början. I nästa del kommer vi att täcka alla nödvändiga tillägg som alla fysikmotorer kräver, inklusive:
Jag hoppas att du haft den här artikeln och jag ser fram emot att svara på frågor i kommentarerna.