Sviluppare un Gioco per Android – Lezione 8: Animazioni Sprite Android Blog Italia.
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.
Ora, la domanda viene spontanea: quante immagini sono? La risposta è molto semplice:
Il passo successivo è dare un ordinamento, com’è logico che sia e, anche in questo caso, il problema è molto banale:
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.
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:
Sviluppare un Gioco per Android – Lezione 8: Animazioni Sprite Android Blog Italia.