【问题标题】:How to calculate FPS in my empty game?如何在我的空游戏中计算 FPS?
【发布时间】:2012-07-31 11:05:06
【问题描述】:

为了创建游戏循环,我关注了this excellent tutorial。但我认为下一个关于显示 FPS 的教程有点不确定,所以我尝试单独进行。我对我所做的trackFps() 方法相当有信心;它在计算帧之间的时间差后调用,每次运行时测量预测的 FPS,将这些预测的 FPS 值存储在 ArrayList 中,然后每经过一秒钟,将这些预测的 FPS 相加并除以添加的值的数量以获得平均 FPS。

当我使用调试器运行它时,它运行正常,但是当我正常运行它时,我得到以下异常:

FATAL EXCEPTION: Thread-11
java.lang.IndexOutOfBoundsException: Invalid index 26, size is 6
at java.util.ArrayList.throwIndexOutOfBoundsException(ArrayList.java:257)
at java.util.ArrayList.get(ArrayList.java:311)
at biz.hireholly.engine.GameLoop.trackFps(GameLoop.java:120)
at biz.hireholly.engine.GameLoop.run(GameLoop.java:73)

这很容易解释,出现在fps += fpsStore.get(fpsTrackCount-1); 这一行。但我看不出fpsTrackCount 变量怎么会高达26,它应该只和ArrayList fpsStore 中存储的变量数量一样高。

有人会查看我的GameLoop 类,尤其是底部的trackFps() 方法吗?我会提供整个事情。它被大量评论,但内容不多,对你们中的一些人来说应该非常熟悉。

GameLoop(底部包含trackFps()计算FPS的方法):

package biz.hireholly.engine;

import android.graphics.Canvas;
import android.view.SurfaceHolder;
import java.util.ArrayList;


/**
 * The GameLoop is a thread that will ensure updating and drawing is done at set intervals.
 * The thread will sleep when it has updated/rendered quicker than needed to reach the desired fps.
 * The loop is designed to skip drawing if the update/draw cycle is taking to long, up to a MAX_FRAME_SKIPS.
 * The Canvas object is created and managed to some extent in the game loop,
 * this is so that we can prevent multiple objects trying to draw to it simultaneously.
 * Note that the gameloop has a reference to the gameview and vice versa.
 */

public class GameLoop extends Thread {

    private static final String TAG = GameLoop.class.getSimpleName();
    //desired frames per second
    private final static int MAX_FPS = 30;
    //maximum number of drawn frames to be skipped if drawing took too long last cycle
    private final static int MAX_FRAME_SKIPS = 5;
    //ideal time taken to update & draw
    private final static int CYCLE_PERIOD = 1000 / MAX_FPS;

    private SurfaceHolder surfaceHolder;
    //the gameview actually handles inputs and draws to the surface
    private GameView gameview;

    private boolean running;
    private long beginTime = 0; // time when cycle began
    private long timeDifference = 0; // time it took for the cycle to execute
    private int sleepTime = 0; // milliseconds to sleep (<0 if drawing behind schedule) 
    private int framesSkipped = 0; // number of render frames skipped

    private double lastFps = 0; //The last FPS tracked, the number displayed onscreen
    private int fpsTrackCount = 1; // number we'll divide the fpsSTore by to get average
    private ArrayList<Double> fpsStore = new ArrayList<Double>(); //For the previous fps values
    private long lastTimeFpsCalculated = System.currentTimeMillis(); //used in trackFps

    public GameLoop(SurfaceHolder holder, GameView gameview) {
        super();
        this.surfaceHolder = holder;
        this.gameview = gameview;
    }

    public void setRunning(boolean running) {
        this.running = running;     
    }
    @Override
    public void run(){

        Canvas c;

        while (running) {
            c = null;
            //try locking canvas, so only we can edit pixels on surface
            try{
                c = this.surfaceHolder.lockCanvas();
                //sync so nothing else can modify while were using it
                synchronized (surfaceHolder){ 

                    beginTime = System.currentTimeMillis();
                    framesSkipped = 0; //reset frame skips

                    this.gameview.update();
                    this.gameview.draw(c);

                    //calculate how long cycle took
                    timeDifference = System.currentTimeMillis() - beginTime;
                    //good time to trackFps?
                    trackFps();

                    //calculate potential sleep time
                    sleepTime = (int)(CYCLE_PERIOD - timeDifference);

                    //sleep for remaining cycle
                    if (sleepTime >0){
                        try{
                            Thread.sleep(sleepTime); //saves battery! :)
                        } catch (InterruptedException e){}
                    }
                    //if sleepTime negative then we're running behind
                    while (sleepTime < 0 && framesSkipped < MAX_FRAME_SKIPS){
                        //update without rendering to catch up
                        this.gameview.update();
                        //skip as many frame renders as needed to get back into
                        //positive sleepTime and continue as normal
                        sleepTime += CYCLE_PERIOD;
                        framesSkipped++;
                    }

                }

            } finally{
                //finally executes regardless of exception, 
                //so surface is not left in an inconsistent state
                if (c != null){
                    surfaceHolder.unlockCanvasAndPost(c);
                }
            }
        }

    }

    /* Calculates the average fps every second */
    private void trackFps(){
        long currentTime = System.currentTimeMillis();

        if(timeDifference != 0){
            fpsStore.add((double)(1000 / timeDifference));
        }
        //If a second has past since last time average was calculated,
        // it's time to calculate a new average fps to display
        if ((currentTime - 1000) > lastTimeFpsCalculated){
            int fps = 0;
            int toDivideBy = fpsTrackCount;
            while ((fpsStore !=  null) && (fpsTrackCount > 0 )){
                fps += fpsStore.get(fpsTrackCount-1);
                fpsTrackCount--;
            }
            lastFps = fps / toDivideBy;
            lastTimeFpsCalculated = System.currentTimeMillis();
            fpsTrackCount = 1;
            fpsStore.clear();
        }
        else{   
        fpsTrackCount++;
        }
    }
    /* So That it can be drawn in the gameview */
    public String getFps() {
        return String.valueOf(lastFps);
    }

}

【问题讨论】:

  • 我没有看到对你的 TextDrawable 的调用,你能把代码贴在你用它来绘制 FPS 值的地方吗?啊,找到了。
  • 抱歉,这是我上传到 Pastebin 的 GameView 中的内容,因为我不想让我的问题变得混乱。

标签: java android game-engine frame-rate game-loop


【解决方案1】:

好的,让我们先来看看...

java.lang.IndexOutOfBoundsException: Invalid index 26, size is 6
at java.util.ArrayList.throwIndexOutOfBoundsException(ArrayList.java:257)
at java.util.ArrayList.get(ArrayList.java:311)
at biz.hireholly.engine.GameLoop.trackFps(GameLoop.java:120)

所以在 trackFPS 的某个地方,get() 是荒谬的……

private void trackFps()
{
    long currentTime = System.currentTimeMillis();

    if(timeDifference != 0)
    {
        fpsStore.add((double)(1000 / timeDifference));
    }
    //If a second has past since last time average was calculated,
    // it's time to calculate a new average fps to display
    if ((currentTime - 1000) > lastTimeFpsCalculated)
    {
        int fps = 0;
        int toDivideBy = fpsTrackCount;
        while ((fpsStore !=  null || !fpsStore.isEmpty()) && (fpsTrackCount > 0 ) && (fpsTrackCount < fpsStore.getCount()))
        {
            //synchronized(this) {
            fps += fpsStore.get(fpsTrackCount-1);
            fpsStore.remove(fpsTrackCount-1);  //otherwise we'll get stuck because of getCount condition
            fpsTrackCount--;
            //}
        }
        lastFps = fps / toDivideBy;
        lastTimeFpsCalculated = System.currentTimeMillis();
        //fpsTrackCount = 1;
        //fpsStore.clear();
        Log.d("trackFPS()", "fpsTrackCount = "+fpsTrackCount+"\tfpsStore.size() = "+fpsStore.size()+"\t"+fpsStore.toString());
    }
    else   
        fpsTrackCount++;
}

试一试。如果效果不太好,请尝试取消同步块的注释。

至于您的其他问题,关于 TextDrawable,我查看了您的 GameView...

这是我在 SurfaceChanged() 中发现的

  fps = new TextDrawable();
  fps.setText("HELLO");

现在,你为什么不把它移到 SurfaceCreated()?也许您收到了很多 surfaceChanged() 回调,但由于您没有 Logcat 调用而没有意识到这一点? :) 它是 TextDrawable 被实例化的唯一地方。

【讨论】:

  • 非常感谢您抽出宝贵时间!首先这是日志的结果,并不是我想要的打印结果i.imgur.com/D2sG7.jpg。我认为fpsTrackCount =1 行可能仍应保留。当我取消注释它时,虽然日志输出并没有好得多,但它只显示 fpsTrackCount 为 1,并且 fpsStore 中仍然有数字 500 和 1000,就像在上图。关于将初始化位移动到 SurfaceCreated 的好点,但我担心我仍然在屏幕上看到模糊的重叠数字,所以我不确定这就是原因。感谢您的帮助!
  • 也 .getCount() 未被识别,我认为它应该是 .size() ?
  • yes :) size()... 至于 TextDrawable,可能是这样的... canvas.drawColor(Color.TRANSPARENT, PorterDuff.Mode.CLEAR);我稍微修改了一下。
  • 感谢您的评论,我突然想到,在绘制其他任何内容之前,一个简单的canvas.drawColor(Color.BLACK); 会很好地清除屏幕,所以如果这是一种可行的方法,那就解决了一个问题!
  • 一个下来,一个去。那么,您可以发布一些新日志的 logcat 输出吗?并排记录 fpsTrackCount 和 fpsStore.size() 的那个?
【解决方案2】:

您还可以使用 android play 商店中的应用来测量 FPS。最近发布的一个非常好的应用程序是 GameBench。它不仅可以捕获 FPS,还可以截取屏幕截图,以便您了解 FPS 下降时发生的情况。它还提供 CPU 使用率,在某些设备中也提供 GPU 使用率。我相信你会发现它很有用。

链接是this

【讨论】:

    【解决方案3】:

    我认为这段代码应该对你有用

    cputhrottle=(int) System.currentTimeMillis();
    cputhrottle = (int)System.currentTimeMillis() - cputhrottle;cputhrottle=33-cputhrottle;
    

    每秒 30 帧,对你很有用

    【讨论】:

    • 对不起,但这个答案需要更多解释,虽然它是相关的,但我不确定它是否真的能回答我的问题。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2016-04-14
    • 1970-01-01
    • 2020-02-04
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-11-08
    相关资源
    最近更新 更多