ホームへ戻る

s2.3 FPSを表示させる


 それではFPSを表示させてみましょう。
FPSとは1秒間に何フレーム描画したかを表現する値で、一般的なPCのディスプレイや携帯端末のディスプレイは60FPSです。
ゲームプログラムの処理は先ほどお伝えしたように、メインループでグルグル回っているため、
「現在の時刻 - 前回表示した時刻」(※1) が分かればFPSが分かります。
例えば60FPSで動作している時の1フレームの時間は1000[ms]÷60=16.6666...[ms]です。
逆に言えば※1が16msであれば約60FPSで動作していることが分かります。

しかし、1秒間に60回表示する値を変えるとチラチラして見えにくいですし、測定値が1回では精度が低いです。
そこで、60回計測した平均を使います。

と言っても1秒間に60回時間を計測して平均を算出する必要はありませんので以下のような処理を行います。


1フレーム目:現在時刻を記憶
2フレーム目:何もしない
3フレーム目:何もしない
4フレーム目:何もしない
  ・
  ・
  ・
 60フレーム目:「現在時刻 - 1フレーム目の時刻」÷60で1フレーム当たりの平均処理時間を計算


このような手順で、計算すれば、60フレームに2回仕事をするだけで計算出来ます。

ではこれをコードにしてみましょう。
前章で説明した通り、まず「Task」クラスを作り、それを継承した「FpsController」を作ってみましょう。

また、これも前述の通り、全てのタスクを管理する「GameMgr」クラスも作ります。
この3つのクラスを新規追加して下さい。
(importは省略してありますので、例によって必要な処理は Ctrl + Shift + O で追加して下さい)

↓GameSurfaceView.java の変更点(赤字部)

class GameSurfaceView extends SurfaceView implements SurfaceHolder.Callback, Runnable {
        private GameMgr _gameMgr = new GameMgr();
        private Thread _thread;

        public GameSurfaceView(Context context) {
                super(context);
                getHolder().addCallback(this);
        }

        @Override
        public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
                //解像度情報変更通知 
        }

        @Override
        public void surfaceCreated(SurfaceHolder holder) {
                _thread = new Thread(this);             //別スレッドでメインループを作る
                _thread.start();
        }

        @Override
        public void surfaceDestroyed(SurfaceHolder holder) {
                _thread = null;
        }

        @Override
        public void run() {
                while (_thread!=null) { //メインループ
                        _gameMgr.onUpdate();
                        onDraw(getHolder());
                }
        }

        private void onDraw(SurfaceHolder holder) {
                Canvas c = holder.lockCanvas();
                if(c == null){
                              return;
                }
                _gameMgr.onDraw(c);
                holder.unlockCanvasAndPost(c);
        }
}

GameSurfaceViewはただMgrクラスを呼び出すだけなので、今後ほとんど手を加えることは無いでしょう。

↓Task.java
(新規追加)
public abstract class Task {

        public boolean onUpdate(){
                return true;
        }
        
        public void onDraw(Canvas c){           
        }

}

こちらはただのひな形です。
「更新」と「描画」が存在します。
実際にはこれを継承して使用します。

↓FpsController.java
(新規追加)
public class FpsController extends Task {

        private long _startTime = 0;            //測定開始時刻
        private int  _cnt = 0;                  //カウンタ
        private Paint _paint = new Paint();     //paint情報
        private float _fps;                     //fps
        private final static int N = 60;        //平均を取るサンプル数
        private final static int FONT_SIZE = 20;//フォントサイズ
        
        public FpsController(){
                _paint.setColor(Color.BLUE);    //フォントの色を青に設定
                _paint.setTextSize(FONT_SIZE);  //フォントサイズを設定
        }
        
        @Override
        public boolean onUpdate(){
                if( _cnt == 0 ){ //1フレーム目なら時刻を記憶
                        _startTime = System.currentTimeMillis();
                }
                if( _cnt == N ){ //60フレーム目なら平均を計算する
                        long t = System.currentTimeMillis();
                        _fps = 1000.f/((t-_startTime)/(float)N);
                        _cnt = 0;
                        _startTime = t;
                }
                _cnt++;
                return true;
        }

        @Override
        public void onDraw(Canvas c){
                c.drawText(String.format("%.1f", _fps), 0, FONT_SIZE-2, _paint);
        }

}

onUpdateとonDrawを継承して使いました。
文字の描画には描画設定を記憶した_paintを使用します。
実際に描画しているのはdrawText部です。

↓GameMgr.java
(新規追加)
public class GameMgr {

        private LinkedList<Task> _taskList = new LinkedList<Task>(); //タスクリスト
        
        GameMgr(){
                _taskList.add( new FpsController() );
        }
        
        public boolean onUpdate() {
                for(int i=0; i<_taskList.size(); i++){
                        if(_taskList.get(i).onUpdate() == false){ //更新失敗なら
                                _taskList.remove(i);              //そのタスクを消す
                                i--;
                        }
                }
                return true;
        }

        public void onDraw(Canvas c) {
                c.drawColor(Color.WHITE);       //白で塗りつぶす
                for(int i=0; i<_taskList.size(); i++){
                        _taskList.get(i).onDraw(c);//描画
                }
        }

}

タスクは _taskList に追加していきます。

UpdateもDrawもリストに入っている物を呼んでいるだけなので、
今後自機や敵、背景、エフェクト等他のタスクが増えたとしても、このGameMgrには

_taskList.add( new XXXX );

この部分だけ追加していけば後は何も変更しなくて良いのです。

実際には描画される順序等他に考えるべきこともありますが、今回はその辺を省略します。

では実際に実行してみて下さい。


ほとんどの環境では60前後が表示されるのではないかと思います。

この章のプロジェクトをダウンロード

→分からないことがあれば掲示板で質問して下さい


Portions of this page are modifications based on work created and shared by Google and used according to terms described in the Creative Commons 3.0 Attribution License.

- Remical Soft -