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.
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:
Before we start write code, add LWJGL libraries first.
- Download LWJGL jar files
- Attach these jar files to out project
- Add native GL files to the project. This step is crucial. Select the files which match you current/target operation system
- (optional) Add Javadoc
- Now it should looks like the image below
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.
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.
- Initialize OpenGL
- Initialize all necessary Objects
- Make some Objects “move”
- Listening users key-press actions
- Also, AI should response
- Game logic (hit? miss? win? lose?)
- 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();
}
}