Sviluppare un Gioco per Android – Lezione 7: Misurare il numero di FPS Android Blog Italia.
// Statistiche */ private DecimalFormat df = new DecimalFormat("0.##"); // 2 dp // Leggeremo le statistiche ogni secondo private final static int STAT_INTERVAL = 1000; //ms // la media sarà calcolata memorizzando // l'ultimo valore di FPS private final static int FPS_HISTORY_NR = 10; // L'ultima volta che lo stato è stato memorizzato private long lastStatusStore = 0; // contatore dello stato private long statusIntervalTimer = 0l; // numero di fram saltati da quando il gioco è iniziato private long totalFramesSkipped = 0l; // numero di frame saltati in un ciclo di memorizzazione (1 sec) private long framesSkippedPerStatCycle = 0l; // numero di frame visualizzati in un intervallo private int frameCountPerStatCycle = 0; private long totalFrameCount = 0l; // gli ultimi valori FPS private double fpsStore[]; // numero di volte che le statistiche sono state lette private long statsCount = 0; // media FPS da quando il gioco è iniziato private double averageFps = 0.0;
A questo punto, ci dovremmo chiedere come modificare la classe MainThread.java affinché sia possibile visualizzare le statistiche da noi desiderate. Per farlo, adotteremo principalmente due metodi e modificheremo leggermente il metodo run(). Gli altri due metodi che utilizzeremo saranno rispettivamente storeStats() per memorizzare i dati e initTimingElements() per inizializzare gli elementi utili al timer per le statistiche. Di seguito potete dare un’occhiata ai tre metodi:
public void run() { Canvas canvas; Log.d(TAG, "Starting game loop"); // inizializza gli elementi utili ai timer per le statistiche initTimingElements(); long beginTime; // il tempo quando inizia il ciclo long timeDiff; // tempo impiegato per il ciclo int sleepTime; // ms per lo stato di sleep (può essere <0) int framesSkipped; // numero di frame saltati sleepTime = 0; while (running) { canvas=null; //Proviamo a bloccare la "tela" per la modifica dei pixel sulla superficie try { canvas = this.surfaceHolder.lockCanvas(); synchronized (surfaceHolder) { beginTime = System.currentTimeMillis(); framesSkipped = 0; // resettiamo i frame saltati // Aggiornamento dello stato di gioco this.gamePanel.update(); // disegna la "tela" (canvas) nel pannello this.gamePanel.onDraw(canvas); // Calcoliamo quanto tempo prende il ciclo timeDiff = System.currentTimeMillis() - beginTime; // Calcoliamo sleepTime sleepTime = (int)(FRAME_PERIOD - timeDiff); if (sleepTime > 0) { // Caso ottimale se > 0 try { // mandiamo il thread a dormire per un breve periodo // molto utile per risparmiare batteria Thread.sleep(sleepTime); } catch (InterruptedException e) {} } while (sleepTime < 0 & framesSkipped < MAX_FRAME_SKIPS) { // Abbiamo bisogno di recuperare this.gamePanel.update(); // aggiornamento senza rendering sleepTime += FRAME_PERIOD; // aggiungere frame period per il controllo del frame successivo framesSkipped++; } if (framesSkipped > 0) { Log.d(TAG, "Skipped:" + framesSkipped); } // per le statistiche framesSkippedPerStatCycle += framesSkipped; // memorizza le statistiche storeStats(); } } finally { // Se scatta l'eccezione la superficie non viene lasciata // in uno stato incoerente if (canvas != null) { surfaceHolder.unlockCanvasAndPost(canvas); } } //fine finally } } private void storeStats() { frameCountPerStatCycle++; totalFrameCount++; // controlla il tempo attuale statusIntervalTimer += (System.currentTimeMillis() - statusIntervalTimer); if (statusIntervalTimer >= lastStatusStore + STAT_INTERVAL) { // calcola i frames attuali per ogni intervallo double actualFps = (double)(frameCountPerStatCycle / (STAT_INTERVAL / 1000)); //memorizza l'ultimo valore FPS nell'array fpsStore[(int) statsCount % FPS_HISTORY_NR] = actualFps; // incrementa il numero di volte che le stat vengono calcolate statsCount++; double totalFps = 0.0; // somma i valore memotizzati nell'array for (int i = 0; i < FPS_HISTORY_NR; i++) { totalFps += fpsStore[i]; } // otteniamo la media if (statsCount < FPS_HISTORY_NR) { averageFps = totalFps / statsCount; } else { averageFps = totalFps / FPS_HISTORY_NR; } // salviamo il numero totale dei frame saltati totalFramesSkipped += framesSkippedPerStatCycle; // resettiamo i contatori dopo la memorizzazione di uno stato (1 sec) framesSkippedPerStatCycle = 0; statusIntervalTimer = 0; frameCountPerStatCycle = 0; statusIntervalTimer = System.currentTimeMillis(); lastStatusStore = statusIntervalTimer; Log.d(TAG, "Average FPS:" + df.format(averageFps)); gamePanel.setAvgFps("FPS: " + df.format(averageFps)); } } private void initTimingElements() { // inizializziamo gli elementi per il timer fpsStore = new double[FPS_HISTORY_NR]; for (int i = 0; i < FPS_HISTORY_NR; i++) { fpsStore[i] = 0.0; } Log.d(TAG + ".initTimingElements()", "Timing elements for stats initialised"); }
La funzione non è molto difficile da capire. Essa si basa sul conteggio dei fotogrammi e sulla relativa memorizzazione nell’array fpsStore[]. Ad ogni intervallo di 1 secondo, viene chiamato storeStats(). Se l’intervallo di un secondo non viene raggiunto, aggiunge semplicemente il numero dei fotogrammi al conteggio attuale, altrimenti prende il numero di fotogrammi visualizzati e li aggiunge all’array, dopodiché azzera i contatori. La media viene calcolata sui valori memorizzati negli ultimi dieci secondi.
A questo punto, l’ultimo passo è implementare i metodi setAvgFps e displayFps per la visualizzazione della media sul display in alto a destra. Di seguito potete dare un’occhiata alle modifiche da apportare alla classe MainGamePanel.java:
private String avgFps: protected void onDraw(Canvas canvas) { //Riempiamo la "tela" di nero canvas.drawColor(Color.BLACK); droid.draw(canvas); displayFps(canvas, avgFps); } public void setAvgFps(String avgFps) { this.avgFps = avgFps; } private void displayFps(Canvas canvas, String fps) { if (canvas != null & fps != null) { Paint paint = new Paint(); paint.setARGB(255, 255, 255, 255); canvas.drawText(fps, this.getWidth() - 50, 20, paint); } }
Una volta effettuate le modifiche in maniera corretta, potete avviare l’applicazione e vi dovreste trovare dinanzi qualcosa di molto simile al seguente:
A questo punto, non ci rimane che darci appuntamento alla prossima settimana. Nelle prossime lezioni metteremo da parte il progetto Droid per dare risalto ad altre attività relative allo sviluppo di un gioco per Android, come le animazioni Sprite e non solo.
Sviluppare un Gioco per Android – Lezione 7: Misurare il numero di FPS Android Blog Italia.