Game loop and Animation

In this tutorial we are going to see how to move a circle around our canvas. We get this animation by painting the circle in a position and then erasing it and drawing it in a near by position. What we get is a moving circle.

 

The position of the circle

As we said before, every time we paint something we have to define its position (x,y). To make the circle move, we have to modify the position (x,y) each time and paint the circle in the new position.

In our example, we keep the current position of our circle in two properties called "x" and "y". We also create a method called moveBall() which will increase in 1 both "x" and "y", each time we call it. In the paint method we draw a circle with a diameter of 30 pixels in the position (x,y) given by the properties before described; "g2d.fillOval(x, y, 30, 30);".

Game loop

At the end of the main method we start an infinite cicle "while (true)" where we repeatedly call moveBall() to change the position of the circle and then we call repaint(), which forces de AWT engine to call the paint method to paint again the canvas.

This cycle is known as "Game loop" and carries out two operations:

  1. Update: update of the physics of our world. In our case the update is given by the moveBall() method, which increases the "x" and "y" in 1.
  2. Render: painting of the current state of our world including the changes made before. In our example, it is carried out by the call to the method repaint() and the following call to the "paint" method carried out by the AWT engine, and more specifically by the "event queue thread".
package com.edu4java.minitennis2;

import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.RenderingHints;
import javax.swing.JFrame;
import javax.swing.JPanel;

@SuppressWarnings("serial")
public class Game extends JPanel {

	int x = 0;
	int y = 0;

	private void moveBall() {
		x = x + 1;
		y = y + 1;
	}

	@Override
	public void paint(Graphics g) {
		super.paint(g);
		Graphics2D g2d = (Graphics2D) g;
		g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
				RenderingHints.VALUE_ANTIALIAS_ON);
		g2d.fillOval(x, y, 30, 30);
	}

	public static void main(String[] args) throws InterruptedException {
		JFrame frame = new JFrame("Mini Tennis");
		Game game = new Game();
		frame.add(game);
		frame.setSize(300, 400);
		frame.setVisible(true);
		frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
		
		while (true) {
			game.moveBall();
			game.repaint();
			Thread.sleep(10);
		}
	}
}

When we run this code we obtain:

Analyzing our paint method

As we said in the last tutorial, this method is run each time the operative system tells the AWT engine that it is necessary to paint the canvas. If we run the repaint() method of a JPanel object, what we are doing is telling the AWT engine to execute the paint method as soon as possible. Calling repaint(), the canvas is painted again and we can see the changes in the position of the circle.

	@Override
	public void paint(Graphics g) {
		super.paint(g);
		Graphics2D g2d = (Graphics2D) g;
		g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
				RenderingHints.VALUE_ANTIALIAS_ON);
		g2d.fillOval(x, y, 30, 30);
	}

The call to "super.paint(g)", cleans the screen and if we comment this line we can see the following effect:

The instruction; "g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON)" makes the borders of the figures smoother, as you can see in the following graphic. The circle on the left is without applying ANTIALIAS and the one on the right; applying ANTIALIAS.

Analyzing the concurrence and the behaviour of the threads.

At the beginning of the execution of the main method, there is only one thread. We can see this, putting a breakpoint in the first line of the main method.

If we add a breakpoint in the line with; game.repaint() and in the first line of the paint method and we then press F8 (Resume: it orders to continue the execution to the end o till it gets to the following breakpoint), we obtain:

In the left hand side we can see that four threads have been created and two of them are stopped in breakpoints. The main Thread is stopped in line 40 in the game.repaint() instruction. The AWT-EventQueue thread is stopped in the paint method in line 22.

If we select the AWT-EventQueue thread in the Debug view and we press F8 twice, we will see that it no longer stops in the paint method. This is because the operative system doesn´t have a reason to ask for the canvas to repaint itself, once it is initialized.

If we press F6 (the thread execution moves only one line); (this time over the main thread) we will see that the paint method is called again by the AWT-EventQueue thread. We now take out the breakpoint of the paint method, we press F8 and we once again have only the main thread stopped.

The following animation shows us what happens in the canvas each time we press F8 repeatedly. Each call to moveBall() increases the position (x,y) of the circle and the call to repaint() tells the AWT-EventQueue thread to paint again the canvas.

Lastly we are going to analyze the line "Thread.sleep(10)" (the last instruction inside the "Game loop"). For this we comment the line with // and we execute without debug. The result is that the circle is not painted in the canvas. Why does this happen? This is because the main thread takes over the processor and does not share it with the AWT-EventQueue thread, which cannot then call the paint method.

"Thread.sleep(10)" it tells the processor that the thread which is being run must sleep for 10 milliseconds, which allows the processor to execute other threads and in particular the AWT-EventQueue thread which calls the paint method.

I would like to say that this solution is very poor and it only wants to ilustrate the concepts of "game loop", threads and concurrence. There are better ways to manage the game loop and the concurrence in a game, and we will take a look at them in the next tutorials.

<< JFrame, JPanel, paint method Sprites - Speed and direction >>