Home > Guide e tutorial per Android > Sviluppare un Gioco per Android – Lezione 8: Animazioni Sprite

Sviluppare un Gioco per Android – Lezione 8: Animazioni Sprite

programmazione-android8Se avete seguito le lezioni fino ad ora saprete come gestire i tocchi, posizionare le immagini e farle muovere. Tuttavia, far muovere le immagini è uno spettacolo piuttosto noioso sotto certi punti di vista, considerando che molti oggetti non si muovono per caso, ma proprio perché sono animati. Oggi, infatti, parleremo di animazioni e, per la precisione, di Animazioni Sprite. Immaginate, ad esempio, un uomo che cammina. Proiettando l’esempio sul display dello smartphone, avremo un’animazione composta da un determinato numero di immagini, le quali possono ripetersi all’infinito, oppure no.

Animazioni Sprite

Prendiamo la gif qui di seguito. Si tratta, come potete intuire, di un’animazione a frame rate basso. Si tratta proprio di una Sprite e, se volessimo riprodurla, abbiamo bisogno di ogni fotogramma che compone l’animazione.

walk_elaine_anim

Ora, la domanda viene spontanea: quante immagini sono? La risposta è molto semplice:

walk_elaine

Il passo successivo è dare un ordinamento, com’è logico che sia e, anche in questo caso, il problema è molto banale:

walk_elaine_frames

L’immagine sopra, a dirla tutta, è larga 150 pixel poiché ogni frame è di 30 pixel. Per avere la nostra animazione con Android, ci toccherà semplicemente caricare ogni fotogramma come immagine separata e visualizzare ognuna di queste ad intervalli regolari. Un altro metodo è quello di caricare l’immagine intera con tutti i frame e utilizzare i metodi Android per ritagliarla (in quanto conosciamo le dimensioni di ciascun frame), ed è proprio questo il procedimento che utilizzeremo. L’immagine di seguito mostra come verranno selezionati e ritagliati i frame.

walk_frames_cut

Ora che conosciamo le basi, andiamo a creare un nuovo progetto in Eclipse. Utilizzeremo le conoscenze acquisite nelle precedenti lezioni (in particolare per ciò che concerne il ciclo di gioco) per sviluppare le animazioni Sprite. 

Passiamo al codice

Innanzitutto, avremo bisogno di un oggetto da animare. Useremo proprio Elaine di Monkey Island e creeremo la classe ElaineAnimated.java:

package it.androidblog.lessons;

import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.Rect;

public class ElaineAnimated {

	private static final String TAG = ElaineAnimated.class.getSimpleName();

	private Bitmap bitmap;		// La sequenza animata
	private Rect sourceRect;	// il rettangolo da disegnare 
	private int frameNr;		// il numero di frame nell'animazione
	private int currentFrame;	// il frame corrente
	private long frameTicker;	// il tempo passato dall'ultimo frame aggiornato
	private int framePeriod;	// ms tra ogni frame (1000/fps)

	private int spriteWidth;	// la larghezza della Sprite
	private int spriteHeight;	// l'altezza della Sprite

	private int x;				// la coordinata X dell'oggetto
	private int y;				// la coordinata Y dell'oggetto

	public ElaineAnimated(Bitmap bitmap, int x, int y, int width, int height, int fps, int frameCount) {
		this.bitmap = bitmap;
		this.x = x;
		this.y = y;
		currentFrame = 0;
		frameNr = frameCount;
		spriteWidth = bitmap.getWidth() / frameCount;
		spriteHeight = bitmap.getHeight();
		sourceRect = new Rect(0, 0, spriteWidth, spriteHeight);
		framePeriod = 1000 / fps;
		frameTicker = 0l;
	}

	public Bitmap getBitmap() {
		return bitmap;
	}
	public void setBitmap(Bitmap bitmap) {
		this.bitmap = bitmap;
	}
	public Rect getSourceRect() {
		return sourceRect;
	}
	public void setSourceRect(Rect sourceRect) {
		this.sourceRect = sourceRect;
	}
	public int getFrameNr() {
		return frameNr;
	}
	public void setFrameNr(int frameNr) {
		this.frameNr = frameNr;
	}
	public int getCurrentFrame() {
		return currentFrame;
	}
	public void setCurrentFrame(int currentFrame) {
		this.currentFrame = currentFrame;
	}
	public int getFramePeriod() {
		return framePeriod;
	}
	public void setFramePeriod(int framePeriod) {
		this.framePeriod = framePeriod;
	}
	public int getSpriteWidth() {
		return spriteWidth;
	}
	public void setSpriteWidth(int spriteWidth) {
		this.spriteWidth = spriteWidth;
	}
	public int getSpriteHeight() {
		return spriteHeight;
	}
	public void setSpriteHeight(int spriteHeight) {
		this.spriteHeight = spriteHeight;
	}
	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;
	}

	// Il metodo update per Elaine
	public void update(long gameTime) {
		if (gameTime > frameTicker + framePeriod) {
			frameTicker = gameTime;
			// incremento del frame
			currentFrame++;
			if (currentFrame >= frameNr) {
				currentFrame = 0;
			}
		}
		// definiamo l'area rettangolare da tagliare
		this.sourceRect.left = currentFrame * spriteWidth;
		this.sourceRect.right = this.sourceRect.left + spriteWidth;
	}

	// il metodo che disegna il corrispondente frame
	public void draw(Canvas canvas) {
		// dove disegnare la sprite
		Rect destRect = new Rect(getX(), getY(), getX() + spriteWidth, getY() + spriteHeight);
		canvas.drawBitmap(bitmap, sourceRect, destRect, null);
		canvas.drawBitmap(bitmap, 20, 150, null);
		Paint paint = new Paint();
		paint.setARGB(50, 0, 255, 0);
		canvas.drawRect(20 + (currentFrame * destRect.width()), 150, 20 + (currentFrame * destRect.width()) + destRect.width(), 150 + destRect.height(),  paint);
	}
}

Tutti gli attributi privati sono commentati, ma vale la pena spendere due parole per alcuni di questi:

  • bitmap è l’immagine che contiene tutti i frame (come quella che abbiamo visto sopra);
  • sourceRect è il rettangolo che “ritaglia” ogni frame e si sposta al successivo;
  • frameTicker è il tempo che passa dall’ultimo frame aggiornato;
  • framePeriod è il tempo in millisecondi che passa da un frame all’altro (se il ciclo viene completato in un secondo, essendo 5 i frame, ognuno di questi verrà visualizzato ogni 0,2 secondi).

Parliamo, ora, del costruttore. Assumendo che ogni frame abbia la stessa larghezza, possiamo ricavare la larghezza del rettangolo dividendo quella dell’immagine per il numero di frame che contiene. Naturalmente, Elaine avrà bisogno anche di un metodo per il proprio aggiornamento, essendo un oggetto animato e, poiché il tempo di aggiornamento del ciclo di gioco è diverso da quello di Elaine, passeremo all’update() della nostra protagonista il tempo di gioco effettivo come variabile in modo da sapere quando visualizzare il frame successivo.

Nel pannello di gioco (MainGamePanel.java) questo sarà il metodo d’aggiornamento:

public void update() {
	elaine.update(System.currentTimeMillis());
}

Il funzionamento è semplice, si incrementa il frame solo se il tempo passato (System.currentTimeMillis()) è maggiore di frameTicker più framePeriod, il cui significato a questo punto dovrebbe essere chiaro a tutti. Se il fotogramma passato è oltre l’ultimo il ciclo si azzera.

Una volta definita l’area da ritagliare (sourceRect), non ci resta che dargli una destinazione (destRect) e disegnarla (date un’occhiata al metodo draw di Elaine). Quest’ultima viene istanziata nel costruttore del pannello passandogli i parametri necessari (alcuni vengono ignorati, ma sono utili per apportare modifiche al codice), tra i quali hanno rilevante importanza il numero di FPS e il numero di frame.

Nel metodo draw di Elaine, inoltre, creiamo un oggetto Paint in modo da disegnarci sopra il frame corrente. Con il metodo setARGB creiamo una vernice verde semitrasparente. Dopo tutto questo, dipingiamo un rettangolo delle dimensioni di un fotogramma sull’immagine originale in modo da vedere quale fotogramma viene visualizzato durante l’animazione.

Per quanto riguarda le classi MainThread, MainActivity e la restante parte di MainGamePanel, le potete trovare nel codice completo (scaricabile qui), ma comunque sono le stesse viste nelle lezioni precedenti. L’immagine è walk_elaine.png e la potete trovare in res/drawable-mdpi.

Quando avviate l’applicazione il risultato sarà il seguente:

Screenshot from 2014-02-19 13:20:28