Stuttering and rending issues in java4k game

Hi guys!

I’ve made a little avoid’em’up-game for this years Java4k. It runs smooth on some computers, but when I test it on one some “older” computers the animation is a bit jerky and I run into some irritating stuttering :(.

I have these problems on an older Linux laptop (in Firefox), and an older Windows 7 box (in Internet Explorer). However, when I run Chrome on these computers the game runs fine ???.

I’ve made some Java2D games before, but I have not experience rendering issues then. For example, my entry for last years Java4K runs just fine.

I’ve done everything I can think of (even desperate things): testing with different timing approaches, busy timing looping, non-busy timing looping, reducing painting operations, removing object allocations, reducing floating number operations, removing code, and so on… but nothing seems to solve this. Obviously I’m optimizing the wrong things, and I have a hard time to find the real issue.

This is starting to drive me crazy. What am I doing wrong? Do you have any suggestions?

I’m attaching the source code for the project. It’s not very many lines of code, but since it’s a Java4K-project the readability may not be the best. Just ask me if you need any specific details or info.

If someone wants to help me I would be very happy and grateful.
Help me JavaGaming, you are my only hope!

/*
 * Legend of the package delivering hero
 * Copyright (C) 2012 Jens Stääf
 *
 * Legend of the package delivering hero is free software; you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as published
 * by the Free Software Foundation; either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful, but 
 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
 * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 
 * for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with this program. If not, see <http://www.gnu.org/licenses/>.
 *
 */

import java.applet.Applet;
import java.awt.AWTEvent;
import java.awt.Color;
import java.awt.Graphics;
import java.awt.event.KeyEvent;
import java.awt.image.BufferedImage;
import java.util.Random;

public class G extends Applet implements Runnable {
	// SIZE
	static final int WINDOW_WIDTH = 320;
	static final int WINDOW_HEIGHT = 480;
	static final int MAP_WIDTH = 20;
	static final int MAP_HEIGHT = 30;

	// DIRECTION
	static final int UP = 0;
	static final int DOWN = 1;
	static final int LEFT = 2;
	static final int RIGHT = 3;

	// BOOLEAN
	static final int NO = 0;
	static final int YES = 1;

	static final int PLAYER_SPEED = 220;
	static final int STAR_SPEED = 1;
	static final int SUN_SPEED = 1;
	static final int MAX_NUMBER_OF_ENEMIES = 50;

	// ENEMY TYPES
	static final int POWERUP_BOMB = -3;
	static final int POWERUP_SCORE = -2;
	static final int POWERUP_HEALTH = -1;
	static final int ENEMY_STRAIGHT = 0;
	static final int ENEMY_BOUNCING = 1;
	static final int ENEMY_HOMING = 2;
	static final int ENEMY_TERMINATOR = 3;
	static final int ENEMY_NARROWER = 4;
	static final int ENEMY_CONFUSER = 5;
	static final int ENEMY_BLACKHOLE = 6;

	// ENEMY SPEED
	static final int ENEMY_SPEED_Y = 3;
	static final int ENEMY_SPEED_X = 2;

	// BUTTONS
	static int KEY_LEFT;
	static int KEY_RIGHT;
	static int KEY_UP;
	static int KEY_DOWN;
	static int KEY_SPACE;

	// STATES
	static final int STATE_TITLE = 0;
	static final int STATE_START = 1;
	static final int STATE_BREIFING = 2;
	static final int STATE_LEVELCOMPLETED = 3;
	static final int STATE_RUNNING = 4;
	static final int STATE_GAMEOVER = 5;

	// SCORE LIMITS, USED FOR CALCULATING ADAPTIVE DIFFICULTY
	static final int SCORE_LIMIT_1 = 300;
	static final int SCORE_LIMIT_2 = 500;

	// PLANETS
	static final String[] planets = { "Mercury", "Venus", "Earth", "Mars",
			"Jupiter", "Saturn", "Uranus" };

	// PARTICLES
	static final int MAX_NUMBER_OF_PARTICLES = 50;
	static final int NUMBER_OF_PARTICLES_IN_EXPLOSION = 20;
	static final int NUMBER_OF_PARTICLES_IN_SMALL_EXPLOSION = 5;

	public void start() {
		this.enableEvents(AWTEvent.KEY_EVENT_MASK);
		new Thread(this).start();
	}

	public void run() {
		setSize(WINDOW_WIDTH, WINDOW_HEIGHT);
		BufferedImage screen = new BufferedImage(WINDOW_WIDTH, WINDOW_HEIGHT,
				BufferedImage.TYPE_INT_RGB);
		Graphics g = screen.getGraphics();
		Graphics appletGraphics = getGraphics();
		Random random = new Random();

		// Variable for looping
		int i = 0;
		int a = 0;
		int b = 0;

		// Tile sizes
		int TILE_SIZE = 16;
		int TILE_SIZE_QUARTER = 4;

		// Set up player
		int playerX = 160;
		int playerY = 440;
		int playerVelocityX = 0;
		int playerVelocityY = 0;
		int[] playerXPoints = new int[3];
		int[] playerYPoints = new int[3];
		int score = -10;
		int health = 5;

		// Level
		int scoreLimit = 30;
		int level = 0;

		// Timings
		int timeBeforePlanet = 0;
		int currentPlanetIndex = 0;
		int flickerCounter = 0;
		int powerupTimer = 1200;

		// Ship status effects
		int confused = 0;
		int narrowTimer = 0;
		int isHit = 0;
		int isPower = 0;

		// Enemy animation
		int animationCounter = 0;
		int animationFactor = 0;
		int animationEnlarge = NO;

		int blackHoleinPlace = NO;

		// Set up enemies
		int[] enemyX = new int[MAX_NUMBER_OF_ENEMIES];
		int[] enemyY = new int[MAX_NUMBER_OF_ENEMIES];
		int[] enemyDirection = new int[MAX_NUMBER_OF_ENEMIES];
		int[] enemyType = new int[MAX_NUMBER_OF_ENEMIES];
		int numberOfEnemies = 10;
		int terminatorTimer = 0;
		enemyY[0] = 800;

		// Set up particles
		int[] particleX = new int[MAX_NUMBER_OF_PARTICLES];
		int[] particleY = new int[MAX_NUMBER_OF_PARTICLES];
		int[] particleVelocityX = new int[MAX_NUMBER_OF_PARTICLES];
		int[] particleVelocityY = new int[MAX_NUMBER_OF_PARTICLES];
		int[] particleLife = new int[MAX_NUMBER_OF_PARTICLES];

		int starsX = -100;
		int starsY = 0;
		int sunX = 100;
		int sunY = -200;

		// Game state
		int state = STATE_TITLE;

		// Create star image
		BufferedImage starImage = new BufferedImage(WINDOW_WIDTH + 200,
				WINDOW_HEIGHT, BufferedImage.TYPE_INT_RGB);
		Graphics starGfx = starImage.getGraphics();
		for (i = 0; i < 200; i++) {
			a = random.nextInt(200);
			starGfx.setColor(new Color(250 - a, 250 - a, 250 - a));
			starGfx.fillOval(random.nextInt(WINDOW_WIDTH + 200),
					random.nextInt(WINDOW_HEIGHT), 2, 2);
		}

		BufferedImage sunImage = new BufferedImage(WINDOW_WIDTH, WINDOW_HEIGHT,
				BufferedImage.TYPE_INT_ARGB);
		Graphics sunGfx = sunImage.getGraphics();
		i = random.nextInt(3);
		for (a = 0; a < 150; a++) {
			sunGfx.setColor(new Color(a, 0, a / 2));

			for (b = 0; b < a; b += 1) {
				sunGfx.fillOval(
						random.nextInt(200 - a) - random.nextInt(200 - a) + 100,
						random.nextInt(200 - a) - random.nextInt(200 - a) + 150,
						3, 3);
			}
		}

		// Game loop.
		long nextFrameStartTime = System.nanoTime();
		while (true) {
			// Start the game
			if (state == STATE_START) {
				// Set up the score limit
				scoreLimit = 20;

				// Reset the level
				score = -10;
				level = 0;

				// Set up enemies
				numberOfEnemies = 1;
				terminatorTimer = 0;

				// Reset planet times
				timeBeforePlanet = 0;

				// Remove the black hole
				blackHoleinPlace = NO;

				// Now run the game!
				state = STATE_BREIFING;
			}

			// Set up enemies
			if (state == STATE_RUNNING) {
				for (i = 0; i < numberOfEnemies; i += 1) {
					if (enemyY[i] > WINDOW_HEIGHT) {
						enemyY[i] = -random.nextInt(WINDOW_HEIGHT);
						enemyX[i] = random.nextInt(WINDOW_WIDTH);

						if (score > SCORE_LIMIT_2 && level > 0) {
							enemyType[i] = random.nextInt(10) - 3;
						} else if (score > SCORE_LIMIT_1) {
							enemyType[i] = random.nextInt(7) - 3;
						} else {
							enemyType[i] = random.nextInt(6) - 3;
						}

						if (enemyType[i] <= POWERUP_HEALTH) {
							if (powerupTimer == 0) {
								powerupTimer = 1200;
							} else {
								enemyType[i] = ENEMY_STRAIGHT;
							}
						}

						if (enemyType[i] == ENEMY_BLACKHOLE) {
							enemyX[i] = 160;
						}

						// Make sure there's only one terminator at once.
						// Start the terminator timer.
						if (enemyType[i] >= ENEMY_TERMINATOR) {
							if (terminatorTimer == 0) {
								terminatorTimer = 3000;
							} else {
								enemyType[i] = ENEMY_STRAIGHT;
							}
						}

						score += 10;

						// Create new enemies
						if (score >= scoreLimit) {
							if (numberOfEnemies < MAX_NUMBER_OF_ENEMIES - 1) {
								numberOfEnemies += 1;

								scoreLimit *= 3;
								enemyY[numberOfEnemies - 1] = 800;
							}
						}
					}

					// Check collision with player
					if (playerX <= (enemyX[i] + TILE_SIZE)
							&& enemyX[i] <= (playerX + TILE_SIZE)
							&& playerY <= (enemyY[i] + TILE_SIZE)
							&& enemyY[i] <= (playerY + TILE_SIZE)) {

						score -= 10;

						if (enemyType[i] >= ENEMY_TERMINATOR) {
							terminatorTimer = 0;
						}

						if (enemyType[i] == ENEMY_CONFUSER) {
							confused = 400;
						} else if (enemyType[i] == ENEMY_NARROWER) {
							narrowTimer = 400;
						} else if (enemyType[i] == POWERUP_HEALTH) {
							health = 5;
						} else if (enemyType[i] == POWERUP_SCORE) {
							score += 500;
						} else if (enemyType[i] == POWERUP_BOMB) {
							for (b = 0; b < numberOfEnemies; b += 1) {
								enemyY[b] = -(random.nextInt(350) + 350);
							}

							blackHoleinPlace = NO;
						} else {
							health -= 1;
						}

						enemyY[i] = 800;

						a = 0;
						// Set up the explosion
						for (b = 0; b < MAX_NUMBER_OF_PARTICLES - 1; b += 1) {
							// Check if the particle is free
							if (particleLife[b] == 0) {
								particleX[b] = playerX;
								particleY[b] = playerY;
								particleVelocityX[b] = random.nextInt(180) - 90;
								particleVelocityY[b] = random.nextInt(180) - 90;
								particleLife[b] = 100;

								a += 1;

								if (a == NUMBER_OF_PARTICLES_IN_EXPLOSION) {
									break;
								}
							}
						}

						// If the player has no health, then it's game over
						if (health <= 0) {
							state = STATE_GAMEOVER;
							confused = 0;
							narrowTimer = 0;
							isHit = 0;
							isPower = 0;
							scoreLimit = 30;
							currentPlanetIndex = 0;
							playerVelocityX = 0;
							playerVelocityY = 0;
							score += 10; // This is a quick fix
						} else {
							if (enemyType[i] <= POWERUP_HEALTH) {
								isPower = 25;
							} else {
								isHit = 25;
							}
						}
					}
				}
			}

			flickerCounter -= 2;
			if (flickerCounter <= 0) {
				flickerCounter = 100;
			}

			if (state == STATE_RUNNING) {
				// If there is a terminator, decrease the terminator timer.
				if (terminatorTimer > 0) {
					terminatorTimer -= 1;
				}

				if (powerupTimer > 0) {
					powerupTimer -= 1;
				}

				if (isHit > 0) {
					isHit -= 1;
				}

				if (isPower > 0) {
					isPower -= 1;
				}

				if (confused > 0) {
					confused -= 1;
				}

				if (narrowTimer > 0) {
					narrowTimer -= 1;

					if (TILE_SIZE < 32) {
						TILE_SIZE += 1;
						TILE_SIZE_QUARTER = TILE_SIZE / 4;
					}
				} else {
					if (TILE_SIZE > 16) {
						TILE_SIZE -= 1;
						TILE_SIZE_QUARTER = TILE_SIZE / 4;
					}
				}

				// Time to complete the level
				if (timeBeforePlanet > 0) {
					timeBeforePlanet -= 1;

					// The level is completed
					if (timeBeforePlanet == 0) {
						state = STATE_LEVELCOMPLETED;

						if (numberOfEnemies < MAX_NUMBER_OF_ENEMIES - 1) {
							numberOfEnemies += 1;

							enemyY[numberOfEnemies - 1] = 800;
						}
						playerVelocityX = 0;
						playerVelocityY = -(PLAYER_SPEED * 2);
					}
				}
			}

			do {
				if (state == STATE_RUNNING) {
					// Detect keys
					if ((KEY_LEFT == YES && confused == 0)
							|| (KEY_RIGHT == YES && confused > 0)) {
						playerVelocityX = -PLAYER_SPEED;
					} else if ((KEY_RIGHT == YES && confused == 0)
							|| (KEY_LEFT == YES && confused > 0)) {
						playerVelocityX = PLAYER_SPEED;
					}
					if ((KEY_DOWN == YES && confused == 0)
							|| (KEY_UP == YES && confused > 0)) {
						playerVelocityY = PLAYER_SPEED;
					} else if ((KEY_UP == YES && confused == 0)
							|| (KEY_DOWN == YES && confused > 0)) {
						playerVelocityY = -PLAYER_SPEED;
					}
				}

				if (KEY_SPACE == YES) {
					if (state == STATE_TITLE || state == STATE_GAMEOVER) {
						state = STATE_START;
					} else if (state == STATE_BREIFING) {
						// Start a new level
						state = STATE_RUNNING;

						// Set up player
						playerX = 160;
						playerY = 385;
						playerVelocityX = 0;
						playerVelocityY = 0;
						health = 5;

						// Set up enemies
						enemyX = new int[MAX_NUMBER_OF_ENEMIES];
						enemyY = new int[MAX_NUMBER_OF_ENEMIES];
						enemyDirection = new int[MAX_NUMBER_OF_ENEMIES];
						enemyType = new int[MAX_NUMBER_OF_ENEMIES];
						enemyY[0] = 800;

						confused = 0;
						narrowTimer = 0;
						isHit = 0;
						isPower = 0;
						blackHoleinPlace = NO;
						timeBeforePlanet = 3000 + (level * 500);

						sunY = -300;
					}

					KEY_SPACE = NO;
				}

				if (state == STATE_RUNNING) {
					// Reduce player velocity
					if (playerVelocityX > 0) {
						playerVelocityX -= 10;
					} else if (playerVelocityX < 0) {
						playerVelocityX += 10;
					}
					if (playerVelocityY > 0) {
						playerVelocityY -= 10;
					} else if (playerVelocityY < 0) {
						playerVelocityY += 10;
					}

					// Black hole!
					if (blackHoleinPlace == YES) {
						if (playerX < 160) {
							if (playerVelocityX < 220) {
								playerVelocityX += 16;
							}
						} else if (playerVelocityX > -220) {
							playerVelocityX -= 16;
						}

						if (playerY < 240) {
							playerVelocityY += 11;
						} else {
							playerVelocityY -= 11;
						}
					}
				}

				if ((playerVelocityX < 0 && playerX >= 16)
						|| (playerVelocityX > 0 && playerX <= 302)) {
					playerX += (playerVelocityX / 50);
				} else {
					playerVelocityX = 0;
				}

				if ((playerVelocityY < 0 && playerY >= 10)
						|| (playerVelocityY > 0 && playerY <= 460)
						|| state == STATE_LEVELCOMPLETED) {
					playerY += (playerVelocityY / 50);
				} else {
					playerVelocityY = 0;
				}

				if (state == STATE_LEVELCOMPLETED && playerY < -200) {
					confused = 0;

					level += 1;
					currentPlanetIndex = random.nextInt(6);
					confused = 0;
					narrowTimer = 0;
					isHit = 0;
					isPower = 0;
					state = STATE_BREIFING;
				}

				// Move stars
				starsY += STAR_SPEED;
				starsX -= (playerVelocityX / 100);
				sunX -= (playerVelocityX / 100);
				sunY += SUN_SPEED;

				if (starsY == WINDOW_HEIGHT) {
					starsY = 0;
				}
				if (sunY > WINDOW_HEIGHT) {
					sunY = -300;
					sunX = random.nextInt(WINDOW_WIDTH);
				}

				// Update particles
				for (i = 0; i < MAX_NUMBER_OF_PARTICLES - 1; i += 1) {
					if (particleLife[i] > 0) {
						particleLife[i] -= 1;

						particleX[i] += particleVelocityX[i] / 10;
						particleY[i] += particleVelocityY[i] / 10;

						if (particleVelocityY[i] > 0) {
							particleVelocityY[i] -= 1 / 2;
						} else if (particleVelocityY[i] < 0) {
							particleVelocityY[i] += 1 / 2;
						}
						if (particleVelocityX[i] > 0) {
							particleVelocityX[i] -= 1 / 2;
						} else if (particleVelocityX[i] < 0) {
							particleVelocityX[i] += 1 / 2;
						}

						// If the particle is outside the screen, then set it as
						// inactive
						if ((particleX[i] > WINDOW_HEIGHT || particleX[i] < 0)
								&& (particleY[i] > WINDOW_HEIGHT || particleY[i] < 0)) {
							particleLife[i] = 0;
						}
					}
				}

				// Move enemies
				if (state == STATE_RUNNING) {
					for (i = 0; i < numberOfEnemies; i += 1) {
						enemyX[i] -= (playerVelocityX / 100);

						// Straight moving enemies and powerups
						if (enemyType[i] == ENEMY_STRAIGHT
								|| enemyType[i] <= POWERUP_HEALTH) {
							enemyY[i] += ENEMY_SPEED_Y;
						}

						// Bouncing enemies
						else if (enemyType[i] == ENEMY_BOUNCING) {
							if (enemyDirection[i] == LEFT) {
								enemyX[i] -= ENEMY_SPEED_Y;

								if (enemyX[i] <= 0) {
									enemyDirection[i] = RIGHT;
								}
							} else {
								enemyX[i] += ENEMY_SPEED_Y;

								if (enemyX[i] >= 290) {
									enemyDirection[i] = LEFT;
								}
							}

							enemyY[i] += ENEMY_SPEED_X;
						}

						// Homing enemies
						else if (enemyType[i] == ENEMY_HOMING) {
							enemyY[i] += ENEMY_SPEED_Y;

							if (playerX < enemyX[i]) {
								enemyX[i] -= 2.;
							} else if (playerX > enemyX[i]) {
								enemyX[i] += 2;
							}
						}

						else if (enemyType[i] == ENEMY_BLACKHOLE) {
							if (terminatorTimer > 0) {
								if (enemyY[i] < 240) {
									enemyY[i] += 1;
								} else {
									blackHoleinPlace = YES;
								}

								if (enemyX[i] < 160) {
									enemyX[i] += 1;
								} else if (enemyX[i] > 160) {
									enemyX[i] -= 1;
								}
							} else {
								enemyY[i] += ENEMY_SPEED_Y;
								blackHoleinPlace = NO;
							}
						}

						// Terminator, confuser- and narrower-enemies
						else {
							if (playerX < enemyX[i]) {
								enemyX[i] -= 1;
							} else if (playerX > enemyX[i]) {
								enemyX[i] += 1;
							}

							if (terminatorTimer > 0) {
								if (playerY < enemyY[i]) {
									enemyY[i] -= 1;
								} else if (playerY > enemyY[i]) {
									enemyY[i] += 1;
								}
							} else {
								enemyY[i] += ENEMY_SPEED_Y;
							}
						}
					}
				}

				// Enemy animation
				animationCounter += 1;
				if (animationCounter == 10) {
					if (animationEnlarge == YES) {
						animationFactor += 1;

						if (animationFactor >= TILE_SIZE_QUARTER) {
							animationEnlarge = NO;
						}
					} else {
						animationFactor -= 1;

						if (animationFactor <= -TILE_SIZE_QUARTER) {
							animationEnlarge = YES;
						}
					}

					animationCounter = 0;
				}
				
				nextFrameStartTime += 16666667;
			} while (nextFrameStartTime < System.nanoTime());

			// Update the player position arrays, so we can draw the spaceship
			a = playerVelocityX / 60;
			playerXPoints[0] = playerX + a;
			playerXPoints[1] = playerX - (TILE_SIZE_QUARTER * 2 + a);
			playerXPoints[2] = (playerX + TILE_SIZE_QUARTER * 2 - a);
			playerYPoints[0] = playerY;
			playerYPoints[2] = playerY + TILE_SIZE;
			playerYPoints[1] = (playerYPoints[2] - a);

			g.setColor(Color.BLACK);
			g.fillRect(0, 0, WINDOW_WIDTH, WINDOW_HEIGHT);

			// Draw stars
			if (!((confused > 0 || narrowTimer > 0) && flickerCounter > 50)) {
				g.drawImage(starImage, starsX, starsY, null);
				g.drawImage(starImage, starsX, starsY - WINDOW_HEIGHT, null);
			}

			g.drawImage(sunImage, sunX, sunY, null);

			// Draw stuff
			if (isHit > 0) {
				g.setColor(Color.RED);
				g.fillRect(0, 0, WINDOW_WIDTH, WINDOW_HEIGHT);
			} else if (isPower > 0) {
				g.setColor(Color.GREEN);
				g.fillRect(0, 0, WINDOW_WIDTH, WINDOW_HEIGHT);
			} else if (confused > 0 && flickerCounter > 50) {
				g.setColor(Color.YELLOW);
				g.fillRect(0, 0, WINDOW_WIDTH, WINDOW_HEIGHT);
			} else if (narrowTimer > 0 && flickerCounter > 50) {
				g.setColor(Color.PINK);
				g.fillRect(0, 0, WINDOW_WIDTH, WINDOW_HEIGHT);
			}

			g.setColor(Color.WHITE);

			// Draw particles
			for (i = 0; i < MAX_NUMBER_OF_PARTICLES - 1; i += 1) {
				if (particleLife[i] > 0) {
					a = 55 + (particleLife[i] * 2);

					g.setColor(new Color(a, a, a));
					g.fillOval(particleX[i], particleY[i], TILE_SIZE_QUARTER,
							TILE_SIZE_QUARTER);
				}
			}

			// Draw on state running
			if (state == STATE_RUNNING || state == STATE_LEVELCOMPLETED) {
				// Draw player
				g.setColor(Color.GREEN);
				g.fillPolygon(playerXPoints, playerYPoints, 3);

				// Draw enemies
				for (i = 0; i < numberOfEnemies; i += 1) {
					if (enemyType[i] == ENEMY_STRAIGHT) {
						g.setColor(Color.MAGENTA);
					}

					else if (enemyType[i] == ENEMY_BOUNCING) {
						g.setColor(Color.CYAN);
					}

					else if (enemyType[i] == ENEMY_HOMING) {
						g.setColor(Color.RED);
					}

					else if (enemyType[i] == ENEMY_TERMINATOR) {
						g.setColor(Color.DARK_GRAY);
					}

					else if (enemyType[i] == ENEMY_CONFUSER) {
						g.setColor(Color.YELLOW);
					}

					else if (enemyType[i] == ENEMY_NARROWER) {
						g.setColor(Color.PINK);
					}

					else if (enemyType[i] <= POWERUP_HEALTH) {
						g.setColor(Color.GREEN);

						if (enemyType[i] == POWERUP_HEALTH) {
							g.drawString("HEALTH", enemyX[i]
									- (TILE_SIZE_QUARTER * 3), enemyY[i]
									+ (TILE_SIZE * 2));
						} else if (enemyType[i] == POWERUP_SCORE) {
							g.drawString("POINTS", enemyX[i]
									- (TILE_SIZE_QUARTER * 3), enemyY[i]
									+ (TILE_SIZE * 2));
						} else if (enemyType[i] == POWERUP_BOMB) {
							g.drawString("BOMB!!", enemyX[i]
									- (TILE_SIZE_QUARTER * 3), enemyY[i]
									+ (TILE_SIZE * 2));
						}
					}

					if (enemyType[i] == ENEMY_BLACKHOLE) {
						g.setColor(Color.BLACK);
						g.fillOval(enemyX[i], enemyY[i], TILE_SIZE
								+ animationFactor, TILE_SIZE + animationFactor);

						g.setColor(Color.WHITE);
						g.drawOval(enemyX[i], enemyY[i], TILE_SIZE
								+ animationFactor, TILE_SIZE + animationFactor);
					} else {
						g.fillOval(enemyX[i], enemyY[i], TILE_SIZE
								+ animationFactor, TILE_SIZE + animationFactor);
					}
				}
			}

			if (state == STATE_RUNNING || state == STATE_LEVELCOMPLETED
					|| state == STATE_GAMEOVER) {
				// Draw texts
				if ((confused > 0 || narrowTimer > 0) && flickerCounter > 50) {
					g.setColor(Color.BLACK);
				} else {
					g.setColor(Color.WHITE);
				}

				if (state != STATE_GAMEOVER && timeBeforePlanet != 0
						&& (timeBeforePlanet / 100) <= 1 && flickerCounter > 50) {
					g.drawString("Going in for landing on "
							+ planets[currentPlanetIndex] + ".", 60, 200);
				}

				g.drawString("Score: " + score, 10, 419);

				g.drawString("Health:", 10, 437);
				for (i = 0; i < health; i += 1) {
					g.fillOval(60 + (12 * i), 428, 8, 8);
				}

				g.drawString("Pkgs delivered: " + level, 10, 455);
				g.drawString("Distance to " + planets[currentPlanetIndex]
						+ ": " + (timeBeforePlanet / 100), 10, 473);
			}

			else if (state == STATE_BREIFING) {
				g.setColor(Color.WHITE);

				if (level == 0) {
					g.drawString("Hi package delivering hero!", 45, 120);
					g.drawString("You need to deliver a very secret", 45, 150);
				} else {
					g.drawString("Well done! The package is now safe.", 45, 120);
					g.drawString("Now you need to deliver another secret", 45,
							150);
				}

				if (flickerCounter > 50) {
					g.drawString("Press space to start mission.", 45, 250);
				}

				g.drawString("package to our base on "
						+ planets[currentPlanetIndex] + ".", 45, 170);
			}

			// Draw on state title
			else if (state == STATE_TITLE) {
				g.drawString("Legend of the package delivering hero", 45, 200);

				if (flickerCounter > 50) {
					g.drawString("Press space to start", 100, 250);
				}
			}

			// Draw on state game over
			if (state == STATE_GAMEOVER) {
				g.setColor(Color.WHITE);
				g.drawString("GAME OVER", 127, 210);

				if (flickerCounter > 50) {
					g.drawString("Press space to restart.", 100, 250);
				}
			}

			// Draw the entire results on the screen.
			appletGraphics.drawImage(screen, 0, 0, null);

			// Burn off extra cycles
			long remaining = nextFrameStartTime - System.nanoTime();
			if (remaining > 0) {
				try {
					Thread.sleep(remaining / 1000000);
				} catch (Exception e) {
				}
			}
		}
	}

	/**
	 * Check for key events
	 */
	public void processKeyEvent(KeyEvent e) {
		int keyCode = e.getKeyCode();
		if (e.getID() == KeyEvent.KEY_PRESSED) {
			if (keyCode == KeyEvent.VK_LEFT) {
				KEY_LEFT = YES;
			} else if (keyCode == KeyEvent.VK_RIGHT) {
				KEY_RIGHT = YES;
			} else if (keyCode == KeyEvent.VK_UP) {
				KEY_UP = YES;
			} else if (keyCode == KeyEvent.VK_DOWN) {
				KEY_DOWN = YES;
			} else if (keyCode == KeyEvent.VK_SPACE) {
				KEY_SPACE = YES;
			}
		} else if (e.getID() == KeyEvent.KEY_RELEASED) {
			if (keyCode == KeyEvent.VK_LEFT) {
				KEY_LEFT = NO;
			} else if (keyCode == KeyEvent.VK_RIGHT) {
				KEY_RIGHT = NO;
			} else if (keyCode == KeyEvent.VK_UP) {
				KEY_UP = NO;
			} else if (keyCode == KeyEvent.VK_DOWN) {
				KEY_DOWN = NO;
			}
		}
	}
}

I am just doing the basic sleep in my code…


try {
       Thread.sleep(10);
} catch (Exception e) {
}
            
if(!isActive())
      return;

I haven’t had any real problems with frame rate. (You can test to see how it runs by trying it.) Older computers always have a much slower frame rate for Applets by my experiences, but they usually run okay when dealing with JFrames.

Can you give an example of an “older” system that presents the issues? I have started 10 parallel instances and there was no slower frame rates… but that said, if this is for java 4k then you should really only care about making sure that it will work well with JRE7 and hardware that most people would have… i guess one would look at Steam’s statistics for that info.

I had a quick skim and there’s no obvious need for regular garbage collection - Event objects will be generated by AWT, but they’re small. You could try running with -verbose:gc just to check that.

The other obvious step for some non-intrusive profiling is to print remaining to System.out. Are some frames taking longer than the allocated time, or is the problem in the waiting?

I’m pretty sure that the issue have nothing to do with the timing code. I’ve tried with different kind of timing strategies, but the problem still remains. :frowning:

Both computer have Intel Core2 Duo processors and 2GB ram. Both are using cheap build in graphic cards. They are not really old, but it’ no state-of-the-art machine. Still, I feel they should be good enough to run a simple Java applet game.

One the Windows laptop the game is still playable, but the framerate is bad and the animation is not very smooth. The experience on the Linux box is both better and worse - the framerate is much smoother, but every now and then there are short freeze-moments. I usually associate such freezing-moments with the garbage collector, but I can’t see any reason for the GC to kick in (since I’m not allocating very many object).

Thanks for the tips about running with -verbose:gc! The gc kicks in from time to time, but there seems to be no connection with the freezing-moments.

I’ve tried to print the “remaining”-value, on both computer. On my main computer (where it all work fine), there is a very small variation between the time frames. On the “slower” computers the variation is bigger, but I can’t see a straight correlation between the variation and the stuttering.

To diagnose the problem I’ve tried to remove almost all the code, and just keeping the scrolling stars, but the problem still remains on the two computer:

import java.applet.Applet;
import java.awt.AWTEvent;
import java.awt.Color;
import java.awt.Graphics;
import java.awt.event.KeyEvent;
import java.awt.image.BufferedImage;
import java.util.Random;

public class G extends Applet implements Runnable {
	public void start() {
		new Thread(this).start();
	}

	public void run() {
		setSize(320, 480);
		BufferedImage screen = new BufferedImage(320, 480,
				BufferedImage.TYPE_INT_RGB);
		Graphics g = screen.getGraphics();
		Graphics appletGraphics = getGraphics();
		Random random = new Random();

		int a = 0;
		int starsX = -100;
		int starsY = 0;

		// Create star image
		BufferedImage starImage = new BufferedImage(320 + 200, 480,
				BufferedImage.TYPE_INT_RGB);
		Graphics starGfx = starImage.getGraphics();
		for (int i = 0; i < 200; i++) {
			a = random.nextInt(200);
			starGfx.setColor(new Color(250 - a, 250 - a, 250 - a));
			starGfx.fillOval(random.nextInt(320 + 200), random.nextInt(480), 2, 2);
		}

		// Game loop.
		while (true) {
			// Move stars
			starsY += 1;
			if (starsY == 320) {
				starsY = 0;
			}

			// Draw stars
			g.drawImage(starImage, starsX, starsY, null);

			// Draw the entire results on the screen.
			appletGraphics.drawImage(screen, 0, 0, null);

			// Burn off extra cycles
			try {
				Thread.sleep(10);
			} catch (InterruptedException e) {
			}
		}
	}
}

I’ve also tried to replace to replace the timing code with a more busy timing loop, but the problem still remains:


long nextFrameStartTime = System.nanoTime() + 1666667;
while (System.nanoTime() < nextFrameStartTime) {
  Thread.yield();
}

And also an extremely greedy timing solution, without any sleeping/yielding at all. This solutions makes the animation more smooth, but the freezing/stuttering still seems to occur:


long nextFrameStartTime = System.nanoTime() + 1666667;
while (System.nanoTime() < nextFrameStartTime) {}		

So the issue is not related to the application logic, not to the rendering and not to the timing… ???

Am I doing something incredibly wrong, or is there something terribly wrong with my computer (most likely I’m doing something wrong ;)).

The Linux box is running OpenJDK, but the game is running fine with OpenJDK on my main computer. The windows laptop is running the official Java 6 from Oracle, and the official browser plugin for IE. In Chrome everything works fine for some reason.

[quote=“Jens_S,post:7,topic:40692”]
If it doesn’t go negative at all then that rules out one important candidate problem.

I’m out of relatively sensible ideas. The more outlandish ones would include using renice on the Linux box to see whether it’s a scheduling issue; disconnecting from the network to eliminate some interrupts; disabling antivirus on the Windows box; and using fullscreen exclusive mode to limit the other traffic to the graphics card. But I’m not particularly bullish about any of those.

Probably a silly suggestion, especially for a java 4k game, but have you tried promoting the priority of java process running your game? Perhap your OS is being a little to sparing to the process?

[quote=“pjt33,post:8,topic:40692”]

I’ve seen the “remaining” value go negative a few times, but I can see no connection between the negative values and the freezing.

Since the problem seems to occur even if I run the program with just the bare minimums, and since it works fine on most computers, I guess that I have to ignore this problem. I’ve spent to much time already on this issue. I’ll upload the game to Java4k wait for feedback - if everyone complains about performance issues I have to take another look.

Thanks for your suggestions, and thanks for taking time to look at the code!

There must be something weird going on with the Linux box. I see serious stuttering even if I remove all application logic, all timing code and almost all rendering code. The framerate is not perfect on the Windows laptop (in IE; it runs fine in Chrome), but the game is still playable. I’ll upload the game to Java4k wait for feedback - if everyone complains about performance issues I have to take another look.

Thanks for your input!