Wednesday 6 March 2013

Write a Simple Game with LWJGL (LightWeight Java GL)

This post will show a demo on how to write an OpenGL game by using Java.

LWJGL is a framework not only a java tool package but also contains cross-platform complied OpenGL API files. These native clients could work on the following operation systems: Windows, Linux, MacOSX, and Solaris. Despite Java program efficiency issues, LWJGL gets out a possible solution that developers need only deploy the core code and the rests (OpenGL relative) will auto-adjusted by the framework.

Pong is a very famous & simple console game. So I implement it as the ‘Hello World’ project.

image

Initially, let me introduce the basic game logic (although most of you know it XD)

  • 2 Players (one human and one computer), each player controls a bat
  • 1 Ball
  • Control the bat move up and down, try to hit the ball, do not miss it

Sketch:

image

Before we start write code, add LWJGL libraries first.

  1. Download LWJGL jar files
  2. Attach these jar files to out project
  3. Add native GL files to the project. This step is crucial. Select the files which match you current/target operation system
  4. (optional) Add Javadoc
  5. Now it should looks like the image below

image

Great! Ready to work.

============

Firstly, define the entities.

This operation may unnecessary for such small project. But it still helps us to build a well-formed OO program.

image

Two interfaces: Entity & MoveableEntity

Two abstract classes: AbstractEntity & AbstraceMoveableEntity

Code:

package entites;

public interface Entity {
public void draw();
public void update(int delta);
public void setLocation(double x, double y);
public void setX(double x);
public void setY(double y);
public void setWidth(double width);
public void setHeight(double height);
public double getX();
public double getY();
public double getHeight();
public double getWidth();
public boolean intersects(Entity other);

}



package entites;

import java.awt.Rectangle;

public abstract class AbstractEntity implements Entity {

protected double x, y, width, height;
protected Rectangle hitbox = new Rectangle();

public AbstractEntity(double x, double y, double width, double height) {
this.x = x;
this.y = y;
this.width = width;
this.height = height;
}

@Override
public void setLocation(double x, double y) {
this.x = x;
this.y = y;
}

@Override
public void setX(double x) {
this.x = x;
}

@Override
public void setY(double y) {
this.y = y;

}

@Override
public void setWidth(double width) {
this.width = width;

}

@Override
public void setHeight(double height) {
this.height = height;

}

@Override
public double getX() {
return x;
}

@Override
public double getY() {
return y;
}

@Override
public double getHeight() {
return height;
}

@Override
public double getWidth() {
return width;
}

@Override
public boolean intersects(Entity other) {
hitbox.setBounds((int) x, (int) y, (int) width, (int) height);
return hitbox.intersects(other.getX(), other.getY(), other.getWidth(),
other.getHeight());
}

}



package entites;

public interface MoveableEntity extends Entity {
public void setDX(double dx);
public void setDY(double dy);
public double getDX();
public double getDY();
}



package entites;

public abstract class AbstractMoveableEntity extends AbstractEntity implements
MoveableEntity {

protected double dx, dy;

public AbstractMoveableEntity(double x, double y, double width,
double height) {
super(x, y, width, height);
this.dx = 0;
this.dy = 0;
}

@Override
public void update(int delta) {
this.x += delta * dx;
this.y += delta * dy;
}

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

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

public double getDX() {
return dx;
}

public double getDY() {
return dy;
}

}



 


>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>


Time to setup the main body of Pong.



  1. Initialize OpenGL

  2. Initialize all necessary Objects

  3. Make some Objects “move”

  4. Listening users key-press actions

  5. Also, AI should response

  6. Game logic (hit? miss? win? lose?)

  7. Refresh the screen

The codes are very simple. I believe you can understand most of them.
( Because I am lazy ^_^ )


package pong;

import java.util.Random;

import org.lwjgl.*;
import org.lwjgl.input.Keyboard;
import org.lwjgl.opengl.*;
import entites.AbstractMoveableEntity;
import static org.lwjgl.opengl.GL11.*;

public class PongGame {

public static final int WIDTH = 640, HEIGHT = 480;
private boolean isRunning = true;
private long lastFrame;
private Ball ball;
private Bat bat;
private Bat computerBat;

public PongGame() {
setUpDisplay();
setUpOpenGL();
setUpEntities();
setUpTimer();

while (isRunning) {
render();
logic(getDelta());
input();
computer();
Display.update();
Display.sync(60);
if (Display.isCloseRequested()) {
isRunning = false;
}
}

Display.destroy();
}

private void computer() {
if (ball.getX() > 350 && ball.getDX()>0) {
if (ball.getY() + ball.getHeight() / 2 > computerBat.getY()
+ computerBat.getHeight() / 2 + 4
&& computerBat.getY() + computerBat.getHeight() <= HEIGHT) {
computerBat.setDY(0.2);
} else if (ball.getY() + ball.getHeight() / 2 < computerBat.getY()
+ computerBat.getHeight() / 2 - 4
&& computerBat.getY() >= 0) {
computerBat.setDY(-0.2);
} else {
computerBat.setDY(0);
}
}
else{
computerBat.setDY(0);
}
}

private void input() {
if (Keyboard.isKeyDown(Keyboard.KEY_UP) && bat.getY() >= 0) {
bat.setDY(-0.2);
} else if (Keyboard.isKeyDown(Keyboard.KEY_DOWN)
&& bat.getY() <= HEIGHT - bat.getHeight()) {
bat.setDY(0.2);
} else {
bat.setDY(0);
}
}

public long getTime() {
return (Sys.getTime() * 1000) / Sys.getTimerResolution();
}

public int getDelta() {
long time = getTime();
int delta = (int) (time - lastFrame);
lastFrame = time;
return delta;
}

private void logic(int delta) {
ball.update(delta);
computerBat.update(delta);
bat.update(delta);
//ball hit the bat
if (ball.getX() <= bat.getX() + bat.getWidth()
&& ball.getX() >= bat.getX()
&& ball.getY() + ball.getHeight() >= bat.getY()
&& ball.getY() <= bat.getY() + bat.getHeight()) {
ball.setDX(Math.abs(ball.getDX()));
Random ran = new Random();
ball.setDY(ball.getDY() + bat.getDY() / 3
+ (double) (1 - ran.nextInt(2)) / 200);
}
//ball hit the computerBat
if (ball.getX() + 10 >= computerBat.getX()
&& ball.getX() + 10 <= computerBat.getX() + 10
&& ball.getY() + ball.getHeight() >= computerBat.getY()
&& ball.getY() <= computerBat.getY() + computerBat.getHeight()) {
ball.setDX(-Math.abs(ball.getDX()));
}

//top & bot border
if (ball.getY() < 0 || ball.getY() > HEIGHT - ball.getHeight()) {
ball.setDY(-ball.getDY());
}

if (ball.getX() < 0 || ball.getX() > WIDTH - ball.getWidth()) {
//reset game
setUpEntities();
}

//fix DY if DY is too large
if (Math.abs(ball.getDY()) > Math.abs(ball.getDX() * 1.5)) {
ball.setDY(ball.getDY() / 1.5);
}

}

private void render() {
glClear(GL_COLOR_BUFFER_BIT);
ball.draw();
bat.draw();
computerBat.draw();
}

private void setUpTimer() {
lastFrame = getTime();
}

private void setUpEntities() {
bat = new Bat(20, (HEIGHT / 2 - 80 / 2), 10, 80);
computerBat = new Bat(WIDTH - 20 - 10, (HEIGHT / 2 - 80 / 2), 10, 80);
ball = new Ball(WIDTH / 2 - 10 / 2, HEIGHT / 2 - 10 / 2, 10, 10);
ball.setDX(-0.15);
}

private void setUpOpenGL() {
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
glOrtho(0, WIDTH, HEIGHT, 0, 1, -1);
glMatrixMode(GL_MODELVIEW);
}

private void setUpDisplay() {
try {
Display.setDisplayMode(new DisplayMode(WIDTH, HEIGHT));
Display.setTitle("pong");
Display.create();
} catch (LWJGLException e) {
e.printStackTrace();
System.exit(0);
}

}

private static class Bat extends AbstractMoveableEntity {

public Bat(double x, double y, double width, double height) {
super(x, y, width, height);
}

@Override
public void draw() {
//glColor3d(1.0, 1.0, 1.0);
glRectd(x, y, (x + width), (y + height));
}
}

private static class Ball extends AbstractMoveableEntity {

public Ball(double x, double y, double width, double height) {
super(x, y, width, height);
}

@Override
public void draw() {
//glColor3d(1.0, 1.0, 1.0);
glRectd(x, y, x + width, y + height);
}

}

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