Jump to content




Recent Status Updates

  • Photo
      30 Sep
    rhossis

    laptop hard disk seated beneath motherboard but with no access panel. 7 hours to replace :(

    Show comments (3)
  • Photo
      19 Sep
    Chall

    I love it when you go to write a help thread, then while writing, you reach an enlightenment, and figure it out yourself.

    Show comments (3)
View All Updates

Developed by TechBiz Xccelerator
Photo
- - - - -

Double buffering, movement, and collision detection.


  • Please log in to reply
16 replies to this topic

#1 farrell2k

farrell2k

    CC Addict

  • Advanced Member
  • PipPipPipPipPip
  • 158 posts

Posted 06 May 2009 - 11:00 PM

////////////EDIT: This is NOT Thread safe, but you should not have any problems with it./////////////////

Welcome to this exciting Java tutorial!

Those looking to get into game programming with java will likely find this tutorial more interesting than others.

This tutorial will cover: Collision detection, movement via keyboard, double buffered animation, and a basic game loop. We are basically going to create a window with swing, actively render two double-buffered rectangles, move them around the screen, and check for collisions between them, all while using a basic game loop. I am going to be as Object Oriented (OO) as I can, so we will be creating a few classes.

The very first thing we need to do is get something on the screen. Let's create a window with Swing. I am going to assume that everyone knows at least a little swing.


Create a new java class named Gui.java with your favorite ide or editor. I'm a newb, so I use netbeans. :)

package collide;

import javax.swing.*;

public class Gui
{
     JFrame window;

    public Gui()
    {
        window = new JFrame("Collision detection, movement, double buffering, and a game loop!");
        window.setSize(800,600);
        window.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        window.setVisible(true);
    }

    public static void main(String[]args)
    {
        Gui game = new Gui();
    }
}


Go ahead and compile/run. This should pop up a window. If not, check your code and fix it. O.K. good.

Next, we need a surface on which to draw and in this case we'll use a JPanel. Go ahead and create another class, name it DrawPanel.java, then add this code:


package collide;

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

public class DrawPanel extends JPanel implements KeyListener
{
    public DrawPanel()
    {
        setIgnoreRepaint(true);
        addKeyListener(this);
        setFocusable(true);
    }

    public void keyTyped(KeyEvent e)
    {

    }

    public void keyPressed(KeyEvent e)
    {
        
    }

    public void keyReleased(KeyEvent e)
    {

    }
}

You'll notice that when we create our DrawPanel class, we did some other things; we made it "extend" JPanel, and "implement" KeyListener. When we declare that one class "extends" another, we are telling the JVM that our class, DrawPanel, is a sub-class of JPanel. This way we can have access to JPanel's graphics object which will allow us to draw to the screen. One of the first things we need to do is to tell awt not to repaint the JPanel. We will use a custom paint method later.

We implement KeyListener to let the JVM know that we are going to be processing keyboard input. KeyListener is an interface, and whenever we implement an interface in java, we must also override all of that interface's methods. In this case, there are only 3, which are: keyTyped(), keyPressed(), and keyReleased(). I am sure you can figure out what they do just by reading their names.

We need to do two more things to ensure that our keyListener works, and they are both shown in DrawPanel's constructor.

addKeyListener(this) and setFocusable(true). the method addKeyListener(this) does just what it looks like. It tell the JVM to listen for keys in this class. SetFocusable(true) says "Hey, concentrate on me!"

Now we need to create a DrawPanel object and add it to the content pane of the JFrame. Our Gui.java code changes to this:

package collide;

import javax.swing.*;

/**
 *
 * @author Tom
 */
public class Gui
{
     JFrame window;
     DrawPanel panel;

    public Gui()
    {
        window = new JFrame("Collision detection, movement, double buffering, and a game loop!");
        panel = new DrawPanel();
        window.setSize(800,600);
        window.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        window.getContentPane().add(panel);
        window.setVisible(true);
    }

    public static void main(String[]args)
    {
        Gui game = new Gui();
    }
}

Check out the changes. We've created a drawPanel reference named panel, instantiated it in Gui's constructor, and also called getContentPane().add() to add to to the JFrame. You won't see any difference from the last time you compiled and ran, but go ahead and do it again just to make sure things work. If you get errors, check your code!

Now we need to get things drawn to the JPanel, but first, I'd like to get the outline for our game loop going. A typical game loop basically consists of this:

1. Initialize the game.
2. Update the game.
3. Check for collisions.
4. Draw to the screen.
5. Wait for a bit, then do again and again.

Our DrawPanel class will handle all of this, so add these methods to it:

public void Initialize()
{
}

public void update()
{
}

public void checkCollisions()
{
}

public void drawBuffer()
{
}

public void drawScreen()
{
}

public void startGame()
{
}

Now we need to tell our panel to execute these 6 methods, wait for a little bit, the do it again...and again...and again. I like to use a while loop. Add this into the startGame() method:

initialize();
while(true)
{
    try
    {
        update();
        checkCollisions();
        drawBuffer();
        drawScreen();
        Thread.sleep(15);
    }
    catch(Exception e)
    {
        e.printStackTrace();
    }    
}

When the startGame method is run, it will constantly loop these methods. You will see how this is important. The biggest games in the world all use this simple game loop technique.

O.K. now we're going to start drawing things on the screen. We're going to use a tried and true technique called double buffering. Double buffering will help us to avoid flickering when we start moving things around. The idea behind double buffering is this: Instead of drawing all of our movement updates directly to the screen, which takes a lot of time and causes annoying, ugly flicker, we draw out movements to an image that is the same size as our window 800x600 in ram, then just copy that single image to our screen. It takes much less time drawing to an image in memory than it does to the screen, so this technique avoids that annoying flicker effect. The first thing we need to do is to create an image the size of our window. I like to use a BufferedImage. We need to make an instance variable at the beginning of our DrawPanel class, just above the DrawPanel() constructor.

 BufferedImage buffer;

then we need to import java.awt.image.* and java.awt.event.* at the top of our class with the other imports. If you're using netbeans, you can just hit ctrl+shift+i

Now, in our initialize method(), we do:

buffer = new BufferedImage(800,600,BufferedImage.TYPE_INT_RGB);

This creates a buffered image in which we can draw our game updates :)

Now we need to get a graphics2d object from our DrawPanel class that we can use to draw into the BufferedImage (buffer). In our drawBuffer() method, add this:

Graphics2D b = buffer.createGraphics();

Now we need another graphics2d object that we can use to draw directly to the screen with, so in our drawScreen() method, add this:

Graphics2D g = (Graphics2D)this.getGraphics();

*Quick explanation: We must explicitly cast this.getGraphics() with (Graphics2D) as this.getGraphics only returns an object of type Graphics.

We have almost everything we need to start doing some drawing, but first we need to make a few modifications to our Gui.java class. Open Gui.java and above our main method, add:

public void go()
{
    panel.startGame();
}

then add

game.go();

to Gui.java's main method, right under Game gui = new Game(); Go back to DrawPanel.java. We're going to add some drawing code to make sure that things are working. In the drawBuffer() method, under the Graphics2D object, add:

b.setColor(Color.bkack);
b.fillRect(0,0,800,600);
b.dispose();

then in our drawScreen() method we're going to add this code under our Graphics2D object:

g.drawImage(buffer,0,0,this);
Toolkit.getDefaultToolkit().sync();
g.dispose();

This will draw whatever we draw to our buffer image to the screen. From here on out we will only change drawing code in our drawBuffer method(). Go ahead and compile and run the program. You should now see a black window. If you do, you have succeeded. If not, backtrack to see where you screwed up. This concludes the double buffering portion of this tutorial.

Let's move on to some more Drawing. We're going to create two rectangles on screen. One will represent our player, and the other will represent an enemy. You'll be able to move the player with the up, down, left, and right arrow keys. When our player bumps into our obstacle, our player will be stopped in his tracks! The first thing we need to do is create a new class to represent our player and our enemy. I am calling this new class Entity.java. Here is the code for it:

package collide;

import java.awt.Rectangle;

public class Entity
{
     int x,y,speed,width,height;
     boolean up, down, left, right,stop;
   
    public Entity(int x, int y)
    {
        this.x = x;
        this.y = y;
        speed = 3;
        width = 100;
        height = 100;
        up = false;
        down = false;
        left = false;
        right = false;
   }
    
    public int getX()
    {
        return x;
    }
    
    public int getY()
    {
        return y;
    }
    
    public int getWidth()
    {
        return width;
    }
    
    public int getHeight()
    {
        return height;
    }
    
    public Rectangle getBounds()
    {
        return new Rectangle(getX(),getY(),getWidth(),getHeight());
    }

    public void move()
    {
        if (up)
            y -= speed;
        if (down)
            y += speed;
        if (left)
            x -= speed;
        if (right)
            x += speed;
    }
}

I feel the need to explain a few things. Obviously the int variables for x, y, are the object's location on the screen, and speed is how fast it will move. The up, down, left, and right booleans are directions of movement. Check the move() method to get a better idea about how the object moves. If up is true, the we move the y value of the player upwards by subtracting speed from y. Confusing? Yeah. I know. You may need to read the move() method a few times before you get the hang of it. The getBounds() method simply returns a new rectangle that encompases our player or enemy. It will be used for collision detection. getX(), getY(), getWidth(), getHeight() shoudl all be self-explanatory.

Let's get our player and enemy draw on the screen. Open up DrawPanel.java and under BufferedImage buffer; add:

Entity player;
Entity enemy;


Now, in the initialize method() we add:

player = new Entity(100,100);
enemy = new Entity(400,400);

Now head on over to the drawBuffer method so we can draw our player and enemy. Our player and enemy are just rectangles so we use the fillRect() method to draw them. We want to make sure that we can tell the difference between them, so we also want to use the setColor() method for each as shown below. Your drawBuffer() should look like this:

public void drawBuffer()
    {
        Graphics2D b = buffer.createGraphics();
        b.setColor(Color.black);
        b.fillRect(0,0,800,600);
        b.setColor(Color.red);
        b.fillRect(player.getX(),player.getY(),player.getWidth(),player.getHeight());
        b.setColor(Color.blue);
        b.fillRect(enemy.getX(),enemy.getY(),enemy.getWidth(),enemy.getHeight());
        b.dispose();
    }

Compile and run to make sure you have done everything correctly. If not, check your code. Now that you have 2 rectangles on screen representing both our player and enemy, it is time to make our player move. Remember those keyPressed() and keyReleased() methods in DrawPanel? We're going to use them now.

Head on over to the keyPressed() method in DrawPanel. The first thing you should notice is that the method accepts an argument, which is KeyEvent e. When you press or release a button on the keyboard, java fires off a KeyEvent object to whichever method is appropriate. If you just pressed a key, it sends it to keyPressed(). If you released a key, it sends an event to keyReleased(). Our keyPressed() and keyReleased methods should look like this. While this is not technically the ideal way to move objects, as I am breaking an important rule of encapsulation, I found it to be easier to understand when I was first learning.

public void keyPressed(KeyEvent e)
    {
       int key = e.getKeyCode();
       if (key == KeyEvent.VK_LEFT)
           player.left = true;
       if (key == KeyEvent.VK_RIGHT)
           player.right = true;
       if (key == KeyEvent.VK_UP)
           player.up = true;
       if (key == KeyEvent.VK_DOWN)
           player.down = true;
    }

    public void keyReleased(KeyEvent e)
    {
      int key = e.getKeyCode();
       if (key == KeyEvent.VK_LEFT)
           player.left = false;
       if (key == KeyEvent.VK_RIGHT)
           player.right = false;
       if (key == KeyEvent.VK_UP)
           player.up = false;
       if (key == KeyEvent.VK_DOWN)
           player.down = false;
    }

As you can see when a key is presed, a boolean from the player's Entity class is toggled to true, and when a key is released, the corresponding direction is set to false. Now we need to modify DrawPanel's update method so that we can actually see our movement on screen. This is done by running the player's move() method in DrawPanel's update() method like so:

public void update()
    {
        player.move();
    }

Go ahead and compile and run. Move your arrow keys. Do you see movement? Notice how you move right through the other rectangle? if so, you've done everything correctly. If not, you know the drill.

Now it's time to check for collisions! First, we have to make a few small changes to our Entity class,though. Add a boolean instance variable to the Entity class. Find where we have:

boolean up, down, left, right;

Now make it:

boolean up, down, left, right,collision; 

Then in the constructor of Entity, add:

collision = false;

Open up our DrawPanel class and find the checkCollisions() method. Add:

if (player.getBounds().intersects(enemy.getBounds()))
           player.collision = true;
else
    player.collision = false;

Java has a nice little method to determine whether two objects are "intersecting" one another, and that is the intersects() method. It's a great little tool that you will use all of the time in java game programming. It's so cool that even MS copied it on over to C# as the "IntersectsWith()" method...along with everything else in java. :)

Now, head on over drawBuffer() method. We'll use the value of player.collision to do something when it's true, this way we'll have a visual indicator showing that both objects are colliding.

Modify the drawBuffer() method as such:

    public void drawBuffer()
    {
        Graphics2D b = buffer.createGraphics();
        b.setColor(Color.black);
        b.fillRect(0,0,800,600);
        if (player.collision == false)
        {
            b.setColor(Color.red);
            b.fillRect(player.getX(),player.getY(),player.getWidth(),player.getHeight());
            b.setColor(Color.blue);
            b.fillRect(enemy.getX(),enemy.getY(),enemy.getWidth(),enemy.getHeight());
            b.dispose();
        }
        else
            b.setColor(Color.white);
            b.drawString("C O L L I S I O N !",350,300);
            b.dispose();
    }

And that's that. I hope this little? tutorial gave you some insight on the topics covered. Remember, there is always more than one way to do something, and I can promise you that my movement code is far from desirable. Can you think of a better way to handle movement? I can, but that is for another time.

Edited by farrell2k, 16 March 2012 - 12:28 AM.

  • 3

#2 WingedPanther

WingedPanther

    A spammer's worst nightmare

  • Moderator
  • 17,158 posts
  • Location:Upstate, South Carolina
  • Programming Language:C, C++, PL/SQL, Delphi/Object Pascal, Pascal, Transact-SQL, Others
  • Learning:Java, C#, PHP, JavaScript, Lisp, Fortran, Haskell, Others

Posted 07 May 2009 - 03:29 AM

Nicely done!
  • 0

Programming is a branch of mathematics.
My CodeCall Blog | My Personal Blog

My MineCraft server site: http://banishedwings.enjin.com/


#3 Turk4n

Turk4n

    ???

  • Expert Member
  • PipPipPipPipPipPipPip
  • 1,919 posts
  • Location:Sweden
  • Programming Language:C, Java, PHP, Python, Bash
  • Learning:C++, C#, JavaScript, Visual Basic .NET, Others

Posted 10 May 2009 - 01:30 AM

Quite interesting !
  • 0

#4 chili5

chili5

    CC Mentor

  • Expert Member
  • PipPipPipPipPipPipPipPip
  • 3,033 posts
  • Programming Language:Java, C#, PHP, JavaScript, Ruby, Transact-SQL
  • Learning:C, Java, C++, C#, PHP, JavaScript, Ruby, Transact-SQL, Assembly, Scheme, Haskell, Others

Posted 06 June 2009 - 03:22 AM

Wow!

to Gui.java's main method, right under Game gui = new Game(); Go back to DrawPanel.java. We're going to add some drawing code to make sure that things are working. In the drawBuffer() method, under the Graphics2D object, add:

Code:

b.setColor(Color.bkack);
b.fillRect(0,0,800,600);
b.dispose();


You have a typo here: in the first line it should be:

b.setColor(Color.black);

:)


While this is not technically the ideal way to move objects, as I am breaking an important rule of encapsulation, I found it to be easier to understand when I was first learning.


What rule is that?

This tutorial was incredible and a great learning experience for me. Keep it up. =) +rep for you.
  • 0

#5 farrell2k

farrell2k

    CC Addict

  • Advanced Member
  • PipPipPipPipPip
  • 158 posts

Posted 06 June 2009 - 06:19 AM

What rule is that?

This tutorial was incredible and a great learning experience for me. Keep it up. =) +rep for you.


I should have written a mutator/setter method in the player class that modifies the values of the booleans up, down, left, and right, instead of toggling their values trough direct manipulation. i.e. player.left = true.
  • 0

#6 chili5

chili5

    CC Mentor

  • Expert Member
  • PipPipPipPipPipPipPipPip
  • 3,033 posts
  • Programming Language:Java, C#, PHP, JavaScript, Ruby, Transact-SQL
  • Learning:C, Java, C++, C#, PHP, JavaScript, Ruby, Transact-SQL, Assembly, Scheme, Haskell, Others

Posted 06 June 2009 - 06:22 AM

Oh yeah, but that wasn't the point of the tutorial right? :)

Very ewll done1 :D
  • 0

#7 Marak

Marak

    CC Lurker

  • Just Joined
  • Pip
  • 2 posts

Posted 03 April 2010 - 04:37 PM

Working through this atm, great! one issue(only slight) with the first class(Gui), there's a bug, if you run and compile it as is, the package collide kicks up an error, i commented it out and it worked fine(im assuming its erroring because the rest isnt up yet.)

Looking forward to finishing this one, thanks for the time :)

- Marak

EDIT: hmm maybe i have a bug on my end, 2nd class made im still getting that collide error when i re-enable them... grr ill keep workign on it, and fix this post when im finished.
  • 0

#8 comrin

comrin

    CC Lurker

  • Just Joined
  • Pip
  • 1 posts

Posted 25 April 2010 - 07:39 AM

Very helpful tutorial!

I just wonder if it's possible to replace the rectangles with images easily?
  • 0

#9 GMVResources

GMVResources

    CC Resident

  • Just Joined
  • PipPipPipPip
  • 71 posts

Posted 14 June 2010 - 01:29 PM

Good Job! This tutorial helped me alot!
  • 0

#10 GMVResources

GMVResources

    CC Resident

  • Just Joined
  • PipPipPipPip
  • 71 posts

Posted 14 June 2010 - 01:42 PM

I just have one suggestion, I got confused not when i read it but by adding everything. Maybe you should add the full versions of the classes into your post?
  • 0

#11 lawrs

lawrs

    CC Lurker

  • Just Joined
  • Pip
  • 2 posts

Posted 02 July 2010 - 12:42 AM

Hi farrel, thank you for this tutorial. I'm a pure newbie at Java, and am trying to learn how to place images instead of the rectangles. Also, I've looked around on how to set the background of the JFrame( or is it JPanel ?) , but I get confused on which class to include/create the background in. Would really appreciate it if you could show how to use an img for background and for the rectangles, based on this tutorial.

Also I tried adding another rect ( enemy2 = new Entity) and added the collision codes respectively, but it still doesn't detect.

///////////////////////////////////////////////
public void checkCollision()
{
if (player.getBounds().intersects(enemy.getBounds()))
player.collision = true;
if (player.getBounds().intersects(enemy2.getBounds()))
player.collision = true;
else
player.collision = false;

///////////////////////////////////////////////

What makes it worse, the first "enemy" doesn't detect collision too once I add collision codes for "enemy2". Could you explain how about doing so if I would like to add say.. 4 more enemies? Thank you in advance! :D
  • 0

#12 farrell2k

farrell2k

    CC Addict

  • Advanced Member
  • PipPipPipPipPip
  • 158 posts

Posted 02 July 2010 - 10:18 AM

Hi farrel, thank you for this tutorial. I'm a pure newbie at Java, and am trying to learn how to place images instead of the rectangles. Also, I've looked around on how to set the background of the JFrame( or is it JPanel ?) , but I get confused on which class to include/create the background in. Would really appreciate it if you could show how to use an img for background and for the rectangles, based on this tutorial.

Also I tried adding another rect ( enemy2 = new Entity) and added the collision codes respectively, but it still doesn't detect.

///////////////////////////////////////////////
public void checkCollision()
{
if (player.getBounds().intersects(enemy.getBounds()))
player.collision = true;
if (player.getBounds().intersects(enemy2.getBounds()))
player.collision = true;
else
player.collision = false;

///////////////////////////////////////////////

What makes it worse, the first "enemy" doesn't detect collision too once I add collision codes for "enemy2". Could you explain how about doing so if I would like to add say.. 4 more enemies? Thank you in advance! :D


You have to understand that code I posted is very sloppy. There are better ways to check for collisions and to draw. The code is meant to show basic principles.

You can add images for the enemies by creating an Image like so:
Image enemyImage = Toolkit.getDefaultToolkit().getImage("enemy.jpg");

Stick that somewhere at the top of the DrawPanel class, and then find the drawBuffer() method and instead of the code that draws the player rectangle ' b.fillRect(x,x,x,x)', use b.drawImage(enemyImage, player.getX(), player.getY(), null);

As far as you adding extra enemies, duplicating what I did for the first two, if done correctly, will produce the same results. In checkCollision(), you need to make sure that every enemy is checking for each other.

PM your code to me, or make it available for download somewhere, and I'll look at it for you when I have time.
  • 0




Powered by binpress