В предыдущей записи мы создали игровой цикл, который работает с постоянной скоростью и постоянным (более или менее) FPS.
Как мы можем измерить это?
Проверьте новый класс MainThread.java .
001
002
003
004
005
006
007
008
009
010
011
012
013
014
015
016
017
018
019
020
021
022
023
024
025
026
027
028
029
030
031
032
033
034
035
036
037
038
039
040
041
042
043
044
045
046
047
048
049
050
051
052
053
054
055
056
057
058
059
060
061
062
063
064
065
066
067
068
069
070
071
072
073
074
075
076
077
078
079
080
081
082
083
084
085
086
087
088
089
090
091
092
093
094
095
096
097
098
099
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
|
package net.obviam.droidz; import java.text.DecimalFormat; import android.graphics.Canvas; import android.util.Log; import android.view.SurfaceHolder; /** * @author impaler * * The Main thread which contains the game loop. The thread must have access to * the surface view and holder to trigger events every game tick. */ public class MainThread extends Thread { private static final String TAG = MainThread. class .getSimpleName(); // desired fps private final static int MAX_FPS = 50 ; // maximum number of frames to be skipped private final static int MAX_FRAME_SKIPS = 5 ; // the frame period private final static int FRAME_PERIOD = 1000 / MAX_FPS; // Stuff for stats */ private DecimalFormat df = new DecimalFormat( "0.##" ); // 2 dp // we'll be reading the stats every second private final static int STAT_INTERVAL = 1000 ; //ms // the average will be calculated by storing // the last n FPSs private final static int FPS_HISTORY_NR = 10 ; // last time the status was stored private long lastStatusStore = 0 ; // the status time counter private long statusIntervalTimer = 0l; // number of frames skipped since the game started private long totalFramesSkipped = 0l; // number of frames skipped in a store cycle (1 sec) private long framesSkippedPerStatCycle = 0l; // number of rendered frames in an interval private int frameCountPerStatCycle = 0 ; private long totalFrameCount = 0l; // the last FPS values private double fpsStore[]; // the number of times the stat has been read private long statsCount = 0 ; // the average FPS since the game started private double averageFps = 0.0 ; // Surface holder that can access the physical surface private SurfaceHolder surfaceHolder; // The actual view that handles inputs // and draws to the surface private MainGamePanel gamePanel; // flag to hold game state private boolean running; public void setRunning( boolean running) { this .running = running; } public MainThread(SurfaceHolder surfaceHolder, MainGamePanel gamePanel) { super (); this .surfaceHolder = surfaceHolder; this .gamePanel = gamePanel; } @Override public void run() { Canvas canvas; Log.d(TAG, "Starting game loop" ); // initialise timing elements for stat gathering initTimingElements(); long beginTime; // the time when the cycle begun long timeDiff; // the time it took for the cycle to execute int sleepTime; // ms to sleep (<0 if we're behind) int framesSkipped; // number of frames being skipped sleepTime = 0 ; while (running) { canvas = null ; // try locking the canvas for exclusive pixel editing // in the surface try { canvas = this .surfaceHolder.lockCanvas(); synchronized (surfaceHolder) { beginTime = System.currentTimeMillis(); framesSkipped = 0 ; // resetting the frames skipped // update game state this .gamePanel.update(); // render state to the screen // draws the canvas on the panel this .gamePanel.render(canvas); // calculate how long did the cycle take timeDiff = System.currentTimeMillis() - beginTime; // calculate sleep time sleepTime = ( int )(FRAME_PERIOD - timeDiff); if (sleepTime > 0 ) { // if sleepTime > 0 we're OK try { // send the thread to sleep for a short period // very useful for battery saving Thread.sleep(sleepTime); } catch (InterruptedException e) {} } while (sleepTime < 0 && framesSkipped < MAX_FRAME_SKIPS) { // we need to catch up this .gamePanel.update(); // update without rendering sleepTime += FRAME_PERIOD; // add frame period to check if in next frame framesSkipped++; } if (framesSkipped > 0 ) { Log.d(TAG, "Skipped:" + framesSkipped); } // for statistics framesSkippedPerStatCycle += framesSkipped; // calling the routine to store the gathered statistics storeStats(); } } finally { // in case of an exception the surface is not left in // an inconsistent state if (canvas != null ) { surfaceHolder.unlockCanvasAndPost(canvas); } } // end finally } } /** * The statistics - it is called every cycle, it checks if time since last * store is greater than the statistics gathering period (1 sec) and if so * it calculates the FPS for the last period and stores it. * * It tracks the number of frames per period. The number of frames since * the start of the period are summed up and the calculation takes part * only if the next period and the frame count is reset to 0. */ private void storeStats() { frameCountPerStatCycle++; totalFrameCount++; // check the actual time statusIntervalTimer += (System.currentTimeMillis() - statusIntervalTimer); if (statusIntervalTimer >= lastStatusStore + STAT_INTERVAL) { // calculate the actual frames pers status check interval double actualFps = ( double )(frameCountPerStatCycle / (STAT_INTERVAL / 1000 )); //stores the latest fps in the array fpsStore[( int ) statsCount % FPS_HISTORY_NR] = actualFps; // increase the number of times statistics was calculated statsCount++; double totalFps = 0.0 ; // sum up the stored fps values for ( int i = 0 ; i < FPS_HISTORY_NR; i++) { totalFps += fpsStore[i]; } // obtain the average if (statsCount < FPS_HISTORY_NR) { // in case of the first 10 triggers averageFps = totalFps / statsCount; } else { averageFps = totalFps / FPS_HISTORY_NR; } // saving the number of total frames skipped totalFramesSkipped += framesSkippedPerStatCycle; // resetting the counters after a status record (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() { // initialise timing elements 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" ); } } |
Я ввел простую функцию измерения. Я подсчитываю количество кадров каждую секунду и сохраняю их в массиве fpsStore [] . StoreStats () вызывается каждый тик, и если 1-секундный интервал ( STAT_INTERVAL = 1000; ) не достигнут, то он просто добавляет количество кадров к существующему количеству.
Если нажата одна секунда, то она берет количество визуализированных кадров и добавляет их в массив FPS. После этого я просто сбрасываю счетчики для текущего цикла статистики и добавляю результаты в глобальный счетчик. Среднее значение рассчитывается на основе значений, сохраненных за последние 10 секунд.
Строка 171 регистрирует FPS каждую секунду, в то время как строка 172 устанавливает значение avgFps экземпляра gamePanel , отображаемого на экране.
Метод render класса MainGamePanel.java содержит вызов displayFps, который просто рисует текст в верхнем правом углу экрана каждый раз, когда отображается состояние. Он также имеет закрытый член, который устанавливается из потока.
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
|
// the fps to be displayed private String avgFps; public void setAvgFps(String avgFps) { this .avgFps = avgFps; } public void render(Canvas canvas) { canvas.drawColor(Color.BLACK); droid.draw(canvas); // display fps displayFps(canvas, 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); } } |
Попробуйте запустить его. Вы должны отобразить FPS в правом верхнем углу.
FPS отображается |
Ссылка: Измерение FPS от нашего партнера JCG Тамаса Яно из блога « Против зерна ».
- Введение в разработку игр для Android Введение
- Разработка игр для Android — Идея игры
- Разработка игр для Android — Создать проект
- Разработка игр для Android — базовая игровая архитектура
- Разработка игр для Android — основной игровой цикл
- Разработка игр для Android — Отображение изображений с Android
- Разработка игр для Android — перемещение изображений на экране
- Разработка игр для Android — The Game Loop
- Разработка игр для Android — Sprite Animation
- Разработка игр для Android — Particle Explosion
- Разработка игр для Android — Дизайн игровых объектов — Стратегия
- Разработка игр для Android — Использование растровых шрифтов
- Разработка игр для Android — переход с Canvas на OpenGL ES
- Разработка игр для Android — отображение графических элементов (примитивов) с помощью OpenGL ES
- Разработка игр для Android — OpenGL Texture Mapping
- Разработка игр для Android — Дизайн игровых сущностей — Государственный паттерн
- Серия игр для Android