Saturday, April 12, 2014

Making Pong for JAVA

This is the game we will be creating


Pong. It's a pretty simple game -- you have two paddles, the player controls one of them and the AI controls the other, all in a game of don't-miss. It is usually the first or second game you would make after learning the fundamentals of a language or library (sometimes Tic-Tac-Toe is first). Using LWJGL doesn't make coding this game much easier, since the focus is primarily on the logic in this one. It does, however, make setting up the screen and drawing things a whole lot quicker.

LWJGL and OpenGL Initialization

When I program in JAVA, I usually add in comments to set up a simple template for me to follow, so all I have to do is fill in the blanks. It helps me remember what the initialization code is for the library I'm using and things like that. The order is as follows:

//import - this is where you would statically/import all the libraries you need
//display - Display.setDisplayMode etc would go here
//ogl - setting glMatrixMode 
//main loop - our main game loop
//destroy display - destroy our display
main - this is where we call our game loop by invoking new Main()

Fleshed out, it would look something like this:

//import
import static org.lwjgl.opengl.GL11.*;
import org.lwjgl.LWJGLException;

public class Game(){
    //display
    try{
        Display.setDisplayMode(new DisplayMode(HEIGHT, WIDTH));
        Display.setTitle("Game");
        Display.create();
    } catch(LWJGLException e){
        e.printStackTrace();
    }
    //ogl
    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();
    glOrtho(0, HEIGHT, WIDTH, 0, 1, -1);
    glMatrixMode(GL_MODELVIEW);

    //main loop
    while(!Display.isCloseRequested()){
        
        glClear(GL_COLOR_BUFFER_BIT);

        Display.update();
        Display.sync(FPS);
    }
    Display.destroy();
    //destroy display

    public static void main(String args[]){
        new Game();
    }

}


Pretty straightforward.

Managing Entities in our game - the Entity Class


For Pong, the first important thing we need to do is set up a simple way that we can handle all the objects. From drawing them to the screen, to updating their x and y position, it would be nice if we could make a superclass that manages this for us. This is where the Entity class comes in:

package _pong;

import java.awt.Rectangle;
import java.util.ArrayList;
import java.util.List;

public abstract class Entity {

protected int x;
protected int y;
protected double dx;
protected double dy;
protected int height;
protected int width;
protected static List<Entity> entities = new ArrayList<Entity>();
protected Rectangle bounds = new Rectangle();

public Entity(int x, int y, int height, int width) {
entities.add(this);
this.x = x;
this.y = y;
this.height = height;
this.width = width;
this.dx = 0;
this.dy = 0;

}

public int getX() {
return x;
}

public void setX(int x) {
this.x = x;
}

public int getY() {
return y;
}

public void setY(int y) {
this.y = y;
}

public double getDx() {
return dx;
}

public void setDx(double dx) {
this.dx = dx;
}

public double getDy() {
return dy;
}

public void setDy(double dy) {
this.dy = dy;
}

public int getHeight() {
return height;
}

public void setHeight(int height) {
this.height = height;
}

public int getWidth() {
return width;
}

public void setWidth(int width) {
this.width = width;
}

public void update(int delta){

}

public void draw(){

}

public void logic(){

}

public void destroy(){
entities.remove(this);
}

public boolean intersect(Entity e) {
return this.bounds.intersects(e.bounds);
}


}

As you can see, this class doesn't really house anything fancy other than a bunch of getters and setters. The most interesting thing here that I will point out is the Entity Index at the top. That ArrayList will allow us to invoke Global methods on our Entities -- things like drawing and updating, which would be a pain to do for each individual entity in a game with many more objects. Getting this down now will help you much in the future when managing multiple entities on the screen. Essentially what it does is add the entity that has been created to a global list of entities. Anytime we want to do something involving every Entity, all we have to do is iterate over this list. You'll see how handy this is pretty soon.

Sub-Entity Classes: the Box Class

Normally, Entity is too broad a class if you have multiple entities on your screen that each behave differently. If we wanted to make a Sphere Entity and a Block Entity, it would be difficult creating them by calling a new Entity. Instead what we do is create Sub-classes that inherit from the Entity superclass. These sub-classes will each behave differently. but will all derive from the Entity class so they can be managed like any other Entity.

private class Box extends Entity{
public Box(int x, int y, int height, int width){
super(x, y, height, width);
}
public void update(int delta){
logic();
super.x +=  dx * delta;
super.y +=  dy * delta;
//bounds.setBounds(x, y, width, height);
return;
}
public void draw(){
glColor3f(0.9f, 0.7f, 0);
glRecti(x, y, x + width,y + height);
}

Again, really simple stuff here. We create a Box sub-class that extends the Entity superclass. The only way that Boxes differ from a generic Entity is the way that they are drawn to the screen and how they are updated. If you recall in the Entity class we have no method body for update() and draw(). These methods must be overriden by a subclass. 

In our update() method, we take as input an integer called delta, which may confuse some who have not seen it before. Motion interpolation is a technique that allows us to smoothly render objects to the screen by making use of a dynamic variable called a Delta. To perform this trick, the computer needs a variable that changes by a constant interval over a range of time. This is why many later games used what is called a System Clock that is ever-changing over a constant interval of time. By measuring the amount of time it takes to complete 1 cycle in the main loop, we can use that and base all of our update values on that time, thus updating the game as smoothly as the System Clock updates it's time. 

You will also notice a call to a method called logic(). This method is even more specific to each individual Box entity. It is the most specific Entity method we have, and one which we have created for this purpose.

Creating Box Entities: the Box, the Paddle, and the Opponent

This is where our logic() method will do nearly all of the work. We begin by creating the boxes themselves by instantiating some new Boxes. After that, we open up an Anonymous Class after our calls and override the logic() method.

ball = new Box(640>>1, 480>>1, 4, 4){
@Override
public void logic(){
//collision detection
//top half of paddle
if(ball.intersect(paddle)){
if(ball.y <= paddle.y +(paddle.height/2) 
&& ball.y >= paddle.y){
ball.setDx(-1 * ball.dx);
ball.setDy(-0.4);
} else if(ball.y <= (paddle.y) + paddle.height
&& ball.y >= paddle.y +(paddle.height/2)){
ball.setDx(-1 * ball.dx);
ball.setDy(0.4);
} else {
ball.setDx(-1 * ball.dx);
ball.setDy(-0.4);
};
}
if(ball.intersect(opponent)){
if(ball.y <= opponent.y + (opponent.height/2) 
&& ball.y >= opponent.y){
ball.setDx(-1 * ball.dx);
ball.setDy(-0.4);
} else if(ball.y <= (opponent.y) +opponent.height
&& ball.y >= opponent.y +(opponent.height/2)){
ball.setDx(-1 * ball.dx);
ball.setDy(0.4);
} else {
ball.setDx(-1 * ball.dx);
ball.setDy(-0.4);
};
}
//bounds control
if(ball.y > 480 || ball.y < 0){
ball.dy *= -1;
}
else if (ball.x > 640 || ball.x < 0){
ball.dx *= -1;
}
//speed control
if(dy > 1){
dy = 1;
} else if (dy < -1){
dy = -1;
}
}
};
paddle = new Box(20, 10, 60, 5){
@Override
public void logic(){
if(Keyboard.isKeyDown(Keyboard.KEY_S)){
setDy(0.5);
}
else if (Keyboard.isKeyDown(Keyboard.KEY_W)){
setDy(-0.5);
}
else setDy(0);
}
};
opponent = new Box(610, 10, 60, 5){
@Override
public void logic(){
if(ball.getY() >= y+(height/2)){
dy = 0.4;
}
else if (ball.getY() <= y+(height/2)){
dy = -0.4;
}
else dy = 0;
}
};

I know it's a bit compact, but you can always review the source code directly. Anyhow, you can see the paddle and opponent Entities are pretty small. They only contain the code that moves them up and down and it's pretty lazily done, I admit. But the ball class - now that's a monster! The reason for that is the collision detection that checks if the ball has hit the upper or lower half of the paddles themselves. Essentially, the math works like this:

If the ball's Y position is greater than the top of the paddle, but less than the middle of the paddle, then fling that sucker upwards. However, if it is greater than the middle of the paddle, and less than the bottom of the paddle, then send it flying downwards. 

Simple, no? I added a few checks that I could afterwards should I decide to edit anything in there including the bounds control, which ensures the ball is always on the screen, and the speed control, which ensures the ball is never going too fast in one direction or the other.

The Incredibly Simple Main Loop

while(!Display.isCloseRequested()){
glClear(GL_COLOR_BUFFER_BIT);
if(Keyboard.isKeyDown(Keyboard.KEY_ESCAPE)){
Display.destroy();
System.exit(0);
}
line.draw();
paddle.draw();
ball.draw();
opponent.draw();
updateAll(getDelta(), Entity.entities);
Display.update();
Display.sync(60);
}

That, ladies and gentlemen, is Pong! In our main loop, we simply draw all of the Entities, and then we call our updateAll function utilizing the Entity Index I mentioned earlier. In future projects, I decided to also revamp the draw() method into a drawAll() method that does essentially the same thing, but I tried not to edit anything in my old code. For completion's sake, here is the updateAll() method:

private void updateAll(int delta, List<Entity> l){
for(Entity f : l){
f.bounds.setBounds(f.x, f.y, f.width, f.height);
f.update(delta);
}
}

If you need to see the source code, here is the link to it:


If you have any issues with the code that you need help with, leave a comment, I will answer it as soon as I can.

No comments:

Post a Comment