|
This example shows Lines, Anti-Aliasing, Orthographic Projection, Timing, Basic Sound Effects, and Simple Game Logic.
This is the Java port of the one of the NeHe OpenGL tutorials.
You can get complete IntelliJ IDEA project structure (all source, resources, build script, …) by downloading the source distribution from here.
The original post of the programmer who ported the examples can be found here.
package demos.nehe.lesson21;
import demos.common.GLDisplay;
import javax.swing.*;
public class Lesson21 {
public static void main(String[] args) {
try {
Class.forName("com.dnsalias.java.timer.AdvancedTimer");
} catch (ClassNotFoundException e) {
JOptionPane.showMessageDialog(
null,
"The GAGETimer API could not be found in the classpath.\n" +
"This API is required by this lesson.\n" +
"It can be downloaded at http://java.dnsalias.com/.",
"Could not find GAGETimer",
JOptionPane.ERROR_MESSAGE
);
System.exit(0);
}
GLDisplay neheGLDisplay = GLDisplay.createGLDisplay(
"Lesson 21: Lines, timing, sound");
Renderer renderer = new Renderer();
InputHandler inputHandler = new InputHandler(renderer, neheGLDisplay);
neheGLDisplay.addGLEventListener(renderer);
neheGLDisplay.addKeyListener(inputHandler);
neheGLDisplay.start();
}
}
package demos.nehe.lesson21;
import demos.common.GLDisplay;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
class InputHandler extends KeyAdapter {
private Renderer renderer;
public InputHandler(Renderer renderer, GLDisplay display) {
this.renderer = renderer;
// display.registerKeyStrokeForHelp(KeyStroke.getKeyStroke(KeyEvent.VK_, 0), "");
}
public void keyPressed(KeyEvent e) {
// Set flags
processKeyEvent(e, true);
}
public void keyReleased(KeyEvent e) {
switch (e.getKeyCode()) {
// Toggle properties
case KeyEvent.VK_SPACE:
renderer.resetGame();
break;
default:
// Unset flags
processKeyEvent(e, false);
}
}
private void processKeyEvent(KeyEvent e, boolean pressed) {
switch (e.getKeyCode()) {
case KeyEvent.VK_UP:
renderer.moveUp(pressed);
break;
case KeyEvent.VK_DOWN:
renderer.moveDown(pressed);
break;
case KeyEvent.VK_LEFT:
renderer.moveLeft(pressed);
break;
case KeyEvent.VK_RIGHT:
renderer.moveRight(pressed);
break;
}
}
}
package demos.nehe.lesson21;
import demos.common.ResourceRetriever;
import javax.sound.sampled.*;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.Timer;
import java.util.TimerTask;
class AudioSample {
private final Clip clip;
public AudioSample(InputStream inputStream) throws IOException {
/* Load Sound*/
try {
// The inputstreams that we get by loading files from jars do not support
// mark() and reset(), which are required by AudioSystem.getAudioInputStream().
// To work around this problem, we first read the entire contents
// of the inputstream to a byte array and wrap it in a ByteArrayInputStream.
// This class does support mark() and reset().
InputStream in = ensureMarkResetAvailable(inputStream);
AudioInputStream audioInputStream = AudioSystem.getAudioInputStream(in);
AudioFormat audioFormat = audioInputStream.getFormat();
int size = (int) (audioFormat.getFrameSize() *
audioInputStream.getFrameLength());
byte[] audio = new byte[size];
audioInputStream.read(audio, 0, size);
DataLine.Info info = new DataLine.Info(Clip.class, audioFormat, size);
clip = (Clip)AudioSystem.getLine(info);
clip.open(audioFormat, audio, 0, size);
} catch (UnsupportedAudioFileException e) {
throw new RuntimeException(e);
} catch (LineUnavailableException e) {
throw new RuntimeException(e);
}
}
private static InputStream ensureMarkResetAvailable(InputStream inputStream)
throws IOException {
if (inputStream.markSupported()) {
return inputStream;
} else {
return new ByteArrayInputStream(readEntireStream(inputStream));
}
}
private static byte[] readEntireStream(InputStream inputStream) throws IOException {
byte[] buffer = new byte[8];
byte[] data = null;
int dataLength = 0;
int bytesRead;
while ((bytesRead = inputStream.read(buffer)) != -1) {
data = append(buffer, bytesRead, data, dataLength);
dataLength += bytesRead;
}
return trim(data, dataLength);
}
private static byte[] append(byte[] data, int amount, byte[] array, int offset) {
if (array == null) {
array = new byte[amount];
}
if (offset + amount >= array.length) {
byte[] newArray = new byte[array.length * 2];
System.arraycopy(array, 0, newArray, 0, offset);
array = newArray;
}
System.arraycopy(data, 0, array, offset, amount);
return array;
}
private static byte[] trim(byte[] data, int amount) {
if (data == null) {
return new byte[amount];
} else if (data.length == amount) {
return data;
} else {
byte[] newArray = new byte[amount];
System.arraycopy(data, 0, newArray, 0, amount);
return newArray;
}
}
public void play() {
play(false, false);
}
public void play(boolean wait, boolean loop) {
if (wait) {
WaitUntilFinishedLineListener waitUntilFinishedLineListener =
new WaitUntilFinishedLineListener();
clip.addLineListener(waitUntilFinishedLineListener);
synchronized (clip) {
play(loop);
try {
clip.wait();
} catch (InterruptedException e) {
}
}
clip.removeLineListener(waitUntilFinishedLineListener);
} else {
play(loop);
}
}
private void play(boolean loop) {
clip.stop();
clip.setFramePosition(0);
if (loop) {
clip.loop(Clip.LOOP_CONTINUOUSLY);
} else {
clip.start();
}
}
private class WaitUntilFinishedLineListener implements LineListener {
public WaitUntilFinishedLineListener() {
}
public void update(LineEvent event) {
if (event.getType().equals(LineEvent.Type.STOP) ||
event.getType().equals(LineEvent.Type.CLOSE)) {
synchronized (clip) {
clip.notify();
}
}
}
}
public void stop() {
clip.stop();
}
}
package demos.nehe.lesson21;
import com.sun.opengl.util.BufferUtil;
import demos.common.ResourceRetriever;
import demos.common.TextureReader;
import javax.media.opengl.GL;
import javax.media.opengl.GLAutoDrawable;
import javax.media.opengl.GLEventListener;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.util.Random;
class Renderer implements GLEventListener {
private Random random = new Random();
private boolean[][] vline = new boolean[11][11]; // Keeps Track Of Verticle Lines
private boolean[][] hline = new boolean[11][11]; // Keeps Track Of Horizontal Lines
private boolean filled; // Done Filling In The Grid?
private boolean gameover; // Is The Game Over?
private boolean anti = true; // Antialiasing?
private float delay; // Enemy Delay
private int adjust = 3; // Speed Adjustment For Really Slow Video Cards
private int lives = 5; // Player Lives
private int level = 1; // Internal Game Level
private int level2 = level; // Displayed Game Level
private int stage = 1; // Game Stage
private GameObject player = new GameObject(); // Player Information
private GameObject[] enemy = new GameObject[9]; // Enemy Information
private GameObject hourglass = new GameObject(); // Hourglass Information
// Stepping Values For Slow Video Adjustment
private int steps[] = {1, 2, 4, 5, 10, 20};
private int textures[] = new int[2]; // Font Texture Storage Space
private int base;
private boolean resetGame = false;
private boolean moveRight = false;
private boolean moveLeft = false;
private boolean moveDown = false;
private boolean moveUp = false;
private AudioSample dieSample;
private AudioSample hourglassSample;
private AudioSample freezeSample;
private AudioSample completerSample;
private ByteBuffer stringBuffer = BufferUtil.newByteBuffer(256);
private long lastUpdateTime;
public void moveUp(boolean move) {
moveUp = move;
}
public void moveDown(boolean move) {
moveDown = move;
}
public void moveLeft(boolean move) {
moveLeft = move;
}
public void moveRight(boolean move) {
moveRight = move;
}
public void resetGame() {
resetGame = true;
}
private void resetObjects() { // Reset Player And Enemies
player.x = 0; // Reset Player X Position To Far Left Of The Screen
player.y = 0; // Reset Player Y Position To The Top Of The Screen
player.fx = 0; // Set Fine X Position To Match
player.fy = 0; // Set Fine Y Position To Match
// Loop Through All The Enemies
for (int i = 0; i < (stage * level); i++) {
enemy[i] = new GameObject();
enemy[i].x = 5 + (int) (Math.random() * 6); // Select A Random X Position
enemy[i].y = (int) Math.random() * 11; // Select A Random Y Position
enemy[i].fx = enemy[i].x * 60; // Set Fine X To Match
enemy[i].fy = enemy[i].y * 40; // Set Fine Y To Match
}
}
private void loadGLTextures(GL gl) throws IOException {
String tileNames [] = {"demos/data/images/font.png", "demos/data/images/Image.png"};
gl.glGenTextures(2, textures, 0);
for (int i = 0; i < 2; i++) {
TextureReader.Texture texture = TextureReader.readTexture(tileNames[i]);
//Create Nearest Filtered Texture
gl.glBindTexture(GL.GL_TEXTURE_2D, textures[i]);
gl.glTexParameteri(GL.GL_TEXTURE_2D, GL.GL_TEXTURE_MAG_FILTER, GL.GL_LINEAR);
gl.glTexParameteri(GL.GL_TEXTURE_2D, GL.GL_TEXTURE_MIN_FILTER, GL.GL_LINEAR);
gl.glTexImage2D(GL.GL_TEXTURE_2D,
0,
3,
texture.getWidth(),
texture.getHeight(),
0,
GL.GL_RGB,
GL.GL_UNSIGNED_BYTE,
texture.getPixels());
}
}
void buildFont(GL gl) { // Build Our Font Display List
base = gl.glGenLists(256); // Creating 256 Display Lists
for (int i = 0; i < 256; i++) // Loop Through All 256 Lists
{
float cx = (float) (i % 16) / 16.0f; // X Position Of Current Character
float cy = (float) (i / 16) / 16.0f; // Y Position Of Current Character
gl.glNewList(base + i, GL.GL_COMPILE); // Start Building A List
gl.glBegin(GL.GL_QUADS); // Use A Quad For Each Character
gl.glTexCoord2f(cx, 1.0f - cy - 0.0625f); // Texture Coord (Bottom Left)
gl.glVertex2d(0, 16); // Vertex Coord (Bottom Left)
// Texture Coord (Bottom Right)
gl.glTexCoord2f(cx + 0.0625f, 1.0f - cy - 0.0625f);
gl.glVertex2i(16, 16); // Vertex Coord (Bottom Right)
gl.glTexCoord2f(cx + 0.0625f, 1.0f - cy); // Texture Coord (Top Right)
gl.glVertex2i(16, 0); // Vertex Coord (Top Right)
gl.glTexCoord2f(cx, 1.0f - cy); // Texture Coord (Top Left)
gl.glVertex2i(0, 0); // Vertex Coord (Top Left)
// Done Building Our Quad (Character)
gl.glEnd();
// Move To The Right Of The Character
gl.glTranslated(15, 0, 0);
gl.glEndList(); // Done Building The Display List
} // Loop Until All 256 Are Built
}
// Where The Printing Happens
private void glPrint(GL gl, int x, int y, int set, String message)
{
if (set > 1) { // Did User Choose An Invalid Character Set?
set = 1; // If So, Select Set 1 (Italic)
}
gl.glEnable(GL.GL_TEXTURE_2D); // Enable Texture Mapping
gl.glLoadIdentity(); // Reset The Modelview Matrix
gl.glTranslated(x, y, 0); // Position The Text (0,0 - Bottom Left)
gl.glListBase(base - 32 + (128 * set)); // Choose The Font Set (0 or 1)
if (set == 0) { // If Set 0 Is Being Used Enlarge Font
gl.glScalef(1.5f, 2.0f, 1.0f); // Enlarge Font Width And Height
}
if (stringBuffer.capacity() < message.length()) {
stringBuffer = BufferUtil.newByteBuffer(message.length());
}
stringBuffer.clear();
stringBuffer.put(message.getBytes());
stringBuffer.flip();
// Write The Text To The Screen
gl.glCallLists(message.length(), GL.GL_UNSIGNED_BYTE, stringBuffer);
gl.glDisable(GL.GL_TEXTURE_2D); // Disable Texture Mapping
}
public void init(GLAutoDrawable drawable) {
GL gl = drawable.getGL();
for (int i = 0; i < 9; i++)
enemy[i] = new GameObject();
try {
loadGLTextures(gl);
} catch (IOException e) {
throw new RuntimeException(e);
}
buildFont(gl);
gl.glShadeModel(GL.GL_SMOOTH); // Enables Smooth Color Shading
// This Will Clear The Background Color To Black
gl.glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
gl.glClearDepth(1.0); // Enables Clearing Of The Depth Buffer
gl.glEnable(GL.GL_DEPTH_TEST);
gl.glDepthFunc(GL.GL_LEQUAL); //T he Type Of Depth Test To Do
// Really Nice Perspective Calculations
gl.glHint(GL.GL_LINE_SMOOTH_HINT, GL.GL_NICEST);
gl.glEnable(GL.GL_BLEND); // Enable Blending
// Type Of Blending To Use
gl.glBlendFunc(GL.GL_SRC_ALPHA, GL.GL_ONE_MINUS_SRC_ALPHA);
gl.glEnable(GL.GL_TEXTURE_2D); // Enable 2D Texture Mapping
resetObjects(); // Reset Player / Enemy Positions
try {
dieSample = new AudioSample(
ResourceRetriever.getResourceAsStream("demos/data/samples/Die.wav"));
completerSample = new AudioSample(
ResourceRetriever.getResourceAsStream("demos/data/samples/Complete.wav"));
freezeSample = new AudioSample(
ResourceRetriever.getResourceAsStream("demos/data/samples/Freeze.wav"));
hourglassSample = new AudioSample(
ResourceRetriever.getResourceAsStream("demos/data/samples/Hourglass.wav"));
} catch (IOException e) {
throw new RuntimeException(e);
}
}
private void update() {
if (!gameover) // If Game Isn't Over And Programs Active Move Objects
{
// Loop Through The Different Stages
for (int loop1 = 0; loop1 < (stage * level); loop1++)
{
if ((enemy[loop1].x < player.x) && (enemy[loop1].fy ==
enemy[loop1].y * 40)) {
enemy[loop1].x++; // Move The Enemy Right
}
if ((enemy[loop1].x > player.x) && (enemy[loop1].fy ==
enemy[loop1].y * 40)) {
enemy[loop1].x--; // Move The Enemy Left
}
if ((enemy[loop1].y < player.y) && (enemy[loop1].fx ==
enemy[loop1].x * 60)) {
enemy[loop1].y++; // Move The Enemy Down
}
if ((enemy[loop1].y > player.y) && (enemy[loop1].fx ==
enemy[loop1].x * 60)) {
enemy[loop1].y--; // Move The Enemy Up
}
// If Our Delay Is Done And Player Doesn't Have Hourglass
if (delay > (3 - level) && (hourglass.fx != 2))
{
delay = 0; // Reset The Delay Counter Back To Zero
// Loop Through All The Enemies
for (int loop2 = 0; loop2 < (stage * level); loop2++)
{
// Is Fine Position On X Axis Lower Than Intended Position?
if (enemy[loop2].fx < enemy[loop2].x * 60)
{
// If So, Increase Fine Position On X Axis
enemy[loop2].fx += steps[adjust];
enemy[loop2].spin += steps[adjust]; // Spin Enemy Clockwise
}
< | |