0

我有一个android游戏的开始。它有一个GameLoop(线程)类来跟踪 fps 值和一个GameView(表面视图),它使一个GameLoop对象实例化并设置它运行等等。现在我有一个单独的FpsText对象,它知道如何将自己绘制到屏幕上,但要更新我需要做类似的事情fpsText.setText(gameloop.getFps());

如果我在GameView课堂上这样做,这不是问题,但我想尝试拥有一个FpsText可以自行更新的对象。我对线程没有任何其他经验,我认为它可能会适得其反,但我仍然尝试将fpsText 引用gameloop作为参数传递。不出所料,我每次运行应用程序时都会得到不同的不需要的结果,所以我想知道您认为处理这种情况的最佳方法是什么?

作为fpsTextGameViewi 中更新的替代方法,总是可以将 gameloop 类中的 fps 值公开,但我知道这是不好的做法!听起来都不是好的解决方案,也许有更好的方法?

作为参考,这是我的游戏循环,RedOrav 建议的第二个更改已实现:

package biz.hireholly.engine;

import android.graphics.Canvas;
import android.util.Log;
import android.view.SurfaceHolder;
import biz.hireholly.gameplay.FpsText;

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 ArrayList<Double> fpsStore = new ArrayList<Double>(); //For the previous fps values
    private long lastTimeFpsCalculated = System.currentTimeMillis(); //used in trackFps
    FpsText fpsText;

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

    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(){
        synchronized(fpsStore){
            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){


                for (Double fps : fpsStore){
                lastFps += fps;
                }

                lastFps /= fpsStore.size();

                synchronized(fpsText){ //update the Drawable FpsText object
                fpsText.setText(String.valueOf((int)lastFps));
                }

                lastTimeFpsCalculated = System.currentTimeMillis();

                //Log.d("trackFPS()", " fpsStore.size() = "+fpsStore.size()+"\t"+fpsStore.toString());
                fpsStore.clear();


            }
        }
    }

}
4

1 回答 1

1

你不想要的结果是什么?公开你的 fps 变量和设置 getfps() 就获取你的 fps 值而言是完全相同的事情,并且在你的 GameView 中修改 fps 值永远不会有任何意义。

我怀疑您在 GameLoop 中所做的是在几秒钟之间修改您的 fps 变量,即获取您的 GameView 可能也在读取的中间值。我建议仅每秒修改一次 fps,并在两者之间保留时间变量以进行计算。假设你一开始有 0 fps。GameLoop 进行计算,一旦过了一秒,您就已经收集了它处理的帧数和时间(1 秒)。在那之前,无论 GameLoop 在做什么,GameView 始终读取 0。在那一秒之后,假设你完成了 60 fps,fps 变量被修改,并且直到下一秒都保持不变。因此,在接下来的一秒中,即使 GameView 对您的变量进行了更多的读取,它也总是会得到 60。

// Inside GameLoop

private int fps; // Variable you're going to read
private int frameCount; // Keeps track or frames processed
private long lastUpdated;

while(running) {

   if(System.currentTimeMillis() - lastUpdated > 1000) { // 1 second elapsed
      fps = frameCount;
      frameCount = 0;
   }
   frameCount++;
}

public int getFps() {
   return fps;
}

另一种方法是将 fpsText 的引用传递给 GameLoop,它只会在每一秒过去后修改它。这样,您的渲染线程只需要担心渲染,而您的 GameLoop 只需担心每秒帧数。但是请记住,您可能会遇到竞争条件(在 GameLoop 中的 setText() 就在您的渲染方法中间),您不必担心其他方式。渲染和修改时将 fpsText 对象括在同步括号中应该可以解决问题。

让我们知道这是否有帮助!

于 2012-08-04T11:20:19.637 回答