În acest tutorial, vom extinde platformerul platformer pe bază de rețea, astfel încât acesta să poată face față cu caracterele care ocupă mai mult de o celulă din rețea.
Dacă nu ați adăugat încă codul de suport pentru o singură cale, vă recomandăm să faceți acest lucru, dar nu este necesar pentru a urma acest tutorial.
Puteți reda demonstrația Unity sau versiunea WebGL (100MB +), pentru a vedea rezultatul final în acțiune. Utilizare WASD pentru a muta caracterul, stânga-clic pe un loc pentru a găsi o cale pe care să o poți urma pentru a ajunge acolo, Click dreapta o celulă pentru a comuta la sol în acel moment și apasa si trage glisoarele să-și schimbe valorile.
Caracterele cu dimensiuni diferite trebuie să ia diferite căi - algoritmul actualizat recunoaște acest lucru.Pathfinder-ul acceptă poziția, lățimea și înălțimea caracterului ca intrare. În timp ce lățimea și înălțimea sunt ușor de interpretat, trebuie să clarificăm la ce bloc se referă coordonatele poziției.
Poziția pe care o trecem trebuie să fie în termeni de coordonate ale hărții, ceea ce înseamnă că, din nou, trebuie să îmbrățișăm unele inexactități. Am hotărât că ar fi sensibil ca poziția să se refere la țigla de caractere din stânga jos, deoarece aceasta se potrivește cu sistemul de coordonate al hărții.
Caracteristicile pozițiilor dalelor pentru un caracter de 3x3.Odată cu rezolvarea acestei situații, putem să actualizăm calea de urmat.
În primul rând, trebuie să ne asigurăm că personajul nostru de dimensiuni personalizate se poate potrivi în locația destinației. Până în acest moment, am verificat doar un bloc pentru a face acest lucru, deoarece aceasta a fost dimensiunea maximă (și numai) a personajului:
dacă (mGrid [end.x, end.y] == 0) returnați null;
Acum, însă, trebuie să iterăm prin fiecare celul pe care personajul ar ocupa dacă ar sta în poziția finală și să verifice dacă oricare dintre ele este un bloc solid. Dacă sunt, atunci, desigur, personajul nu poate sta acolo, astfel încât obiectivul nu poate fi atins.
Pentru a face acest lucru, să declare mai întâi un boolean la care vom seta fals
dacă personajul se află într - o țiglă solidă și Adevărat
in caz contrar:
var inSolidTile = false;
Apoi, vom repeta în fiecare bloc al personajului:
pentru (var w = 0; w < characterWidth; ++w) for (int h = 0; h < characterHeight; ++h)
În interiorul acestei bucle, trebuie să verificăm dacă un anumit bloc este solid; dacă da, am stabilit inSolidTile
la Adevărat
, și ieșiți din buclă:
pentru (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;
Dar acest lucru nu este suficient. Luați în considerare următoarea situație:
Blocuri verzi: caracter; bloc albastru: obiectiv.Dacă trebuia să mutăm caracterul astfel încât fundul său-stânga blocul a ocupat scopul, apoi partea de jos-dreapta blocul ar fi blocat într-un bloc solid - astfel încât algoritmul ar crede că, din moment ce caracterul nu se potrivește cu poziția țintă, este imposibil să ajungă la punctul final. Desigur, asta nu este adevărat; nu ne pasă care parte a personajului atinge scopul.
Pentru a rezolva această problemă, vom muta punctul de sfârșit spre stânga, pas cu pas, până la punctul în care locația inițială a obiectivului s-ar potrivi cu partea de jos-dreapta bloc de caractere:
pentru (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;
Rețineți că nu trebuie să verificăm pur și simplu colțurile din stânga și din dreapta jos, deoarece poate apărea următorul caz:
Din nou, blocuri verzi: caracter; bloc albastru: obiectiv.Aici, puteți observa că dacă unul dintre colțurile de jos ocupă poziția țintă, atunci caracterul ar fi încă în sol solid pe cealaltă parte. În acest caz, trebuie să potrivim partea de jos-centru blocați cu scopul.
În cele din urmă, dacă nu găsim niciun loc în care personajul se potrivește, s-ar putea să ieșim cât mai repede de la algoritm:
if (inSolidTile == true) returnați null;
Pentru a vedea dacă caracterul nostru este pe teren, trebuie să verificăm dacă orice din cele mai de jos celule ale personajului sunt direct deasupra unei plăci solide.
Să ne uităm la codul pe care l-am folosit pentru un caracter 1x1:
dacă (mMap.IsGround (start.x, start.y - 1)) firstNode.JumpLength = 0; altceva firstNode.JumpLength = (scurt) (maxCharacterJumpHeight * 2);
Determinăm dacă punctul de plecare se află la sol prin verificarea faptului că țigla imediat inferioară punctului de plecare este o piesă de sol. Pentru a actualiza codul, vom face pur și simplu verificarea sub toate cele mai de jos blocuri ale personajului.
În primul rând, să declarăm un boolean care ne va spune dacă caracterul începe pe teren. Inițial, presupunem că nu:
bool startsOnGround = false;
Apoi, vom repeta toate blocurile de caractere de fund și vom verifica dacă oricare dintre ele este direct deasupra unei plăci de sol. Dacă da, atunci am stabilit startsOnGround
la Adevărat
și ieșiți din buclă:
pentru (int x = start.x; x < start.x + characterWidth; ++x) if (mMap.IsGround(x, start.y - 1)) startsOnGround = true; break;
În cele din urmă, am setat valoarea saltului în funcție de caracterul inițiat pe teren:
dacă (startOnGround) firstNode.JumpLength = 0; altceva firstNode.JumpLength = (scurt) (maxCharacterJumpHeight * 2);
Trebuie să schimbăm și verificarea limitelor succesorului nostru, dar aici nu trebuie să verificăm fiecare piesă. E suficient să verifici contur a caracterului - blocurile din jurul margii - pentru că știm că poziția părintelui era bună.
Să vedem cum am verificat anterior limitele succesorului:
dacă (mGrid [mNewLocationX, mNewLocationY] == 0) continuați; dacă (mMap.IsGround (mNewLocationX, mNewLocationY - 1)) onGround = adevărat; altfel dacă (mGrid [mNewLocationX, mNewLocationY + characterHeight] == 0) atCeiling = true;
Vom actualiza acest lucru verificând dacă oricare dintre blocurile de contur se află într-un bloc solid. Dacă oricare dintre ele o face, atunci personajul nu se poate încadra în poziție și succesorul ar trebui să fie omis.
Mai întâi, să repetăm toate blocurile de sus și de jos ale personajului și să verificăm dacă ele se suprapun unei plăci solide pe grila noastră:
pentru (var w = 0; w < characterWidth; ++w) if (mGrid[mNewLocationX + w, mNewLocationY] == 0 || mGrid[mNewLocationX + w, mNewLocationY + characterHeight - 1] == 0) goto CHILDREN_LOOP_END;
CHILDREN_LOOP_END
eticheta duce la sfârșitul bucla succesor; prin utilizarea acesteia, noi sărim mai întâi necesitatea pauză
din bucla și apoi continua
următorului succesor din bucla succesorală.
Dacă vreunul dintre blocurile inferioare este chiar deasupra unei plăci solide, atunci succesorul trebuie să fie pe pământ. Aceasta înseamnă că, chiar dacă nu există nici o piesă solidă direct sub celula succesoare în sine, succesorul va fi în continuare considerat un Pe pamant
nod, dacă caracterul este suficient de larg.
pentru (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;
Dacă vreunul dintre plăcile de deasupra caracterului este solid, atunci caracterul este la tavan.
pentru (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;
Acum trebuie doar să verificăm dacă nu există blocuri solide în celulele stângi și drepte ale personajului. Dacă există, atunci putem trece în siguranță în succesor, deoarece caracterul nostru nu se va potrivi acelei poziții particulare:
pentru (var h = 1; h < characterHeight - 1; ++h) if (mGrid[mNewLocationX, mNewLocationY + h] == 0 || mGrid[mNewLocationX + characterWidth - 1, mNewLocationY + h] == 0) goto CHILDREN_LOOP_END;
Am eliminat o restricție destul de semnificativă din algoritm; acum, aveți mai multă libertate în ceea ce privește dimensiunea personajelor jocului dvs..
În tutorialul următor din serie, vom folosi algoritmul nostru de căutare a traseului pentru a alimenta un bot care poate urma calea însăși; trebuie doar să faceți clic pe o locație și să ruleze și să sară pentru a ajunge acolo. Acest lucru este foarte util pentru NPC-uri!