Siden vi nettopp har fullført arbeidet på bakken, kan vi også legge til enveisplattformer mens vi er på den. De kommer til å bekymre bare bakken kollisjon sjekk uansett. Enveisplattformer skiller seg fra faste blokker, fordi de bare vil stoppe et objekt hvis det faller ned. I tillegg tillater vi også et tegn å falle ned fra en slik plattform.
Først og fremst, når vi ønsker å slippe av en enveisplattform, vil vi i utgangspunktet ignorere kollisjonen med bakken. En enkel vei ut her er å sette opp en forskyvning, etter å ha passert som tegnet eller objektet ikke lenger vil kollidere med en plattform.
For eksempel, hvis tegnet allerede er to piksler under toppen av plattformen, bør den ikke oppdage en kollisjon lenger. I så fall, når vi vil slippe av plattformen, er alt vi trenger å gjøre, flytte tegnet to piksler ned. La oss lage denne kompensasjonen konstant.
offentlig const float cOneWayPlatformThreshold = 2.0f;
La oss nå legge til en variabel som vil fortelle oss om et objekt er for tiden på en enveis plattform.
offentlig bool mOnOneWayPlatform = false;
La oss endre definisjonen av HasGround
funksjon å også ta en henvisning til en boolesk som vil bli satt hvis objektet har landet på en enveis plattform.
offentlig bool HasGround (Vector2 oldPosition, Vector2 posisjon, Vector2 speed, ut float groundY, ref bool onOneWayPlatform)
Nå, etter at vi har sjekket om flisen vi er på, er et hinder, og det er ikke, bør vi sjekke om det er en enveis plattform.
hvis (mMap.IsObstacle (tileIndexX, tileIndexY)) returnere sant; ellers hvis (mMap.IsOneWayPlatform (tileIndexX, tileIndexY)) onOneWayPlatform = true;
Som forklart før, må vi også sørge for at denne kollisjonen blir ignorert hvis vi har gått bortom cOneWayPlatformThreshold
under plattformen.
Selvfølgelig kan vi ikke bare sammenligne forskjellen mellom toppen av flisen og sensoren, fordi det er lett å forestille at selv om vi faller, kan vi gå godt under to piksler fra plattformens topp. For enveisplatformene å stoppe et objekt, vil vi ha sensoravstanden mellom toppen av flisen og sensoren til å være mindre enn eller lik den cOneWayPlatformThreshold
pluss forskyvningen fra denne rammens posisjon til den forrige.
hvis (mMap.IsObstacle (tileIndexX, tileIndexY)) returnere sant; ellers hvis (mMap.IsOneWayPlatform (tileIndexX, tileIndexY) && Mathf.Abs (checkedTile.y - groundY) <= Constants.cOneWayPlatformThreshold + mOldPosition.y - position.y) onOneWayPlatform = true;
Til slutt er det en ting å vurdere. Når vi finner en enveisplattform, kan vi ikke gå ut av sløyfen, fordi det er situasjoner når tegnet er delvis på en plattform og delvis på en solid blokk.
Vi burde egentlig ikke tenke på en slik posisjon som "på en enveis plattform", fordi vi ikke egentlig kan slippe ned derfra - den faste blokken stopper oss. Derfor må vi først fortsette å lete etter en solid blokk, og hvis man er funnet før vi returnerer resultatet, må vi også sette onOneWayPlatform
til falsk.
hvis (mMap.IsObstacle (tileIndexX, tileIndexY)) onOneWayPlatform = false; returnere sant; annet hvis (mMap.IsOneWayPlatform (tileIndexX, tileIndexY) && Mathf.Abs (checkedTile.y - groundY) <= Constants.cOneWayPlatformThreshold + mOldPosition.y - position.y) onOneWayPlatform = true;
Nå, hvis vi gikk gjennom alle fliser vi trengte å sjekke horisontalt og vi fant en enveis plattform, men ingen faste blokker, så kan vi være sikre på at vi er på en enveis plattform som vi kan slippe ned.
hvis (mMap.IsObstacle (tileIndexX, tileIndexY)) onOneWayPlatform = false; returnere sant; annet hvis (mMap.IsOneWayPlatform (tileIndexX, tileIndexY) && Mathf.Abs (checkedTile.y - groundY) <= Constants.cOneWayPlatformThreshold + mOldPosition.y - position.y) onOneWayPlatform = true; if (checkedTile.x >= bottomRight.x) hvis (onOneWayPlatform) returnerer true; gå i stykker;
Det er det, så la oss legge til tegnet klassen et alternativ å slippe ned plattformen. I både stand og løpstilstandene må vi legge til følgende kode.
hvis (KeyState (KeyInput.GoDown)) if (mOnOneWayPlatform) mPosition.y - = Constants.cOneWayPlatformThreshold;
La oss se hvordan det fungerer.
Alt fungerer som det skal.
Vi må skape en analog funksjon til HasGround for hver side av AABB, så la oss starte med taket. Forskjellene er som følger:
Her er den modifiserte funksjonen.
offentlig bool HasCeiling (Vector2 oldPosition, Vector2 posisjon, ut float ceilingY) var senter = posisjon + mAABBOffset; var oldCenter = oldPosition + mAABBOffset; ceilingY = 0.0f; var oldTopRight = oldCenter + mAABB.halfSize + Vector2.up - Vector2.right; var newTopRight = senter + mAABB.halfSize + Vector2.up - Vector2.right; var newTopLeft = ny 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; for (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) pause; returnere false;
På samme måte som vi håndterte kollisjonskontrollen for tak og bakken, må vi også sjekke om objektet kolliderer med veggen til venstre eller veggen til høyre. La oss starte fra venstre veggen. Ideen her er stort sett den samme, men det er noen forskjeller:
til
sløyfe trenger å iterere gjennom flisene vertikalt, fordi sensoren er nå en vertikal linje.offentlig bool CollidesWithLeftWall (Vector2 oldPosition, Vector2 posisjon, ut float wallX) var center = posisjon + mAABBOffset; var oldCenter = oldPosition + mAABBOffset; wallX = 0.0f; var oldBottomLeft = oldCenter - mAABB.halfSize - Vector2.right; var newBottomLeft = center - mAABB.halfSize - Vector2.right; var newTopLeft = newBottomLeft + ny Vector2 (0.0f, mAABB.halfSize.y * 2.0f); int tileIndexY; var endX = mMap.GetMapTileXAtPoint (newBottomLeft.x); var begX = Mathf.Max (mMap.GetMapTileXAtPoint (oldBottomLeft.x) - 1, endX); int dist = Mathf.Max (Mathf.Abs (endX - begX), 1); for (int tileIndexX = begX; tileIndexX> = endX; --tileIndexX) var bottomLeft = Vector2.Lerp (newBottomLeft, oldBottomLeft, (float) Mathf.Abs (endX - tileIndexX) / dist); var topLeft = bottomLeft + ny Vector2 (0.0f, mAABB.halfSize.y * 2.0f); for (var checkedTile = bottomLeft;; checkedTile.y + = Map.cTileSize) checkedTile.y = Mathf.Min (checkedTile.y, topLeft.y); tileIndexY = mMap.GetMapTileYAtPoint (checkedTile.y); hvis (mMap.IsObstacle (tileIndexX, tileIndexY)) wallX = (float) tileIndexX * Map.cTileSize + Map.cTileSize / 2.0f + mMap.mPosition.x; returnere sant; hvis (checkedTile.y> = topLeft.y) bryter; returnere false;
Til slutt, la oss lage CollidesWithRightWall
funksjon, som som du kan forestille deg, vil gjøre en veldig lignende ting som CollidesWithLeftWall
, men i stedet for å bruke en sensor til venstre bruker vi en sensor på høyre side av tegnet.
Den andre forskjellen her er at i stedet for å sjekke flisene fra høyre til venstre, vil vi sjekke dem fra venstre til høyre, siden det er den antatte bevegelige retningen.
offentlig bool CollidesWithRightWall (Vector2 oldPosition, Vector2 posisjon, ut float wallX) var senter = posisjon + mAABBOffset; var oldCenter = oldPosition + mAABBOffset; wallX = 0.0f; var oldBottomRight = oldCenter + ny Vector2 (mAABB.halfSize.x, -mAABB.halfSize.y) + Vector2.right; var newBottomRight = center + ny Vector2 (mAABB.halfSize.x, -mAABB.halfSize.y) + Vector2.right; var newTopRight = newBottomRight + ny Vector2 (0.0f, mAABB.halfSize.y * 2.0f); var endX = 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; for (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) pause; returnere false;
Alle våre kollisjonssensorfunksjoner er ferdige, så la oss bruke dem til å fullføre kollisionsresponsen mot tilkartet. Før vi gjør det, må vi imidlertid finne ut i hvilken rekkefølge vi skal sjekke kollisjonene. La oss vurdere følgende situasjoner.
I begge disse situasjonene kan vi se tegnet til slutt overlappe med en flis, men vi må finne ut hvordan skal vi løse overlappingen.
Situasjonen til venstre er ganske enkel - vi kan se at vi faller rett ned, og på grunn av det skal vi definitivt lande på toppen av blokken.
Situasjonen til høyre er litt vanskeligere, siden vi i sannhet kunne lande på selve flisens hjørne, og å skyve karakteren til toppen er like rimelig som å skyve den til høyre. La oss velge å prioritere den horisontale bevegelsen. Det spiller ingen rolle mye hvilken innstilling vi ønsker å gjøre først; begge valgene ser riktig ut i handling.
La oss gå til vår UpdatePhysics
funksjon og legg til variablene som vil holde resultatene av våre kollisionsforespørsler.
flyte groundY = 0.0f, ceilingY = 0.0f; float rightWallX = 0.0f, leftWallX = 0.0f;
La oss begynne med å se om vi skal flytte objektet til høyre. Betingelsene her er at:
Den siste er en nødvendig betingelse, for hvis det ikke var oppfylt, ville vi håndtere en situasjon som ligner den til venstre i bildet ovenfor, der vi sikkert ikke burde flytte tegnet til høyre.
hvis (mSpeed.x <= 0.0f && CollidesWithLeftWall(mOldPosition, mPosition, out leftWallX) && mOldPosition.x - mAABB.halfSize.x + mAABBOffset.x >= leftWallX)
Hvis betingelsene er sanne, må vi justere venstre side av vår AABB til høyre side av flisen, pass på at vi slutter å flytte til venstre, og merk at vi er ved siden av veggen til venstre.
hvis (mSpeed.x <= 0.0f && CollidesWithLeftWall(mOldPosition, mPosition, out leftWallX) && mOldPosition.x - mAABB.halfSize.x + mAABBOffset.x >= leftWallX) mPosition.x = leftWallX + mAABB.halfSize.x - mAABBOffset.x; mSpeed.x = Mathf.Max (mSpeed.x, 0,0f); mPushesLeftWall = true;
Hvis noen av betingelsene foruten den siste er falsk, må vi sette mPushesLeftWall
til falsk. Det er fordi den siste tilstanden er feil, forteller ikke nødvendigvis at tegnet ikke skyver veggen, men omvendt forteller det oss at det kolliderte med det allerede i forrige ramme. På grunn av dette er det best å endre mPushesLeftWall
å bare falle hvis noen av de to første betingelsene er falske også.
hvis (mSpeed.x <= 0.0f && CollidesWithLeftWall(mOldPosition, mPosition, out leftWallX)) if (mOldPosition.x - mAABB.HalfSizeX + AABBOffsetX >= leftWallX) mPosition.x = leftWallX + mAABB.HalfSizeX - AABBOffsetX; mPushesLeftWall = true; mSpeed.x = Mathf.Max (mSpeed.x, 0,0f); annet mPushesLeftWall = false;
La oss nå sjekke for kollisjonen med høyre veggen.
hvis (mSpeed.x> = 0.0f && CollidesWithRightWall (mOldPosition, mPosition, ut rightWallX)) if (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;
Som du kan se, er det samme formel vi brukte for å sjekke kollisjonen med venstre vegg, men speilet.
Vi har allerede koden for å sjekke kollisjonen med bakken, så etter det må vi sjekke kollisjonen med taket. Ingenting nytt her også, pluss vi trenger ikke å gjøre noen ekstra sjekker bortsett fra at vertikal hastighet må være større eller lik null, og vi kolliderer faktisk med en flis som er på toppen av oss.
hvis (mSpeed.y> = 0.0f && HasCeiling (mOldPosition, mPosition, out ceilingY)) mPosition.y = ceilingY - mAABB.halfSize.y - mAABBOffset.y - 1.0f; mSpeed.y = 0.0f; mAtCeiling = true; ellers mAtCeiling = false;
Før vi tester om kollisionssvarene virker, er det enda en viktig ting å gjøre, som er å runde verdiene av hjørnene vi beregner for kollisjonskontrollene. Vi må gjøre det, slik at våre sjekker ikke blir ødelagt av flytende punktfeil, som kan komme fra merkelig kartposisjon, karakterskala eller bare en merkelig AABB-størrelse.
For det første, la oss lage en funksjon som forvandler en vektor av flyter til en vektor av avrundede flyter.
Vector2 RoundVector (Vector2 v) returner ny Vector2 (Mathf.Round (v.x), Mathf.Round (v.y));
La oss nå bruke denne funksjonen i hver kollisjonskontroll. Først, la oss fikse det HasCeiling
funksjon.
var oldTopRight = RoundVector (oldCenter + mAABB.HalfSize + Vector2.up - Vector2.right); var newTopRight = RoundVector (senter + mAABB.HalfSize + Vector2.up - Vector2.right); var newTopLeft = RoundVector (ny Vector2 (newTopRight.x - mAABB.HalfSizeX * 2.0f + 2.0f, newTopRight.y));
Neste er På bakken
.
var oldBottomLeft = RoundVector (oldCenter - mAABB.HalfSize - Vector2.up + Vector2.right); var newBottomLeft = RoundVector (senter - mAABB.HalfSize - Vector2.up + Vector2.right); var newBottomRight = RoundVector (ny Vector2 (newBottomLeft.x + mAABB.HalfSizeX * 2.0f - 2.0f, newBottomLeft.y));
PushesRightWall
.
var oldBottomRight = RoundVector (oldCenter + ny Vector2 (mAABB.HalfSizeX, -mAABB.HalfSizeY) + Vector2.right); var newBottomRight = RoundVector (senter + ny Vector2 (mAABB.HalfSizeX, -mAABB.HalfSizeY) + Vector2.right); var newTopRight = RoundVector (newBottomRight + ny Vector2 (0.0f, mAABB.HalfSizeY * 2.0f));
Og endelig, PushesLeftWall
.
var oldBottomLeft = RoundVector (oldCenter - mAABB.HalfSize - Vector2.right); var newBottomLeft = RoundVector (senter - mAABB.HalfSize - Vector2.right); var newTopLeft = RoundVector (newBottomLeft + ny Vector2 (0.0f, mAABB.HalfSizeY * 2.0f));
Det skal løse våre problemer!
Det kommer til å bli det. La oss teste hvordan kollisjonene våre jobber nå.
Det er det for denne delen! Vi har et fullt fungerende sett med tilskjoldskollisjoner, som bør være veldig pålitelige. Vi vet i hvilken posisjon staten objektet for øyeblikket er: om det er på bakken, berører en flis til venstre eller til høyre, eller støter på et tak. Vi har også implementert enveisplattformene, som er et svært viktig verktøy i alle plattformspill.
I neste del legger vi til ledge-gripende mekanikk, noe som vil øke den mulige bevegelsen av tegnet enda lenger, så hold deg innstilt!