Crea sito

Come “spawnare” oggetti in unity

Ciao a tutti, oggi affrontiamo un problema che chiunque si avvicini allo sviluppo di videogiochi, prima o poi dovr√† affrontare, ovvero quello di “spawnare” (dall’inglese to spawn: “generare”) e mostrare oggetti all’interno della nostra scena. Speriamo di aiutare qualcuno di voi alle prime armi! ūüôā

Capiamo subito quanto sia utile avere uno strumento flessibile per affrontare il problema, in quanto questo ci potr√† essere utile in innumerevoli occasioni, ad esempio far comparire nuvole, uccelli, mostri, auto, ostacoli, nemici, amici, bolle d’acqua, conigli, insomma di tutto!

Per comodità affrontiamo il problema in un gioco bidimensionale (meno coordinate da gestire!), la soluzione è facilmente estendibile in un gioco 3d.

COSA VOGLIAMO OTTENERE?

Ok per prima cosa diamoci dei requisiti, supponiamo di voler “spawnare” delle nuvole in cielo, vorremmo che esse:

  • Compaiano intorno a un determinato punto.
  • Non compaiano per√≤ nella stessa esatta posizione, ma all’interno di un intervallo da noi specificato
  • La frequenza di spawn sia variabile (le nuvole non compaiono tutte con lo stesso ritmo!)

INIZIAMO

Apriamo Unity, creiamo un nuovo progetto 2D, salviamo la scena di default e procuriamoci qualcosa da spawnare:

un ottima fonte di disegni in formato vettoriale e no¬†da cui attingere, senza incorrere in problemi di copyright √® https://openclipart.org/ , nel mio caso, ho scelto un po’ di nuvole:

spawner 1

Diamo subito un comportamento adatto alla nostra nuvole, scrivendo un breve script che le permetta di muoversi da una direzione a un altra autonomamente, in modo da poterci concentrare subito sullo spawner.

  • Movimento della nuvola: clicchiamo col mouse destro nella cartella assets, andiamo su Create –> C# Script, chiamiamolo CloudMovement e scriviamo il seguente codice

  • :Mettiamo una nuvola nella scena in posizione (0,0,0) e trasciniamo sopra lo script che abbiamo appena preparato.
  • Se mandiamo in play dovremmo vedere la nuvola muoversi in una delle direzioni specificate. Agendo sulle variabili pubbliche nell’inspector regoliamo direzione e velocit√†.

spawner 2

SPAWNER BASE

Ok, passiamo ora allo spawner:

  • piazziamo un oggetto vuoto nella scena cliccando su GameObject –> Create Empty, chiamiamo l’oggetto “Spawner” e piazziamolo nel punto da cui vogliamo fare comparire le nostre nuvole, nel mio caso appena fuori dalla viewport della camera, sulla destra
  • Creiamo un prefab della nostra nuvola trascinandola dalla hierarchy, alla cartella asset, e rimuoviamo il prefab dalla scena.
  • Creiamo un nuovo script c#, chiamiamolo GenericSpawner e attacchiamolo al nostro empty object.

spawner 3

Ottimo, iniziamo a scrivere il nostro script:

Dichiariamo un po’ di variabili pubbliche:

Abbiamo detto, che vogliamo creare un po’ di variabilit√† per cui utilizziamo un numero casuale, in un range stabilito in fase di level design per generare un offset rispetto alla posizione di spawning prevista. Questo range sar√† settabile tramite inspector grazie alle variabili pubbliche mostrate qu√† sopra.

Concentriamoci ora sulla funzione di spawn vera e propria:

L’idea √® quella di controllare se possiamo spawnare un oggetto (ovvero se elemToSpawn √® negativo, oppure se non lo √® ma non abbiamo ancora spawnato tutti gli elementi).

A questo punto calcoliamo la posizione da cui vorremo fare comparire il nostro oggetto e con una Instantiate, la creiamo!

Infine richiamiamo il metodo¬†Spawn ricorsivamente, con un delay dato da un’altro numero casuale estratto in un intervallo identificato da minSpawnTime e maxSpawnTime.

Ottimo, cosa manca?

Ovviamente, la prima chiamata al metodo Spawn, possiamo metterla all’interno del metodo Start:

Se ora torniamo in Unity¬†e¬†assegnamo il nostro prefab della nuvola al campo “Object To Spawn” e clicchiamo play, otteniamo questo effetto:

spawner 4

Possiamo giocare un po’ coi parametri, settando ad esempio minPosY a -3 e maxPosY a +3: come possiamo vedere ora lo spawner assegner√† un offset diverso rispetto al punto di origine, ottenendo un effetto pi√Ļ realistico:

spawner 5

Perfetto! Ora, il problema evidente, √® che le nuvole non vengono eliminate dalla scena, questo per√≤ non dovrebbe essere affrontato dallo spawner, in quanto si esso si dovrebbe occupare semplicemente della comparsa degli oggetti, non della loro rimozione, per cui affrontiamo il problema addirittura nello script relativo al movimento delle nuvole, nel nostro caso possiamo sfruttare l’evento : OnBecameInvisible, che come suggerisce il nome viene chiamato ogniqualvolta, un oggetto che prima era visibile diventa invisibile: per cui nel nostro CloudMovement, sotto al metodo update inseriamo:

Come √® possibile osservare, mandando nuovamente in play, e tenendo d’occhio la Hierarchy ora le nuvole compaiono sullo schermo, ma nel momento in cui escono dallo schermo, vengono rimosse.

POSSIBILI OTTIMIZZAZIONI – POOL DI OGGETTI

Il nostro spawner sembra funzionare correttamente, per√≤ ha l’evidente¬†problema di tenere poco conto dell’overhead generato dall’istanziazione e distruzione di oggetti, e come tutti ben sappiamo nel mondo dei videogiochi, le prestazioni contano.

Per spawn ciclici di oggetti come nel caso delle nuvole è veramente necessario continuare a istanziare e rimuovere gli stessi prefab?

La risposta è no!

Come fare allora?

L’idea √® quella di pre-istanziare gli oggetti che vogliamo fare spawnare, assegnandoli, ad esempio a un array che chiameremo objectPool (lo scopo √® quello di farli caricare, al momento del caricamento del livello), e, in seguito, utilizzare una struttura dati di supporto, una coda dovrebbe fare al caso nostro, per accodare gli elementi non ancora spawnati e attivarli col¬†metodo¬†Spawn.

A questo punto, quando il nostro oggetto esce dal campo visivo, contatta lo spawner, chiedendo di essere disattivato, e riaccodato per uno spawn successivo.

In questo modo, prepariamo, che ne so, 5 –¬†10 nuvolette, le istanziamo una sola volta, ed esse ciclicamente verranno rese visibili/invisibili e riposizionate dallo spawner, evitando centinaia di istanziazioni/distruzioni di prefab al nostro gioco.

Vediamo che modifiche dobbiamo apportare allo spawner:

Cosa abbiamo fatto?

  • Abbiamo inserito un array di Game Objects tra le variabili pubbliche: objectsPool, che riempiremo con le istanze delle nostre nuvole.
  • Abbiamo aggiunto una coda di supporto, l’abbiamo chiamata objectsQueue… (da notare l’importazione della libreria System.Collections.Generic, in alto)
  • Abbiamo modificato la logica di spawn in modo da rimuovere dalla coda (se non √® vuota) il prossimo gameObject da spawnare.
  • Abbiamo rimosso l’istanziazione sostituendola con un assegnamento della nuova posizione, e un settaggio del parametro “active” del gameobject
  • Abbiamo aggiunto un metodo pubblico, che, il gameObject dovr√† richiamare per essere riaccodato, una volta che esso uscir√† dallo schermo.

A questo punto, non ci resta che modificare la logica di rimozione del gameobject dentro al nostro CloudMovement:

  • Abbiamo aggiunto un campo pubblico, a cui assegneremo un riferimento al nostro spawner
  • Abbiamo modificato il comportamento OnBecameInvisible, richiamando il metodo RequeueObject che abbiamo preparato in precedenza.

Ora dobbiamo sistemare un po’ di cose dall’inspector in Unity, salviamo i nostri script:

  • Prepariamo un po’ di nuvole, importandole nella scena, controllando di avere assegnato lo script CloudMovement, e per ordine parentiamole al nostro Spawner (non √® necessario, ma lo trovo pi√Ļ ordinato)
  • Per ogni istanza delle nostre nuvole, nell’inspector, trasciniamo in corrispondenza del campo The Spawner, l’empty object del nostro spawner
  • Selezioniamo lo spawner, e, sempre nell’inspector, in corripondenza del campo Objects pool, specifichiamo una dimensione per l’array (il numero di nuvole che abbiamo istanziato) assegnamo ognuna delle nostre nuvole al relativo campo.
  • Eseguiamo il gioco, dovremmo ottenere lo stesso effetto di prima, ma utilizzando molte meno risorse!

spawner 6

QUALCHE CHICCA!

Ok, abbiamo il nostro spawner, lo abbiamo anche ottimizzato, e ora?

Non sempre nei nostri giochi vogliamo avere un comportamento casuale da parte dello spawner, a volte pu√≤ capitarci di voler fare comparire i nostri oggetti seguendo un pattern specifico o prevedibile, facciamo un po’ di refactoring per prevedere diverse modalit√† di spawning.

SPAWNARE LUNGO UNA SINUSOIDE

Supponiamo di non voler pi√Ļ spawnare i nostri game object casualmente ma in funzione del seno di x, dove x sar√† il tempo di gioco, l’effetto che vogliamo ottenere √® quello di onde che disegnano sullo schermo la funzione sin(x).

Dovremmo ovviamente agire sul punto del codice che calcola la posizione di spawn del nuovo oggetto, concentriamoci solo sulla funzione di spawn:

Vogliamo anche mantenere il comportamento randomico implementato prima (può sempre servire) per cui per cui prendiamo il pezzo di codice che calcola il punto di spawn e lo mettiamo in una funzione dedicata, la chiamiamo GetRandomSpawnPos().

A questo punto creiamo una nuova funzione GetSinSpawnPos(), che fa la stessa cosa, ma utilizzando sin(x), l’idea √® di ottenere una sinusoide centrata nella posizione corrente, ma di ampiezza stabilita da minPos e maxPos, per cui regolando questi valori da inspector, riusciamo a modificare la forma della nostra sinusoide.

Per la frequenza, ovviamente bisogna agire su Time.time, nel senso che, se vogliamo pi√Ļ “sali e scendi”, dovremmo moltiplicare Time.time per un qualche valore… vi lascio sperimentare, sulle mille forme che potete dare a questa curva!

Il risultato ottenuto, sarà qualcosa del genere:

spawner 7

SPAWNARE LUNGO UNA QUALSIASI CURVA

Soddisfatti? Non ancora?

ecco forse la chicca pi√Ļ divertente…

Perché spawnare semplicemente lungo funzioni matematiche note? Cerchiamo un modo di farlo con qualsiasi funzione! Non avete voglia di stare a pensare a una funzione che generi una curva di forma particolare? No problem, ci sono le AnimationCurves!

Per realizzare quest’ultima parte del nostro spawner “abusiamo” di un oggetto nato per risolvere tutt’altro problema, le Animation Curves.

Questi strumenti nascono, in realt√† per lavorare su problemi come transizioni tra animazioni, smoothing ecc… per√≤ hanno una caratteristica che pu√≤ fare al caso nostro!

Proviamo a dichiarare nella classe del nostro spawner una variabile pubblica di tipo AnimationCurve, guardate cosa compare nell’inspector:

spawner 8

Ora se noi riuscissimo a scorrere questa curva in funzione del tempo, così come abbiamo fatto per il seno di x, potremmo spawnare oggetti seguendo qualsiasi percorso ci possa venire in mente!

Al lavoro:

Ok la faccenda, qu√¨ si fa un po’ pi√Ļ incasinata, vediamo di capire che succede:

  • In primo luogo cicliamo lungo tutti i keyframe nella nostra animation curve, lo scopo √® recuperare tutti i punti di minimo e massimo locali… ora, siccome la curva l’abbiamo disegnata noi, proprio introducendo i keyframe, tutti i punti di massimo e minimo, saranno in corrispondenza dei keyframe!
  • A questo punto ciclando abbiamo ottenuto minimo e massimo per entrambe le curve, sia x che y.
  • Valutiamo le due curve, in funzione del tempo di gioco (come abbiamo fatto per il sin(x) prima)
  • E a questo punto normalizziamo il valore, all’interno del range che abbiamo prefissato min/max di x e y (dall’inspector)
  • Ritorniamo la nuova posizione.

Ora non ci resta che chiamare questa nuova funzione, e disegnare almeno la curva della y dall’inspector:

Il risultato √® osservabile in quest’immagine:

spawner 9

Ora, l’effetto √® un po’ confusionario, il problema √® la dimensione delle nuvole… se provaste a spawnare altri oggetti, tipo piccoli pallini, a una velocit√† elevata (ne dovete mettere tanti) vi rendeste conto che la forma della curva rispecchia esattamente quanto disegnato da voi!

Ottimo!

Ultima cosa, visto che adesso utilizziamo ben 3 funzioni di spawning diverse, sarebbe bello poterle selezionare dall’inspector, senza dover modificare il codice ogni volta, per fare ci√≤ apportiamo le seguenti modifiche al codice dello spawner:

Possiamo ora switchare, tra le funzioni che abbiamo scritto, da inspector, tramite il menu a tendina!

spawner 10

Tutto qua!

Abbiamo il nostro spawner funzionante!

Come vi accennavo all’inizio del tutorial, con qualche semplice accorgimento, dovreste essere in grado di adattarlo al caso tridimensionale con poco sforzo.

Trovate il codice completo del progetto, corredato di scena di test tra i miei repositories di github!

Per domande, dubbi, correzioni o suggerimenti, non esitate a commentare quì sotto! Alla prossima!

, , , , ,

No comments yet.

Lascia un commento

Powered by WordPress. Designed by WooThemes