Crea un effetto di avvolgimento dello schermo simile ad asteroidi con Unity

Dai un'occhiata alla demo qui sotto e cominciamo!

Fare clic sulla demo per focalizzarla, quindi utilizzare i tasti freccia per spostare la nave.

Come puoi vedere, ci sono due modi per farlo. Il primo è più facile da avvolgere la testa. La seconda soluzione non è molto più complicata, ma richiede un pensiero immediato. Li copriremo entrambi.

Impostazione della scena

Iniziamo a impostare la scena. Accendi Unity, avvia un nuovo progetto e imposta la posizione della videocamera su x = 0, y = 0 (z può essere quello che vuoi). Probabilmente vorrai che la fotocamera sia in modalità ortografica; il nostro screen wrapping funzionerà in modalità prospettiva, ma potrebbe non apparire nel modo desiderato. Sentiti libero di sperimentare.

Nota dell'editore: Vedi il commento di Clemens per informazioni su come fare in modo prospettico.

Aggiungi un oggetto che si avvolge e fallo spostare. Puoi usare il ShipMovementBehaviour script dalla fonte demo.

Nel mio caso, ho una semplice nave spaziale (un cono) con movimento simile ad asteroidi. Come puoi vedere nell'immagine, preferisco che la mesh sia sincronizzata con l'oggetto principale. Che tu lo faccia o meno, se hai una o più mesh, non importa; faremo una bella sceneggiatura che funziona in ogni caso.

Avvolgimento semplice

L'idea alla base dello screen wrapping è questa:

  1. Dai un'occhiata se l'oggetto è andato fuori campo.
  2. Scoprire dove è andato fuori campo. È andato oltre il bordo sinistro o giusto? Sopra o sotto?
  3. Teletrasporta l'oggetto proprio dietro il di fronte bordo dello schermo. Ad esempio, se va oltre il bordo sinistro, lo teletrasportiamo dietro il bordo destro. Noi teletrasportiamo l'oggetto dietro a il bordo opposto, in modo che in realtà sembra essere avvolto intorno al posto di essere teletrasportato.

Fare questo in unità

Quindi, la prima cosa che vogliamo fare è verificare se l'oggetto è andato completamente fuori dallo schermo. Un modo semplice per farlo in Unity è controllare se i renderer dell'oggetto sono visibili. Se non lo sono, significa che l'oggetto è completamente fuori dalla telecamera e quindi fuori dallo schermo.

Prendiamo i renderer Inizio() e fare una funzione di utilità per controllarli:

Renderer [] renderer; void Start () renderers = GetComponentsInChildren ();  bool CheckRenderers () foreach (renderer var nei renderer) // Se almeno un rendering è visibile, restituisce true se (renderer.isVisible) return true;  // Altrimenti, l'oggetto è invisibile return false; 

Ora possiamo dire se il nostro oggetto è andato fuori dallo schermo, ma dobbiamo ancora scoprirlo dove è andato via e poi teletrasportato sul lato opposto. Per fare questo, possiamo guardare gli assi separatamente. Ad esempio, se la posizione x della nostra nave è fuori dai limiti dello schermo, significa che è andata a sinistra oa destra.

Il modo più semplice per verificare è quello di convertire per prima cosa la posizione del mondo della nave in posizione viewport, quindi eseguire il controllo. In questo modo, funzionerà sia che tu utilizzi una videocamera ortogonale o prospettica.

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

Per chiarire le cose, lascia che ti spieghi coordinate del viewport. Lo spazio della vista è relativo alla fotocamera. Le coordinate vanno da 0 a 1 per tutto ciò che è sullo schermo, che significa:

  • x = 0 è la coordinata del bordo sinistro dello schermo.
  • x = 1 è la coordinata del bordo destro dello schermo.

allo stesso modo,

  • y = 0 è la coordinata del bordo dello schermo inferiore.
  • y = 1 è la coordinata del bordo dello schermo superiore.

Ciò significa che, se un oggetto è fuori schermo, avrà una coordinata negativa (meno di 0) o una coordinata maggiore di 1.

Dato che la posizione della nostra cam è a x = 0, y = 0, la scena è strutturata come uno specchio. Tutto a destra ha coordinate x positive; tutto a sinistra, negativo. Tutto nella metà superiore ha coordinate y positive; tutto nella metà inferiore, negativo. Quindi, per posizionare il nostro oggetto sul lato opposto dello schermo, invertiamo semplicemente la sua posizione lungo l'asse appropriato. Per esempio:

  • Se la nostra nave si sposta a destra e la sua posizione è (20, 0), diventa (-20, 0).
  • Se la nostra nave si muove oltre il bordo inferiore e la sua posizione è (0, -15), diventa (0, 15).

Nota che stiamo trasformando la nave trasformare posizione, non la sua viewport posizione.


Nel codice, appare così:

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

Se si esegue il progetto ora, funzionerà bene la maggior parte del tempo. Ma a volte, l'oggetto potrebbe non avvolgersi. Questo accade perché il nostro oggetto scambia continuamente posizioni fuori dallo schermo, invece che una sola volta. Possiamo evitarlo aggiungendo un paio di variabili di controllo:

bool isWrappingX = false; bool isWrappingY = false;

Tutto dovrebbe funzionare perfettamente ora e il codice finale dello schermo dovrebbe apparire così:

void ScreenWrap () var isVisible = CheckRenderers (); if (isVisible) isWrappingX = false; isWrappingY = false; ritorno;  if (isWrappingX && isWrappingY) return;  var cam = Camera.main; var viewportPosition = cam.WorldToViewportPoint (transform.position); var newPosition = transform.position; if (! 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; 

Avvolgimento avanzato

Il semplice involucro funziona bene, ma potrebbe sembrare migliore. Invece dell'oggetto che va fuori campo prima di avvolgersi, potresti avere un involucro perfetto, come nell'immagine qui sotto:


Il modo più semplice per farlo è imbrogliare un po 'e avere più navi sulla scena. In questo modo creeremo l'illusione di una singola nave che si avvolge. Avremo bisogno di otto navi aggiuntive (le chiamerò fantasmi): uno per ciascun bordo e uno per ogni angolo dello schermo.

Vogliamo che queste navi fantasma siano visibili solo quando il giocatore si avvicina. Per fare ciò, dobbiamo posizionarli a certe distanze dalla nave principale:

  • Due navi posizionavano una larghezza dello schermo verso sinistra e verso destra, rispettivamente.
  • Due navi hanno posizionato un'altezza dello schermo al di sopra e al di sotto, rispettivamente.
  • Quattro navi angolari posizionavano una larghezza dello schermo in orizzontale e un'altezza dello schermo in verticale.

Fare questo in unità

Dobbiamo prima recuperare le dimensioni dello schermo, in modo da poter posizionare le nostre navi fantasma. Il fatto è che abbiamo bisogno delle dimensioni dello schermo in coordinate del mondo relative alla nave del giocatore. Questo non importa se utilizziamo una macchina ortografica, ma con la vista prospettica è molto importante che le navi fantasma si trovino sulla stessa coordinata z della nave principale. 

Quindi, per fare tutto ciò in modo catch-all, trasformeremo le coordinate del viewport degli angoli dello schermo in alto a destra e in basso a sinistra nelle coordinate del mondo che giacciono sullo stesso asse Z della nave principale. Quindi usiamo queste coordinate per calcolare la larghezza e l'altezza dello schermo in unità del mondo relative alla posizione della nostra nave.

Dichiarare screenwidth e ScreenHeight come variabili di classe e aggiungere questo a Inizio():

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

Ora che possiamo posizionarli correttamente, generiamo le navi fantasma. Useremo un array per memorizzarli:

Transform [] ghosts = new Transform [8];

E creiamo una funzione che farà la deposizione delle uova. Ho intenzione di clonare la nave principale per creare i fantasmi, e quindi rimuoverò il ScreenWrapBehaviour da loro. La nave principale è l'unica che dovrebbe avere ScreenWrapBehaviour, perché può avere il pieno controllo dei fantasmi e non vogliamo che i fantasmi generino i propri fantasmi. Potresti anche avere un prefabbricato separato per le navi fantasma e istanziarlo; questa è la strada da percorrere se vuoi che i fantasmi abbiano un comportamento speciale.

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

Quindi posizioniamo i fantasmi come nell'immagine sopra:

void PositionGhostShips () // Tutte le posizioni dei ghost saranno relative alle navi (this) transform, // quindi iniziamo con quello. var ghostPosition = transform.position; // Stiamo posizionando i fantasmi in senso orario dietro i bordi dello schermo. // Iniziamo con l'estrema destra. ghostPosition.x = transform.position.x + screenWidth; ghostPosition.y = transform.position.y; ghosts [0] .position = ghostPosition; // Bottom-right ghostPosition.x = transform.position.x + screenWidth; ghostPosition.y = transform.position.y - screenHeight; ghosts [1] .position = ghostPosition; // Bottom ghostPosition.x = transform.position.x; ghostPosition.y = transform.position.y - screenHeight; ghosts [2] .position = ghostPosition; // Bottom-left ghostPosition.x = transform.position.x - screenWidth; ghostPosition.y = transform.position.y - screenHeight; ghosts [3] .position = ghostPosition; // Left ghostPosition.x = transform.position.x - screenWidth; ghostPosition.y = transform.position.y; ghosts [4] .position = ghostPosition; // In alto a sinistra ghostPosition.x = transform.position.x - screenWidth; ghostPosition.y = transform.position.y + screenHeight; ghosts [5] .position = ghostPosition; // Top ghostPosition.x = transform.position.x; ghostPosition.y = transform.position.y + screenHeight; ghosts [6] .position = ghostPosition; // In alto a destra ghostPosition.x = transform.position.x + screenWidth; ghostPosition.y = transform.position.y + screenHeight; ghosts [7] .position = ghostPosition; // Tutte le navi fantasma dovrebbero avere la stessa rotazione della nave principale per (int i = 0; i < 8; i++)  ghosts[i].rotation = transform.rotation;  

Esegui il tuo progetto e provalo. Se controlli la vista scena, vedrai che tutte le navi fantasma si muovono con la nave principale e girano quando gira. Non lo abbiamo codificato esplicitamente, ma funziona ancora. Hai un'idea del perché?

Le navi fantasma sono cloni della nave principale senza il ScreenWrappingBehaviour. Dovrebbero comunque avere il comportamento di movimento separato e dato che ricevono tutti lo stesso input, si muovono tutti allo stesso modo. Se vuoi generare i fantasmi da un prefabbricato, non dimenticare di includere un componente di movimento o qualche altro script che sincronizzerà i loro movimenti con la nave principale.

Tutto sembra funzionare bene ora, giusto? Be 'quasi. Se continui in una direzione, la prima volta che si avvolge funzionerà bene, ma una volta raggiunto il bordo, non ci sarà una nave dall'altra parte. Ha senso, visto che non stiamo facendo nessun teletrasporto questa volta. Risolviamolo.

Una volta che la nave principale va fuori bordo, una nave fantasma sarà sullo schermo. Dobbiamo scambiare le loro posizioni e quindi riposizionare le navi fantasma attorno alla nave principale. Abbiamo già una serie di fantasmi, abbiamo solo bisogno di determinare quale di essi è sullo schermo. Quindi facciamo lo scambio e il riposizionamento. Nel codice:

void SwapShips () foreach (var ghost in ghosts) if (ghost.position.x < screenWidth && ghost.position.x > -screenWidth && ghost.position.y < screenHeight && ghost.position.y > -screenHeight) transform.position = ghost.position; rompere;  PositionGhostShips (); 

Provalo ora, e tutto dovrebbe funzionare perfettamente.

Pensieri finali

Ora hai un componente di avvolgimento dello schermo funzionante. Se questo è abbastanza per te dipende dal gioco che stai facendo e da ciò che stai cercando di ottenere.

L'involucro semplice è piuttosto semplice da usare: basta collegarlo a un oggetto e non devi preoccuparti dei suoi comportamenti. D'altra parte, devi stare un po 'attento se usi il wrapping avanzato. Immagina una situazione in cui un proiettile o un asteroide colpisce una nave fantasma: dovrai propagare gli eventi di collisione sulla nave principale o su un oggetto controller esterno.

Potresti anche volere che i tuoi oggetti di gioco si avvolgano lungo un solo asse. Stiamo già facendo controlli separati per ciascun asse, quindi è solo questione di aggiungere un paio di booleani al codice.

Un'altra cosa interessante da considerare: cosa succede se si desidera che la fotocamera si muova un po 'invece di essere fissata nello spazio? Forse vuoi avere un'arena più grande dello schermo. In tal caso, potresti comunque utilizzare lo stesso script di wrapping. Avresti solo bisogno di un comportamento separato che controlli i limiti del movimento della fotocamera. Poiché il nostro codice è basato sulla posizione del viewport, la posizione di gioco della videocamera non ha molta importanza.

Probabilmente avrai qualche idea per ora. Quindi vai avanti, provali e fai alcuni giochi!

Riferimenti

  • Immagine di credito: Rocket di William J. Salvador del progetto Noun