Question

At university last spring I studied Android and the last assignment was to develop a game. I will not give any details about the game itself but I used a class that extends SurfaceView as a gamepanel and put the game-loop in a background-thread. The game took one month hard work to finish but the outcome was very good - so good that I launched it on android market and now have more and more downloads. But there is one rather big drawback and it follows here:

To get the highest rate in this assignment one had to make a game where the frame-rate must be constant. And because I got the highest degree and the teachers was very satisfied with my source code I thought there were no bugfix. But - the thing is that the frame-rate is NOT constant. And more strange: the graphical objects that moves across the screen - moves slower the faster the CPU is.

I have three phones of different models that shows three different speeds:

  • SAMSUNG GALAXY MINI (processor clock speed 1 GHz) Here the objects move fast
  • SAMSUNG GALAXY S3 (processor clock speed 1.8 GHz) Here the objects move at medium speed
  • SAMSUNG GALAXY S4 (processor clock speed 2.3 GHz) Here the objects move slow

So briefly - the more powerful the smartphone are in the context of processor the slower the graphical objects move over screen - One could expext the opposite - that the frame-rate is higher for faster smartphones.

I present the backgroundthread below that holds the gameloop - I cannot see what I did wrong in this code - nor my teachers

public class MainThread extends Thread {

  private static final String TAG = MainThread.class.getSimpleName();

  private final static int  MAX_FPS = 50;   // Antal frames per sekund.
  private final static int  MAX_FRAME_SKIPS = 5;    // Antal förlorade frames.
  private final static int  FRAME_PERIOD = 1000 / MAX_FPS;

  private SurfaceHolder surfaceHolder;  
  private GamePanel gamePanel;

  private boolean running;
  public void setRunning(boolean running) {
    this.running = running;
 }

 public MainThread(SurfaceHolder surfaceHolder, GamePanel gamePanel) {
    super();
    this.surfaceHolder = surfaceHolder;
    this.gamePanel = gamePanel;
}

@Override
public void run() {
    Canvas canvas;
    Log.d(TAG, "Starting game loop");

    long beginTime;     // Cykelns starttid
    long timeDiff;      // Tiden det tar för en cykel
    int sleepTime;      // Cykel (update + render = timediff) + sleeptime => frameperiod. 
    int framesSkipped;  // Antalet förlorade frames. 

    sleepTime = 0;
    while (running) {
        canvas = null;
        try {
            canvas = this.surfaceHolder.lockCanvas();
            synchronized (surfaceHolder) {
                beginTime = System.currentTimeMillis();
                framesSkipped = 0;  

                try {
                    this.gamePanel.update_graphics();
                    this.gamePanel.render_graphics(canvas);             
                }
                catch (Exception e) {
                    System.out.println("fel uppstod i bakgrundstråden: " + e);
                }
                timeDiff = System.currentTimeMillis() - beginTime;
                sleepTime = (int)(FRAME_PERIOD - timeDiff);
                //System.out.println("sleeptime: " + sleepTime);
                if (sleepTime > 0) {
                    // Sleeptime ska vara positiv, med marginal. (Annars risk för hackig animation)
                    try {
                        Thread.sleep(sleepTime);    // Tråden sover en stund.
                    } catch (InterruptedException e) {}
                }

                while (sleepTime < 0 && framesSkipped < MAX_FRAME_SKIPS) {
                    // Används för att "komma igen/komma ikapp".
                    try {
                        this.gamePanel.update_graphics(); // endast updatering.
                    }
                    catch (Exception e) {
                        System.out.println("fel uppstod i bakgrundstråden: " + e);
                    }
                    sleepTime += FRAME_PERIOD;
                    framesSkipped++;
                }

                if (framesSkipped > 0) {
                    //Log.d(TAG, "Skipped:" + framesSkipped);
                }

            }
        } finally {

            if (canvas != null) {
                surfaceHolder.unlockCanvasAndPost(canvas);
            }
        }   // slut finally
    }
}   

}

Thanks in advance

Était-ce utile?

La solution

I am assuming your update_graphics method is based on a fixed delta time of FRAME_PERIOD.

The problem is your while (sleepTime < 0 && framesSkipped < MAX_FRAME_SKIPS) catch-up loop does not account for the time left over when it finishes catching up the model.

For example: Your FRAME_PERIOD is currently 20ms. Suppose the Galaxy Mini takes 2ms to run update_graphics and 19ms to run render_graphics. Now it will enter the catch-up loop with sleepTime=-1ms, so it will run update_graphics once. Then on the next time the while loop continues, it calls update_graphics and render_graphics again in your try block. By the time render_graphics finishes drawing this second time, it has been 23ms since the last time it finished, but update_graphics has been called twice. Therefore, it appears that 40ms of game time have passed in the space of 23ms, so your game would appear to be running 40/23 or 173% of the intended speed.

Meanwhile, on a fast device where sleep time is always positive, the game will always appear correct.

Also note, that the speed difference between Galaxy Mini and Galaxy S3 could have gone in the other direction (with S3 appearing faster) depending on relative times it takes for the update and render methods and their ratio to the FRAME_PERIOD.


But aside from the above simplified situation (which ignores Vsync), sleep time is never going to be applied exactly by the OS--it will vary plus or minus a few ms each time.

For a simple game that doesn't have a physics simulation, it is a simpler solution to use a variable delta time in your update method.

If you have a fixed delta time (which you may need for a game with physics, or for your game which is already using fixed delta time), this site presents a clear explanation of how to implement it.

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top