Just for quick self introduction, I’m fairly new to computer programming.
My background is just a semester of single CS introductory class. (I was majoring mechanical engineering back then a year ago.)
This semester I decided upon a personal long term project, and you might have guessed that it is making a game.
I delved into some forums such as this and watched tutorial videos, and I got “something” going on for now.
However there seems to be few problems with my program.
First thing is that the program seems to have a memory leakage.
Currently, the only task it does is to perpetually update steady, unchanging game state.
However, I checked on the task manager and the program kept on hogging more memory space.
It had some occassional memory drops, but it just kept on increaseing in the long run.
The parts that I coded by myself didn’t seem to have problems, but I’m not sure of some parts that I got from "follow-along"s.
Second probem is that its image buffering sucks.
I’m using a double buffering method from a video tutorial of user “thejavahub”, but I don’t see much effect.
Also it takes more than 20ms just to do the task I mentioned above, which I believe will cause the game to go lower than 50 fps.
Below I’m posting two classes of my program that should hold enough source code for those willing to help.
/*EDIT
I forgot to mention what my questions were:
- What’s causing the memory leak? How do I prevent it? (Or is it even a memory leak?)
- How do I get a smooth panning mechanism instead of the current ugly one?
- What do you think about the program’s “drawing algorithm”? Any improvement and/or criticism?
- Any other criticism that is not related to my questions?
I want to learn programming well and from lots of perspective.
Harsh criticisms (criticisms, not insults) or even minor details such as missed programming convention is welcomed.
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
@SuppressWarnings("serial")
public class GameBoard extends JPanel implements Runnable {
// Dependent to tile image size.
private final int SCALE = 16;
private final long PERIOD = 20 * 1000000; // Milliseconds to Nanoseconds
private static int uIndex = 0;
private static int vIndex = 0;
private static int wIndex = 1;
long beforeTime;
long afterTime;
long difference;
long sleepTime;
private Image TILE_0;
private Image TILE_0_TOP;
private Image TILE_A;
private Image TILE_A_TOP;
private Image TILE_B;
private Image TILE_B_TOP;
private Graphics bufferGraphics;
private Image bufferImage;
private Thread game;
private volatile boolean running = false;
private GameTile tileGrid;
public GameBoard() {
loadTileImage();
tileGrid = new GameTile(2);
setBackground(Color.WHITE);
setFocusable(true);
requestFocus();
addKeyListener(new KeyAdapter() {
@Override
public void keyPressed(KeyEvent e) {
switch(e.getKeyCode()) {
default:
break;
case KeyEvent.VK_UP:
if(uIndex < tileGrid.getU() / 2)
uIndex++;
break;
case KeyEvent.VK_DOWN:
if(uIndex > -(tileGrid.getU() / 2))
uIndex--;
break;
case KeyEvent.VK_LEFT:
if(vIndex < tileGrid.getV() / 2)
vIndex++;
break;
case KeyEvent.VK_RIGHT:
if(vIndex > -(tileGrid.getV() / 2))
vIndex--;
break;
case KeyEvent.VK_PAGE_UP:
if(wIndex < tileGrid.getW() - 2)
wIndex++;
break;
case KeyEvent.VK_PAGE_DOWN:
if(wIndex > 2)
wIndex--;
break;
case KeyEvent.VK_SPACE:
uIndex = 0;
vIndex = 0;
break;
case KeyEvent.VK_ESCAPE:
System.exit(0);
break;
}
}
@Override
public void keyReleased(KeyEvent e) {}
@Override
public void keyTyped(KeyEvent e) {}
});
}
public void addNotify() {
super.addNotify();
startGame();
}
private void startGame() {
if(game == null || !running) {
game = new Thread(this);
game.start();
running = true;
}
}
public void stopGame() {
if(running) {
running = false;
}
}
@SuppressWarnings("static-access")
public void run() {
while(running) {
beforeTime = System.nanoTime();
gameUpdate();
gameRender();
paintScreen();
afterTime = System.nanoTime();
difference = afterTime - beforeTime;
if(difference < PERIOD) {
sleepTime = PERIOD - difference;
try {
game.sleep(sleepTime / 1000000);
} catch (InterruptedException e) {}
System.out.printf("%d.%03d ms\n", difference / 1000000, difference % 1000000 / 1000);
} else {
System.out.printf("LAG! %d.%03d ms\n", difference / 1000000, difference % 1000000 / 1000);
}
}
}
private void gameUpdate() {
if(running || game != null) {
// TODO Update game board.
}
}
private void gameRender() {
if(bufferImage == null) {
// Create the buffer.
bufferImage = createImage(800, 600);
if(bufferImage == null) {
System.out.println("ERROR: Buffer image still does not exist!");
return;
} else {
bufferGraphics = bufferImage.getGraphics();
}
}
// Clear the screen.
bufferGraphics.setColor(Color.WHITE);
bufferGraphics.fillRect(0, 0, 800, 600);
draw(bufferGraphics);
}
public void draw(Graphics g) {
drawTile(g);
}
public void paintScreen() {
Graphics g;
try {
g = this.getGraphics();
if(bufferImage != null && g != null) {
g.drawImage(bufferImage, 0, 0, null);
}
Toolkit.getDefaultToolkit().sync();
g.dispose();
} catch(Exception e) {}
}
private void loadTileImage() {
TILE_0 = new ImageIcon("tile0.png").getImage();
TILE_0_TOP = new ImageIcon("tile0top.png").getImage();
TILE_A = new ImageIcon("tileA.png").getImage();
TILE_A_TOP = new ImageIcon("tileAtop.png").getImage();
TILE_B = new ImageIcon("tileB.png").getImage();
TILE_B_TOP = new ImageIcon("tileBtop.png").getImage();
}
private void drawTileImage(int i, int j, int k, Graphics g) {
if(tileGrid.getTileID(i, j, k) == 0)
g.drawImage(TILE_0, toX(i, j), toY(i, j, k), null);
else if(tileGrid.getTileID(i, j, k) == 1)
g.drawImage(TILE_A, toX(i, j), toY(i, j, k), null);
else if(tileGrid.getTileID(i, j, k) == 2)
g.drawImage(TILE_B, toX(i, j), toY(i, j, k), null);
}
private void drawTileTopImage(int i, int j, int k, Graphics g) {
if(tileGrid.getTileID(i, j, k) == 0)
g.drawImage(TILE_0_TOP, toX(i, j), toY(i, j, k), null);
else if(tileGrid.getTileID(i, j, k) == 1)
g.drawImage(TILE_A_TOP, toX(i, j), toY(i, j, k), null);
else if(tileGrid.getTileID(i, j, k) == 2)
g.drawImage(TILE_B_TOP, toX(i, j), toY(i, j, k), null);
}
private void drawTile(Graphics g) {
for(int i = 0; i < tileGrid.getU(); i++) {
for(int j = 0; j < tileGrid.getV(); j++) {
drawTileImage(i, j, wIndex - 1, g);
drawTileImage(i, j, wIndex, g);
drawTileTopImage(i, j, wIndex + 1, g);
}
}
}
private int toX(int i, int j) {
return((getWidth() / 2 - SCALE) - SCALE * (i + uIndex) + SCALE * (j + vIndex));
}
private int toY(int i, int j, int k) {
return((getHeight() / 2 - SCALE * tileGrid.getV() / 2) + SCALE * (i + uIndex + j + vIndex) / 2 - SCALE * (k - wIndex));
}
}
public class GameTile {
private static int u;
private static int v;
private static int w;
private static int[][][] tileGrid;
public GameTile(int size) {
if(size == 0)
size = 1;
u = (64 * size);
v = (64 * size);
w = (64 * size);
tileGrid = new int[u][v][w];
generateTileGrid();
}
private void generateTileGrid() {
int test;
for(int i = 0; i < u; i++) {
for(int j = 0; j < v; j++) {
for(int k = 0; k < w; k++) {
test = ((i + j + k) % 4);
if(test == 0)
tileGrid[i][j][k] = 1;
else if(test == 1)
tileGrid[i][j][k] = 2;
else
tileGrid[i][j][k] = 0;
}
}
}
}
public void setTileID(int i, int j, int k, int tileID) {
tileGrid[i][j][k] = tileID;
}
public int getU() {
return u;
}
public int getV() {
return v;
}
public int getW() {
return w;
}
public int getTileID(int i, int j, int k) {
return tileGrid[i][j][k];
}
public int[][][] getTileGrid() {
return tileGrid;
}
}