Magazine

Sviluppare un Gioco per Android – Lezione 4: Disporre le Immagini

Creato il 31 gennaio 2014 da Paolo Dolci @androidblogit

Sviluppare un Gioco per Android – Lezione 4: Disporre le Immagini Android Blog Italia.

programmazione android4 150x150 Sviluppare un Gioco per Android – Lezione 4: Disporre le Immagini guide  sviluppo gioco android immagini gioco android disporre le immagini
Nella scorsa lezione abbiamo visto un ciclo di gioco molto semplice e, prima di passare a crearne uno più complesso, dobbiamo capire come disporre le immagini e visualizzare tutti gli elementi grafici del nostro gioco per Android. A dirla tutta, disporre le immagini con Android è abbastanza semplice. Per ridimensionare il problema immaginiamo di voler visualizzare un’immagine in alto a sinistra del display. Naturalmente, abbiamo innanzitutto bisogno di un’immagine, che potete creare con qualunque strumento desideriate (Photoshop o Gimp, non fa alcuna differenza), dopodiché è necessario posizionarla, vediamo come.

Prima di tutto, dobbiamo posizionare l’immagine nell’apposita cartella e, nel nostro caso, possiamo spostare l’immagine nella directory /res/drawable-mdpi. L’immagine che utilizzeremo, è la seguente:

droid 1 Sviluppare un Gioco per Android – Lezione 4: Disporre le Immagini guide  sviluppo gioco android immagini gioco android disporre le immagini

Come esempio (considerando anche come abbiamo settato il device virtuale), abbiamo scelto la cartella mdpi, che indica una densità media di pixel (qui maggiori informazioni sui DPI). A questo punto, andiamo nel nostro pannello di gioco, che nella lezione precedente abbiamo chiamato MainGamePanel e modifichiamo il metodo onDraw() che abbiamo lasciato vuoto:

protected void onDraw(Canvas canvas) {
		 canvas.drawBitmap(BitmapFactory.decodeResource(getResources(), R.drawable.droid_1), 10, 10, null);
	 }

Il metodo drawBitmap disegna l’immagine del droide nelle coordinate (10,10) grazie al riferimento droid_1. Ora, infatti, in R.java (che contiene gli identificatori di risorsa) troveremo anche il riferimento all’immagine appena inserita. Tuttavia, se avete studiato il codice scritto nella lezione precedente, sapete già che la View MainGamePanel crea il thread istanziando l’oggetto della classe MainThread.java, dunque dobbiamo apportare delle modifiche anche al metodo run():

public void run() {
		Canvas canvas;
		long tickCount = 0L;
		Log.d(TAG, "Starting game loop");
		while (running) {
			canvas=null;
			tickCount++;
			//Proviamo a bloccare la "tela" per la modifica dei pixel sulla superficie
			try {
				canvas = this.surfaceHolder.lockCanvas();
			    synchronized (surfaceHolder) {
				    // Aggiornamento dello stato di gioco
				    // disegna la "tela" (canvas) nel pannello
				    this.gamePanel.onDraw(canvas);
			    }
			} finally {
			// Se scatta l'eccezione la superficie non viene lasciata
		        // in uno stato incoerente
				if (canvas != null) {
				   surfaceHolder.unlockCanvasAndPost(canvas);
			    }
		    } 
		}
		Log.d(TAG, "Game loop executed " + tickCount + " times");
}

Al rigo due abbiamo dichiarato la “tela” (canvas) sulla quale baseremo la nostra immagine. Si tratta di una superficie bitmap nella quale possiamo disegnare e modificare pixel. Al rigo 10, invece, ne entriamo in possesso, mentre al 14 inneschiamo l’evento onDraw, al quale passiamo la nostra “tela” che verrà così disegnata nel pannello. Notate che la porzione di codice si trova nel blocco synchronized, il quale identifica una porzione di codice ad accesso esclusivo, il ché implica che sarà accessibile da un singolo thread per volta.

A questo punto, torniamo a parlare di FPS. Se il numero di frame per seconds scende sotto i 20, diventa abbastanza evidente all’occhio umano, quindi dovremo essere in grado di mantenere un certo numero di FPS se vogliamo sviluppare un gioco di qualità. Vedremo come muovere le immagini nel paragrafo di seguito. Al momento, se provate ad avviare l’applicazione, dovreste avere il seguente risultato.

Screenshot from 2014 01 20 180601 300x465 Sviluppare un Gioco per Android – Lezione 4: Disporre le Immagini guide  sviluppo gioco android immagini gioco android disporre le immagini

Muovere l’immagine

Ora che abbiamo inserito l’immagine è giunta l’ora di farla muovere. Come? Per rendervi chiaro il concetto implementeremo, al momento, una semplice funzione di drag and drop. Fin quando il nostro dito sarà a contatto con il display, aggiorneremo le coordinate dell’immagine e, quando lo rilasceremo, l’immagine si fermerà esattamente dove è stato registrato l’ultimo tocco.

Il droide, come potete intuire voi stessi, è uno degli attori principali del nostro gioco, ci risulterà utile e necessario quindi creare una classe apposita con i metodi basilari utili a restituire le coordinate dell’immagine e la bitmap che conterrà l’immagine. Le variabili x e y indicheranno le coordinate sul display del droide, ma questo non basta a implementare la funzione di drag and drop.

Per implementare quest’ultima utilizzeremo due semplici stati, true e false, che verranno settati rispettivamente quando teniamo il dito sul display e quando lo rilasciamo. Di seguito il codice della classe Droid.java (abbiamo inserito la nuova classe nel package it.androidblog.droidz.model):

package it.androidblog.droidz.model;

import android.graphics.Bitmap;
import android.graphics.Canvas;

public class Droid {

	private Bitmap bitmap; // la bitmap attuale
	private int x;   // coordinata X
	private int y;   // coordinata Y
	private boolean touched; // stato del droide

	public Droid (Bitmap bitmap, int x, int y) {
		this.bitmap=bitmap;
		this.x=x;
		this.y=y;
	}

	public Bitmap getBitmap() {
		return bitmap;
	}

	public void setBitmap(Bitmap bitmap) {
		this.bitmap = bitmap;
	}

	public int getX() {
		return x;
	}

	public void setX(int x) {
		this.x = x;
	}

	public int getY() {
		return y;
	}

	public void setY(int y) {
		this.y = y;
	}

	public boolean isTouched() {
		return touched;
	}

	public void setTouched(boolean touched) {
		this.touched = touched;
	}

	public void draw(Canvas canvas) {
		canvas.drawBitmap(bitmap, x - (bitmap.getWidth() / 2), y - (bitmap.getHeight() / 2), null);
	}

	public void handleActionDown(int eventX, int eventY) {
		if (eventX >= (x - bitmap.getWidth() / 2) & (eventX <= (x + bitmap.getWidth()/2))) {
		   if (eventY >= (y - bitmap.getHeight() / 2) & (y <= (y + bitmap.getHeight() / 2))) {
		     // Droide toccato
		     setTouched(true);
		   } else {
		    setTouched(false);
		   }
		} else {
		   setTouched(false);
		}		 
	}

}

Al momento, tralasciamo i due metodi handleActionDown() e draw(), poiché ne parleremo a breve. I restanti metodi sono molto semplici e costituiscono la base della classe Droid.java. In più, abbiamo aggiunto isTouched() e setTouched() per tenere traccia dello stato del droide. A questo punto, è giunta l’ora di dare una sistemata a MainGamePanel.java, poiché la classe è cambiata un bel po’:

package it.androidblog.droidz;

import it.androidblog.droidz.model.Droid;
import android.content.Context;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Color;
import android.util.Log;
import android.view.MotionEvent;
import android.view.SurfaceHolder;
import android.view.SurfaceHolder.Callback;
import android.view.SurfaceView;

public class MainGamePanel extends SurfaceView implements 
	Callback {

	private MainThread thread;
	private static final String TAG = MainGamePanel.class.getSimpleName();
	private Droid droid;

	public MainGamePanel(Context context) {
		  super(context);
		  //Aggiungiamo callback(this) alla superficie per intercettare gli eventi
		  getHolder().addCallback(this);

		  //Creiamo il droide e carichiamo la bitmap
		  droid = new Droid(BitmapFactory.decodeResource(getResources(), R.drawable.droid_1), 50, 50);

		  // Creiamo il thread per il ciclo di gioco
		  thread = new MainThread(getHolder(), this);
		  setFocusable(true);
	}

	 public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
	 }

	 public void surfaceCreated(SurfaceHolder holder) {
		 thread.setRunning(true);
		 thread.start();
	 }

	 public void surfaceDestroyed(SurfaceHolder holder) {
		 Log.d(TAG, "Surface is being destroyed");

		 // Il thread deve arrestarsi e aspettare che finisca 
		 boolean retry = true;
		   while (retry) {
		    try {
		     thread.join();
		     retry = false;
		    } catch (InterruptedException e) {
		     // Riproviamo ad arrestare il thread
		    }
		   }
		 Log.d(TAG, "Thread was shut down cleanly");
	 }

	 @Override
	 public boolean onTouchEvent(MotionEvent event) {
		 if (event.getAction() == MotionEvent.ACTION_DOWN) {
		    // deleghiamo la gestione degli eventi al droide
		    droid.handleActionDown((int)event.getX(), (int)event.getY());
		    // Verifichiamo se il tocco avviene nella
		    // parte bassa dello schermo per uscire
		    if (event.getY() > getHeight() - 50) {
		      thread.setRunning(false);
		      ((MainActivity)getContext()).finish();
		    } else {
		     Log.d(TAG, "Coords: x=" + event.getX() + ",y=" + event.getY());
		    }
		 } 

		 if (event.getAction() == MotionEvent.ACTION_MOVE) {
		    if (droid.isTouched()) {
		      // il droide viene trascinato
		      droid.setX((int)event.getX());
		      droid.setY((int)event.getY());
		    }
		 } 

		 if (event.getAction() == MotionEvent.ACTION_UP) {
		    // tocco rilasciato
		    if (droid.isTouched()) {
		      droid.setTouched(false);
		    }
		 }
		 return true;
	 }

	 @Override
	 protected void onDraw(Canvas canvas) {
		 //Riempiamo la "tela" di nero
		 canvas.drawColor(Color.BLACK);
		 droid.draw(canvas);
	 }

}

Alla linea 27, creiamo l’oggetto droide (dichiarato come variabile al rigo 19) alle coordinate 50,50. Nel metodo onTouchEvent(), teniamo in considerazione il tocco dello schermo (ACTION_DOWN), il movimento che facciamo senza rilasciare il display (ACTION_MOVE) e infine il rilascio (ACTION_UP).

Soffermandoci su ACTION_DOWN, quello che ci serve sapere riguarda le coordinate del nostro tocco, in parole povere: il tocco avviene nella bitmap del droide o no? Come scritto nel commento, deleghiamo questo controllo a Droid.java e, per la precisione, a handleActionDown(). Il compito di questo metodo, che prima non abbiamo commentato, è proprio quello di verificare se il tocco è avvenuto all’interno della bitmap e, in tal caso, imposta touched su true.

ACTION_MOVE entra in gioco quando il dito inizia a muoversi sullo schermo. Se touched è impostato su true, le vecchie coordinate vengono sostituite con quelle attuali. Questo, in parole semplici, è l’aggiornamento dello stato di gioco (prima lasciato in sospeso). Nel nostro caso, è molto semplice, poiché l’obiettivo è quello di implementare una semplice funzione drag and drop. Siamo giunti alla conclusione ma, tuttavia, manca un dettaglio: l’ouput.

Per visualizzare i cambiamenti, abbiamo il metodo onDraw(), attivato ad ogni esecuzione del thread (date un’occhiata alla classe MainThread.java). Cosa fa onDraw()? Lo abbiamo già visto in precedenza nella classe MainGamePanel.java, riempie la tela di nero e richiama il metodo draw() della classe Droid.java. Quest’ultimo metodo, impostare le coordinate del droide direttamente al centro della bitmap, la cui posizione viene aggiornata in base alle coordinate del tocco attuale. Provate ad avviare l’applicazione e vedrete i risultati con i vostri occhi.

Nella prossima lezione, mostreremo come muovere le immagini in maniera più complessa di un semplice drag and drop. Per qualunque dubbio, come sempre, non esitate a commentare la guida.

Sviluppare un Gioco per Android – Lezione 4: Disporre le Immagini Android Blog Italia.


Ritornare alla prima pagina di Logo Paperblog