Loop and FPS (one more newbie problem...)

Hi all

I’ve a problem with my game loop, sometimes the render is jerking, the drawing is not perfect

  • In my game, the background is scrolling to the left indefinitely, the drawing should be perfect, but the render is not constant (regular)
  • The garbage collector is never in action
  • The background is composed by sprite (bmp) but even with only one bmp it is still jerking
  • I’ve tried the loop on http://www.java-gaming.org/topics/game-loops/24220/view.html and this loop is also jerking (less than mine…)

I’m quasi sure that the loop I’m using is not perfect in term of timing (sleeping thread)

can you help me please?

here is the code I use:




public class GameLoopThread extends Thread
{
	private final static int 	MAX_FPS = 60;					// desired fps
	private final static int	MAX_FRAME_SKIPS = 5;			// maximum number of frames to be skipped
	private final static int	FRAME_PERIOD = 1000 / MAX_FPS;	// the frame period
	private static boolean running;									// flag to hold game state 

	private SurfaceHolder surfaceHolder;	// Surface holder that can access the physical surface
	private GameView gameview;		// The actual view that handles inputs and draws to the surface

	public GameLoopThread(SurfaceHolder surfaceHolder, GameView gameview)
	{
		super();
		this.surfaceHolder = surfaceHolder;
		this.gameview = gameview;
	}
	
	public static void setRunning(boolean runningstate)
	{
		running = runningstate;
	}

	@Override
	public void run()
	{
		Canvas canvas;

		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 = surfaceHolder.lockCanvas();
				synchronized (surfaceHolder)
				{
					beginTime = System.currentTimeMillis();
					framesSkipped = 0;	// resetting the frames skipped

					
					
					
					gameview.update();		// update game state 
					gameview.render(canvas);	// render state to the screen draws the canvas on the panel

					
					
					
					timeDiff  = System.currentTimeMillis() - beginTime;		// calculate how long did the cycle take
					sleepTime = (int)(FRAME_PERIOD - timeDiff);		// calculate sleep time

					if (sleepTime > 0)
					{
						// if sleepTime > 0 we're OK
						try
						{
							Thread.sleep(sleepTime);	// send the thread to sleep for a short period very useful for battery saving
						}
						catch (InterruptedException e) {}
					}


					while (sleepTime < 0 && framesSkipped < MAX_FRAME_SKIPS)
					{
						// we need to catch up
						gameview.update(); // update without rendering
						sleepTime += FRAME_PERIOD;	// add frame period to check if in next frame
						framesSkipped++;
					}
				}
			}
			finally
			{
				// in case of an exception the surface is not left in an inconsistent state
				if (canvas != null)
				{
					surfaceHolder.unlockCanvasAndPost(canvas);
				}
			}	// end finally
		}

	}

}



thanks for having reading this

Bests regards

why are you doing this?


 timeDiff  = System.currentTimeMillis() - beginTime;      // calculate how long did the cycle take
               sleepTime = (int)(FRAME_PERIOD - timeDiff);      // calculate sleep time

               if (sleepTime > 0)
               {
                  // if sleepTime > 0 we're OK
                  try
                  {
                     Thread.sleep(sleepTime);   // send the thread to sleep for a short period very useful for battery saving
                  }
                  catch (InterruptedException e) {}
               }

You should not make your thread sleep… but rather adjust how much you move your models based on relative time.

This may be the cause of your jerkiness.

because sometimes sleepTimes is < 0

And how to do this?

any example please?

say you want to move forward at a rate of 10 feet per second.

but only 0.0001 of a second has gone by since you last drew the screen.

thus deltaTime * rateOfMovement * transformation = deltaPosition.
currentPostion + deltaPosition = newPosition;

if deltaTime is 0… your newPosition and currentPosition will be equal.

Follow this approach instead of waiting for a fixed amount of time to have past.
Youll end up with a smooth animation this way.

j.

thanks for the reply, but still jerking, is my code so wrong?

tested on a galaxy note 2, the garbage collector seems not be the problem (cause no log about it)

Is there some specifics implementations for ANDROID to have a smooth render?

Best regards

The Game Loop:


import android.graphics.Canvas;
import android.util.Log;
import android.view.SurfaceHolder;

// 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 GameLoopThread extends Thread
{
	private static final String TAG = GameLoopThread.class.getSimpleName();

	private SurfaceHolder surfaceHolder;   // Surface holder that can access the physical surface
	private GameView      gameview;       // The actual view that handles inputs and draws to the surface
	private Canvas        canvas;
	
	final   int            TARGET_FPS = 40;
	final   long           OPTIMAL_TIME = 1000000000 / TARGET_FPS;   
	private static boolean running;									 // flag to hold game state 

	int     lastFpsTime;
	int     fps;


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

	public static void setRunning(boolean runningstate)
	{
		running = runningstate;
	}

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

		long lastLoopTime = System.nanoTime();

		while (running)		// keep looping round til the game ends
		{
			long now = System.nanoTime();
			canvas   = null;

			try			// try locking the canvas for exclusive pixel editing in the surface
			{
				canvas = surfaceHolder.lockCanvas();
				synchronized (surfaceHolder)
				{
					// work out how long its been since the last update, this will be used to calculate how far the entities should move this loop
					long updateLength = now - lastLoopTime;
					lastLoopTime      = now;
					double delta      = updateLength / ((double)OPTIMAL_TIME);

					// update the frame counter
					lastFpsTime += updateLength;
					fps++;

					// update our FPS counter if a second has passed since we last recorded
					if (lastFpsTime >= 1000000000)
					{
						System.out.println("(FPS: "+fps+")");
						lastFpsTime = 0;
						fps         = 0;
					}

					gameview.update(delta);		// update game state 
					gameview.render(canvas);	// render state to the screen draws the canvas on the panel
					
					int sleeptime = (int)((lastLoopTime-System.nanoTime() + OPTIMAL_TIME)/1000000);  // remember this is in ms, whereas our lastLoopTime etc. vars are in ns.

					if(sleeptime>0)
					{
						try
						{
							Thread.sleep( sleeptime );
						}
						catch (InterruptedException e) {}
					}
					else
					{
						// WHAT TO DO HERE??? nothing ???
						//Log.d("sleeptime" , String.valueOf(sleeptime));
					}
				}
			}
			finally
			{
				if (canvas != null)				// in case of an exception the surface is not left in an inconsistent state
				{
					surfaceHolder.unlockCanvasAndPost(canvas);
				}
			}
		}
	}

}

The Sprite class


import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.ArrayList;
import android.content.Context;
import android.content.res.AssetManager;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.util.Log;

public class SingleBlock {

	private Context context;

	// sprites

	public ArrayList<Bitmap> bmp_sprite = new ArrayList<Bitmap>();

	// data

	public static double x = 0;           // x position of the sprite
	public static double y = 50;          // y position of the sprite
	public        int    speed = 10;      // speed of the sprite
	public        int    direction = -1;  // -1 means moving to the left, and 1 means movingto the right

	//________________________
	// constructeur           |
	//________________________|

	public  SingleBlock(Context context)
	{
		this.context				= context;
		LoadSprite();
	}

	public void update(double delta)
	{
		// movethe sprite

		if(direction == -1) { x-= speed*delta; }
		if(direction == 1)  {x+= speed*delta;  }

		//change direction

		if(x<=0)    { direction = 1; }
		if(x>=1000) { direction = -1; }
	}

	//________________________
	// Draw                   |
	//________________________|

	public void Draw(Canvas canvas)
	{
		canvas.drawBitmap(bmp_sprite.get(1), (int)x , (int)y , null);
	}

	//________________________
	// LoadSprite             |
	//________________________|
	public void LoadSprite()
	{
		for(int i = 0;i<= 2; i++)
		{
			AssetManager assetManager = context.getAssets();
			InputStream inputStream   = null;
			try
			{
				inputStream = assetManager.open(ScreenConf.asset_sub_directory + "/" + "block" + String.valueOf(i) + ".png");
			}
			catch (IOException e) { e.printStackTrace(); }

			Bitmap bmp_temp = BitmapFactory.decodeStream(inputStream);

			bmp_sprite.add(bmp_temp);
		}
	}
}

And all other class:



import android.annotation.SuppressLint;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.util.Log;
import android.view.MotionEvent;
import android.view.SurfaceHolder;
import android.view.SurfaceView;

// This is the main surface that handles the ontouch events and draws the image to the screen.

public class GameView extends SurfaceView implements SurfaceHolder.Callback {

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

	private        GameLoopThread gameLoopThread;
	public static  Context        context;
	private static GameView       gameview;
	private static SingleBlock   singleblock;

	public GameView(Context context)
	{
		super(context);

		this.context = context;
		gameview     = this;

		getHolder().addCallback(this);                           // adding the callback (this) to the surface holder to intercept events
		gameLoopThread = new GameLoopThread(getHolder(), this);  // create the game loop thread
		setFocusable(true);                                      // make the GamePanel focusable so it can handle events
	}

	@Override
	public void surfaceChanged(SurfaceHolder holder, int format, int width, int height)
	{
	}

	@Override
	public void surfaceCreated(SurfaceHolder holder)
	{
		// at this point the surface is created and we can safely start the game loop

		new ScreenConf(gameview);

		StartNewGame();

		GameLoopThread.setRunning(true);
		gameLoopThread.start();
	}

	@Override
	public void surfaceDestroyed(SurfaceHolder holder)
	{
		Log.d(TAG, "Surface is being destroyed");
		// tell the thread to shut down and wait for it to finish  this is a clean shutdown
		boolean retry = true;
		while (retry)
		{
			try
			{
				gameLoopThread.join();
				retry = false;
			}
			catch (InterruptedException e)
			{
				// try again shutting down the thread
			}
		}
		Log.d(TAG, "Thread was shut down cleanly");
	}

	public static void StartNewGame()
	{
		singleblock = new SingleBlock(context);
	}

	public void render(Canvas canvas)
	{
		if (canvas != null)
		{
			canvas.drawColor(Color.BLACK);
			singleblock.Draw(canvas);
		}
	}

	// This is the game update method. It iterates through all the objects and calls their update method if they have one or calls specific engine's update method./
	public void update(double delta)
	{
		singleblock.update(delta);
	}

}



import android.app.Activity;
import android.os.Bundle;
import android.util.Log;
import android.view.Window;
import android.view.WindowManager;

public class MainActivity extends Activity
{
	/** Called when the activity is first created. */

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


	//##################################
	//  onCreate                       #
	//##################################
	@Override
	public void onCreate(Bundle savedInstanceState)
	{
		super.onCreate(savedInstanceState);

		requestWindowFeature(Window.FEATURE_NO_TITLE);																	// requesting to turn the title OFF
		getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, WindowManager.LayoutParams.FLAG_FULLSCREEN);	// making it full screen

		setContentView(new GameView(this));        // set our GameView as the View

		Log.d(TAG, "View added");
	}

	//##################################
	//  onResume                       #
	//##################################
	@Override
	public void onResume()
	{
		Log.d(TAG, "onResume...");
		super.onResume();  // Always call the superclass method first
	}
	//##################################
	//  onDestroy                      #
	//##################################
	@Override
	protected void onDestroy()
	{
		Log.d(TAG, "Destroying...");
		super.onDestroy();
	}

	//##################################
	//  onStop                         #
	//##################################
	@Override
	protected void onStop()
	{
		Log.d(TAG, "Stopping...");
		super.onStop();
	}
}