Deoarece tocmai am terminat să lucrăm la verificarea coliziunilor la sol, s-ar putea să adăugăm și platforme cu sens unic în timp ce ne aflăm la el. Se vor referi doar la verificarea coliziunii la sol. Platformele cu o singură cale diferă de blocurile solide prin faptul că vor opri un obiect numai dacă se încadrează în jos. În plus, vom permite, de asemenea, ca un personaj să renunțe la o astfel de platformă.
Mai întâi, când vrem să renunțăm la o platformă unidirecțională, în principiu dorim să ignorăm coliziunea cu solul. O cale simplă aici este de a stabili o compensare, după trecerea care caracterul sau obiectul nu se va mai ciocni cu o platformă.
De exemplu, dacă caracterul este deja cu doi pixeli sub partea superioară a platformei, acesta nu ar trebui să detecteze o coliziune. În acest caz, când vrem să renunțăm la platformă, tot ce trebuie să facem este să mutăm caracterul cu doi pixeli în jos. Să creăm această constantă offset.
public const float cOneWayPlatformThreshold = 2.0f;
Acum, să adăugăm o variabilă care ne va comunica dacă un obiect este în prezent pe o platformă cu sens unic.
bool public mOnOneWayPlatform = false;
Să modificăm definiția HasGround
funcția de a lua de asemenea o referință la un boolean care va fi setat dacă obiectul a aterizat pe o platformă cu sens unic.
public bool HasGround (Vector2 oldPosition, Poziție Vector2, Viteză Vector2, Out float groundY, Ref bool onOneWayPlatform)
Acum, după ce verificăm dacă tigla în care ne aflăm este un obstacol și nu este, ar trebui să verificăm dacă este o platformă cu sens unic.
dacă (mMap.IsObstacle (tileIndexX, tileIndexY)) returnează adevărat; altfel dacă (mMap.IsOneWayPlatform (tileIndexX, tileIndexY)) onOneWayPlatform = true;
Așa cum am explicat mai înainte, trebuie să ne asigurăm că această coliziune este ignorată dacă am căzut dincolo de cOneWayPlatformThreshold
sub platforma.
Desigur, nu putem compara diferența dintre partea superioară a plăcii și senzor, deoarece este ușor să ne imaginăm că, chiar dacă cădem, s-ar putea să mergem cu mult sub doi pixeli de la partea superioară a platformei. Pentru platformele cu sens unic pentru a opri un obiect, dorim ca distanța senzorului dintre partea superioară a plăcii și senzor să fie mai mică sau egală cu cOneWayPlatformThreshold
plus decalajul de la poziția acestui cadru la cel anterior.
dacă (mMap.IsObstacle (tileIndexX, tileIndexY)) returnează adevărat; altfel dacă (mMap.IsOneWayPlatform (tileIndexX, tileIndexY) && Mathf.Abs (checkedTile.y - groundY) <= Constants.cOneWayPlatformThreshold + mOldPosition.y - position.y) onOneWayPlatform = true;
În cele din urmă, mai este ceva de luat în considerare. Când găsim o platformă unidirecțională, nu putem ieși cu adevărat din bucle, deoarece există situații în care caracterul este parțial pe o platformă și parțial pe un bloc solid.
Nu ar trebui să considerăm într-adevăr o astfel de poziție ca fiind "pe o platformă unidirecțională", pentru că nu putem scăpa de acolo - blocul solid ne oprește. De aceea, mai întâi trebuie să continuăm să căutăm un bloc solid, iar dacă se găsește înainte de a returna rezultatul, trebuie să stabilim onOneWayPlatform
la fals.
dacă (mMap.IsObstacle (tileIndexX, tileIndexY)) onOneWayPlatform = false; return true; altfel dacă (mMap.IsOneWayPlatform (tileIndexX, tileIndexY) && Mathf.Abs (checkedTile.y - groundY) <= Constants.cOneWayPlatformThreshold + mOldPosition.y - position.y) onOneWayPlatform = true;
Acum, dacă am trecut prin toate plăcile pe care trebuia să le verificăm pe orizontală și am găsit o platformă unidirecțională, dar nu niște blocuri solide, atunci putem fi siguri că suntem pe o platformă unidirecțională de unde putem să scăpăm.
dacă (mMap.IsObstacle (tileIndexX, tileIndexY)) onOneWayPlatform = false; return true; altfel dacă (mMap.IsOneWayPlatform (tileIndexX, tileIndexY) && Mathf.Abs (checkedTile.y - groundY) <= Constants.cOneWayPlatformThreshold + mOldPosition.y - position.y) onOneWayPlatform = true; if (checkedTile.x >= bottomRight.x) if (OnOneWayPlatform) returnează adevărat; pauză;
Asta este, deci acum să adăugăm la clasa de caractere o opțiune de a derula platforma. În ambele stări și stări de rulare, trebuie să adăugăm următorul cod.
dacă (KeyState (KeyInput.GoDown)) dacă (mOnOneWayPlatform) mPoziția - = Constants.cOneWayPlatformThreshold;
Să vedem cum funcționează.
Totul funcționează corect.
Trebuie să creăm o funcție analogică pentru HasGround pentru fiecare parte a AABB, așa că să începem cu plafonul. Diferențele sunt după cum urmează:
Iată funcția modificată.
public bool HasCeiling (Vector2 oldPosition, pozitia Vector2, plafon float out) var center = position + mAABBOffset; var oldCenter = oldPoziție + mAABBOset; ceilingY = 0.0f; var oldTopRight = vechiCenter + mAABB.halfSize + Vector2.up - Vector2.right; var newTopRight = centru + mAABB.halfSize + Vector2.up - Vector2.right; var newTopLeft = nou Vector2 (newTopRight.x - mAABB.halfSize.x * 2.0f + 2.0f, newTopRight.y); int endY = mMap.GetMapTileYAtPoint (newTopRight.y); int begY = Mathf.min (mMap.GetMapTileYAtPoint (oldTopRight.y) + 1, endY); int dist = Mathf.Max (Mathf.Abs (endY-begY), 1); int tileIndexX; pentru (int tileIndexY = begY; tileIndexY <= endY; ++tileIndexY) var topRight = Vector2.Lerp(newTopRight, oldTopRight, (float)Mathf.Abs(endY - tileIndexY) / dist); var topLeft = new Vector2(topRight.x - mAABB.halfSize.x * 2.0f + 2.0f, topRight.y); for (var checkedTile = topLeft; ; checkedTile.x += Map.cTileSize) checkedTile.x = Mathf.Min(checkedTile.x, topRight.x); tileIndexX = mMap.GetMapTileXAtPoint(checkedTile.x); if (mMap.IsObstacle(tileIndexX, tileIndexY)) ceilingY = (float)tileIndexY * Map.cTileSize - Map.cTileSize / 2.0f + mMap.mPosition.y; return true; if (checkedTile.x >= topRight.x) pauză; return false;
În mod similar cu modul în care am gestionat verificarea coliziunii pentru tavan și pământ, trebuie să verificăm dacă obiectul se ciocnește cu peretele din stânga sau de peretele din dreapta. Să începem de la peretele din stânga. Ideea de aici este aproape la fel, dar există câteva diferențe:
pentru
buclă trebuie să iterați prin gresie pe verticală, deoarece senzorul este acum o linie verticală.bool public CollidesWithLeftWall (Vector2 oldPosition, Vector2 poziție, out float wallX) var centru = poziție + mAABBOffset; var oldCenter = oldPoziție + mAABBOset; wallX = 0.0f; var oldBottomLeft = oldCenter - mAABB.halfSize - Vector2.right; var newBottomLeft = centru - mAABB.halfSize - Vector2.right; var newTopLeft = newBottomLeft + nou Vector2 (0.0f, mAABB.halfSize.y * 2.0f); int tileIndexY; var sfârșitX = mMap.GetMapTileXAtPoint (newBottomLeft.x); var begX = Mathf.Max (mMap.GetMapTileXAtPoint (oldBottomLeft.x) - 1, endX); int dist = Mathf.Max (Mathf.Abs (endX - begX), 1); pentru (int tileIndexX = begX; tileIndexX> = endX; -tileIndexX) var bottomLeft = Vector2.Lerp (newBottomLeft, oldBottomLeft, (float) Mathf.Abs (endX - tileIndexX) / dist); var topLeft = bottomLeft + Vector2 nou (0.0f, mAABB.halfSize.y * 2.0f); pentru (var checkedTile = bottomLeft;; checkedTile.y + = Map.cTileSize) checkedTile.y = Mathf.Min (checkedTile.y, topLeft.y); tileIndexY = mMap.GetMapTileYAtPoint (checkedTile.y); dacă (mMap.IsObstacle (tileIndexX, tileIndexY)) wallX = (float) tileIndexX * Map.cTileSize + Map.cTileSize / 2.0f + mMap.mPosition.x; return true; dacă (checkedTile.y> = topLeft.y) pauză; return false;
În cele din urmă, să creați CollidesWithRightWall
care, după cum vă puteți imagina, va face un lucru foarte asemănător CollidesWithLeftWall
, dar în loc să folosim un senzor din stânga, vom folosi un senzor pe partea dreaptă a personajului.
Cealaltă diferență este că, în loc să verificăm dalele de la dreapta la stânga, le vom verifica de la stânga la dreapta, deoarece aceasta este direcția de mișcare presupusă.
bool-ul public CollidesWithRightWall (Vector2 oldPosition, pozitia Vector2, perete plutitor out) var center = position + mAABBOffset; var oldCenter = oldPoziție + mAABBOset; wallX = 0.0f; var oldBottomRight = oldCenter + nou Vector2 (mAABB.halfSize.x, -mAABB.halfSize.y) + Vector2.right; var newBottomRight = centru + nou Vector2 (mAABB.halfSize.x, -mAABB.halfSize.y) + Vector2.right; var newTopRight = newBottomRight + nou Vector2 (0.0f, mAABB.halfSize.y * 2.0f); var sfârșitX = mMap.GetMapTileXAtPoint (newBottomRight.x); var begX = Mathf.min (mMap.GetMapTileXAtPoint (oldBottomRight.x) + 1, endX); int dist = Mathf.Max (Mathf.Abs (endX - begX), 1); int tileIndexY; pentru (int tileIndexX = begX; tileIndexX <= endX; ++tileIndexX) var bottomRight = Vector2.Lerp(newBottomRight, oldBottomRight, (float)Mathf.Abs(endX - tileIndexX) / dist); var topRight = bottomRight + new Vector2(0.0f, mAABB.halfSize.y * 2.0f); for (var checkedTile = bottomRight; ; checkedTile.y += Map.cTileSize) checkedTile.y = Mathf.Min(checkedTile.y, topRight.y); tileIndexY = mMap.GetMapTileYAtPoint(checkedTile.y); if (mMap.IsObstacle(tileIndexX, tileIndexY)) wallX = (float)tileIndexX * Map.cTileSize - Map.cTileSize / 2.0f + mMap.mPosition.x; return true; if (checkedTile.y >= topRight.y) pauză; return false;
Toate funcțiile noastre de detectare a coliziunilor sunt realizate, așa că hai să le folosim pentru a finaliza răspunsul de coliziune împotriva tilemap. Înainte de a face asta, trebuie să ne dăm seama de ordinea în care vom verifica coliziunile. Să luăm în considerare următoarele situații.
În ambele situații, putem vedea că personajul a ajuns să se suprapună cu o țiglă, dar trebuie să ne dăm seama cum ar trebui să rezolvăm suprapunerea.
Situația din stânga este destul de simplă - putem observa că suntem căzuți direct în jos și din acest motiv ar trebui să aterizăm cu siguranță în partea de sus a blocului.
Situația din dreapta este un pic mai dificilă, deoarece în realitate am putea ateriza chiar în colțul plăcii și împingând caracterul la vârf este la fel de rezonabil cum o împingem spre dreapta. Să alegem să acordăm prioritate mișcării orizontale. Nu prea contează ce aliniere vrem să facem mai întâi; ambele alegeri arata corect in actiune.
Să mergem la noi UpdatePhysics
și adăugați variabilele care vor păstra rezultatele interogărilor noastre de coliziune.
flotat groundY = 0.0f, ceilingY = 0.0f; float dreaptaWallX = 0.0f, leftWallX = 0.0f;
Acum, să începem dacă privim dacă ar trebui să mutăm obiectul spre dreapta. Condițiile de aici sunt următoarele:
Ultimul este o condiție necesară, pentru că dacă nu ar fi fost îndeplinită atunci ne vom ocupa de o situație similară celei din stânga din imaginea de mai sus, în care cu siguranță nu ar trebui să ne mișcăm caracterul la dreapta.
dacă (mSpeed.x <= 0.0f && CollidesWithLeftWall(mOldPosition, mPosition, out leftWallX) && mOldPosition.x - mAABB.halfSize.x + mAABBOffset.x >= leftWallX)
Dacă condițiile sunt adevărate, trebuie să aliniem partea stângă a AABB-ului nostru în partea dreaptă a plăcii, să ne asigurăm că nu mai mergem spre stânga și să notăm că suntem lângă peretele din stânga.
dacă (mSpeed.x <= 0.0f && CollidesWithLeftWall(mOldPosition, mPosition, out leftWallX) && mOldPosition.x - mAABB.halfSize.x + mAABBOffset.x >= leftWallX) mPoziția.x = leftWallX + mAABB.halfSize.x - mAABBOffset.x; mSpeed.x = Mathf.Max (mSpeed.x, 0.0f); mPushesLeftWall = adevărat;
Dacă oricare dintre condițiile afară de ultima este falsă, trebuie să stabilim mPushesLeftWall
la fals. Aceasta deoarece ultima condiție care este falsă nu ne spune neapărat că personajul nu împinge zidul, ci, dimpotrivă, ne spune că se ciocnește cu el deja în cadrul anterior. Din acest motiv, este mai bine să se schimbe mPushesLeftWall
la falsă numai dacă oricare dintre primele două condiții este, de asemenea, falsă.
dacă (mSpeed.x <= 0.0f && CollidesWithLeftWall(mOldPosition, mPosition, out leftWallX)) if (mOldPosition.x - mAABB.HalfSizeX + AABBOffsetX >= leftWallX) mPoziția.x = leftWallX + mAABB.HalfSizeX - AABBOffsetX; mPushesLeftWall = adevărat; mSpeed.x = Mathf.Max (mSpeed.x, 0.0f); altfel mPushesLeftWall = false;
Acum hai să verificăm coliziunea cu peretele drept.
dacă mSpeed.x> = 0.0f && CollidesWithRightWall (mOldPosition, mPozi, out rightWallX)) dacă (mOldPosition.x + mAABB.HalfSizeX + AABBOffsetX <= rightWallX) mPosition.x = rightWallX - mAABB.HalfSizeX - AABBOffsetX; mPushesRightWall = true; mSpeed.x = Mathf.Min(mSpeed.x, 0.0f); else mPushesRightWall = false;
După cum puteți vedea, este aceeași formulă pe care am folosit-o pentru a verifica coliziunea cu peretele stâng, dar a fost oglindită.
Avem deja codul pentru verificarea coliziunii cu solul, așa că după aceea trebuie să verificăm coliziunea cu tavanul. Nu este nimic nou aici, plus nu este nevoie să facem verificări suplimentare, cu excepția faptului că viteza verticală trebuie să fie mai mare sau egală cu zero și de fapt ne ciocnesc cu o piesă care este pe noi.
dacă mSpeed.y> = 0.0f && HasCeiling (mOldPosition, mPoziție, out ceilingY)) mPosition.y = plafonY - mAABB.halfSize.y - mAABBOffset.y - 1.0f; mSpeed.y = 0.0f; mAtCeiling = true; altceva mAtCeiling = false;
Înainte de a testa dacă reacțiile de coliziune funcționează, este un lucru mai important de făcut, care este de a rotunji valorile colțurilor pe care le calculează pentru verificările de coliziune. Trebuie să facem acest lucru, astfel încât cecurile noastre să nu fie distruse de erori în virgulă mobilă, ceea ce ar putea rezulta din poziția ciudată a hărții, din scara de caractere sau din dimensiunea ciudată AABB.
În primul rând, pentru ușurința noastră, să creăm o funcție care transformă un vector de flotoare într-un vector de plutitoare rotunjite.
Vector2 RoundVector (Vector2 v) returnează vectorul Vector2 (Mathf.Round (v.x), Mathf.Round (v.y));
Acum, să folosim această funcție în fiecare verificare de coliziune. În primul rând, să rezolvăm problema HasCeiling
funcţie.
var oldTopRight = RoundVector (vechiCenter + mAABB.HalfSize + Vector2.up - Vector2.right); var newTopRight = RoundVector (centrul + mAABB.HalfSize + Vector2.up - Vector2.right); var newTopLeft = RoundVector (noul Vector2 (newTopRight.x - mAABB.HalfSizeX * 2.0f + 2.0f, newTopRight.y));
Următorul este Pe pamant
.
var oldBottomLeft = RoundVector (vechiulCenter - mAABB.HalfSize - Vector2.up + Vector2.right); var newBottomLeft = RoundVector (centrul - mAABB.HalfSize - Vector2.up + Vector2.right); var newBottomRight = RoundVector (noul Vector2 (newBottomLeft.x + mAABB.HalfSizeX * 2.0f - 2.0f, newBottomLeft.y));
PushesRightWall
.
var oldBottomRight = RoundVector (oldCenter + nou Vector2 (mAABB.HalfSizeX, -mAABB.HalfSizeY) + Vector2.right); var newBottomRight = RoundVector (centrul + nou Vector2 (mAABB.HalfSizeX, -mAABB.HalfSizeY) + Vector2.right); var newTopRight = RoundVector (nouBottomRight + nou Vector2 (0.0f, mAABB.HalfSizeY * 2.0f));
Și, în sfârșit, PushesLeftWall
.
var vechiBottomLeft = RoundVector (oldCenter - mAABB.HalfSize - Vector2.right); var newBottomLeft = RoundVector (centrul - mAABB.HalfSize - Vector2.right); var newTopLeft = RoundVector (newBottomLeft + new Vector2 (0.0f, mAABB.HalfSizeY * 2.0f));
Asta ar trebui să ne rezolve problemele!
Asta va fi. Să încercăm cum funcționează coliziunile noastre acum.
Asta este pentru asta! Avem un set complet de seturi de coliziuni cu tilemap, care ar trebui să fie foarte fiabile. Știm în ce poziție starea obiectului în prezent este: dacă e pe pământ, dacă atingeți o faianță în stânga sau în dreapta sau când bateți un tavan. Am implementat, de asemenea, platformele unice, care reprezintă un instrument foarte important în fiecare joc de platformă.
În următoarea parte, vom adăuga mecanica capcană-hapsân, care va spori și mai mult mișcarea posibilă a personajului, deci stați bine reglat!