Il primo gioco che creiamo è molto semplice, pur contenendo gli ingredienti necessari per iniziare a comprendere i meccanismi di base dell'ambiente di sviluppo. Più avanti nella guida lavoreremo ad un gioco dalle caratteristiche avanzate.
Cominciamo col dire che in Unity possiamo creare le diverse parti dell'interfaccia utente, oltre che gli scenari o i quadri del gioco, sfruttando le "Scene"). Per iniziare quindi diciamo che un gioco è composto da diversi scenari (scene) che possiamo utilizzare per suddividerlo in diverse parti.
In particolare utilizziamo tre scene: una per il menu principale, una per il gioco vero e proprio e una per la schermata di game over.
Game Design
Iniziamo definendo le meccaniche di gioco. Poniamo che il gioco si svolga nello spazio, il compito del giocatore sarà quello di pilotare una navicella spaziale per difendere il proprio mondo dagli invasori. Un cannone laser sparerà il suo raggio letale verso la posizione indicata dal tocco dell'utente sullo schermo.
Quando il laser colpirà il nemico, esso morirà e il giocatore guadagnerà dei punti. Se invece il nemico colpisce la navicella spaziale, la partita sarà finita con il solito "game over". L'obiettivo del gioco è sopravvivere il più a lungo possibile.
Implementare il gioco, creare la prima Scene
Anzitutto preoccupiamoci di avere un ambiente "spaziale" per il nostro gioco, quindi il classico sfondo nero con le stelle.
Avremo bisogno di un po' di texture, quindi aggiungimo al nostro progetto una cartella per contenerle. Clicchiamo col tasto destro sulla cartella Assets nel tab Project e clicchiamo su Create > Folder
, poi chiamiamo la nuova cartella "Textures".
Ora scarichiamo questa texture e salviamola nella cartella appena creata.
Non ci resta che inserire questa immagine come sfondo. Per aggiungere la nostra immagine ad un oggetto sulla scena dobbiamo creare un materiale e applicarvi la texture. Quindi creiamo un altra cartella "Materials
" sotto la cartella Assets
.
Nella cartella Materials
aggiungiamo un nuovo Material
e lo rinominiamo "spaceBackground" (click destro su Materials poi Create > Material
).
Dovremmo ottenere qualcosa del genere:
Clicchiamo sul material spaceBackground
. Appare il pannello Inspector in cui vengono mostrate alcune proprietà, tra cui la proprietà texture. non ci resta che prendere dalla cartella Textures
la nostra immagine e trascinarla sulla relativa proprietà del materiale.
Nella preview c'è una sfera che mostra come apparirà una superficie dopo l'applicazione della texture.
Ora, il nostro sfondo può essere creato in diversi modi. In questo tutorial scegliamo di creare un piano sul quale applicare il nostro nuovo materiale. Per aggiungere un piano alla scena clicchiamo sul menu GameObject > Create Other > Plane
:
Il nuovo piano sarà aggiunto alla scena, come si vede anche nella finestra Hierarchy.
La finestra Hierarchy contiene due elementi, uno è il nostro piano l'altro è la telecamera principale (Main Camera). L'oggetto Main Camera
è molto importante perché con essa stabiliamo cosa vedranno i giocatori nel gioco.
Normalmente utilizziamo la vista Scene per posizionare oggetti e modelli e creare gli scenari, ma possiamo passare alla vista Game per controllare cosa si vede realmente da punto di vista della Main Camera.
(Scene view e Game view a confronto)
La vista Game ci mostra infatti uno scenario tutto blu al più il piano grigio come se fosse un pavimento. Quindi prima di tutto dovremo ruotare il nostro piano per utilizzarlo come sfondo e vedere solo quello.
Torniamo alla vista Scene, clicchiamo sul piano (Plane
) nella Hierarchy e modifichiamo alcune proprietà nel pannello Inspector. Assicuriamoci prima che tutte le coordinate della proprietà Position siano impostate a zero. Poi diamo alla rotazione sull'asse X il valore di -90
. Poi dovremo allargare il piano per fargli coprire tutto lo schermo, quindi impostiamo a 5 il fattore di scala per tutti gli assi.
Aggiungiamo la texture dello spazio al piano semplicemente trascinando il materiale dalla cartella Materials sul piano nella vista Hierarchy.
Poiché le dimensioni del piano sono molto più grandi di quelle della texture, questa viene stiracchiata fino a coprire tutto il piano. Clicchiamo sul materiale spaceBackground
e modifichiamo le proprietà di tiling a 8,8
(X,Y) in questo modo la texture sarà replicata 8 volte in larghezza e in altezza:
Va meglio, ma tutto risulta ancora un po' scuro. Questo perché il piano usa uno Shader che tiene conto della luce diffusa e poiché non abbiamo previsto luci nella scena risulta così poco visibile. Cambiamo quindi lo shader da "diffuse" a "background". Clicchiamo sul materiale, nell'Inspector impostiamo lo Shader a Mobile > Background
.
È il momento di salvare la scena (Ctrl-S
oppure File > Save Scene
. La prima volta che salviamo appare la maschera di salvataggio, creiamo una nuova cartella Scenes
e vi salviamo la scena con il nome "gameScene
".
Per vedere meglio la scena dovremo posizionare la camera al centro del nostro mondo ma ad una distanza di 10 unità dal nostro piano quindi riposizioniamola alle coordinate 0,0,-10
(X,Y,Z) cliccando su Main Camera e modificando le proprietà Position. Controlliamo che la proprietà Projetion
sia impostata su Perspective
e che il campo visivo (Field Of View
) sia su 60
.
Creare i nemici
Il passo successivo è creare i nemici. Creeremo navicelle nemiche che viaggeranno da una parte all'altra dello schermo ad una certa velocità.
Il procedimento è simile al precedente, per aggiungere il piano di sfondo abbiamo utilizzato un GameObject (Plane
) e faremo altrettanto per la creazione dei nemici. Un gioco realizzato con Unity è composto di numerosi GameObject e un GameObject può essere un oggetto di qualunque tipo.
Inoltre possiamo stabilire gerarchie di GameObject, iin modo tale da poter definire comportamenti simili per molte istanze diverse. Per generare i nemici creiamo un GameObject vuoto e lo utilizziamo come oggetto padre in cui inserire altri GameObject come un piano per mostrare l'immagine del nemico e un oggetto "particle" che utilizzeremo per simulare la scia del motore.
Poiché lavoriamo ad un gioco 2d utilizziamo le texture per definire completamente i nemici. È possibile scaricare un pacchetto di asset già pronti tra i quali trovare il file SpaceInvader.png. Per utilizzarlo lo inseriamo nella nostra cartella Textures
.
Clicchiamo quindi su GameObject > Create Empty
, clicchiamo sul nuovo oggetto e nella finestra Inspector lo rinominiamo "SpaceInvader
". Poi impostiamo la posizione di SpaceInvader a 0, 0, -2
Allo stesso modo possiamo rinomianre il Plane
che abbiamo utilizzato come background come "SpaceBackground".
Ora creiamo il materiale che ci servirà per collegare l'immagine dei nemici ai relativi GameObject, quindi vi applichiamo la texture dei nemici, lo chiamiamo spaceInvader
e lo mettiamo nella cartella Materials
. Prendiamo ora il png del nemico dalla cartella delle texture e trasciniamolo sulla proprietà texture del nostro materiale. Poi facciamo una cosa che sarà molto importante: modifichiamo lo shader in Mobile > Transparent > Vertex Color
Questo shader farà sì che il materiale renda trasparenti le parti dell'oggetto coperte dalle trasparenze delle texture basate su file png (il canale alpha). Se non applichiamo lo shader le parti trasparenti della texture appariranno nere.
Ora creiamo un Quad, questo oggetto è un piano semplificato, costruito con soli due triangoli, fatto apposta per mostrare immagini o video. A questo oggetto associamo il materiale spaceInvader (GameObject > Create Other > Quad).
Una volta presente sulla scena il nuovo Quad vuoto, all'interno della finestra Hierarchy, lo trasciniamo sul GameObject SpaceInvader
creato prima.
Fatto questo appare una freccetta nel GameObject SpaceInvader che indica che esso contiene qualcosa (il Quad), come possiamo vedere cliccando sulla freccetta.
Rinominiamo il Quad in Spaceship, e finalmente vi trasciniamo il materiale spaceInvader.
Qui entra in gioco l'ereditarietà: poiché il quad Spaceship
è dentro SpaceInvader
, le sue coordinate (X,Y,Z) saranno relative al GameObject padre (SpaceInvader
). Per posizionare il quad in mezzo all'oggetto SpaceInvader, gli la posizione 0,0,0
.
Se effettuiamo un doppio click su un GameObject (in Hierachy) la vista Scene zoomerà fino ad ingrandire al massimo l'oggetto. Possiamo utilizzare questa tecnica per concentrarci meglio su SpaceInvader.
Nella vista Game il nostro nemico dovrebbe essere piazzato al centro, poiché anche la telecamera è puntata sul centro (dove è posizionato l'oggetto).
Importante ricordare che possiamo vedere le trasparenze del png applicato al nemico, poiché abbiamo applicato il transparency shader
al suo materiale.
Dare vita ai nemici
Ora abbiamo i nemici, ma dobbiamo farli muovere e qui inizia ad entrare in gioco il codice: creiamo una nuova cartella "Scripts" nella cartella degli Assets
. Clicchiamo sulla nuova cartella con il tasto destro e scegliamo Create > C# Script
Chiamiamo lo script EnemyController
e avremo un nuovo script C# vuoto nel nostro progetto. La prima cosa da fare è collegare questo script al GameObject SpaceInvader
e lo facciamo trascinando lo script sull'oggetto SpaceInvader
nella vista Hierarchy.
Se clicchiamo su SpaceInvader
, nelle proprietà in Inspector, troviamo lo script come nuovo componente dell'oggetto. Un GameObject può essere controllato e definito aggiungendo diversi componenti e un componente script serve a definirne il comportamento (behaviour).
Gestire gli script con Visual Studio 2013
Come abbiamo detto in precedenza, effettuando un doppio click su EnemyController
, potremo modificare lo script direttamente in Visual Studio 2013, assicurandoci di aver in precedenza customizzato gli External tools dalle preferenze (Edit > Preferences
), altrimenti si aprirà MonoDevelop, l'IDE di default.
La composizione di un Behaviour
Se effettuiamo doppio click sullo script, viene aperta una soluzione su Visual Studio e il codice della nostra classe che estende MonoBehaviour, la classe base per i comportamenti di Unity, che espone due importanti funzioni:
- Start(), che è invocata quando il GameObject viene caricato o aggiunto alla scena (quando la scena inizia)
- Update(), che viene chiamata ad ogni frame.
using UnityEngine;
using System.Collections;
public class EnemyController : MonoBehaviour
{
void Start () {
// Inserire qui le inizializzazioni
}
// Update viene chiamata ogni frame
void Update () {
}
}
Aggiungiamo una variabile che conterrà la velocità del nemico.
using UnityEngine;
using System.Collections;
public class EnemyController : MonoBehaviour
{
public float speed;
void Start ()
{
}
void Update ()
{
}
}
Vogliamo che lo SpaceInvader si sposti verso il lato sinistro dello schermo. Questo significa cambiare il valore della coordinata X-position
con piccole modifiche da applicare ad ogni frame utilizzando Update()
, tenendo presente che il singolo movimento dovrà esser ripetuto diverse volte al secondo.
Abbiamo aggiunto la variabile speed
come float per meglio controllare la velocità di ciascun nemico. Poiché è una variabile public
, può essere modificata direttamente all'interno dell'ambiente di sviluppo di Unity. Salviamo lo script e clicchiamo ull'oggetto SpaceInvader per vedere le proprietà e verificare che sia stata aggiunta la proprietà Speed al componente script. Non ci resta che impostarla su 5
.
Movimenti dei nemici e temporizzazioni
Come si intuisce, non possiamo banalizzare la questione della velocità e cavarcela con un incremento (o decremento) fisso della posizione ad ogni frame. Possiamo ottenere risultati diversi al cambiare del client e poco prevedibili.
Per ottenere lo stesso movimento su tutti i device possiamo utilizzare un timer fornito direttamente dal framework di Unity. Esso ci aiuta a tracciare i tempi di diversi aspetti del gioco, incluso il tempo necessario al rendering di un frame (in secondi).
Torniamo alla funzione Update e aggiungiamo una riga:
void Update ()
{
this.transform.position -= new Vector3(speed, 0, 0) * Time.deltaTime;
}
Stiamo modificando così la proprietà trasform
del GameObject tramite codice. Una vota collegato questo comportamento al GameObject SpaceInvader
, esso si applicherà sia a SpaceInvader
sia a tutto ciò che c'è al suo interno.
La modifica della posizione avviene con un semplice passaggio algebrico: ad ogni frame viene sottratto un vettore direzione che punta in direzione. Il modulo del vettore è basato sulla velocità che abbiamo stabilito, adattata ad ogni client grazie alla moltiplicazione per il tempo necessario al rendering di un frame.
Se il gioco gira su una macchina più lenta il tempo di rendering sarà maggiore e lo spostamento per unità di tempo aumenterà per compensare, in caso contrario esso diminuirà. Per vedere se funziona non resta che premere il tasto Play.
Aggiungere particelle per simulare le scie delle navicelle
Per dare un aspetto più realistico alle navicelle spaziali possiamo mostrare la scia generata dal loro motore. Per simulare la scia possiamo utilizzare un "particle engine". Unity fornisce un ottimo tool per creare effetti di particelle utili per creare esplosioni, fuoco, fumo, etc.
Non descriveremo qui i dettagli del particle editor. Utilizzeremo uno dei pacchetti standard di asset messi a disposizione da Unity, che contiene diversi motori di particelle da testare e utilizzare.
Possiamo trovare diversi asset nell'Asset Store (Window > Asset Store
), sia gratuiti sia a pagamento. Per il momento però ci limitiamo ad utilizzare gli Asset standard forniti da Unity (anche questi disponibili sullo store).
Da menu clicchiamo su Asset > Import Package > Particles
e nella finestra che appare scegliamo di importare tutto per semplicità.
Alla cartella Assets sarà aggiunta la cartella Standard Assets in cui troviamo la sottocartella Particles. In Particles troviamo Smoke e da lì possiamo trascinare il prefab Smoke Trail
sul GameObject SpaceShip
.
Un nuovo particle engine viene aggiunto alla gerarchia di SpaceInvader
Se ora premiamo play possiamo vedere il nemico con la sua scia:
Prefab
Un prefab è un prototipo di GameObject che può essere utilizzato diverse volte in una scena. Possiamo pensarlo come lo "stampino" che ci permette di avere molti oggetti con caratteristiche simili, ma tutti con una propria identità. È il classico concetto di classe e istanza di un oggetto.
Vogliamo trasformare il nemico che abbiamo creato in un prefab, così da avere un modello unico per sfornare una serie di nemici tutti uguali.
Non vogliamo poi modificare tutti gli oggetti sulla scena, ma sarà sufficiente applicare alcune modifiche al prefab (quindi allo "stampo"), per modificare tutti gli oggetti che esso fabbricherà.
Creiamo una nuova cartella nel nostro progetto sotto Assets
e la chiamiamo Prefabs
Ricapitoliamo quanto fatto per creare il GameObject SpaceInvader
. Abbiamo creato un prototipo per capire come avrebbe dovuto essere il nostro nemico mettendo insieme diversi GameObject, aggiungendo script, particles e materiali. Ora vogliamo sfruttare questo prototipo e sruttarlo per la produzione in serie.
Per trasformare il nostro prototipo in un modello replicabile, ovvero in un prefab, basterà trascinare il GameObject nella cartella Prefabs
(il nome "Prefabs" non è obbligatorio, possiamo anche chiamare questa cartella diversamente). Quindi effettuiamo un drag and drop dal pannello hierarchy alla cartella Prefabs.
SpaceInvader ora è un prefab e appare nella relativa cartella, mentre l'oggetto SpaceInvader nella finestra Hierarchy diventa azzurro, a rappresentare che esso è basato su un prefab.
Ora possiamo eliminare l'oggetto SpaceInvadere dalla scena cancellandolo dalla finestra Hierarchy (se vogliamo rifarlo apparire possiamo trascinarne uno nuovo dalla cartella Prefab).
Nota: Se in un certo momento (non lo facciamo ora) aggiungiamo degli elementi ad un oggetto che abbiamo creato a partire da un prefab (es. componenti, GameObject, script, particle etc.), queste nuove parti saranno nere poiché esse non solo parte del prefab. Per aggiornare il prefab (e di conseguenza tutti gli oggetti in scena generati a partire dal prefab) possiamo cliccare sull'oggetto SpaceInvader presente in scena e cliccare su Apply
nel pannello Inspector. Tutti i cambiementi saranno acquisiti dal modello che utilizziamo per generare tutti gli oggetti nella scena (il prefab).
Ora cancelliamo il GameObject SpaceInvader definitivamente. Genereremo i nemici da codice.
Creare un laser!
Poi vogliamo creare un cannone laser che spari quando l'utente tocca o clicca sullo schermo. Creeremo un prefab per il laser. Quindi creiamo un nuovo GameObject vuoto, lo chiamiamo "Laser" e ci assicuriamo che la sua posizione sia (0, 0, -2)
.
Aggiungiamo il file laser.jpg alla cartella Textures (lo troviamo anche nello zip con tutti gli asset) e al solito, creiamo un nuovo materiale ("laser
") e vi associamo la texure.
Ora modifichiamo lo Shader in Mobile > Particles/Additive
che lo renderà leggermente trasparente e permetterà di mostrare le sovrapposizioni tra più laser.
Non ci resta che aggiungere un nuovo Quad (GameObjects > Create Other > Quad
) che chiamiamo LaserBody
e trascinarlo sul GameObject Laser perché diventi parte di esso. Poi dobbiamo trascinare il materiale laser
su LaserBody e modificare le proprietà di LaserBody in questo modo: Position = (0,0,0)
, Rotation = (0,0,0)
, Scale = (0.5, 0.05, 1)
.
In questo modo il quad LaserBody
si troverà nella posizione (0,0,0)
relativamente al suo GameObject padre (Laser
) e sarà ridimensionato in modo tale da trasformare la sua forma quadrata in qualcosa di più simile ad un raggio laser.
Per creare il comportamento del laser, creiamo un nuovo C# Script nella cartella degli scripts, lo chiamiamo LaserController
, lo trasciniamo sul GameObject Laser e facciamo doppio click per modificarlo.
Anche il laser, come i nemici, dovrà muoversi da una parte all'altra dello schermo ad una certa velocità, che stavolta fissiamo direttamente nel codice, uguale per tutti i laser:
using UnityEngine;
using System.Collections;
public class LaserController : MonoBehaviour
{
void Start () {
}
void Update () {
this.transform.position += new Vector3(10, 0, 0) * Time.deltaTime;
}
}
Niente di nuovo quindi, il laser si sposta in base ad un vettore direzione che moltiplichiamo per il tempo di refresh dei frame. Se premiamo play vediamo il laser muoversi.
Ora creiamo un prefab del laser trascinando il relativo GameObject Laser dalla finestra Hierarchy alla cartella Prefab. Poi, come per i nemici, cancelliamo l'oggetto che è già sulla scena: creeremo poi i laser via codice.
Molti nemici... molte istanze!
Dobbiamo far generare i nemici. Vogliamo che ogni nemico appaia in una posizione casuale nello schermo e che avanzi con velocità dal valore casuale in un intervallo tra 4 e 8.
Creiamo un nuovo script GameController
nella cartella Scripts e lo modifichiamo nell'editor con un doppio click. Prima di tutto dobbiamo creare due variabili che conterranno i prefabs per i nostri laser e nemici.
using UnityEngine;
using System.Collections;
public class GameController : MonoBehaviour
{
public GameObject enemy;
public GameObject laser;
void Start () { }
void Update () { }
}
Poi abbiamo bisogno di una variabile che faccia da timer, per contare il tempo che passa e stabilire un ritmo di generazione dei nemici.
using UnityEngine;
using System.Collections;
public class GameController : MonoBehaviour
{
public GameObject enemy;
public GameObject laser;
float spawnTimer;
void Start () { }
void Update () { }
}
Quando la scena inizia, vogliamo che il nostro tempo di generazione (spawnTimer
) sia impostato a 1.0f
. Ogni volta che viene richiamata la funzione Update()
riduciamo questo valore in base alla variabile deltaTime
.
In questo modo, se spawnTimer
vale 1.0f
all'inizio, impiegherà un secondo a diventare 0.0f
.
using UnityEngine;
using System.Collections;
public class GameController : MonoBehaviour
{
public GameObject enemy;
public GameObject laser;
float spawnTimer;
void Start () {
spawnTimer = 1.0f;
}
void Update () {
spawnTimer -= Time.deltaTime;
}
}
Ora sfruttiamo questo semplice meccanismo per sfornare un nemico ogni secondo. Lo facciamo sfruttando la funzione Instantiate di Unity. Vediamo il codice:
using UnityEngine;
using System.Collections;
public class GameController : MonoBehaviour
{
public GameObject enemy;
public GameObject laser;
float spawnTimer;
void Start () {
spawnTimer = 1.0f;
}
void Update () {
spawnTimer -= Time.deltaTime;
// verifichiamo che sia passato un secondo
if (spawnTimer <= 0.0f)
{
// se è passato un secondo generiamo un nuovo nemico
GameObject instance = (GameObject) Instantiate(enemy,
new Vector3(10, Random.Range(-4.0f,4.0f), -2.0f),
transform.rotation);
// ...e resettiamo il timer
spawnTimer = 1.0f;
}
}
}
Quando spawnTimer
viene azzerato (o diventa negativo) creiamo un nuovo nemico. Vediamo meglio come.
Il primo parametro passato a Instantiate è enemy
. Questa variabile serve a stabilire che tipo di GameObject sarà generato (indica l'originale da clonare). Il passaggio interessante è questo: ora enemy
è vuota, così possiamo decidere noi di collegarla al prefab dei nemici, direttamente in Unity.
Per il resto stabiliamo posizione. L'oggetto sarà 10 unità davanti a noi (molto a destra, fuori dallo schermo), la sua quota sarà casuale in un range da -4
a +4
. Il timer sarà impostato a 1.0f
per generare nemici con un ritmo di uno al secondo.
Ora perché questo script abbia effetto, torniamo a Unity e colleghiamolo all'oggetto Main Camera
nella finestra Hierarchy.
Clicchiamo su Main Camera, nel pannello Inspector troviamo il componente script e al suo interno troviamo le variabili laser e enemy.
Ora il passaggio importante è quello che ci consente di collegare i prefab del laser e del nemico alle relative variabili su questo comportamento. Come sempre basterà trascinare i due prefab dalla cartella Prefabs alle relative proprietà del componente (script) GameController di Main Camera (il prefab SpaceInvader
su enemy
e Laser
su laser
).
La scelta della telecamera non è obbligatoria, in effetti essa diventa l'oggetto controller della nostra scena, ma avremmo potuto scegliere un altro oggetto come un giocatore o qualcos'altro.
Premiamo play e vediamo apparire i nemici in posizioni differenti e poi muoversi sullo schermo.
Sparare i laser
Dobbiamo colpire gli invasori con i laser! Perciò dobbiamo gestire il tocco o il click e rilevare la posizione verso la quale orientare il raggio.
Grazie a Unity possiamo gestire l'input in modo molto semplice. Clicchiamo su Edit > Project Settings > Input
per accedere alla finestra InputManager che contiene le proprietà degli input del progetto.
Clicchiamo su Axes e poi su Fire1 e modifichiamo Positive Button
da "left ctrl" a "space" (barra spaziatrice):
Come si vede la proprietà Alt Positive Button è impostata a mouse 0
. Ciò significa che possiamo utilizzare sia la barra spaziatrice sia il tasto sinistro del mouse per scatenare l'evento "Fire1" di Unity.
Torniamo ora allo script GameController
. Ci mettiamo in ascolto dell'evento Fire1
nella funzione Update()
.
public class GameController : MonoBehaviour {
// ...
void Update () {
// ...
if (Input.GetButton("Fire1"))
{
Vector3 spawnLaserPos = Camera.main.ScreenToWorldPoint(
new Vector3(-5.0f, Input.mousePosition.y, 8));
Instantiate(laser, spawnLaserPos, Quaternion.identity);
}
}
}
Ciò che facciamo qui è controllare se viene premuto Fire1
e nel caso generiamo un nuovo laser sul lato sinistro dello schermo. Per l'altezza utilizziamo una funzione che rilevi il punto in cui avviene il click o il tocco e ricavi le coordinate relative al mondo 3D (con mousePosition
), che possiamo utilizzare nel nostro gioco. Fatto questo istanziamo il laser.
Se premiamo play ora, possiamo notare che è possibile sparare numerosi laser e che sarà necessario introdurre un limite utilizzando un timer simile a quello utilizzato per i nemici.
using UnityEngine;
using System.Collections;
public class GameController : MonoBehaviour
{
public GameObject enemy;
public GameObject laser;
float spawnTimer;
float shootTimer;
void Start () {
spawnTimer = 1.0f;
}
void Update () {
spawnTimer -= Time.deltaTime;
shootTimer -= Time.deltaTime;
if (spawnTimer <= 0.0f)
{
GameObject instance = (GameObject)Instantiate(enemy,
new Vector3(10,Random.Range(-4.0f,4.0f),-2.0f),
transform.rotation);
spawnTimer = 1.0f;
}
if (shootTimer <= 0.0f)
{
if (Input.GetButton("Fire1"))
{
Vector3 spawnLaserPos = Camera.main.ScreenToWorldPoint(
new Vector3(-5.0f, Input.mousePosition.y,8));
Instantiate(laser, spawnLaserPos, Quaternion.identity);
shootTimer = 0.4f;
}
}
}
}
Ora possiamo sparare al massimo ogni 0.4 secondi.
Le collisioni, preparare gli oggetti
cambiare tutto per 3d
È il momento di affrontare il tema delle collisioni: dobbiamo sapere quando il raggio laser colpisce il nemico.
Unity prevede un sistema di tag (etichette) per i GameObjects. Questo ci permette di gestire in modo particolare tutti gli oggetti che sono stati etichettati con un certo tag. Etichettiamo quindi tutti i nostri laser con il tag "laser" e tutti i nemici con il tag "enemy"
Clicchiamo sul prefab Laser all'interno della cartella Prefabs e troviamo nell'Inspector, in alto, la proprietà tag. Clicchiamo su Add Tag...
Nel pannello Tags & Layers aggiungiamo i tag Laser e Enemy:
Ora torniamo sui prefab Laser e SpaceInvader e assegnamo loro rispettivamente i tag Laser e Enemy.
Anzitutto abbiamo bisogno di aggiugere un collider ai prefab dei laser e dei nemici. Possiamo utlizzare un rettangolo che definisca l'area di collisione dei nostri oggetti. Clicchiamo su prefab Laser e poi su Add Components
nella finestra Inspector. Effettuiamo una ricerca con la parola "box":
e selezioniamo un BoxCollider. Questo aggiungerà un rettangolo (un parallelepipedo in realtà) di collisione intorno ai laser. Se trasciniamo il prefab sulla scena adesso, lo possiamo anche vedere.
Dobbiamo modellarlo impostando i fattori di scala a (0.5, 0.05, 1)
per adattare perfettamente l'area al laser.
Clicchiamo ancora sul prefab Laser e aggiungiamo un nuovo componente chiamato Rigidbody questo componente serve per aggiungere al Prefab la capacità di applicare proprietà fisiche: cinematiche. Non utilizzeremo la gravità, quindi togliamo la spunta dalla proprietà Gravity (se utilizziamo il sistema 2d degli sprite, possiamo fare altrettanto utilizzando un Rigidbody2d e azzerando la proprietà gravityScale
):
Facciamo lo stesso per il nostro prefab SpaceInvader. In questo caso il box collider avrà già le dimensioni giuste:
Anche qui aggiungiamo il Rigidbody togliendo il segno di spunta da Gravity
.
Forzando oggetti tridimensionali a funzionare come oggetti bidimensionali dobbiamo fare attenzione quando utilizziamo caratteristiche come collider e rigidbody che hanno a che fare con la "fisica" degli elementi in gioco. Una delle attenzioni da avere in questo caso è quella di cancellare dai GameObject figli laserBody
e spaceShip
il componente Mesh Collider, che potrebbe creare errori dovuti all'assenza di massa di Plane
e Quad
e rallentamenti del gioco.
Ora finalmente, dopo aver aggiunto i tag e i componenti collider e rigidbody ai prefab, siamo pronti a gestire le collisioni tra loro.
Gestire le collisioni da script
Modifichiamo lo script EnemyController
perché esso gestisca le collisioni. Prima di tutto, se c'è una collisione tra laser e nemico, dovremo eliminare entrambi dal gioco.
In Unity abbiamo l'evento OnCollisionEnter() che viene scatenato quando avviene una collisione tra un certo oggetto e un altro. Aggiungiamo la relativa funzione al nostro script:
using UnityEngine;
using System.Collections;
public class EnemyController : MonoBehaviour {
public float speed;
void Start () { }
void OnCollisionEnter(Collision collision) { }
void Update () {
this.transform.position -= new Vector3(speed, 0, 0) * Time.deltaTime;
}
}
Ora abbiamo bisogno solo di gestire le collisioni tra questo oggetto (etichettato "Enemy") e gli oggetti etichettati con "Laser". Se si toccano li distruggiamo entrambi.
void OnCollisionEnter(Collision other) {
if (other.gameObject.tag.Equals("Laser")) {
Destroy(other.gameObject);
Destroy(this.gameObject);
}
}
Semplice vero? Ma cosa succede ai laser che mancano gli invasori? Continuano a viaggiare per sempre! Correggiamo anche questo modificando lo script LaserController con l'aggiunta di un if nella funzione Update():
public class LaserController : MonoBehaviour {
// ...
void Update () {
this.transform.position += new Vector3(10, 0, 0) * Time.deltaTime;
if (this.transform.position.x > 20.0f) {
Destroy(this.gameObject);
}
}
}
Aggiungere le esplosioni
Non c'è gusto senza esplosioni! Aggiungiamo anche questo effetto. Dichiariamo un GameObject che chiamiamo explosion
nello script EnemyController
. Questo oggetto servirà per il prefab delle esplosioni. Poi impostiamo un comportamento per le collisioni scrivendo un handler per OnCollisionEnter
:
using UnityEngine;
using System.Collections;
public class EnemyController : MonoBehaviour
{
public float speed;
public GameObject explosion; // il GameObject per le esplosioni
void Start () { }
void OnCollisionEnter(Collision other)
{
if (other.gameObject.tag.Equals("Laser"))
{
Destroy(other.gameObject);
Destroy(this.gameObject);
Instantiate(explosion, this.transform.position, this.transform.rotation);
}
}
void Update () {
this.transform.position -= new Vector3(speed, 0, 0) * Time.deltaTime;
}
}
Ora dobbiamo associare il prefab al GameObject explosion
che abbiamo creato. Come sempre possiamo farlo direttamente nell'interfaccia di Unity. Nella cartella Prefabs clicchiamo sul prefab SpaceInvader
per vedere le proprietà. Ora nella cartella Particles
, che troviamo sotto Standard Assets
, e apriamo la cartella Legacy Particles
, quindi trasciniamo il prefab explosion
sulla proprietà Explosion dello script EnemyController
del prefab SpaceInvader
:
Se ora lanciamo il gioco possiamo vedere le esplosioni.
Game Over?
Se un invasore sopravvive e riesce a raggiungere il lato sinistro dello schermo il gioco è finito, e il gioco mostrerà la schermata di Game Over.
Prima di tutto dobbiamo creare la scena Game Over. Clicchiamo su File > New Scene
per aggiungerla al progetto, poi salviamo la scena creata nella cartella "Scenes" con il nome gameOver
.
Ora, per non complicare troppo, aggiungiamo un componente GUI Text Game Component alla scena:
E ne modifichiamo le proprietà
Poi cambiamo le proprietà della Main Cameras per rendere nero lo sfondo:
Ora dobbiamo aggiungere la scena appena costruita al progetto perché possa essere utilizzata. Facciamo doppio click sulla scena gameScene all'interno della cartella Scenes
e clicchiamo File gt; Build Settings
:
Poi clicchiamo su Add Current
per aggiungere gameScene (che notiamo assumerà ID pari a 0
:
Allo stesso modo aggiungiamo la scena del GameOver:
Ora modifichiamo il comportamento dello script EnemyController
, dove lanciamo una funzione che richiama la scena Game Over se il nemico raggiunge la parte sinistra dello schemo
public class EnemyController : MonoBehaviour
{
// ...
void Update () {
this.transform.position -= new Vector3(speed, 0, 0) * Time.deltaTime;
// aggiunta la condizione di game over
if (this.transform.position.x <= -10.0f)
{
GameOver();
}
}
void GameOver()
{
Application.LoadLevel(1); // cambio di scena
}
}
Tornare al gioco dopo il Game Over
Una volta visualizzata la schermata di game over, vogliamo che il gioco possa ricominciare e che il giocatore possa provare ancora. In questo esempio possiamo mostrare la scena game over per 5 secondi e poi far ripartire di nuovo il livello di gioco.
Per fare questo, creiamo un nuovo script chiamato GameOverController
e inseriamolo sulla Main Camera
nella scena gameOver
. Poi modifichiamolo in questo modo:
using UnityEngine;
using System.Collections;
public class GameOverController : MonoBehaviour
{
float gameOverTimer;
void Start () {
gameOverTimer = 5.0f;
}
void Update () {
gameOverTimer -= Time.deltaTime;
if(gameOverTimer <= 0.0f)
Application.LoadLevel(0);
}
}
Niente di nuovo qui, cambiamo solo la scena dopo 5 secondi. Se tutto va bene lanciando il gioco dovrebbe funzionare tutto.
Il primo gioco è creato e se avete avuto la pazienza di arrivare fino a qui avrete certamente appreso la filosofia di utilizzo di base di Unity 3D. Per il menu principale lasciamo il compito (molto semplice) ormai al lettore.
Più avanti nella guida vedremo come aggiungere effetti speciali e audio, come creare una interfaccia utente e come gestire i punteggi.