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

Sviluppare l'interfaccia di gioco

Imparare ad utilizzare OpenGL ES su Java per sviluppare l'interfaccia di un gioco per Android: ecco come fare.
Imparare ad utilizzare OpenGL ES su Java per sviluppare l'interfaccia di un gioco per Android: ecco come fare.
Link copiato negli appunti

In questa lezione vedremo come implementare le transizioni da una schermata ad un'altra. Prima di proseguire, è bene scaricare il codice allegato, che useremo come riferimento da qui fino alla fine della guida, in quanto contiene il progetto completo.

Oggetti di gioco

Nel nostro framework, vedremo presto che praticamente ogni elemento interattivo è definito come un oggetto di gioco. Un oggetto di gioco è un qualsiasi componente, statico o dinamico, che abbia una funzione all'interno del gioco stesso. Un pulsante che risponde ad un evento, l'astronave del giocatore, un meteorite e il fuoco della nostra astronave rientrano tutti in questa categoria. Se apriamo il package com.game.framework.objects noteremo al suo interno le classi GameObject e DynamicGameObject. La prima modella un oggetto di gioco statico, come ad esempio un pulsante o uno sfondo; la seconda, invece, rappresenta un oggetto di gioco dinamico (il giocatore, un alieno, un meteorite e così via).

Le schermate di cui abbiamo parlato finora sono quindi costruite da insiemi di GameObject o DynamicGameObject.

Un oggetto di gioco, oltre ad incapsulare una logica (quale può essere la sua capacità di movimento), è dotato di uno "scheletro", rivestito da un'immagine chiamata texture. Tale scheletro, a sua volta, è associato ad una mesh di triangoli, e per questo motivo, ogni oggetto GameObject o DynamicGameObject include un riferimento alla classe GameObjectMesh, che rappresenta proprio la mesh cui è applicata una texture. Approfondiremo meglio le texture e le mesh nelle prossime lezioni.

I costruttori della classe GameObject (il cui codice completo è disponibile nei sorgenti) sono definiti come segue:

public GameObject(float x, float y, int width, int height,
	GameImage gameImage, CollisionMask collisionMask) {...}
public GameObject(float x, float y, int width, int height,
	GameAnimation gameImageAnimation, CollisionMask collisionMask) {...}

La classe GameObject crea una mesh di 2 triangoli, in modo che ogni oggetto nel nostro gioco 2D sarà, di fatto, un rettangolo. I parametri x ed y specificano le coordinate, all'interno della scena, del centro del rettangolo, mentre width ed height denotano larghezza ed altezza in pixel. Dobbiamo quindi passare un'immagine di texture che "ricoprirà" la mesh, rappresentata dalla classe GameImage nel caso di immagini statiche, o da GameAnimation nel caso di sequenze di immagini per realizzare oggetti animati. Infine dobbiamo specificare, attraverso l'enumerazione CollisionMask, che tipo di maschera di collisione vogliamo applicare all'oggetto. Una maschera di collisione è necessaria gestire le collisioni tra oggetti, e la definizione di CollisionMask specifica i valori possibili:

public enum CollisionMask {
				RECTANGLE, CIRCLE, NONE
			}

Una maschera di collisione di tipo RECTANGLE ricopre l'intera
mesh, una di tipo CIRCLE crea invece un cerchio di diametro pari
all'ampiezza del rettangolo della mesh e concentrico con centro il centro del
rettangolo. Con NONE invece decidiamo di non dotare l'oggetto di una
maschera di collisione.

Pulsanti interattivi

Con la classe GameObject siamo in grado di
creare i pulsanti nelle schermate e farli rispondere agli eventi
attraverso il metodo booleano isTouched.

Il seguente codice mostra come la classe PhMenuScreen, che estende
GameScreen, utilizza GameObject per creare i vari pulsanti.

private GameObject background;
			private GameObject play;
			private GameObject options;
			private GameObject scores;
			private GameObject exit;
			public PhGameMenuScreen(Game game, Context context, GL10 gl10) {
				super(game, context, gl10);
				play = new GameObject(240, 500, 255, 95, GameAtlas.getGameImage(1, 65,
						255, 95), GameObject.CollisionMask.NONE);
				scores = new GameObject(240, 400, 255, 85, GameAtlas.getGameImage(1,
						161, 255, 95), GameObject.CollisionMask.NONE);
				options = new GameObject(240, 290, 255, 115, GameAtlas.getGameImage(
						257, 65, 255, 95), GameObject.CollisionMask.NONE);
				exit = new GameObject(240, 170, 255, 85, GameAtlas.getGameImage(257,
						161, 255, 95), GameObject.CollisionMask.NONE);
				background = new GameObject(240, 400, 480, 800,
						GameBackgroundAtlas.getBackgroundImage(0, 0, 480, 800),
						GameObject.CollisionMask.NONE);
			}

Prima di approfondire la gestione dei touch, è bene soffermarsi sui
parametri passati ai costruttori. Come si vede, le immagini sono ottenute
tramite la classe GameAtlas (che approfondiremo successivamente).
Le coordinate si riferiscono alla nostra risoluzione target 480X800,
con origine in basso a sinistra della schermata del dispositivo.
L'oggetto background rappresenta lo sfondo, ed è infatti
il primo che viene disegnato. Esso utilizza il metodo
GameBackgroundAtlas.getBackgroundImage() per ottenere l'immagine,
ed ha la caratteristica (come tutti gli sfondi) di non necessitare
di alcuna maschera di collisione (GameObject.CollisionMask.NONE).

A questo punto, è il metodo update() che utilizza
isTouched per gestire la navigazione verso altre schermate:

public void update(float deltaTime) {
				if (play.isTouched(touchX, touchY)) {
					PhGamePlayScreen playScreen = new PhGamePlayScreen(game, context, gl10);
					game.setCurrentScreen(playScreen);
				} else if (options.isTouched(touchX, touchY)) {
					PhGameOptionsScreen optionsScreen = new PhGameOptionsScreen(game,
							context, gl10);
					game.setCurrentScreen(optionsScreen);
				} else if (scores.isTouched(touchX, touchY)) {
					PhGameScoresScreen scoresScreen = new PhGameScoresScreen(game,
							context, gl10);
					game.setCurrentScreen(scoresScreen);
				} else if (exit.isTouched(touchX, touchY)) {
					game.setState(Game.State.Finished);
					System.exit(0);
				}
			}

Le coordinate touchX e touchY rappresentano il
punto in cui si è verificato un evento di touch sullo schermo. Sono ottenute grazie alla classe
GameView. Per capire come, analizziamone il metodo
onDrawFrame; tra le prime righe di codice troviamo:

GameScreen gameScreen = game.getCurrentScreen();
				if(game.isTransition()) {
					touchX = 0;
					touchY = 0;
				}
				gameScreen.setTouchX(touchX);
				gameScreen.setTouchY(touchY);

GameView, grazie all'oggetto game di classe Game
(approfondita più avanti), ottiene lo schermo attivo e successivamente
ne controlla lo stato per capire se è in corso un evento di transizione.
Se sì, le coordinate vengono resettate prima di essere passate allo schermo.

All'interno di GameView risiede un oggetto OnTouchListener,
che implementa il metodo di gestione onTouch come segue:

public boolean onTouch(View view, MotionEvent event) {
				int action = event.getAction() & MotionEvent.ACTION_MASK;
				switch (action) {
				case MotionEvent.ACTION_DOWN:
					touchToWorld.worldXTransform(event);
					touchToWorld.worldYTransform(event);
					return true;
				case MotionEvent.ACTION_POINTER_DOWN:
					touchToWorld.worldXTransform(event);
					touchToWorld.worldYTransform(event);
					return true;
				case MotionEvent.ACTION_UP:
					touchToWorld.worldXTransform(event);
					touchToWorld.worldYTransform(event);
					actionUp=true;
					return true;
				case MotionEvent.ACTION_POINTER_UP:
					touchToWorld.worldXTransform(event);
					touchToWorld.worldYTransform(event);
					actionUp=true;
					return true;
				}
				return false;
			}

L'oggetto touchToWorld appartiene ad una classe privata
definita all'interno di GameView:

private  class TouchToWorld {
				public  void worldXTransform(MotionEvent event) {
					touchX = (event.getX(event.getActionIndex()) / (float) getWidth()) * 480;
				}
				public  void worldYTransform(MotionEvent event) {
					touchY = (1 - event.getY(event.getActionIndex()) / (float) getHeight()) * 800;
				}
			}

Questa classe accetta come input l'evento di touch, e per prima cosa ne ricava
le coordinate rispetto al sistema di riferimento del piano della
camera che osserva la nostra scena, noto come near
clipping plane
. Tale sistema di riferimento usa coordinate
x ed y normalizzate, che variano tra 1 e -1;
le coordinate x ed y del nostro mondo, invece,
variano rispettivamente tra 0 e 480, e tra 0 e 800.
Ciò di cui si occupa la classe TouchToWorld è la conversione
dal near clipping plane alle coordinate del mondo in cui i nostri oggetti sono posizionati.
Vedremo questo aspetto più in dettaglio quando parleremo della pipeline grafica di OpenGL ES.

Ad ogni esecuzione del metodo onTouch(), GameView
acquisice le coordinate dell'evento di touch e le passa alla schermata corrente affinchè esso possa gestirle.
Questo accade, quindi, anche per la schermata del menu. Infine,
attraverso il metodo present(), la classe PhGameMenuScreen
disegna lo schermo completo del menu funzionante.

La classe Game

La classe astratta Game (che abbiamo visto utilizzata
in GameView) rappresenta un generico gioco con al
suo interno i vari stati possibili, definiti attraverso l'enumerazione State:

public enum State {
				Initialized,
				Running,
				Paused,
				Finished,
				Idle
			}

Nel metodo onPause() della classe Game possiamo notare la
sincronizzazione che consente la corretta terminazione del gioco:

public void onPause(boolean isFinishing) {
				synchronized(monitor) {
					if(isFinishing) {
						state = State.Finished;
					} else {
						state = State.Paused;
					}
					while(true) {
						try {
							monitor.wait();
							break;
						} catch (InterruptedException e) {
							// ...
						}
					}
				}
				wakeLock.release();
			}

Nella classe Game troviamo anche altre informazioni,
come lo startTime e la gestione del cambio di schermata:

public void setCurrentScreen(GameScreen currentScreen) {
				if(currentScreen == null) {
					throw new IllegalArgumentException("Screen is null!");
				}
				if(this.currentScreen != null) {
					this.currentScreen.pause();
					this.currentScreen.dispose();
				}
				currentScreen.resume();
				currentScreen.update(0);
				transition=true;
				this.currentScreen = currentScreen;
			}

Ti consigliamo anche