Showing posts with label paintComponent(). Show all posts
Showing posts with label paintComponent(). Show all posts

Wednesday, August 4, 2010

A Simple Java Video Game Kernel

Most video games run continuously, rather than waiting for user input before they do something. The heart of a video game of this sort is called the kernel. The kernel is running in the background, collecting user input and updating the display. It uses logic to determine if the game has been won or lost or otherwise control the state of the game.


A bouncing ball in the simple Java video game kernel program.
This simple kernel sends a ball bouncing around on the screen. Turn it into your own Pong, Breakout, or Tank clone.

To run this way, Threads are usually used to allow more than one thing to be going on at a time in a Java program. We've looked at a simple way of using threads before, the Timer class.

Here's a really, really simple video game kernel. It has all the basic elements of a video game.
/* A simple video game style kernel
   by Mark Graybill, August 2010
   Uses the Timer Class to move a ball on a playfield.
*/

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

public class VGKernel extends JPanel{

// Set up the objects and variables we'll want.
public Rectangle screen, ball; // The screen area and ball location/size.
public Rectangle bounds;  // The boundaries of the drawing area.
public JFrame frame; // A JFrame to put the graphics into.
public VGTimerTask vgTask; // The TimerTask that runs the game.
public boolean down, right; // Direction of ball's travel.

// Create a constructor method that initializes things:
  public VGKernel(){
    super();
    screen = new Rectangle(0, 0, 600, 400);
    ball   = new Rectangle(0, 0, 20, 20);
    bounds = new Rectangle(0, 0, 600, 400); // Give some starter values.
    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(){
      moveBall();
      frame.repaint();
    }
  }

// 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 void moveBall(){
  // Ball should really be its own class with this as a method.
    if (right) ball.x+=ball.width; // If right is true, move ball right,
    else ball.x-=ball.width;       // otherwise move left.
    if (down)  ball.y+=ball.height; // Same for up/down.
    else ball.y-=ball.width;
    if (ball.x > (bounds.width - ball.width)) // Detect edges and bounce.
      { right = false; ball.x = bounds.width -  ball.width; }
    if (ball.y > (bounds.height - ball.height))
      { down  = false; ball.y = bounds.height - ball.height;}
    if (ball.x <= 0) { right = true; ball.x = 0; }
    if (ball.y <= 0) { down  = true; ball.y = 0; }
  }

  public static void main(String arg[]){
    java.util.Timer vgTimer = new java.util.Timer();  // Create a Timer.
    VGKernel panel = new VGKernel(); // Create and instance of our kernel.
    
    // Set the intial ball movement direction.
    panel.down = true;
    panel.right = true;

    // Set up our JFRame
    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, 100);
  }
}

This example can be expanded with methods to get control inputs, additional players on the playfield (like paddles), and logic to determine when someone scores.

The code here is far from perfect, but I've made some compromises to make things as simple as I could while still showing a full working example. Not that any code that runs and does what is supposed to is really bad, but there are other, better ways of doing this. But this works and is fairly easy to understand.

What the program does is create a JPanel that has an inner class (a class defined within itself) of VGTimerTask. The VGTimerTask is a kind of TimerTask, which can be scheduled to occur on a regular basis by a Timer. Since VGTimerTask is an inner class of VGPanel, it has access to all the members of VGPanel. This is critical. Without that, it wouldn't be able to access the ball and redraw the screen easily (it can still be done, but in a more complex way.)

Timer is a decent way of running a simple game, but more complex games should use some other timing mechanism. java.util.Timer is affected by a number of outside events, so to get smoother, more reliable timing you a timer like the one in the Java3D package would work better.

A Simple Improvement

There are many ways of improving on this basic example. One way that is very simple is to smooth the animation. The movement of the ball is pretty jerky. This is caused by both the distance that the ball moves each "turn", and by the time between screen updates. We can smooth out the animation by addressing both of these.

First, let's change moveBall() to shift the ball a smaller distance each time:
public void moveBall(){
  // Ball should really be its own class with this as a method.
    if (right) ball.x+=ball.width/4; // If right is true, move ball right,
    else ball.x-=ball.width/4;       // otherwise move left.
    if (down)  ball.y+=ball.height/4; // Same for up/down.
    else ball.y-=ball.width/4;
    if (ball.x > (bounds.width - ball.width)) // Detect edges and bounce.
      { right = false; ball.x = bounds.width -  ball.width; }
    if (ball.y > (bounds.height - ball.height))
      { down  = false; ball.y = bounds.height - ball.height;}
    if (ball.x <= 0) { right = true; ball.x = 0; }
    if (ball.y <= 0) { down  = true; ball.y = 0; }
  }
Now the ball is being moved only one quarter of its size each turn.

Next, change the Timer schedule to draw the screen every 20 milliseconds instead of every 100 milliseconds:
// Set up a timer to do the vgTask regularly.
    vgTimer.schedule(panel.vgTask, 0, 20);

Now you have a ball that moves a lot smoother.

I'll be expanding on this basic kernel and improving it in future articles, starting with Java Video game Programming: Game Logic
StumbleUpon

Sunday, July 20, 2008

Controlling Java's Graphics: paintComponent() and repaint()

Why do we put all our graphical drawing code into a paintComponent() method? It seems odd, since it would seem we should be able to simply stick some simple graphics commands into our main() method in a Java application and just get the drawing done. Where does paintComponent come from? If we never call it in our code, how does it get executed?

It seems like magic, doesn't it? You just sort of set it up and somehow it happens. Hopefully when and how we want it to. It seems like a vaguely disquieting form of magic, perhaps.

When Java does graphics, suddenly you're not in the strict control of the program that you are with command line applications. With graphics, your program is now interacting with the other parts of your computer's operating system. It's not just running in its own little corner of the machine. Now it's got a window out on that shared resource, the display, and the window has to interact properly with everything else that's on the display.

Think of it this way, if you are just driving around on private property all by yourself you don't have to worry about traffic. Off on your own stretch of road, with an unused parking lot or two to tool around in, you don't have to worry about sharing. You can cross the center line, pull on and off the road without signalling, you don't even need a license so long as you don't go on a public road.

But once you head for the public roads and freeway, you've got to share. The rules matter. It gets more complicated than buzzing around in a go-cart or whatever on private property. And things happen that you don't have any direct control over, like red lights and merging traffic.

When you move your program on to the display in Java, you don't have to do everything on your own as you would have to in a car. Java's graphics system takes care of a lot for you. Unfortunately this means your code starts looking like magic. Methods get called without you doing the calling. But it's OK. All you need to know is what to expect.

The paintComponent() method redraws your graphics area. In our examples so far this has been a JPanel. There are two ways paintComponent() can get called. One is automatic, the other is up to you.

The automatic way is what we've used so far. The computer's window manager automatically calls for your program to redraw its graphics whenever a window it owns appears to need redrawing. The window manager can decide this for any of a number of reasons. Such as when the window initially appears on screen. Or if the window has been covered up in part or in whole by another window on screen, and the covered part has just been uncovered. Maybe the window had been minimized, and has just been restored.

When these sort of things happen, the Java Runtime Environment calls the paint() method for your container. In our examples so far, our container has been a JFrame. It will be a "heavyweight" component (see the JFrames article for a brief discussion on this.) Once the container redraws itself, it'll pass the command down to its children, that is, the lightweight components that live inside it. A lightweight component from the Swing package uses the paintComponent() method to redraw itself. So that's what gets called. Shazam! It's like magic!

This means you want to set up paintComponent() to draw things the way you want them when it gets called. If you're doing drawings that don't change, like the examples we've done to date, then you don't need to look up any information. If you're doing anything animated or that otherwise changes over time, you need to have paintComponent() be able to look things up at the time of drawing to properly reflect conditions in the drawing.

Now, let's say you've changed something in your program and you want to redraw to show the change. You probably don't want to wait until the window manager thinks your window needs redrawing. If nobody is changing things on the screen otherwise, it may not redraw until way too late!

Does that mean you put a call to paintComponent() in your program? No! You would be taking a chance of breaking something if you did. You want to start from the top, like the Java Runtime Environment did. In your case, you want to call repaint(). This will take things from the top. It also handles some possibilities that jumping in and calling paintComponent() on your own will not.

Like, let's say both you and the window manager decide to redraw at the same time. If you bypass repaint() you'll be slowing down your program by making it repaint twice when it really only needs to redraw once. When you call repaint(), Java catches this for you and saves you some computer time by only redrawing once. It'll also catch redraw requests made while the redrawing is going on and deal with them appropriately. Your paintComponent() method will get called in its own turn when you use repaint().

So, if you have new information you want depicted on screen, use repaint().

Put all your drawing inside paintComponent() and let the system take care of knowing when to call it otherwise. Give paintComponent() the ability to get the information it needs to draw things the way you want them now. If you're getting ahead of what I've written here, and aren't sure how to get through the Object Oriented barriers, look up "inner classes" for a start. It gives you a way to get the information where you need it when you need it.
StumbleUpon

Saturday, July 19, 2008

Very Basic Java Graphics: 3 Examples

We're going to build up a graphics app from something so basic it doesn't really work, like the example from Java Graphics--Start with a JFrame and building to something more sophisticated. In the JFrame article and in A Most Basic Graphics App I've avoided the use of comments in the code to keep it shorter. In these examples I'm going to include comments in the code to describe what's happening. So don't be scared of the length of these examples, most of what's there is just comments, which Java ignores. They're there just for humans.
/* BasicFrame.java

This is a really simple graphics program.
It opens a frame on the screen with a single
line drawn across it.

It's not very polished, but it demonstrates
a graphical program as simply as possible.mag-27Apr2008
*/

// Import the basic graphics classes.
import java.awt.*;
import javax.swing.*;

public class BasicFrame extends JFrame{

// Create a constructor method
public BasicFrame(){
// All we do is call JFrame's constructor.
// We don't need anything special for this
// program.
super();
}

// The following methods are instance methods.
/* Create a paint() method to override the one in JFrame.
This is where the drawing happens.
We don't have to call it in our program, it gets called
automatically whenever the frame needs to be redrawn,
like when it it made visible or moved or whatever.*/
public void paint(Graphics g){
g.drawLine(10,10,150,150); // Draw a line from (10,10) to (150,150)
}

public static void main(String arg[]){
// create an identifier named 'window' and
// apply it to a new BasicFrame object
// created using our constructor, above.
BasicFrame frame = new BasicFrame();

// Use the setSize method that our BasicFrame
// object inherited to make the frame
// 200 pixels wide and high.
frame.setSize(200,200);

// Make the window show on the screen.
frame.setVisible(true);
}
}

This example draws a line on the JFrame. The window decorations take up some of our drawing space, so the title bar may cover some of our line. With some JVMs the background area of the JFrame won't be cleared before you draw. Also, when you close the window using the close button, the application doesn't shut down. For many applications on Mac OS X this is normal behavior, but on other OSes, and even many applications under OS X it's normal to expect an application to close down completely if you click the close button on the last open window (for applications that have multiple windows) or the close button on the main window for other applications.

So here's our next example, where we take care of that last problem:
/* CloseFrame.java
This is a really simple graphics program.
It opens a frame on the screen with a single
line drawn across it.

We're starting to add a little bit of polish
here--we make the program close nicely when
the close box is clicked, rather than just
sort of hanging around half-dead.
mag-28Apr2008
*/

// Import the basic graphics classes.
import java.awt.*;
import javax.swing.*;

public class CloseFrame extends JFrame{

// Create a constructor method
public CloseFrame(){
// All we do is call JFrame's constructor.
// We don't need anything special for this.
super();
}

// The following methods are instance methods.

/* Create a paint() method to override the one in JFrame.
This is where the drawing happens.
We don't have to call it in our program,
it gets called automatically whenever the
frame needs to be redrawn, like when it is
made visible or moved or whatever.
*/
public void paint(Graphics g){
g.drawLine(10,10,150,150); // Draw a line from (10,10) to (150,150)
}

public static void main(String arg[]){
BasicFrame frame = new BasicFrame();
// This uses a constant EXIT_ON_CLOSE that's a member of JFrame.
// The constant is passed to the setDefaultCloseOperation method
// of our frame object, which is a CloseFrame object,
// which inherits the method from its parent JFrame.
// It makes the program exit (close completely) when we click
// the close button.
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setSize(200,200);frame.setVisible(true);
}
}

The magic line that makes the application exit when we click the close button is the one that reads: frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);. We can tell that EXIT_ON_CLOSE is a type of variable called a "constant" because the name follows the convention of being written in all capital letters. So we're not YELLING when we say "EXIT_ON_CLOSE" we're using the value stored in EXIT_ON_CLOSE as part of the JFrame class.
A JFrame has this constant available because it implements the interface WindowConstants. By implementing this interface, in essence, the JFrame class of objects promise to do the right thing if one of these constants is passed to a method that handles it, in this case setDefaultCloseOperation.

Our final example for this entry goes one big step further:
/* BasicJPanel.java
This is a somewhat more sophisticated drawing program.
It uses a new child of JPanel as the drawing surface
for a JFrame, to avoid the problems with drawing
directly on a JFrame.

A custom JPanel child class called BasicJPanel is created
with its own paintComponent method, which contains our
drawing code.

A generic JFrame is then created to hold the BasicJPanel
object, the BasicJPanel is created, made into the JPanel's
content pane, and our paintComponent method is called
automatically. *Whew!*
mag-28Apr2008
*/

// Import the basic graphics classes.
import java.awt.*;
import javax.swing.*;

public class BasicJPanel extends JPanel{

// Create a constructor method
public BasicJPanel(){
super();
}

// The following methods are instance methods.

/* Create a paintComponent() method to override the one in
JPanel.This is where the drawing happens. We don't have
to call it in our program, it gets called automatically
whenever the panel needs to be redrawn, like when it is
made visible or moved or whatever.
*/
public void paintComponent(Graphics g){
g.drawLine(10,10,150,150); // Draw a line from (10,10) to (150,150)
}

public static void main(String arg[]){
JFrame frame = new JFrame("BasicJPanel");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setSize(200,200);

// Create a new identifier for a BasicJPanel called "panel",
// then create a new BasicJPanel object for it to refer to.
BasicJPanel panel = new BasicJPanel();

// Make the panel object the content pane of the JFrame.
// This puts it into the drawable area of frame, and now
// we do all our drawing to panel, using paintComponent(), above.
frame.setContentPane(panel);
frame.setVisible(true);
}
}

Here, we've added a JPanel inside the drawable area of our JFrame. This means that if we start drawing at 0,0 we'll actually be drawing somewhere we can see it, it won't be hidden by the window decorations.

The other big change is the change of our drawing method's name from paint() to paintComponent(). This is because we changed from a JFrame to a JPanel. If you take a look at JPanel in the API Specification you'll see that it extends JComponent. This family of objects uses paintComponent() to manage drawing.

This last example is a good starting point for any graphical operation. In future examples, I'll be extending it further to add additional features.
StumbleUpon

Monday, July 7, 2008

Java Graphics--Start with a JFrame

Jerry introduces Pierre to the JFrame
In A Most Basic Graphics App I presented a skeleton program that's suitable as a starting point for a graphics application. It deserves some explanation before we add more to it.

There are two graphics "components" that appear in this program, a JPanel and a JFrame. Why use both? If we want a really basic graphics app, why isn't one or the other enough?

If we're going to choose between the two, we'd have to take the JFrame. A Frame component is a special graphics component that lives on top of the host computer's graphics system. In Java it's called a "heavyweight" component. The "backboard" on which you draw, or place other components, has to be a heavyweight component. Only a heavyweight component can be guaranteed to be drawn correctly when placed directly on the user's display. Lightweight components have to be placed on top of a heavyweight component.

Since JPanel is a lightweight component, we can't use it without a heavyweight like JFrame to place it on. So if we're just picking one component, it's going to be the JFrame:

import java.awt.*;
import javax.swing.*;

public class JustaFrame extends JFrame{

public JustaFrame(){
super();
}

public void paint(Graphics g){
g.drawLine(10,10,150,150);
}

public static void main(String[] arg){
JustaFrame frame = new JustaFrame();

frame.setSize(200,200);
frame.setVisible(true);
}
}

You'll notice a problem here if you run the program. The "window decorations" for the title bar, window borders, and so on take away a piece of the space from the drawing area. Unless you've got pretty narrow title bars on your system, the line drawn by this program will start up underneath the title bar.

It's possible to find out how much space these items take up, and compensate. Or you can add another component in the visible drawing area as I did with the JPanel in the original program.

There's another difference here. The drawing is done by a paint() method, rather than a paintComponent() method. This is part of the difference between different types of components. If you change paint() into paintComponent() this program will compile and run, but it won't draw the line.

Later, we'll talk about the magic behind the paint() and paintComponent() methods and some of the other hidden mysteries of Java's graphics.
StumbleUpon