A * Pathfinding for 2D Grid-Based Platformers Ulike karakterstørrelser

I denne opplæringen vil vi utvide vår nettbaserte plattformsporingsleder slik at den kan takle tegn som tar opp mer enn en celle i rutenettet.

Hvis du ikke har lagt til enveisplattformstøtte til koden din ennå, anbefaler jeg at du gjør det, men det er ikke nødvendig for å følge denne opplæringen.

Demo

Du kan spille Unity demo, eller WebGL-versjonen (100 MB +), for å se det endelige resultatet i aksjon. Bruk WASD å flytte tegnet, venstre klikk på et sted for å finne en sti du kan følge for å komme dit, Høyreklikk en celle for å skifte bakken på det punktet, og Klikk og dra skyvekontrollene for å endre sine verdier.

Tegn i forskjellige størrelser må ta forskjellige baner - den oppdaterte algoritmen gjenkjenner dette.

Karakter posisjon

Banebryteren aksepterer posisjonen, bredden og høyden på tegnet som en inngang. Selv om bredde og høyde er enkle å tolke, må vi avklare hvilken blokk posisjonskoordinatene refererer til.

Stillingen som vi passerer, må være når det gjelder kartkoordinater, noe som betyr at vi igjen må omfatte noe unøyaktighet. Jeg bestemte meg for at det ville være fornuftig å få stillingen til å referere til den nederste venstre tegnflisen, da dette samsvarer med kartkoordinatsystemet.

Karakterens fliser posisjoner for en 3x3 karakter. 

Med det rydde opp, kan vi oppdatere stifinderen.

Kontrollerer at målet er realistisk

Først må vi sørge for at vår egendefinerte størrelse kan passe inn i destinasjonsstedet. Inntil dette punktet har vi bare sjekket en blokk for å gjøre dette, da det var den maksimale (og eneste) størrelsen på tegnet:

hvis (mGrid [end.x, end.y] == 0) returnere null;

Nå må vi imidlertid gjenta gjennom hver celle at tegnet ville okkupere hvis det stod i sluttposisjonen, og sjekke om noen av dem er en solid blokk. Hvis de er, så kan karakteren ikke selv stå der, så målet kan ikke nås.

For å gjøre dette, la oss først erklære en boolsk som vi vil sette til falsk hvis tegnet er i en solid flis og ekte ellers:

var inSolidTile = false;

Deretter vil vi iterere gjennom hver blokk av tegnet:

for (var w = 0; w < characterWidth; ++w)  for (int h = 0; h < characterHeight; ++h)   

Inne i denne sløyfen må vi sjekke om en bestemt blokk er solid; Hvis så, setter vi inSolidTile til ekte, og gå ut av sløyfen:

for (var w = 0; w < characterWidth; ++w)  for (int h = 0; h < characterHeight; ++h)  if (mGrid[end.x + w, end.y + h] == 0 || mGrid[end.x + w, end.y + h] == 0)  inSolidTile = true; break;   if (inSolidTile) break; 

Men dette er ikke nok. Vurder følgende situasjon:

Grønne blokker: karakter; blå blokk: mål.

Hvis vi skulle flytte tegnet slik at det er bunn-venstre blokk okkupert målet, så bunnen-Ikke sant blokk ville være fast i en solid blokk - så algoritmen ville tro at siden karakteren ikke passer til målstillingen, er det umulig å nå sluttpunktet. Selvfølgelig er det ikke sant; Vi bryr oss ikke om hvilken del av karakteren som når målet. 

For å løse dette problemet vil vi flytte sluttpunktet til venstre, steg for trinn, opp til punktet der den opprinnelige målplasseringen ville matche bunnen-Ikke sant tegnblokk:

for (var i = 0; i < characterWidth; ++i)  inSolidTile = false; for (var w = 0; w < characterWidth; ++w)  for (var h = 0; h < characterHeight; ++h)  if (mGrid[end.x + w, end.y + h] == 0 || mGrid[end.x + w, end.y + h] == 0)  inSolidTile = true; break;   if (inSolidTile) break;  if (inSolidTile) end.x -= 1; else break; 

Vær oppmerksom på at vi ikke bare skal sjekke nederste venstre og høyre hjørne, fordi følgende tilfelle kan oppstå:

Igjen, grønne blokker: karakter; blå blokk: mål.

Her kan du se at hvis noen av de nederste hjørner okkuperer målplasseringen, vil tegnet fortsatt være i fast bakke på den andre siden. I dette tilfellet må vi matche bunnen-senter blokkere med målet.

Til slutt, hvis vi ikke finner noe sted der tegnet passer, kan vi også gå ut av algoritmen tidlig:

hvis (inSolidTile == true) returnerer null;

Bestemme startposisjon

For å se om karakteren vår er på bakken, må vi sjekke om noen av tegnets bunn-fleste celler er rett over en solid flis.

La oss se på koden vi brukte for et 1x1 tegn:

hvis (mMap.IsGround (start.x, start.y - 1)) firstNode.JumpLength = 0; ellers firstNode.JumpLength = (short) (maxCharacterJumpHeight * 2);

Vi avgjør om startpunktet er på bakken ved å sjekke om flisen umiddelbart under startpunktet er en flis. For å oppdatere koden, gjør vi det enkelt under alle de nederste blokkene av tegnet. 

Først, la oss erklære en boolsk som vil fortelle oss om tegnet starter på bakken. I utgangspunktet antar vi at det ikke gjør det:

bool starterOnGround = false;

Deretter vil vi gjenta gjennom alle de nederste tegnblokkene og sjekke om noen av dem er rett over en flis. Hvis så, så setter vi startsOnGround til ekte og gå ut av sløyfen:

for (int x = start.x; x < start.x + characterWidth; ++x)  if (mMap.IsGround(x, start.y - 1))  startsOnGround = true; break;  

Endelig setter vi hoppverdien avhengig av om tegnet startet på bakken:

 hvis (starterOnGround) firstNode.JumpLength = 0; ellers firstNode.JumpLength = (short) (maxCharacterJumpHeight * 2);

Kontrollerer etterfølgerens bånd

Vi må også endre etterfølgerens grensekontroll, men her trenger vi ikke å sjekke alle fliser. Det er godt nok å sjekke kontur av tegnet - blokkene rundt kanten - fordi vi vet at forelderens stilling var bra.

La oss se hvordan vi sjekket etterfølgerens grenser tidligere:

hvis (mGrid [mNewLocationX, mNewLocationY] == 0) fortsette; hvis (mMap.IsGround (mNewLocationX, mNewLocationY - 1)) onGround = true; ellers hvis (mGrid [mNewLocationX, mNewLocationY + characterHeight] == ​​0) atCeiling = true;

Vi oppdaterer dette ved å sjekke om noen av konturblokkene er innenfor en solid blokk. Hvis noen av dem gjør det, kan karakteren ikke passe i posisjonen og etterfølgeren skal hoppes over.

Kontrollerer topp- og bunnblokkene

Først, la oss iterere over alle de øverste og nederste blokkene av tegnet, og kontroller om de overlapper en solid flis på nettet vårt:

for (var w = 0; w < characterWidth; ++w)  if (mGrid[mNewLocationX + w, mNewLocationY] == 0 || mGrid[mNewLocationX + w, mNewLocationY + characterHeight - 1] == 0) goto CHILDREN_LOOP_END; 

De CHILDREN_LOOP_END Etiketten fører til slutten av etterfølgerløkken; ved å bruke det, hopper vi over behovet først gå i stykker ut av løkken og da Fortsette til neste etterfølger i etterfølgerløkken. 

Når en flise i luften kan betraktes "OnGround"

Hvis noen av bunnblokkene ligger rett over en solid flis, må etterfølgeren være på bakken. Dette betyr at selv om det ikke er en fast flis direkte under selve etterfølgercellen, vil etterfølgeren fortsatt bli ansett som en På bakken node, hvis karakteren er bred nok.

Den røde noden er en "OnGround" node, selv om den ikke egentlig er på bakken.
for (var w = 0; w < characterWidth; ++w)  if (mGrid[mNewLocationX + w, mNewLocationY] == 0 || mGrid[mNewLocationX + w, mNewLocationY + characterHeight - 1] == 0) goto CHILDREN_LOOP_END; if (mMap.IsGround(mNewLocationX + w, mNewLocationY - 1)) onGround = true; 

Kontrollerer om tegnet er i taket

Hvis noen av flisene over karakteren er solide, så er tegnet i taket.

for (var w = 0; w < characterWidth; ++w)  if (mGrid[mNewLocationX + w, mNewLocationY] == 0 || mGrid[mNewLocationX + w, mNewLocationY + characterHeight - 1] == 0) goto CHILDREN_LOOP_END; if (mMap.IsGround(mNewLocationX + w, mNewLocationY - 1)) onGround = true; if (mGrid[mNewLocationX + w, mNewLocationY + characterHeight] == 0) atCeiling = true; 

Kontrollerer blokkene ved siden av tegnet

Nå må vi bare sjekke at det ikke finnes noen faste blokker i tegnets venstre og høyre celler. Hvis det er, kan vi trygt hoppe over etterfølgeren, fordi vår karakter ikke passer til den aktuelle posisjonen:

 for (var h = 1; h < characterHeight - 1; ++h)  if (mGrid[mNewLocationX, mNewLocationY + h] == 0 || mGrid[mNewLocationX + characterWidth - 1, mNewLocationY + h] == 0) goto CHILDREN_LOOP_END; 

Konklusjon

Vi har fjernet en ganske betydelig begrensning fra algoritmen; nå har du mye mer frihet når det gjelder størrelsen på spillets tegn.

I neste veiledning i serien bruker vi vår algoritme til å finne en bot som kan følge selve stien. bare klikk på et sted, og det vil løpe og hoppe for å komme dit. Dette er veldig nyttig for NPCs!