Page 1 of 1

Java Game (the fastest way to repaint bis) Using a thread to perform calculations Rate Topic: ***** 2 Votes

#1 pbl  Icon User is offline

  • There is nothing you can't do with a JTable
  • member icon

Reputation: 6372
  • View blog
  • Posts: 25,882
  • Joined: 06-March 08

Post icon  Posted 09 July 2009 - 08:34 PM

Since I post my first tutorial on the fastest way to repaint()

http://www.dreaminco...topic113451.htm

I received a lot of questions:
refreshing every 16 milli seconds, with the VBL, is nice but what happens if my program spends a few milli seconds to effectively to the repaint ? Then if my Timer calls my program every 16 milli seconds and I actually spend 4 milli seconds to actually do the calculations and the repaint() I will miss a VBL cycle.

This is true.

So here is a better deigned version.
The calculations are done in a different thread (included in the class GameCanvas)
The thread updates the balls positions (it performs the calculation)
The method that does the paint() by itself is called by another thread of the Swing Timer class every 15 milli seconds
If the calculations are terminated it does the repaint(), and just the repaint, it does not waste time performing calculations.
The thread that perform calculation does not perform them if a repaint() has not refresh the display

The class used for the calculations of the sin and the cos. Quite the same as the one in the previous post.
// for speed purpose I wont call the sin and cos math fuction every time
// (these functions are a little time consuming)
// as I work with degrees (from 0 to 359 as int) I build a static table of sin and cos
// for these 360 values then we just have to fetch these values 
// also no need to convert from degrees to radian each time
public class SinCosGravity {

	// for how much I increment for every 30 degrees it is a "gravity"
	// this the number by which I will mutiply the speed of the balls (in degree) according
	// to 30 degrees slices
	// so from 0 to 30 degrees for 30 to 60 degrees fro 60 to 90 degrees.....
	static final int[] weight = {2, 2, 1, 1, 2, 2, 3, 3, 4, 4, 3, 3};
	// static arrays shered by all instances
	static double[] sinValue = new double[360];
	static double[] cosValue = new double[360];
	static int[] gravityValue = new int[360];
	
	// all instances share this array build in a static initializer
	static {
		for(int i = 0; i < 360; i++) {
			// have to convert in radian (and negate them if I go counter clockwise)
			double radiant = -Math.toRadians((double) i);
			// because Java sin/cos method use radian
			sinValue[i] = Math.sin(radiant);
			cosValue[i] = Math.cos(radiant);
			gravityValue[i] = weight[i / 30];
		}
	}
	// ok just a constructor
	SinCosGravity() {	
	}
	
	// retunrs sin for an int degree passed as parameter
	double sin(int degree) {
		return sinValue[degree % 360];
	}
	// retunrs cos for an int degree passed as parameter
	double cos(int degree) {
		return cosValue[degree % 360];
	}
	// retuns gravity for an int degree passed as parameter
	int gravity(int degree) {
		return gravityValue[degree % 360];
	}
}



The main class, the one extending JFrame, has now a WindowListener to it can stop the threads when the program exists

import java.awt.BorderLayout;
import java.awt.event.WindowEvent;
import java.awt.event.WindowListener;

import javax.swing.*;

public class GameFrame extends JFrame implements WindowListener {
	// number of balls and balls speed in degree
	private static final int NB_BALLS = 10, DEF_SPEED = 1;
	
	GameCanvas canvas;
	
	GameFrame() {
		// frame description
		super("Rotating balls");
		// our Canvas 
		canvas = new GameCanvas(NB_BALLS, DEF_SPEED);
		add(canvas, BorderLayout.CENTER);
		// set it's size and make it visible
		setSize(600+12, 600);
		// have to know when I will be stop by user to stop the thread
		addWindowListener(this);
		// show us
		setVisible(true);		
		// now that is visible we can tell it that we will use 2 buffers to do the repaint
		// before being able to do that, the Canvas as to be visible
		canvas.createBufferStrategy(2);
		// start the thread that upgrades balls position
		Thread thread = new Thread(canvas);
		thread.start();
	}
	// just to start the application
	public static void main(String[] args) {
		// instance of our stuff
		SwingUtilities.invokeLater(new Runnable() {
			public void run() {
				new GameFrame();
			}
		});
	}
	
	//my windows listeners
	// we need at least that one to stop the threads performing the calculation and doing the repaint
	public void windowClosing(WindowEvent e) {
		canvas.threadStop = true;
		dispose();
	}
	public void windowActivated(WindowEvent e) {}
	public void windowClosed(WindowEvent e) {}
	public void windowDeactivated(WindowEvent e) {}
	public void windowDeiconified(WindowEvent e) {}
	public void windowIconified(WindowEvent e) {}
	public void windowOpened(WindowEvent e) {}	
}



The GameCanvas class now extends Runnable so calculations are performed in a different thread. I have also add an inner class Ball to get ride of the arrays for degree position, ... more OO :) Now a single array of Ball rather than an array for every state that I have to keep for every ball.

import java.awt.*;
import java.awt.image.BufferStrategy;
import java.util.Random;

import javax.swing.Timer;

// OK this the class where we will draw and where computations
// will be done in another thread
public class GameCanvas extends Canvas implements Runnable {
	// back color LightYellow
	Color backColor = new Color(255, 255, 150);
	// my Swing timer
	Timer timer;
	// the deltaDegree we will apply to each balls times the gravity for the angle
	int deltaDegree;
	// the balls that will round
	Ball[] balls;
	// the size of the ball in pixels
	final int BALLSIZE = 48, HALF_SIZE = BALLSIZE / 2, QUARTER_SIZE = HALF_SIZE / 2;
	// for the computation of the postion and the repaint
	Dimension size;
	int centerX, centerY;
	// a boolean to synchronize computation and drawing
	boolean computationDone = false;
	// a boolean to ask the treat to stop
	boolean threadStop = false;
	
	// this is a Canvas but I wont't let the system when to repaint it I will do it myself
	GameCanvas(int nbBalls, int deltaDegree) {
		super();
		this.deltaDegree = deltaDegree;
		// so ignore System's paint request I will handle them
		setIgnoreRepaint(true);
		balls = new Ball[nbBalls];
		Random ran = new Random();						// for random color
		// determine initial position of the balls
		int deltaEach = 360 / nbBalls;
		for(int i = 0; i < nbBalls; i++) {
			// determine a different color for the ball
			Color color = new Color(ran.nextInt(150), ran.nextInt(150), ran.nextInt(150));
			// build balls at different positions evenly distributed
			balls[i] = new Ball(i * deltaEach, color);
		}

		// build Chrono that will call me 
		Chrono chrono = new Chrono(this);
		// ask the chrono to calll me every 60 times a second so every 16 ms (set say 15 for the time it takes to paint)
		timer = new Timer(15, chrono);
		timer.start();
	}
	
	// my own paint method that repaint off line and switch the displayed buffer
	// according to the VBL
	public synchronized void myRepaint() {
		// computation a lot longer than expected (more than 15ms)... ignore it
		if(!computationDone) {
			return;
		}

		// ok doing the repaint on the not showed page
		BufferStrategy strategy = getBufferStrategy();
		Graphics graphics = strategy.getDrawGraphics();
		// erase all what I had
		graphics.setColor(backColor);	
		graphics.fillRect(0, 0, size.width, size.height);
		// now we draw the center
		graphics.setColor(Color.BLACK);
		// ce center
		graphics.fillOval(centerX + QUARTER_SIZE, centerY + QUARTER_SIZE, HALF_SIZE, HALF_SIZE);
		// repaint all the balls
		for(int i = 0; i < balls.length; i++) {
			balls[i].myRepaint(graphics);
		}
		if(graphics != null)
			graphics.dispose();
		// show next buffer
		strategy.show();
		// synchronized the blitter page shown
		Toolkit.getDefaultToolkit().sync();
		// ok I can be called again
		computationDone = false;
	}
	
	public void run() {
		// in double for sin/cos calculation
		double width, height;
		
		// loop forever
		for(;;) {
			// my father ask me to stop
			if(threadStop) {
				// stop the timer called every 15ms
				timer.stop();
				// and exit myself from the run method
				return;
			}
			// test if the computation where already done without a paint
			if(computationDone) {
				try {
					Thread.sleep(1L);		// if it the case sleep a milli
					continue;				// try again
				}
				catch(Exception e) {
				}
			}
			// else compute the new position
			size = this.getSize();
			centerX = size.width / 2 - HALF_SIZE;
			centerY = size.height / 2 - HALF_SIZE;
			width = size.width / 3.0;
			height = size.height / 3.0;
			for(int i = 0; i < balls.length; i++) {
				balls[i].computePos(width, height, centerX, centerY);
			}
			// tell myRepaint() that new positions have been calculated
			computationDone = true;
		}
	}
	
	// inner class that describes a ball
	class Ball {
		// color of the ball
		Color color;
		// degree of the ball
		int degree;
		// ball x,y positions
		int x, y;
		// to have the sin/cos/gravity
		SinCosGravity scg = new SinCosGravity();

		// constructor that receives the initial degree position
		Ball(int degree, Color color) {
			// save initial position
			this.degree = degree;
			// save that ball color
			this.color = color;
		}
		
		// calculate next postion of the ball
		void computePos(double width, double height, int centerX, int centerY) {
			// gravity factor
			int g = scg.gravity(degree);
			// by how much I will increase my degrees
			g *= deltaDegree;
			// increments
			degree += g;
			// wrap around
			degree %= 360;
			// get sin and cos times size
			double sin = scg.sin(degree) * height;
			double cos = scg.cos(degree) * width;
			// compute to int for pixels
			x = (int) Math.floor(cos);
			y = (int) Math.floor(sin);
			// from the center of the screen
			x += centerX;
			y += centerY;
		}
		
		// draw the ball at the last computed position
		void myRepaint(Graphics g) {
			// set graphics to color of ball
			g.setColor(color);
			// draw it
			g.fillOval(x, y, BALLSIZE, BALLSIZE);			
		}
	}
}



And finally the Chrono class unchanged. This is the class that calls myRepaint() to do only the repaint


import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;

/** Will be called at each blitter page */
public class Chrono implements ActionListener {

	GameCanvas gc;
	// constructor that receives the GameCanvas that we will repaint every 60 milliseconds
	Chrono(GameCanvas gc) {
		this.gc = gc;
	}
	// calls the method to repaint the anim everytime I am called
	public void actionPerformed(ActionEvent e) {
		gc.myRepaint();
	}

}



Enjoy

Is This A Good Question/Topic? 4
  • +

Replies To: Java Game (the fastest way to repaint bis)

#2 cfoley  Icon User is online

  • Cabbage
  • member icon

Reputation: 1096
  • View blog
  • Posts: 2,636
  • Joined: 11-December 07

Posted 19 July 2011 - 07:09 AM

This series of tutorials just helped me again. My game wasn't running smoothly but my profiler told me that nothing was taking too long. A quick look at your tutorial and I notices this line which I hadn't come across before.

Toolkit.getDefaultToolkit().sync();


Added it in and my game is running perfectly now. :)
Was This Post Helpful? 0
  • +
  • -

Page 1 of 1