Monday, August 9, 2010

Java Video Game Programming: Game Logic

The simple video game kernel I presented earlier is a good start, but some compromises were made to make it as simple as possible. Now we're going to start adding back some of the complexity to give us more control over what we can do with it.

The first step is to make the game's ball a real object, rather than some variables within our JFrame.

In simple video games, there is usually some object in the game that does most of the "thinking" with respect to the game's rules. In a game like Pong or Breakout, the ball does most of the "thinking". The paddles move according to user input, but they don't really need to know if they've collided with anything in the game (their own logic keeps them from moving off the playfield.) The bricks don't need to know anything about what's going on, they just need to hang around, then disappear when something tells them to, and perhaps return some information about what score they're worth, if there are different scores for hitting different bricks.

The ball, however, is a more active participant in the game. It needs to know about collisions, respond to the other objects in the game, and generally know what's going on. In other words, it is the object that keeps track of most of the game rules. In a video game, this is referred to as the game logic.


A bouncing ball in the simple Java video game kernel program.

This simple kernel improves on the original. Turn it into your own Pong, Breakout, or Tank clone.

In the example below I've made a Ball class. This is where most of the game's logic will reside. Here it is in the ball's move() method. Since the ball will need access to inside information about the playfield and the objects on it, I've made it an inner class of the playfield's class (VGKernel.) As an inner class, it has access to all of VGKernel's members and methods as if it owned them itself. This simplifies the program a lot.

Here is the modified version of the video game kernel. Changes from the original have been highlighted:
/* A simple video game style kernel, revision 2.
   by Mark Graybill, August 2010
   Uses an inner class to contain game logic,
   with another inner class as a game timer.
*/

// Import Timer and other useful stuff:
import java.util.*;
// Import the basic graphics classes.
import java.awt.*;
import javax.swing.*;
import java.lang.Math;

public class VGKernel extends JPanel{

// This is not a recommended coding practice, just a shortcut.
public Rectangle screen, bounds; // The screen area and boundary.
public JFrame frame; // A JFrame to put the graphics into.
public VGTimerTask vgTask; // The TimerTask that runs the game.
public VGBall ball; // The game ball.
// Create a constructor method:
  public VGKernel(){
    super();
    screen = new Rectangle(0, 0, 600, 400);
    bounds = new Rectangle(0, 0, 600, 400); // Give some temporary values.
    ball = new VGBall();
    frame = new JFrame("VGKernel");
    vgTask = new VGTimerTask();
}

  // Create an inner TimerTask class that has access to the
  // members of the VGKernel.
  class VGTimerTask extends TimerTask{
    public void run(){
      ball.move();
      frame.repaint();
    }
  }

  // Create an inner VGBall class that has our game logic in it.
  class VGBall{
    // Accessor methods would be more proper,
    // but for now I just make the needed variables public.
    public int x, y, width, height; // Ball's location and size.
    int xVel, yVel; // The ball's velocity.

    public VGBall(){
      x = 0;
      y = 0;
      width = 20;
      height = 20;
      xVel = width/4;
      yVel = height/4;
    }
    // Instance methods for VGBall
    public void move(){
      // Move the ball according to the game rules.
      x+=xVel; // Move horizontally.
      y+=yVel; // Move vertically.
      // Detect edges and bounce if necessary.
      if (x > (bounds.width - width)){
        xVel = -xVel; // reverse movement.
        x = bounds.width -  width; // Set location to screen edge.
      }
      if (y > (bounds.height - height)){
        yVel = -yVel; // reverse movement.
        y = bounds.height - height;
      }
      if (x <= 0) { xVel = -xVel; x = 0; }
      if (y <= 0) { yVel = -yVel; y = 0; }
    }
  } // end of class VGBall

// Now the instance methods:
  public void paintComponent(Graphics g){
    // Get the drawing area bounds for game logic.
    bounds = g.getClipBounds();
    // Clear the drawing area, then draw the ball.
    g.clearRect(screen.x, screen.y, screen.width, screen.height);
    g.fillRect(ball.x, ball.y, ball.width, ball.height);
  }

  public static void main(String arg[]){

    java.util.Timer vgTimer = new java.util.Timer();  // Create a Timer object
    VGKernel panel = new VGKernel(); 
    
    panel.frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    panel.frame.setSize(panel.screen.width, panel.screen.height);

    panel.frame.setContentPane(panel); 
    panel.frame.setVisible(true);

    // Set up a timer to do the vgTask regularly.
    vgTimer.schedule(panel.vgTask, 0, 33);
  }
}
Now the ball not only is its own class, it has a more sophisticated means of keeping track of its direction of movement (xVel and yVel, tracking velocity as a positive or negative value, as opposed to the simple right and down booleans in the original.) This means that logic in the game could vary the velocity of the ball easily, as well as change its direction of movement.

Other objects on the playfield should be defined in their own classes, outside VGKernel. This will keep VGKernel from becoming too long and from making the program too monolithic and difficult to maintain.

Simple exercises:
Since the ball has access to VGKernel's members, including the drawing screen, the ball can be made to draw itself. By giving the ball its own draw() method, we can make it easier to change the appearance of the ball at will. Give it a try.