I’m new to both android and game development and have been trying to create a pong clone to get to grips with everything. I have a “PongView” class which extends SurfaceView and a “PongThread” which extends thread.
I have found a way to detect if the ball or “bomb” has gotten past the paddle and reached the wall behind it. I’m not sure if my approach is the best (opinions welcome) but it seems to work as i have a little toast message displaying game over if it happens. Now i want to set it up so that, as well as displaying game over, the view restarts (or whatever the more appropriate term is) so that the bomb is in the center of the screen again and the user can play another round.
Due to inexperience im not really sure how i should handle the thread and what methods to call in the view to achieve this.
I’ve had several stabs at trying to figure it out but i have no idea if i’m on the right track, it’ll probably be pretty clear from my code that i don’t know what i’m doing. (at the moment my thread stops and my surface is just stuck displaying the last frame). Any advice will be welcome!
PongView.java: (scroll down to the update method to get to the section i’m trying to figure out)
package biz.hireholly.pirateponggame;
import android.content.Context;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Color;
import android.os.Handler;
import android.view.MotionEvent;
import android.view.SurfaceHolder;
import android.view.SurfaceHolder.Callback;
import android.view.SurfaceView;
import android.widget.Toast;
public class PongView extends SurfaceView implements Callback{
/*GLOBALS*/
private static final String TAG = PongView.class.getSimpleName();
private PongThread pongThread;
private Bomb bomb;
private Paddle paddleA;
private int viewWidth;
private int viewHeight;
Handler handler;
boolean gameOver;
public PongView(Context context) {
super(context);
// sets current class as the handler for the events happening on the surface
getHolder().addCallback(this);
//instantiate thread, pass the current holder and view so that the thread can access them
pongThread = new PongThread(getHolder(), this);
//make the GameView focusable so it can handle events
setFocusable(true);
handler = new Handler();
}
//CURRENTLY WHERE IM INITIALISING SPRITES
@Override
public void surfaceChanged(SurfaceHolder holder, int format, int width,
int height) {
//INITIALISE viewWidth and viewHeight here
//so that they can be passed as parameters
viewWidth = getWidth();
viewHeight = getHeight();
//NEW BOMB, INITIAL BITMAP
bomb = new Bomb(BitmapFactory.decodeResource(
getResources(), R.drawable.bombsprite),
getWidth() /2, getHeight() /2); //draws in middle
//bombs random starting direction
bomb.getSpeed().randomiseDirection();
//NEW PADDLE, INITIAL BITMAP
paddleA = new Paddle(BitmapFactory.decodeResource(
getResources(), R.drawable.paddlesprite),
getWidth() /2, getHeight() -(getHeight()/30) ); //middle bottom screen
}
@Override
public void surfaceCreated(SurfaceHolder holder) {
pongThread = new PongThread(getHolder(), this); //needed if user exits and returns
pongThread.setRunning(true);
//.start() == PongThread.run() except PongThread does all the work
pongThread.start();
}
@Override
public void surfaceDestroyed(SurfaceHolder holder) {
boolean retry = true;
while(retry){
try{
//tells thread to shut down and waits for it to finish. Clean shutdown
pongThread.setRunning(false);
pongThread.join();
retry = false;
} catch(InterruptedException e){
//try again shutting down the thread
}
}
}
@Override
public boolean onTouchEvent(MotionEvent event)
{
paddleA.onTouchEvents(event, viewWidth);
return true;
}
/**
* CHECKS FOR COLLISIONS, CALLS OBJECT UPDATE METHODS
*/
public void update(){
gameOver = false;
//CHECK IF NULL as objects aren't created till surface change
if(bomb != null && paddleA != null){
bomb.handlePaddleCollision(paddleA);
gameOver = bomb.handleWallCollision(viewWidth, viewHeight);
//object physics updates
bomb.update();
//paddleA.update();
}
//WHEN handleWallColision returns true, one player scored and its game over.
if( gameOver ){
//handler is needed as alerts and toasts can only be made in the ui thread,
handler.post(new Runnable(){
public void run(){
Toast.makeText(getContext(), "GAME OVER", Toast.LENGTH_LONG).show();
}
});
/*THIS IS WHERE I'M TRYING TO RESTART THE GAME*/
///////////////////////////////////////////////////////
//could maybe call surfaceDestroyed here instead?
boolean retry = true;
while(retry){
try{
//tells thread to shut down and waits for it to finish. Clean shutdown
pongThread.setRunning(false);
pongThread.join();
retry = false;
} catch(InterruptedException e){
//try again shutting down the thread
}
}
//copied from surfaceCreated
pongThread = new PongThread(getHolder(), this); //needed if user exits and returns
pongThread.setRunning(true);
//.start() == PongThread.run() except PongThread does all the work
pongThread.start();
////////////////////////////////////////////////////////
}
}
protected void render(Canvas canvas)
{
canvas.drawColor(Color.GREEN);
bomb.draw(canvas);
paddleA.draw(canvas);
}
}
PongThread.java:
package biz.hireholly.pirateponggame;
import android.graphics.Canvas;
import android.util.Log;
import android.view.SurfaceHolder;
public class PongThread extends Thread {
/*GLOBALS*/
private static final String TAG = PongThread.class.getSimpleName();
//desired frames per second
private final static int MAX_FPS = 30;
//maximum number of frames to be skipped if drawing took too long last cycle
private final static int MAX_FRAME_SKIPS = 5;
//cycle period (cycle = update,draw,if excess time sleep)
private final static int CYCLE_PERIOD = 1000 / MAX_FPS;
//Surface holder that can access the physical surface
private SurfaceHolder surfaceHolder;
//the view that actually handles inputs and draws to the surface
private PongView pongView;
//flag to hold game state
private boolean running;
public void setRunning(boolean running){
this.running = running;
}
/**Takes PongView instance and surfaceHolder as parameters
* so that we can lock the surface when drawing.
* @param surfaceHolder
* @param pongView
*/
public PongThread(SurfaceHolder surfaceHolder, PongView pongView){
super();
this.surfaceHolder = surfaceHolder;
this.pongView = pongView;
}
@Override
public void run(){
//canvas is a surface bitmap onto which we can draw/edit its pixels
Canvas canvas;
Log.i(TAG, "Starting pong thread");
long beginTime =0; // time when cycle began
long timeDiff =0; // time it took for the cycle to execute
int sleepTime =0; // milliseconds to sleep (<0 if drawing behind schedule)
int framesSkipped =0; // number of frames skipped
while (running) {
canvas = null;
//try locking canvas, so only we can edit pixels on surface
try{
canvas = this.surfaceHolder.lockCanvas();
//sync so nothing else can modify while were using it
synchronized (surfaceHolder){
beginTime = System.currentTimeMillis();
framesSkipped = 0; //reset frame skips
//UPDATE game state
this.pongView.update();
//RENDER state to screen, draws the canvas on the view
this.pongView.render(canvas);
//calculate how long cycle took
timeDiff = System.currentTimeMillis() - beginTime;
//calculate potential sleep time
sleepTime = (int)(CYCLE_PERIOD - timeDiff);
//sleep for remaining cycle
if (sleepTime >0){
try{
//saves battery
Thread.sleep(sleepTime);
} catch (InterruptedException e){}
}
//if there's no leftover cycle time then we're running behind
while (sleepTime < 0
&& framesSkipped < MAX_FRAME_SKIPS){
//we need to catch up so we update without rendering
this.pongView.update();
sleepTime += CYCLE_PERIOD;
framesSkipped++;
}
}
} finally{
//in case of exception,
//surface is not left in an inconsistent state
if (canvas != null){
surfaceHolder.unlockCanvasAndPost(canvas);
}
}//end finally
}
}
}
If you want to “reboot” the thread so to speak, you have two options. Either start a fresh thread every time the game is supposed to restart or construct your initial thread to loop indefinitely and wait to be notified when it is able to run. Like so:
In this scenario, you start the thread initially and then signal it to actually start with
signal.notify()(signal must be an object visible to both threads). Then, when the game ends, the thread will return to the call towait. Then simply notify it again in order to restart from the beginning.