Creați un efect de asortiment similar ecranului cu unitatea

Uitați-vă la demo de mai jos și să începem!

Faceți clic pe demo pentru a vă concentra, apoi utilizați tastele săgeată pentru a muta nava.

După cum puteți vedea, există două modalități de a face acest lucru. Primul este mai ușor să-ți înfășeri capul. A doua soluție nu este mult mai complicată, dar necesită o gândire extraordinară. Le vom acoperi pe amândoi.

Configurarea scenei

Să facem prima scenă. Aprindeți unitatea, începeți un nou proiect și setați poziția camerei x = 0, y = 0 (z poate fi orice doriți). Probabil doriți ca camera să fie în modul ortografic; învelișul ecranului va funcționa în modul în perspectivă, dar ar putea să nu arate așa cum doriți. Simțiți-vă liber să experimentați.

Nota editorului: Consultați comentariul lui Clemens pentru informații despre modul în care lucrați în modul perspective.

Adăugați un obiect care se va înfășura și îl va muta. Puteți utiliza funcția ShipMovementBehaviour script din sursa demo.

În cazul meu, am o navă spațială simplă (un con) cu mișcare asteroizată. Așa cum puteți vedea în imagine, prefer să aibă o parenteală a ochiului de plasă la obiectul principal. Indiferent dacă o faci așa sau nu, dacă aveți una sau mai multe ochiuri, nu contează; vom face un script frumos de împachetare a ecranului, care funcționează în orice caz.

Simple Wrapping

Ideea de bază din spatele ecranării este următoarea:

  1. Verifica dacă obiectul a ieșit din ecran.
  2. Afla Unde a ieșit din ecran. A trecut peste marginea din stânga sau în dreapta? Sus sau jos?
  3. Teleportați obiectul chiar în spatele lui opus marginea ecranului. De exemplu, dacă trece peste marginea din stânga, o teleportăm în spatele marginii drepte. Teleportim obiectul in spate marginea opusă, astfel încât să pară de fapt împachetarea în loc să fie teleportată.

Fac acest lucru în unitate

Deci, primul lucru pe care vrem să-l facem este să verificăm dacă obiectul sa oprit complet. O modalitate simplă de a face acest lucru în Unitate constă în verificarea faptului că redarerele obiectului sunt vizibile. Dacă nu sunt, înseamnă că obiectul este complet în afara camerei foto și, prin urmare, în afara ecranului.

Haideți să aducem redare Start() și faceți o funcție de utilitate pentru a le verifica:

Renderer []; void Start () renderers = GetComponentsInChildren ();  bool CheckRenderers () foreach (var renderer în renderers) // Dacă cel puțin o redare este vizibilă, returnează true dacă (renderer.isVisible) return true;  // În caz contrar, obiectul este invizibil return false; 

Acum putem spune dacă obiectul nostru a ieșit de pe ecran, dar trebuie să aflăm în continuare Unde a dispărut, apoi a teleportat-o ​​spre partea opusă. Pentru a face acest lucru, putem privi axele separat. De exemplu, dacă poziția x a navei noastre se află în afara limitelor ecranului, aceasta înseamnă că a dispărut fie spre stânga, fie spre dreapta.

Cea mai ușoară modalitate de a verifica dacă este mai întâi să convertiți poziția lumii navei în poziția de vizualizare a portofoliului și apoi să efectuați verificarea. În acest fel, va funcționa dacă utilizați o cameră ortografică sau cu o perspectivă.

var cam = Camera.main; Var viewPosition = cam.WorldToViewportPoint (transform.position);

Pentru a face lucrurile mai clare, permiteți-mi să vă explic coordonatele de vizualizare. Spațiul de vizualizare este relativ la camera foto. Coordonatele variază de la 0 la 1 pentru tot ceea ce este pe ecran, ceea ce înseamnă:

  • x = 0 este coordonatele marginii din stânga a ecranului.
  • x = 1 este coordonatul marginii drepte a ecranului.

asemănător,

  • y = 0 este coordonata marginea ecranului inferior.
  • y = 1 este coordonatul marginea superioară a ecranului.

Aceasta înseamnă că, dacă un obiect este off-screen, va avea fie o coordonată negativă (mai mică de 0), fie o coordonată mai mare de 1.

Din moment ce poziția camului nostru este la x = 0, y = 0, scena este așezată ca o oglindă. Totul la dreapta are coordonate x pozitive; totul la stânga, negativ. Totul din jumătatea superioară are coordonate y pozitive; totul în jumătatea de jos, negativ. Deci, pentru a ne poziționa obiectul în partea opusă a ecranului, inversăm poziția sa de-a lungul axei corespunzătoare. De exemplu:

  • Dacă nava noastră se mișcă spre dreapta și poziția sa este (20, 0), devine (-20, 0).
  • Dacă nava noastră se mișcă peste marginea de jos și poziția ei este (0, -15), devine (0, 15).

Rețineți că transformăm nava transforma nu poziția lui viewport poziţie.


În cod, care arată astfel:

var newPosition = transformare; dacă (viewportPosition.x> 1 || viewportPosition.x < 0)  newPosition.y = -newPosition.y;  if (viewportPosition.y > 1 || viewportPosition.y < 0)  newPosition.y = -newPosition.y;  transform.position = newPosition;

Dacă executați proiectul acum, acesta va funcționa bine în majoritatea timpului. Dar, uneori, obiectul ar putea să nu se înfășoare. Acest lucru se întâmplă deoarece obiectul nostru schimbă în mod constant pozițiile în afara ecranului, în loc de o singură dată. Putem preveni acest lucru adăugând câteva variabile de control:

bool isWrappingX = false; bool isWrappingY = false;

Totul ar trebui să funcționeze perfect acum, iar ultimul cod de împachetare a ecranului ar trebui să arate astfel:

void ScreenWrap () var esteVisible = CheckRenderers (); dacă este (IsVisible) isWrappingX = false; isWrappingY = false; întoarcere;  dacă (isWrappingX && isWrappingY) retur;  var cam = Camera.main; Var viewPosition = cam.WorldToViewportPoint (transform.position); var newPosition = transformare; dacă (! isWrappingX && (viewportPosition.x> 1 || viewportPosition.x < 0))  newPosition.x = -newPosition.x; isWrappingX = true;  if (!isWrappingY && (viewportPosition.y > 1 || viewportPosition.y < 0))  newPosition.y = -newPosition.y; isWrappingY = true;  transform.position = newPosition; 

Ambalare avansată

Ambalajul simplu funcționează bine, dar ar putea să arate mai bine. În locul obiectului care se desfășoară în afara ecranului înainte de împachetare, ați putea avea o împachetare perfectă, ca în imaginea de mai jos:


Cel mai simplu mod de a face acest lucru este să trișezi puțin și să ai mai multe nave pe scenă. În acest fel, vom crea iluzia unei singure nave care se înfășoară. Vom avea nevoie de opt nave suplimentare (îi voi suna fantome): câte unul pentru fiecare margine și unul pentru fiecare colț al ecranului.

Vrem ca aceste nave fantoma să fie vizibile numai atunci când jucătorul ajunge la margine. Pentru a face acest lucru, trebuie să le poziționăm la anumite distanțe față de nava principală:

  • Două nave au amplasat o lățime a ecranului spre stânga și respectiv spre dreapta.
  • Două nave au amplasat o înălțime a ecranului deasupra și dedesubt.
  • Două nave de colț au amplasat o lățime a ecranului pe orizontală și o înălțime a ecranului în poziție verticală.

Fac acest lucru în unitate

Trebuie să scoatem mai întâi dimensiunea ecranului, astfel încât să putem poziționa navele noastre fantomă. Chestia e că avem nevoie de dimensiunea ecranului în coordonatele lumii relative la nava jucătorului. Nu contează dacă folosim o cameră ortografică, dar din punct de vedere vizual este foarte important ca navele-fantomă să aibă aceeași coordonate z ca nava principală. 

Deci, pentru a face acest lucru într-o capcană, vom transforma coordonatele de vizualizare a colțurilor ecranului din dreapta sus și din stânga la coordonatele mondiale care se află pe aceeași axă z cu nava principală. Apoi folosim aceste coordonate pentru a calcula lățimea și înălțimea ecranului în unități mondiale față de poziția navei noastre.

Declara screenWidth și screenHeight ca variabile de clasă și adăugați aceasta la Start():

var cam = Camera.main; var screenBottomLeft = cam.ViewportToWorldPoint (nou Vector3 (0, 0, transform.position.z)); var screenTopRight = cam.ViewportToWorldPoint (nou Vector3 (1, 1, transform.position.z)); screenWidth = screenTopRight.x - screenBottomLeft.x; screenHeight = screenTopRight.y - screenBottomLeft.y;

Acum că putem să le poziționăm corect, să lansăm navele fantoma. Vom folosi un matrice pentru a le stoca:

Transformare [] ghosts = transformare nouă [8];

Și să creăm o funcție care va face reproducerea. Voi clona nava principală pentru a crea fantome și apoi voi elimina ScreenWrapBehaviour de la ei. Nava principală este singura care ar trebui să aibă ScreenWrapBehaviour, deoarece poate avea control complet asupra fantomelor și nu vrem ca fantomele să-și creeze propriile fantome. Ai putea avea, de asemenea, un prefabricat separat pentru navele fantoma și instanțiați-l; aceasta este modalitatea de a merge dacă doriți ca fantomele să aibă un comportament special.

void CreateGhostShips () pentru (int i = 0; i < 8; i++)  ghosts[i] = Instantiate(transform, Vector3.zero, Quaternion.identity) as Transform; DestroyImmediate(ghosts[i].GetComponent());  

Apoi poziționăm fantomele ca în imaginea de mai sus:

void PositionGhostShips () // Toate pozițiile de fantomă vor fi relative la transformarea navei (aceasta), // astfel încât să stea cu asta. var ghostPosition = transformare; // Noi poziționăm fantomele în sensul acelor de ceasornic în spatele marginilor ecranului. // Să începem cu extrema dreaptă. ghostPosition.x = transform.position.x + lățimea ecranului; ghostPosition.y = transform.position.y; fantome [0] .position = ghostPosition; // Bottom-right ghostPosition.x = transform.position.x + lățimea ecranului; ghostPosition.y = transform.position.y - screenHeight; fantome [1] .position = ghostPosition; // Bottom ghostPosition.x = transform.position.x; ghostPosition.y = transform.position.y - screenHeight; fantome [2] .position = ghostPosition; // GhostPosition.x din stânga-stânga = transform.position.x - ecranWidth; ghostPosition.y = transform.position.y - screenHeight; fantome [3] .position = ghostPosition; // Stânga ghostPosition.x = transform.position.x - ecranWidth; ghostPosition.y = transform.position.y; fantome [4] .position = ghostPosition; // Top-left ghostPosition.x = transform.position.x - ecranWidth; ghostPosition.y = transform.position.y + screenHeight; fantome [5] .position = ghostPosition; // Top ghostPosition.x = transform.position.x; ghostPosition.y = transform.position.y + screenHeight; fantome [6] .position = ghostPosition; // Top-right ghostPosition.x = transform.position.x + lățimea ecranului; ghostPosition.y = transform.position.y + screenHeight; fantome [7] .position = ghostPosition; // Toate navele fantoma ar trebui să aibă aceeași rotație ca nava principală pentru (int i = 0; i < 8; i++)  ghosts[i].rotation = transform.rotation;  

Rulați proiectul și încercați. Dacă verificați afișarea scenei, veți vedea că toate navele fantomă se mișcă cu nava principală și se rotesc când se întoarce. Nu am codificat în mod explicit acest lucru, dar funcționează în continuare. Ai o idee de ce?

Navele de fantome sunt clone ale navei principale fără ScreenWrappingBehaviour. Acestea ar trebui să aibă în continuare comportamentul separat al mișcării și, deoarece toți primesc aceeași intrare, toți se mișcă la fel. Dacă doriți să provocați fantomele dintr-un prefabricat, nu uitați să includeți o componentă de mișcare sau alt script care să-și sincronizeze mișcarea cu nava principală.

Totul pare să funcționeze bine acum, nu? Ei bine aproape. Dacă continuați să mergeți într-o singură direcție, prima dată când se va împacheta va funcționa bine, dar odată ce veți ajunge din nou la margine, nu va mai fi o navă pe cealaltă parte. Are sens, pentru că nu facem teleportare de data asta. Să rezolvăm asta.

Odată ce nava principală va ieși de pe margine, va apărea pe ecran o navă fantomă. Trebuie să schimbăm pozițiile și apoi să repoziționăm navele-fantomă în jurul navei principale. Păstrăm deja o gamă de fantome, trebuie doar să determinăm care dintre ele este pe ecran. Apoi vom face schimbarea și repoziționarea. În cod:

void SwapShips () foreach (var ghost în fantome) if (ghost.position.x < screenWidth && ghost.position.x > -ecranWidth && ghost.position.y < screenHeight && ghost.position.y > -screenHeight) transform.position = ghost.position; pauză;  PositionGhostShips (); 

Încercați acum, și totul ar trebui să funcționeze perfect.

Gândurile finale

Acum aveți o componentă de lucru pentru ecranul de lucru. Dacă acest lucru este suficient pentru dvs. depinde de jocul pe care îl faceți și de ceea ce încercați să obțineți.

Ambalarea simplă este destul de simplă: atașați-o doar la un obiect și nu trebuie să vă faceți griji cu privire la comportamentele sale. Pe de altă parte, trebuie să fii puțin atent dacă folosești ambalajul avansat. Imaginați-vă o situație în care un glonț sau un asteroid lovește o navă fantomă: va trebui să propagați evenimentele de coliziune la nava principală sau un obiect de controler extern.

S-ar putea să doriți, de asemenea, ca obiectele jocului să se înfășoare pe o singură axă. Facem deja verificări separate pentru fiecare axă, deci este doar o chestiune de adăugare a câtorva Booleani la cod.

Un lucru mai interesant de luat în considerare: dacă ați fi vrut camera să se miște un pic în loc să fie fixată în spațiu? Poate că doriți să aveți o arenă mai mare decât ecranul. În acest caz, puteți utiliza în continuare același script de înfășurare. Aveți nevoie doar de un comportament separat care să controleze limitele mișcării camerei. Din moment ce codul nostru se bazează pe poziția de vizualizare, poziția în joc a aparatului foto nu contează.

Probabil că aveți niște idei proprii până acum. Deci, mergeți mai departe, încercați-le și faceți niște jocuri!

Referințe

  • Image credit: Rocket de William J. Salvador de la Proiectul Noun