Tutorial 4

From Java Tutorials

Jump to: navigation, search

So far we have covered key and mouse input, and done things when the user clicks a mouse or presses a key. Clearly this isn't very good for a game that we want to keep running all the time. How do we get the applet to keep running and doing things in between key presses and mouse clicks? Or what about if there's not input at all, you just want to run an animation in the applet? The answer is to use a thread.

Contents



[edit] Animations

If we wanted to have an animation of a ball bouncing by itself, and have no interaction with it at all, then it's highly likely that in between frames, we want the applet to pause for some period of time, otherwise the ball will be zooming across the screen in a blur, and that's not a very useful animation. The obvious solution is to waste time doing nothing, for example:

	while (true)
	{
		drawNextFrame();
		for (int i=0; i<1000000; ++i)
			; // do nothing - just waste time!
	}

Unfortunately, computers run at different speeds, and so the loop counting to 1000000 will take a different length of time to run on different computers! That is probably not what you want. What we would like to do instead is have something like:

	while (true)
	{
		drawNextFrame();
		doNothingFor20milliseconds();
	}

If we assume that it takes zero time to draw the frame, then if we wait 20ms between frames, that gives us a frame rate of 50 frames per second which is acceptable, and standard across different computers running at different speeds.

[edit] Threads

So what are threads? Basically, a thread, or thread of execution, is a series of commands that can either run independently of, or in partnership with, other threads. In other words, it's like a mini-program that runs simultaneously with other programs, and may or may not interact with these other programs. (I'm sure that some computer science purist will disagree with that definition, but to the layman that's the essence of it.)

Why are threads useful? In this case, we can tell our thread to pause for a certain period of time - in animations and games, that's exactly what we want to happen between frames. Of course, threads have more uses that being able to go to sleep, in a networked game for example, you may have one thread running the game, and another thread listening to the network, and although these two threads run independently, they can interact with each other.

How do we use threads? Easy. In the java.lang package, there's an interface called Runnable that is implemented by any classes that want to create their own threads. The Runnable interface has just one method that has to be implemented, called void run() - this is called when the thread starts running. Good naming huh.

Here is some code for a basic applet that uses a thread:

public class ThreadedApplet implements Runnable
{
	private Thread theThread;

	public void init()
	{
		theThread = new Thread(this);
		theThread.start();
	}

	public void run() // called by theThread.start()
	{
		// do something
	}
}

The line theThread = new Thread(this); indicates that we want to use the run() method provided by this class.

So that's all very good, but in our game or animation, how do we tell the thread to stop for say 20ms in between frames? The Thread class has a method called sleep() which tells the thread to stop execution for the specified period of time (in milliseconds). So by using the following run() method:

	public void run()
	{
		while (true)
			try
			{		
				// do something
				theThread.sleep(20);
			} catch (InterruptedException _ex)
			{ /* do nothing if this occurs */ }
	}

the thread will run forever, and in each iteration of the loop, it will do something (eg, draw the next frame of the game), and then go to sleep for 20ms before starting the loop again. The sleep() method can throw an InterruptedException if it is interrupted while the thread is sleeping - there's no need to do anything in this case in these tutorials. However, the Exception still needs to be caught. For more information on Exceptions, see http://java.sun.com/docs/books/tutorial/essential/exceptions/index.html



[edit] Exercise 1

Your task here is to create an applet with a thread in it, that at each frame, draws the frame number. You can use the FontMetrics methods previously learned to centre the text in the applet as well. Tell your applet to sleep for 100 milliseconds between frames. Note that you don't have to import the java.lang package (containing the Thread class) because it's imported by default. You will need to import other packages though...

Sample solution:

Sample code: __HIDER__
import java.awt.*;
import java.applet.*;

public class Tutorial_4a extends Applet implements Runnable {
	
	private Thread theThread;
	
	public void init() {
		theThread = new Thread(this);
		theThread.start();
	}

	public void run()
	{
		// Remember the frame number so that we can display it in each frame.
		int frame = 0;
		
		while (true) // do this forever!
		{
			// get a graphics object for the applet
			Graphics g = getGraphics();	
			
			// clear the screen
			g.setColor(Color.white);
			g.fillRect(0, 0, getWidth(), getHeight());
			
			// make a nice big bold font
			g.setFont(new Font("SansSerif", Font.BOLD, 36));
			FontMetrics fm = g.getFontMetrics(); // and get the metrics for it
			
			// make the colour black so we can see the text!
			g.setColor(Color.black);
			// this is a temp String containing the current frame number
			String theString = ""+frame;
			// draw the text, centred in the applet.
			g.drawString(theString, (getWidth()-fm.stringWidth(theString))/2,
				(getHeight()-fm.getHeight())/2);
				
			++frame; // increment the frame number
				
			// now, pause between frames.
			// What happens if you change the 100 to some other number?
			try
			{
				theThread.sleep(100);
			} catch (InterruptedException _ex) { /* do nothing */ }
		}
	}
}

[edit] Exercise 2

Modify your code such that it doesn't do anything until you click the mouse on it.

Sample solution (click inside the box below to start the applet!)

Sample code: __HIDER__

The following code only contains changes from the code given for Exercise 1.

import java.awt.event.*;

public class Tutorial_4a extends Applet implements Runnable, MouseListener {
	
	private Thread theThread;
	
	public void init()
	{
		addMouseListener(this);
	}
	
	public void mouseClicked(MouseEvent e) {}
	public void mouseReleased(MouseEvent e) {}
	public void mouseEntered(MouseEvent e) {}
	public void mouseExited(MouseEvent e) {}
	public void mousePressed(MouseEvent e)
	{
		if (theThread == null) // thread not initialised yet
		{
			theThread = new Thread(this); // make a new thread
			theThread.start(); // and start it
		}	
	}

Use the same run() method as before.

[edit] Exercise 3

The third task in this tutorial is to create a simple animation of a ball bouncing in the applet. When the applet is initialised, wait for the user to click the applet before starting the animation. Make the ball's radius 1/20th of the applet's width, and when it hits a wall, make it bounce off the wall in the opposite direction. Use Math.random() to give the ball a randomly generated starting location, and starting velocity.

Sample solution (click inside the box below to start the applet - reload this page and then click the applet again to see a different random bouncing ball)

Sample code: __HIDER__
import java.awt.*;
import java.applet.*;
import java.awt.event.*;

public class Tutorial_4c extends Applet implements Runnable, MouseListener {
	
	private Thread theThread;
	
	// The ball's current location (the centre of the ball)
	private int x, y;
	
	// the ball's current velocity in the x and y dir respectively
	private int dx, dy;
	
	// the radius of the ball
	private int ballRad;
	
	// the width, height of the applet (in pixels)
	private int nWidth, nHeight;
	
	public void init()
	{
		// get the width and height of the applet
		nWidth = getWidth(); nHeight = getHeight();
		
		//set the ball's radius
		ballRad = nWidth/20;
		
		// add the mouse listener - wait for a click before starting the thread
		addMouseListener(this);
	}
	
	// these methods aren't required
	public void mouseClicked(MouseEvent e) {}
	public void mouseReleased(MouseEvent e) {}
	public void mouseEntered(MouseEvent e) {}
	public void mouseExited(MouseEvent e) {}
	
	// when the mouse is pressed
	public void mousePressed(MouseEvent e)
	{
		// if the thread is not initialised yet
		// don't start multiple threads.
		if (theThread == null) 
		{
			theThread = new Thread(this); // make a new thread
			theThread.start(); // and start it - calls run()
		}	
	}

	// don't worry about using this method because run() will redraw
	// the applet within 20ms anyway.
	public void paint(Graphics g) {}
	
	// This is a dedicated method to draw the next frame to the screen.
	// Whilst this code could all be put inside run(), IMHO it's much
	// easier to separate the code, especially when the code gets longer.
	public void nextFrame()
	{
		// get a handle to draw to the applet.
		Graphics g = getGraphics();
		
		// fill the whole applet in white to clear it.
		// This is why the applet looks flickery :P
		g.setColor(Color.white);
		g.fillRect(0, 0, getWidth(), getHeight());
		
		// The ball's current position is now the old position
		int oldx = x; int oldy = y;
		
		// Update the ball's position.
		x += dx; y += dy;
		
		// If the ball is too close to the right wall (ie, it's collided with it)
		// move it back off the wall, and change the X velocity direction
		if (x > nWidth-ballRad)
		{
			x = nWidth-ballRad; // move it back so it's just touching the ball.
			dx = -dx; // reverse the X velocity
		}
		// same for if the ball hits the left wall
		if (x<ballRad) {x=ballRad;dx=-dx;}
		// and if the ball hits the bottom wall
		// (remember, y=0 is at the top of the applet!)
		if (y>nHeight-ballRad) {y=nHeight-ballRad;dy=-dy;}
		// and if the ball hits the top wall
		if (y<ballRad) {y=ballRad;dy=-dy;}
		
		// now, just draw the ball at it's current location
		g.setColor(Color.red);
		// need to take ballRad from x and y because (x,y) is the ball's centre
		g.fillOval(x-ballRad, y-ballRad, ballRad*2, ballRad*2);
	}
	
	public void run()
	{
		// initialise the ball's position randomly
		// Math.random() returns a random number from 0 to 1, so
		// multiplying by nWidth gives a number from 0 to nWidth.
		x = (int)(Math.random()*nWidth);
		y = (int)(Math.random()*nHeight);
		
		// initialise the ball's velocity
		dx = (int)(Math.random()*ballRad/2);
		dy = (int)(Math.random()*ballRad/2);
		
		// this is the animation loop
		while (theThread != null)
		{
			nextFrame(); // draw the next frame :P
			try{
				theThread.sleep(20L); // sleep between frames
			} catch (InterruptedException _ex) {}	
		}
	}
}


Java Games Programming Tutorials

Getting started
Tutorial 1 - Hello World | Tutorial 2 - Handling mouse input
Tutorial 3 - Handling Key Input | Tutorial 4 - Threads and Animations
to be continued...
oneslime.net programming forum

Personal tools