Elementaire 2D Platformer-fysica, deel 6 botsing van object versus object

In de vorige aflevering van de serie hebben we een botsingsdetectiemechanisme geïmplementeerd tussen de game-objecten. In dit deel gebruiken we het botsingsdetectiemechanisme om een ​​eenvoudige maar robuuste fysieke respons tussen de objecten te bouwen.

De demo toont het eindresultaat van deze tutorial. Gebruik WASD om het personage te verplaatsen. De middelste muisknop zorgt voor een eenrichtingsplatform, de rechtermuisknop zorgt voor een massieve tegel en de spatiebalk zorgt voor een karakterkloon. De schuifregelaars veranderen de grootte van het personage van de speler. 

De demo is gepubliceerd onder Unity 5.4.0f3 en de broncode is ook compatibel met deze versie van Unity.

Botsingsreactie

Nu we alle botsingsgegevens hebben van het werk dat we in het vorige deel hebben gedaan, kunnen we een eenvoudig antwoord toevoegen op botsende objecten. Ons doel is hier om het mogelijk te maken dat de objecten niet door elkaar heen gaan alsof ze zich op een ander vlak bevinden - we willen dat ze solide zijn en als een obstakel of platform fungeren voor andere objecten. Daarvoor moeten we maar één ding doen: verplaats het object uit een overlapping als er zich een voordoet.

Dek de extra gegevens af

We hebben aanvullende gegevens nodig voor de MovingObject klasse om het object- versus objectantwoord te verwerken. Allereerst is het leuk om een ​​boolean te hebben om een ​​object als kinematisch te markeren - dat wil zeggen, dit object wordt niet door een ander voorwerp heen en weer geslingerd. 

Deze objecten zullen goed werken als platforms, en het kunnen ook bewegende platforms zijn. Ze zouden de zwaarste dingen om zich heen moeten zijn, dus hun positie zal op geen enkele manier worden gecorrigeerd - andere objecten moeten weggaan om ruimte te maken voor hen.

public bool mIsKinematic = false;

De andere gegevens die ik graag heb, is informatie over of we boven een object staan ​​of naar de linker- of rechterkant, enz. Tot nu toe konden we alleen interageren met tegels, maar nu kunnen we ook communiceren met andere objecten. 

Om hier wat harmonie in te brengen, hebben we een nieuwe reeks variabelen nodig die beschrijven of het personage iets naar links, rechts, boven of onder duwt.

public bool mPushesRight = false; public bool mPushesLeft = false; public bool mPushesBottom = false; public bool mPushesTop = false; public bool mPushedTop = false; public bool mPushedBottom = false; public bool mPushedRight = false; public bool mPushedLeft = false; public bool mPushesLeftObject = false; public bool mPushesRightObject = false; public bool mPushesBottomObject = false; public bool mPushesTopObject = false; public bool mPushedLeftObject = false; public bool mPushedRightObject = false; public bool mPushedBottomObject = false; public bool mPushedTopObject = false; public bool mPushesRightTile = false; public bool mPushesLeftTile = false; public bool mPushesBottomTile = false; public bool mPushesTopTile = false; public bool mPushedTopTile = false; public bool mPushedBottomTile = false; public bool mPushedRightTile = false; public bool mPushedLeftTile = false;

Dat zijn veel variabelen. In een productieomgeving zou het de moeite waard zijn om deze in vlaggen te veranderen en slechts één integer te hebben in plaats van al deze booleans, maar omwille van de eenvoud zullen we deze laten staan ​​zoals ze zijn. 

Zoals u misschien opmerkt, hebben we hier vrij nauwkeurige gegevens. We weten of het personage in het algemeen een obstakel in een bepaalde richting duwt of duwt, maar we kunnen ook gemakkelijk navragen of we naast een tegel of object staan.

Uit de overlapping komen

Laten we het maken UpdatePhysicsResponse functie, waarin we de object- versus objectreactie afhandelen.

private void UpdatePhysicsResponse () 

Allereerst, als het object als kinematisch is gemarkeerd, keren we gewoon terug. We behandelen de reactie niet omdat het kinematische object niet op een ander object hoeft te reageren - de andere objecten moeten daarop reageren.

als (mIskinematisch) terugkomt;

Dit veronderstelt nu dat we geen kinematisch object nodig hebben om de juiste gegevens te hebben over of het een object aan de linkerkant duwt, enz. Als dat niet het geval is, dan zou dit een beetje aangepast moeten worden, wat ik Ik zal later op de regel ingaan.

Laten we nu beginnen met het verwerken van de variabelen die we onlangs hebben verklaard.

mPushedBottomObject = mPushesBottomObject; mPushedRightObject = mPushesRightObject; mPushedLeftObject = mPushesLeftObject; mPushedTopObject = mPushTopObject; mPushesBottomObject = false; mPushesRightObject = false; mPushesLeftObject = false; mPushesTopObject = false;

We slaan de resultaten van het vorige frame op naar de juiste variabelen en gaan er nu van uit dat we geen ander object aanraken.

Laten we nu beginnen met het herhalen van al onze collisiongegevens.

voor (int i = 0; i < mAllCollidingObjects.Count; ++i)  var other = mAllCollidingObjects[i].other; var data = mAllCollidingObjects[i]; var overlap = data.overlap; 

Laten we eerst de gevallen behandelen waarin de objecten elkaar amper raken, niet echt overlappend. In dit geval weten we dat we niets hoeven te verplaatsen, alleen de variabelen instellen. 

Zoals eerder vermeld, is de indicator dat de objecten elkaar raken dat de overlapping op een van de assen gelijk is aan 0. Laten we beginnen met het controleren van de x-as.

if (overlap.x == 0.0f) 

Als de voorwaarde waar is, moeten we zien of het andere object zich aan de linker- of rechterkant van onze AABB bevindt.

if (overlap.x == 0.0f) if (other.mAABB.center.x> mAABB.center.x)  else 

Uiteindelijk, als het aan de rechterkant staat, stel dan de mPushesRightObject naar waar en stel de snelheid in zodat deze niet groter is dan 0, omdat ons object niet meer naar rechts kan bewegen als het pad is geblokkeerd.

if (overlap.x == 0.0f) if (other.mAABB.center.x> mAABB.center.x) mPushesRightObject = true; mSpeed.x = Mathf.Min (mSpeed.x, 0.0f);  else 

Laten we op dezelfde manier met de linkerkant omgaan.

if (overlap.x == 0.0f) if (other.mAABB.center.x> mAABB.center.x) mPushesRightObject = true; mSpeed.x = Mathf.Min (mSpeed.x, 0.0f);  else mPushesLeftObject = true; mSpeed.x = Mathf.Max (mSpeed.x, 0.0f); 

Ten slotte weten we dat we hier verder niets hoeven te doen, dus laten we doorgaan naar de volgende loop-iteratie.

if (overlap.x == 0.0f) if (other.mAABB.center.x> mAABB.center.x) mPushesRightObject = true; mSpeed.x = Mathf.Min (mSpeed.x, 0.0f);  else mPushesLeftObject = true; mSpeed.x = Mathf.Max (mSpeed.x, 0.0f);  doorgaan met; 

Laten we op dezelfde manier met de y-as omgaan.

if (overlap.x == 0.0f) if (other.mAABB.center.x> mAABB.center.x) mPushesRightObject = true; mSpeed.x = Mathf.Min (mSpeed.x, 0.0f);  else mPushesLeftObject = true; mSpeed.x = Mathf.Max (mSpeed.x, 0.0f);  doorgaan met;  else if (overlap.y == 0.0f) if (other.mAABB.center.y> mAABB.center.y) mPushesTopObject = true; mSpeed.y = Mathf.Min (mSpeed.y, 0.0f);  else mPushesBottomObject = true; mSpeed.y = Mathf.Max (mSpeed.y, 0.0f);  doorgaan met; 

Dit is ook een goede plaats om de variabelen voor een kinematisch lichaam in te stellen, als dat nodig is. Het maakt ons niet uit of de overlapping gelijk is aan nul of niet, omdat we toch geen kinematisch object gaan verplaatsen. We moeten ook de snelheidsaanpassing overslaan omdat we een kinematisch object niet willen stoppen. We zullen dit echter niet overslaan voor de demo, omdat we de hulpvariabelen voor kinematische objecten niet zullen gebruiken.

Nu dit is gedekt, kunnen we de objecten behandelen die op de juiste manier overlapt zijn met onze AABB. Laten we echter, voordat we dat doen, uitleg geven over de aanpak die ik heb gevolgd om te reageren op de botsing in de demo.

Allereerst, als het object niet beweegt, en we tegen het botsen, moet het andere object onbewogen blijven. We behandelen het als een kinematisch lichaam. Ik besloot om deze kant op te gaan omdat ik het gevoel heb dat het generieker is, en het duwgedrag kan altijd verder worden behandeld in de aangepaste update van een bepaald object.

Als beide objecten tijdens de botsing in beweging waren, hebben we de overlapping op basis van hun snelheid gesplitst. Hoe sneller ze gingen, het grootste deel van de overlappingswaarde zullen ze terug worden verplaatst.

Het laatste punt is, vergelijkbaar met de tilemap-responsbenadering, als een object valt en tijdens het naar beneden krassen een ander object zelfs horizontaal horizontaal een pixel raakt, zal het object niet afglijden en verder gaan naar beneden, maar zal op die ene pixel blijven staan.

Ik denk dat dit de meest flexibele benadering is en het zou niet moeilijk moeten zijn om het te wijzigen als je een bepaalde reactie anders wilt behandelen.

Laten we doorgaan met de implementatie door de absolute snelheidsvector voor beide objecten tijdens de botsing te berekenen. We hebben ook de som van de snelheden nodig, dus we weten welk percentage van de overlapping ons object moet worden verplaatst.

Vector2 absSpeed1 = new Vector2 (Mathf.Abs (data.pos1.x - data.oldPos1.x), Mathf.Abs (data.pos1.y - data.oldPos1.y)); Vector2 absSpeed2 = nieuwe Vector2 (Mathf.Abs (data.pos2.x - data.oldPos2.x), Mathf.Abs (data.pos2.y - data.oldPos2.y)); Vector2 speedSum = absSpeed1 + absSpeed2;

Merk op dat in plaats van de snelheid te gebruiken die is opgeslagen in de collisiedata, we de offset gebruiken tussen de positie op het moment van de botsing en het frame daarvoor. Dit zal in dit geval alleen nauwkeuriger zijn, omdat de snelheid de bewegingsvector vóór de fysieke correctie vertegenwoordigt. De posities zelf worden gecorrigeerd als het object bijvoorbeeld een effen tegel heeft geraakt, dus als we een gecorrigeerde snelheidsvector willen krijgen, moeten we deze zo berekenen.

Laten we nu beginnen met het berekenen van de snelheidsverhouding voor ons object. Als het andere object kinematisch is, stellen we de snelheidsverhouding in op één om ervoor te zorgen dat we de hele overlappingsvector verplaatsen, in overeenstemming met de regel dat het kinematische object niet mag worden verplaatst.

zweeftoerentalRatioX, speedRatioY; if (other.mIsKinematic) speedRatioX = speedRatioY = 1.0f; anders 

Laten we nu beginnen met een vreemd geval waarbij beide objecten elkaar overlappen, maar beide geen snelheid hebben. Dit zou niet echt moeten gebeuren, maar als een object wordt uitgelokt dat een ander object overlapt, zouden we willen dat ze op natuurlijke wijze uit elkaar gaan. In dat geval willen we dat beide met 50% van de overlappingsvector bewegen.

if (other.mIsKinematic) speedRatioX = speedRatioY = 1.0f; else if (speedSum.x == 0.0f && speedSum.y == 0.0f) speedRatioX = speedRatioY = 0.5f; 

Een ander geval is wanneer het speedSum op de x-as is gelijk aan nul. In dat geval berekenen we de juiste verhouding voor de y-as en stellen we in dat we 50% van de overlap voor de x-as moeten verplaatsen.

if (speedSum.x == 0.0f && speedSum.y == 0.0f) speedRatioX = speedRatioY = 0.5f;  else if (speedSum.x == 0.0f) speedRatioX = 0.5f; speedRatioY = absSpeed1.y / speedSum.y; 

Op dezelfde manier behandelen we het geval waarin het speedSum is alleen nul op de y-as en voor het laatste geval berekenen we beide verhoudingen juist.

if (other.mIsKinematic) speedRatioX = speedRatioY = 1.0f; else if (speedSum.x == 0.0f && speedSum.y == 0.0f) speedRatioX = speedRatioY = 0.5f;  else if (speedSum.x == 0.0f) speedRatioX = 0.5f; speedRatioY = absSpeed1.y / speedSum.y;  else if (speedSum.y == 0.0f) speedRatioX = absSpeed1.x / speedSum.x; speedRatioY = 0,5f;  else speedRatioX = absSpeed1.x / speedSum.x; speedRatioY = absSpeed1.y / speedSum.y; 

Nu de verhoudingen zijn berekend, kunnen we zien hoeveel we nodig hebben om ons object te compenseren.

float offsetX = overlapping.x * speedRatioX; float offsetY = overlapping.y * speedRatioY;

Voordat we beslissen of we het object uit de botsing op de x-as of de y-as moeten verwijderen, bepalen we eerst de richting van waaruit de overlap is gebeurd. Er zijn drie mogelijkheden: ofwel botsen we tegen een ander object horizontaal, verticaal of diagonaal aan. 

In het eerste geval willen we de overlapping op de x-as verlaten, in het tweede geval willen we de overlapping op de y-as verlaten, en in het laatste geval willen we de overlapping op elk willekeurig moment verlaten as had de minste overlap.

Vergeet niet dat om overlappingen met een ander object te maken, we de AABB's nodig hebben om elkaar te overlappen op zowel de x- als de y-as. Om te controleren of we een object horizontaal tegenkomen, zullen we zien of we het vorige frame al overlappen met het object op de y-as. Als dat het geval is, en we hebben elkaar niet overlappend op de x-as, dan moet de overlap zijn gebeurd omdat in het huidige frame de AABB's elkaar op de x-as begonnen te overlappen, en daarom trekken we af dat we tegen een ander object botsen..

Allereerst, laten we berekenen of we overlappen met de andere AABB in het vorige frame.

bool overlappendLastFrameX = Mathf.Abs (data.oldPos1.x - data.oldPos2.x) < mAABB.HalfSizeX + other.mAABB.HalfSizeX; bool overlappedLastFrameY = Mathf.Abs(data.oldPos1.y - data.oldPos2.y) < mAABB.HalfSizeY + other.mAABB.HalfSizeY;

Laten we nu de voorwaarde instellen om horizontaal uit de overlap te gaan. Zoals eerder uitgelegd, moesten we overlappen op de y-as en niet overlappen op de x-as in het vorige frame.

if (! overlappendLastFrameX && overlappendLastFrameY) 

Als dat niet het geval is, zullen we de overlapping op de y-as verlaten.

if (! overlappendLastFrameX && overlappendLastFrameY)  else 

Zoals hierboven vermeld, moeten we ook het scenario bespreken waarbij diagonaal tegen het object wordt gestoten. We botsten diagonaal op het object als onze AABB's elkaar in het vorige frame op geen van de assen overlappen, omdat we weten dat ze in het huidige frame elkaar overlappen, dus de botsing moet tegelijkertijd op beide assen zijn gebeurd.

if ((! overlappendLastFrameX && overlappendLastFrameY) || (! overlappendLastFrameX && overlappendLastFrameY))  else 

Maar we willen alleen uit de overlapping op de as in het geval van een diagonale bobbel als de overlapping op de x-as kleiner is dan de overlapping op de y-as.

if ((! overlappendLastFrameX && overlappendLastFrameY) || (! overlappendLastFrameX && overlappendLastFrameY && Mathf.Abs (overlappen.x) <= Mathf.Abs(overlap.y)))   else  

Dat is alle gevallen opgelost. Nu moeten we het object uit de overlap verwijderen.

if ((! overlappendLastFrameX && overlappendLastFrameY) || (! overlappendLastFrameX && overlappendLastFrameY && Mathf.Abs (overlappen.x) <= Mathf.Abs(overlap.y)))  mPosition.x += offsetX; if (overlap.x < 0.0f)  mPushesRightObject = true; mSpeed.x = Mathf.Min(mSpeed.x, 0.0f);  else  mPushesLeftObject = true; mSpeed.x = Mathf.Max(mSpeed.x, 0.0f);   else  

Zoals je kunt zien, hanteren we het op dezelfde manier als het geval waarin we amper een andere AABB raken, maar daarnaast verplaatsen we ons object met de berekende offset.

De verticale correctie gebeurt op dezelfde manier.

if ((! overlappendLastFrameX && overlappendLastFrameY) || (! overlappendLastFrameX &&! overlappendLastFrameY && Mathf.Abs (overlapping.x) <= Mathf.Abs(overlap.y)))  mPosition.x += offsetX; if (overlap.x < 0.0f)  mPushesRightObject = true; mSpeed.x = Mathf.Min(mSpeed.x, 0.0f);  else  mPushesLeftObject = true; mSpeed.x = Mathf.Max(mSpeed.x, 0.0f);   else  mPosition.y += offsetY; if (overlap.y < 0.0f)  mPushesTopObject = true; mSpeed.y = Mathf.Min(mSpeed.y, 0.0f);  else  mPushesBottomObject = true; mSpeed.y = Mathf.Max(mSpeed.y, 0.0f);  

Dat is het bijna; er is nog een voorbehoud gemaakt. Stel je het scenario voor waarin we tegelijkertijd op twee objecten landen. We hebben twee bijna identieke collision data-instanties. Terwijl we door alle botsingen heengaan, corrigeren we de positie van de botsing met het eerste voorwerp, waardoor we een beetje omhoog komen. 

Vervolgens behandelen we de botsing voor het tweede object. De opgeslagen overlapping op het moment van de botsing is niet langer up-to-date, omdat we al van de oorspronkelijke positie zijn verwijderd en als we de tweede aanrijding zouden behandelen, dezelfde als waarmee we de eerste hadden afgehandeld, zouden we weer een klein stukje omhoog gaan , waardoor ons object twee keer zo ver wordt gecorrigeerd als het zou moeten.

Om dit probleem op te lossen, houden we bij hoeveel we het object al hebben gecorrigeerd. Laten we de vector declareren offsetSum vlak voordat we beginnen met het itereren door alle botsingen.

Vector2 offsetSum = Vector2.zero;

Laten we er nu voor zorgen dat we alle verschuivingen optellen die we in onze vector op ons object hebben toegepast.

if ((! overlappendLastFrameX && overlappendLastFrameY) || (! overlappendLastFrameX &&! overlappendLastFrameY && Mathf.Abs (overlapping.x) <= Mathf.Abs(overlap.y)))  mPosition.x += offsetX; offsetSum.x += offsetX; if (overlap.x < 0.0f)  mPushesRightObject = true; mSpeed.x = Mathf.Min(mSpeed.x, 0.0f);  else  mPushesLeftObject = true; mSpeed.x = Mathf.Max(mSpeed.x, 0.0f);   else  mPosition.y += offsetY; offsetSum.y += offsetY; if (overlap.y < 0.0f)  mPushesTopObject = true; mSpeed.y = Mathf.Min(mSpeed.y, 0.0f);  else  mPushesBottomObject = true; mSpeed.y = Mathf.Max(mSpeed.y, 0.0f);  

En tot slot, laten we de overlapping van elke opeenvolgende botsing compenseren door de cumulatieve vector van correcties die we tot nu toe hebben gedaan.

var overlapping = data.overlap - offsetSum;

Als we nu op twee objecten van dezelfde hoogte tegelijk landen, zou de eerste botsing correct worden verwerkt en zou de overlap van de tweede botsing worden gecompenseerd naar nul, waardoor ons object niet meer zou bewegen.

Nu onze functie gereed is, laten we ervoor zorgen dat we deze gebruiken. Een goede plaats om deze functie te noemen is na de CheckCollisions noemen. Dit vereist dat we onze splitsen UpdatePhysics functioneer in twee delen, dus laten we nu het tweede deel maken, in de MovingObject klasse.

public void UpdatePhysicsP2 () UpdatePhysicsResponse (); mPushesBottom = mPushesBottomTile || mPushesBottomObject; mPushesRight = mPushesRightTile || mPushesRightObject; mPushesLeft = mPushesLeftTile || mPushesLeftObject; mPushesTop = mPushesTopTile || mPushesTopObject; 

In het tweede deel noemen we onze vers afgewerkt UpdatePhysicsResponse functie en update de algemene naar links, rechts, onder en bovenste variabelen. Hierna hoeven we de positie alleen maar toe te passen.

public void UpdatePhysicsP2 () UpdatePhysicsResponse (); mPushesBottom = mPushesBottomTile || mPushesBottomObject; mPushesRight = mPushesRightTile || mPushesRightObject; mPushesLeft = mPushesLeftTile || mPushesLeftObject; mPushesTop = mPushesTopTile || mPushesTopObject; // update het aabb mAABB.center = mPosition; // pas de wijzigingen toe op de transformatie transform.position = new Vector3 (Mathf.Round (mPosition.x), Mathf.Round (mPosition.y), mSpriteDepth); transform.localScale = new Vector3 (ScaleX, ScaleY, 1.0f); 

Laten we nu, in de hoofdupdate van de update, het tweede deel van de natuurkundige update na de CheckCollisions telefoontje.

void FixedUpdate () for (int i = 0; i < mObjects.Count; ++i)  switch (mObjects[i].mType)  case ObjectType.Player: case ObjectType.NPC: ((Character)mObjects[i]).CustomUpdate(); mMap.UpdateAreas(mObjects[i]); mObjects[i].mAllCollidingObjects.Clear(); break;   mMap.CheckCollisions(); for (int i = 0; i < mObjects.Count; ++i) mObjects[i].UpdatePhysicsP2(); 

Gedaan! Nu kunnen onze objecten elkaar niet overlappen. Natuurlijk moeten we in een game-instelling een paar dingen toevoegen, zoals botsingsgroepen, enzovoort. Het is dus niet verplicht om botsingen met elk object te detecteren of erop te reageren, maar dit zijn dingen die afhankelijk zijn van hoe je wilt dingen in je spel laten zetten, dus daar gaan we ons niet in verdiepen.

Samenvatting

Dat is het voor een ander deel van de eenvoudige 2D-platformer physics-serie. We hebben gebruik gemaakt van het botsingsdetectiemechanisme dat in het vorige deel is geïmplementeerd om een ​​eenvoudige fysieke respons tussen objecten te creëren. 

Met deze tools is het mogelijk om standaardobjecten te maken, zoals bewegende platforms, duwen blokken, aangepaste obstakels en vele andere soorten objecten die niet echt een onderdeel van de tilemap kunnen zijn, maar toch op de een of andere manier deel moeten uitmaken van het niveau terrein. Er is nog een andere populaire functie dat onze fysica-implementatie nog steeds ontbreekt, en dat zijn de hellingen. 

Hopelijk zullen we in het volgende deel beginnen met het uitbreiden van onze tilemap met de ondersteuning hiervan, waarmee de basisset met functies wordt voltooid die een eenvoudige fysica-implementatie voor een 2D-platformer zou moeten hebben, en dat zou de serie beëindigen. 

Natuurlijk is er altijd ruimte voor verbetering, dus als je een vraag hebt of een tip over hoe je iets beters kunt doen, of gewoon een mening hebt over de tutorial, gebruik dan gerust het commentaar om me te laten weten!