Nessun risultato. Prova con un altro termine.
Guide
Notizie
Software
Tutorial

Architettura e schermate del gioco

Imparare a definire l'architettura di un gioco 2D per Android, le schermate principali di cui esso è composto, e come tutto ciò può essere gestito con Java.
Imparare a definire l'architettura di un gioco 2D per Android, le schermate principali di cui esso è composto, e come tutto ciò può essere gestito con Java.
Link copiato negli appunti

Il gioco che realizzeremo durante questa guida sarà molto semplice.
Implementeremo un infinity shooter, in cui il giocatore
dovrà resistere il più possibile con al massimo 3 vite, accumulando
il maggior numero di punti. Il gioco avrà diversi livelli di difficoltà,
impostabili tramite le opzioni. Chiameremo il gioco
(ed il progetto Android ad esso associato) Phantom.

Come abbiamo visto, esistono diversi modi per
pianificare la progettazione di un videogame. Nel nostro caso
immaginiamo di trovarci nella situazione di non avere un budget per
l'acquisto di grafica, avendo a disposizione le immagini seguenti:

Figura 1. Background di gioco
Background di gioco
Figura 2. Atlas (click per ingrandire)

Atlas

Prima di cominciare, allegati a questa lezione troverete i
sorgenti di questa prima parte. Li utilizzeremo come base
per comprendere le fasi iniziali dello sviluppo. Lanciamo quindi Eclipse ed
importiamo nel nostro workspace il progetto appena scaricato (dopo avere decompresso l'allegato).
Per farlo, dal menu File creiamo un nuovo progetto scegliendo
la voce Android Project form Existing Code.
Selezioniamo quindi il progetto dalla directory dove lo abbiamo precedentemente salvato,
selezionando inoltre l'opzione Copy projects into workspace,
e completando l'operazione cliccando su Finish.

Figura 3. Importazione del progetto (click per ingrandire)

Importazione del progetto

L'importazione potrebbe produrre un'errore: l'ambiente cercherà
di definire una libreria che punta ad un file (mancante) di nome annotations.jar.
Possiamo risolvere il problema semplicemente rimuovendo la libreria dal class path,
poichè non usata nel progetto. Per rimuoverla, facciamo click con il
tasto destro sul nome del progetto, scegliamo quindi Properties dal
menu a tendina, selezioniamo Java Build Path e rimuoviamo il file
sotto la voce Android Dependencies.

A questo possiamo verificare la corretta importazione del progetto
lanciandolo su un dispositivo collegato alla porta USB con la modalità debug attiva,
oppure sull'emulatore Android. Se tutto è andato bene, sarà
visualizzato il menu del gioco:

Figura 4. Menu di gioco
Menu di gioco

In questo modo abbiamo preparato il codice necessario a
testare le transizioni tra le varie schermate, in modo da vedere subito in
azione questa parte dell'engine.

Iniziamo con l'osservare che per un gioco abbiamo bisogno di almeno 2 thread:

  • UI thread, il thread principale creato al lancio
    dell'applicazione attraverso il quale possiamo gestire gli eventi
    legati all'interfaccia grafica;
  • Rendering thread, responsabile del disegno di poligoni e
    aggiornamento animazioni.

Il rendering thread è importante per non bloccare l'acquisizione
dell'input mentre stiamo disegnando un frame. Fortunatamente,
OpenGL ES si occupa di creare il rendering thread in maniera
automatica e trasparente, attraverso la classe Android GLSurfaceView. Tutto
quello che dobbiamo fare è implementare l'interfaccia listener
GLSurfaceView.Renderer, e registrarla con la GLSurfaceView.

interface Renderer {
				public void onSurfaceCreated(GL10 gl, EGLConfig config) {
				}
				public void onSurfaceChanged(GL10 gl, int width, int height) {
				}
				public void onDrawFrame(GL10 gl) {
				}
			}

Il metodo onSurfaceCreated() è invocato ogni volta che
viene creata una superficie GLSurfaceView. Ciò succede
la prima volta che l'Activity della nostra applicazione viene creata,
ed ogni volta che si ritorna in esecuzione da uno stato di pausa.
Il parametro GL10 permette di inviare comandi ad OpenGL ES
mentre EGLConfig permette di accedere ad
attributi della superficie, quali colore e profondità.
Il metodo onSurfaceChanged() viene invece invocato ogni volta che la superficie è
ridimensionata, recuperando le nuove dimensioni (larghezza ed altezza)
in pixel. Infine, il metodo onDrawFrame() verrà
invocato il più spesso possibile, in quanto è responsabile del rendering:
in questo metodo verrà gestito il disegno del frame di gioco.

Oltre a registrare il nostro listener con la GLSurfaceView,
dobbiamo anche occuparci di invocare i metodi GLSurfaceView.onPause() e
GLSurfaceView.onResume() all'interno dei metodi onPause() e onResume()
della nostra Activity. Il motivo è che la classe GLSurfaceView avvia il thread
di rendering nel suo metodo onResume(), e lo ferma in onPause().
Noi vogliamo che il listener di rendering non venga eseguito quando la
nostra Activity è in pausa (dal momento che ciò comporterebbe
un inutile spreco di risorse). Per questo lo "fermiamo" con
GLSurfaceView.onPause() quando l'Activity viene messa
in pausa, e lo ripristiniamo con GLSurfaceView.onResume() quando viene ripristinata l'Activity.
È importante sottolineare che quanto detto comporta la distruzione dell'oggetto
GLSurfaceView ogni volta che l'Activity va in pausa, e la creazione
di una nuova superficie quando l'Activity viene ripristinata.
Questo causa la perdita di numerose variabili di stato (problema noto
come Context Loss), con conseguenze significative come la necessità di ricaricare le texture
ed altri problemi che vedremo più avanti come gestire.

Nel nostro engine decidiamo di realizzare una classe GameView,
che estende GLSurfaceView e che sarà responsabile di rilevare
gli eventi di touch
sullo schermo, per gestire il rendering della schermata del gioco.
La sincronizzazione tra il thread della GLSurfaceView e quello
della nostra Activity principale, viene invece realizzata
dalla classe Game, che manterrà anche l'informazione
sullo schermo attivo e ci consentirà di terminare correttamente il gioco.
La classe GameView contiene al suo interno il listener
che si occupa di intercettare gli eventi touch, e l'oggetto
Renderer visto precedentemente. Essa collabora inoltre
con la classe Game per sapere quale schermo visualizzare. GameView
è una classe generica che si occupa di creare una vista di gioco e che
deve essere informata dello schermo su cui intendiamo disegnare. Uno
schermo, a sua volta, è definito attraverso la classe GameScreen. Essa
modella una generica schermata: le nostre saranno il menu, le opzioni, il punteggio ed il
gioco. Attraverso un semplice diagramma UML mostriamo di seguito l'architettura
di base del framework
per quanto riguarda il ciclo di vita del gioco.

Figura 5. Architettura del game engine
Architettura del game engine

Per le varie classi sono mostrati solo i campi significativi alla
comprensione del funzionamento. Essenzialmente, quando il gioco è
avviato, la MainActivity viene eseguita e con essa vengono creati gli
oggetti Game e GameView. Quest'ultimo, a sua volta, fa partire il thread
nascosto per la gestione del rendering. Come si può notare, GameView
è una classe astratta: dobbiamo quindi fornirne un'estensione
implementando il metodo getStartScreen(), che restituisce la prima
schermata da visualizzare (nel nostro caso, il menu). Anche la classe
GameScreen è astratta: essa, infatti, definisce un generica schermata.
Nel nostro gioco, per la creazione delle schermate di
menu, gioco, opzioni e punteggio realizzeremo classi che estendono
GameScreen, collocando tutta la logica di controllo all'interno del
metodo update(), e quel che riguarda il rendering nel metodo
present(). Questi ultimi due metodi vengono invocati ad ogni
ciclo di frame. Tutte le classi parte dell'engine risiedono nei
package com.game.framework, mentre quelle specifiche del gioco
appartengono a com.game.phantom.

Ti consigliamo anche