Dino Game
This commit is contained in:
@@ -7,6 +7,9 @@ import java.io.*;
|
|||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Random;
|
import java.util.Random;
|
||||||
import javax.sound.sampled.*;
|
import javax.sound.sampled.*;
|
||||||
|
import java.nio.file.Files;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
import java.nio.file.Paths;
|
||||||
|
|
||||||
public class DinoGame extends JPanel implements ActionListener, KeyListener {
|
public class DinoGame extends JPanel implements ActionListener, KeyListener {
|
||||||
// Game constants
|
// Game constants
|
||||||
@@ -14,151 +17,178 @@ public class DinoGame extends JPanel implements ActionListener, KeyListener {
|
|||||||
private static final int HEIGHT = 400;
|
private static final int HEIGHT = 400;
|
||||||
private static final int GROUND_Y = 320;
|
private static final int GROUND_Y = 320;
|
||||||
private static final int FPS = 60;
|
private static final int FPS = 60;
|
||||||
private static final int GRAVITY = 2;
|
private static final int GRAVITY = 1;
|
||||||
private static final int JUMP_STRENGTH = -22;
|
private static final int JUMP_STRENGTH = -18;
|
||||||
|
private static final int INITIAL_SPEED = 10;
|
||||||
// Game state
|
private static final int MAX_SPEED = 20;
|
||||||
private Timer timer;
|
private static final int SPEED_INCREMENT = 1;
|
||||||
|
private static final int HITBOX_INSET = 8;
|
||||||
private boolean gameStarted = false;
|
private boolean gameStarted = false;
|
||||||
private boolean gameOver = false;
|
private boolean gameOver = false;
|
||||||
private boolean paused = false;
|
private boolean paused = false;
|
||||||
private int score = 0;
|
private int score = 0;
|
||||||
private int highScore = 0;
|
private int highScore = 0;
|
||||||
private double gameSpeed = 12.0;
|
private int gameSpeed = INITIAL_SPEED;
|
||||||
private int frameCount = 0;
|
private int frameCount = 0;
|
||||||
|
private final int dinoX = 50;
|
||||||
// Player
|
private int dinoY;
|
||||||
private int dinoX = 50;
|
private int dinoWidth;
|
||||||
private int dinoY = GROUND_Y - 90;
|
private int dinoHeight;
|
||||||
private int dinoWidth = 44;
|
private int dinoDuckWidth;
|
||||||
private int dinoHeight = 47;
|
private int dinoDuckHeight;
|
||||||
private int velocityY = 0;
|
private int velocityY = 0;
|
||||||
private boolean isJumping = false;
|
private boolean isJumping = false;
|
||||||
private boolean isDucking = false;
|
private boolean isDucking = false;
|
||||||
private int animFrame = 0;
|
private int animFrame = 0;
|
||||||
|
private int groundY;
|
||||||
// Images
|
|
||||||
private BufferedImage dinoRun1, dinoRun2, dinoJump, dinoDead;
|
private BufferedImage dinoRun1, dinoRun2, dinoJump, dinoDead;
|
||||||
private BufferedImage dinoDownRun1, dinoDownRun2;
|
private BufferedImage dinoDownRun1, dinoDownRun2;
|
||||||
private BufferedImage cactus1, cactus2, cactus3, cactus4, cactus5, cactus6;
|
private BufferedImage cactus1, cactus2, cactus3, cactus4, cactus5, cactus6;
|
||||||
private BufferedImage birdFly1, birdFly2;
|
private BufferedImage birdFly1, birdFly2;
|
||||||
private BufferedImage cloud, land, gameOverImg, replayImg, hiImg;
|
private BufferedImage cloud, land, gameOverImg, replayImg, hiImg;
|
||||||
|
private final ArrayList<Obstacle> obstacles = new ArrayList<>();
|
||||||
// Game objects
|
private final ArrayList<Cloud> clouds = new ArrayList<>();
|
||||||
private ArrayList<Obstacle> obstacles = new ArrayList<>();
|
|
||||||
private ArrayList<Cloud> clouds = new ArrayList<>();
|
|
||||||
private int landX = 0;
|
private int landX = 0;
|
||||||
private Random rand = new Random();
|
private final Random rand = new Random();
|
||||||
private int obstacleTimer = 0;
|
private int obstacleTimer = 0;
|
||||||
|
private int minObstacleDistance = 60;
|
||||||
// Audio
|
private int maxObstacleDistance = 120;
|
||||||
private Clip jumpSound, deadSound, scoreSound;
|
private Clip jumpSound, deadSound, scoreSound;
|
||||||
|
private long lastScoreTime = 0;
|
||||||
|
|
||||||
public DinoGame() {
|
public DinoGame() {
|
||||||
setPreferredSize(new Dimension(WIDTH, HEIGHT));
|
setPreferredSize(new Dimension(WIDTH, HEIGHT));
|
||||||
setBackground(Color.WHITE);
|
setBackground(Color.WHITE);
|
||||||
setFocusable(true);
|
setFocusable(true);
|
||||||
addKeyListener(this);
|
addKeyListener(this);
|
||||||
|
setDoubleBuffered(true);
|
||||||
loadImages();
|
loadAssets();
|
||||||
loadSounds();
|
for (int i = 0; i < 5; i++) {
|
||||||
loadHighScore();
|
clouds.add(new Cloud(i * 250 + rand.nextInt(100), 50 + rand.nextInt(100)));
|
||||||
|
|
||||||
// Initialize clouds
|
|
||||||
for (int i = 0; i < 3; i++) {
|
|
||||||
clouds.add(new Cloud(rand.nextInt(WIDTH), 50 + rand.nextInt(100)));
|
|
||||||
}
|
}
|
||||||
|
resetGameData();
|
||||||
timer = new Timer(1000 / FPS, this);
|
Timer timer = new Timer(1000 / FPS, this);
|
||||||
timer.start();
|
timer.start();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void loadImages() {
|
private void loadAssets() {
|
||||||
try {
|
try {
|
||||||
dinoRun1 = ImageIO.read(getClass().getResourceAsStream("/dino-run-1.png"));
|
loadImages();
|
||||||
dinoRun2 = ImageIO.read(getClass().getResourceAsStream("/dino-run-2.png"));
|
} catch (IOException e) {
|
||||||
dinoJump = ImageIO.read(getClass().getResourceAsStream("/dino-jump.png"));
|
String errorMsg = "Error: cannot load image resources.\n" + e.getMessage();
|
||||||
dinoDead = ImageIO.read(getClass().getResourceAsStream("/dino-dead.png"));
|
System.err.println(errorMsg);
|
||||||
dinoDownRun1 = ImageIO.read(getClass().getResourceAsStream("/dino-down-run-1.png"));
|
JOptionPane.showMessageDialog(this, errorMsg, "Error", JOptionPane.ERROR_MESSAGE);
|
||||||
dinoDownRun2 = ImageIO.read(getClass().getResourceAsStream("/dino-down-run-2.png"));
|
System.exit(1);
|
||||||
|
}
|
||||||
|
dinoWidth = dinoRun1.getWidth();
|
||||||
|
dinoHeight = dinoRun1.getHeight();
|
||||||
|
dinoDuckWidth = dinoDownRun1.getWidth();
|
||||||
|
dinoDuckHeight = dinoDownRun1.getHeight();
|
||||||
|
groundY = GROUND_Y - dinoHeight;
|
||||||
|
dinoY = groundY;
|
||||||
|
loadSoundsAndHighScore();
|
||||||
|
}
|
||||||
|
|
||||||
cactus1 = ImageIO.read(getClass().getResourceAsStream("/cactus-1.png"));
|
private void loadImages() throws IOException {
|
||||||
cactus2 = ImageIO.read(getClass().getResourceAsStream("/cactus-2.png"));
|
dinoRun1 = loadImage("/dino-run-1.png");
|
||||||
cactus3 = ImageIO.read(getClass().getResourceAsStream("/cactus-3.png"));
|
dinoRun2 = loadImage("/dino-run-2.png");
|
||||||
cactus4 = ImageIO.read(getClass().getResourceAsStream("/cactus-4.png"));
|
dinoJump = loadImage("/dino-jump.png");
|
||||||
cactus5 = ImageIO.read(getClass().getResourceAsStream("/cactus-5.png"));
|
dinoDead = loadImage("/dino-dead.png");
|
||||||
cactus6 = ImageIO.read(getClass().getResourceAsStream("/cactus-6.png"));
|
dinoDownRun1 = loadImage("/dino-down-run-1.png");
|
||||||
|
dinoDownRun2 = loadImage("/dino-down-run-2.png");
|
||||||
|
cactus1 = loadImage("/cactus-1.png");
|
||||||
|
cactus2 = loadImage("/cactus-2.png");
|
||||||
|
cactus3 = loadImage("/cactus-3.png");
|
||||||
|
cactus4 = loadImage("/cactus-4.png");
|
||||||
|
cactus5 = loadImage("/cactus-5.png");
|
||||||
|
cactus6 = loadImage("/cactus-6.png");
|
||||||
|
birdFly1 = loadImage("/bird-fly-1.png");
|
||||||
|
birdFly2 = loadImage("/bird-fly-2.png");
|
||||||
|
cloud = loadImage("/cloud.png");
|
||||||
|
land = loadImage("/land.png");
|
||||||
|
gameOverImg = loadImage("/game-over.png");
|
||||||
|
replayImg = loadImage("/replay.png");
|
||||||
|
hiImg = loadImage("/hi.png");
|
||||||
|
}
|
||||||
|
|
||||||
birdFly1 = ImageIO.read(getClass().getResourceAsStream("/bird-fly-1.png"));
|
private BufferedImage loadImage(String path) throws IOException {
|
||||||
birdFly2 = ImageIO.read(getClass().getResourceAsStream("/bird-fly-2.png"));
|
InputStream is = getClass().getResourceAsStream(path);
|
||||||
|
if (is == null) {
|
||||||
cloud = ImageIO.read(getClass().getResourceAsStream("/cloud.png"));
|
throw new IOException("Resource not found: " + path);
|
||||||
land = ImageIO.read(getClass().getResourceAsStream("/land.png"));
|
}
|
||||||
gameOverImg = ImageIO.read(getClass().getResourceAsStream("/game-over.png"));
|
try {
|
||||||
replayImg = ImageIO.read(getClass().getResourceAsStream("/replay.png"));
|
return ImageIO.read(is);
|
||||||
hiImg = ImageIO.read(getClass().getResourceAsStream("/hi.png"));
|
} catch (IOException e) {
|
||||||
} catch (Exception e) {
|
throw new IOException("Cannot read image: " + path, e);
|
||||||
System.err.println("Error loading images: " + e.getMessage());
|
|
||||||
e.printStackTrace();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void loadSounds() {
|
private void loadSoundsAndHighScore() {
|
||||||
|
new Thread(() -> {
|
||||||
|
try {
|
||||||
|
jumpSound = loadSound("/jump.wav");
|
||||||
|
deadSound = loadSound("/dead.wav");
|
||||||
|
scoreSound = loadSound("/score.wav");
|
||||||
|
loadHighScore();
|
||||||
|
} catch (Exception e) {
|
||||||
|
System.err.println("Error loading sounds and high score: " + e.getMessage());
|
||||||
|
}
|
||||||
|
}).start();
|
||||||
|
}
|
||||||
|
|
||||||
|
private Clip loadSound(String path) {
|
||||||
try {
|
try {
|
||||||
jumpSound = AudioSystem.getClip();
|
InputStream is = getClass().getResourceAsStream(path);
|
||||||
jumpSound.open(AudioSystem.getAudioInputStream(getClass().getResourceAsStream("/jump.wav")));
|
if (is != null) {
|
||||||
|
InputStream bufferedIs = new BufferedInputStream(is);
|
||||||
deadSound = AudioSystem.getClip();
|
AudioInputStream ais = AudioSystem.getAudioInputStream(bufferedIs);
|
||||||
deadSound.open(AudioSystem.getAudioInputStream(getClass().getResourceAsStream("/dead.wav")));
|
Clip clip = AudioSystem.getClip();
|
||||||
|
clip.open(ais);
|
||||||
scoreSound = AudioSystem.getClip();
|
return clip;
|
||||||
scoreSound.open(AudioSystem.getAudioInputStream(getClass().getResourceAsStream("/scoreup.wav")));
|
}
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
System.err.println("Error loading sounds: " + e.getMessage());
|
System.err.println("Cannot load sound: " + path + " - " + e.getMessage());
|
||||||
e.printStackTrace();
|
|
||||||
}
|
}
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void loadHighScore() {
|
private void loadHighScore() {
|
||||||
try {
|
try {
|
||||||
InputStream is = getClass().getResourceAsStream("/best-scores.txt");
|
Path scoreFile = Paths.get(System.getProperty("user.home"), "dino-best-scores.txt");
|
||||||
if (is != null) {
|
if (Files.exists(scoreFile)) {
|
||||||
BufferedReader reader = new BufferedReader(new InputStreamReader(is));
|
String content = new String(Files.readAllBytes(scoreFile));
|
||||||
String line = reader.readLine();
|
highScore = Integer.parseInt(content.trim());
|
||||||
if (line != null) {
|
|
||||||
highScore = Integer.parseInt(line.trim());
|
|
||||||
}
|
|
||||||
reader.close();
|
|
||||||
}
|
}
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
highScore = 0;
|
highScore = 0;
|
||||||
|
System.err.println("Cannot load high score: " + e.getMessage());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void saveHighScore() {
|
private void saveHighScore() {
|
||||||
try {
|
new Thread(() -> {
|
||||||
// Save to user's home directory since resources folder is read-only
|
try {
|
||||||
String userHome = System.getProperty("user.home");
|
Path scoreFile = Paths.get(System.getProperty("user.home"), "dino-best-scores.txt");
|
||||||
File scoresFile = new File(userHome, "dino-best-scores.txt");
|
Files.write(scoreFile, String.valueOf(highScore).getBytes());
|
||||||
PrintWriter writer = new PrintWriter(new FileWriter(scoresFile));
|
} catch (IOException e) {
|
||||||
writer.println(highScore);
|
System.err.println("Error saving high score: " + e.getMessage());
|
||||||
writer.close();
|
}
|
||||||
} catch (IOException e) {
|
}).start();
|
||||||
System.err.println("Error saving high score: " + e.getMessage());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void playSound(Clip clip) {
|
private void playSound(Clip clip) {
|
||||||
if (clip != null) {
|
if (clip != null) {
|
||||||
try {
|
new Thread(() -> {
|
||||||
if (clip.isRunning()) {
|
try {
|
||||||
clip.stop();
|
if (clip.isRunning()) {
|
||||||
|
clip.stop();
|
||||||
|
}
|
||||||
|
clip.setFramePosition(0);
|
||||||
|
clip.start();
|
||||||
|
} catch (Exception e) {
|
||||||
|
// Silently fail
|
||||||
}
|
}
|
||||||
clip.setFramePosition(0);
|
}).start();
|
||||||
clip.start();
|
|
||||||
} catch (Exception e) {
|
|
||||||
System.err.println("Error playing audio: " + e.getMessage());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -170,85 +200,99 @@ public class DinoGame extends JPanel implements ActionListener, KeyListener {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (!gameOver) {
|
if (!gameOver) {
|
||||||
frameCount++;
|
updateGameLogic();
|
||||||
|
|
||||||
// Update score
|
|
||||||
if (frameCount % 6 == 0) {
|
|
||||||
score++;
|
|
||||||
if (score % 100 == 0) {
|
|
||||||
playSound(scoreSound);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Increase speed
|
|
||||||
if (frameCount % 600 == 0) {
|
|
||||||
gameSpeed += 0.5;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Update dino
|
|
||||||
if (isJumping) {
|
|
||||||
velocityY += GRAVITY;
|
|
||||||
dinoY += velocityY;
|
|
||||||
|
|
||||||
if (dinoY >= GROUND_Y - 90) {
|
|
||||||
dinoY = GROUND_Y - 90;
|
|
||||||
isJumping = false;
|
|
||||||
velocityY = 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Animation
|
|
||||||
if (frameCount % 5 == 0) {
|
|
||||||
animFrame = (animFrame + 1) % 2;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Update ground
|
|
||||||
landX -= (int)gameSpeed;
|
|
||||||
if (land != null && landX <= -land.getWidth()) {
|
|
||||||
landX = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Update clouds
|
|
||||||
for (Cloud c : clouds) {
|
|
||||||
c.x -= 2;
|
|
||||||
if (cloud != null && c.x < -cloud.getWidth()) {
|
|
||||||
c.x = WIDTH + rand.nextInt(200);
|
|
||||||
c.y = 50 + rand.nextInt(100);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Spawn obstacles
|
|
||||||
obstacleTimer++;
|
|
||||||
if (obstacleTimer > 60 + rand.nextInt(60)) {
|
|
||||||
spawnObstacle();
|
|
||||||
obstacleTimer = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Update obstacles
|
|
||||||
for (int i = obstacles.size() - 1; i >= 0; i--) {
|
|
||||||
Obstacle obs = obstacles.get(i);
|
|
||||||
obs.x -= (int)gameSpeed;
|
|
||||||
|
|
||||||
if (obs.x < -obs.width) {
|
|
||||||
obstacles.remove(i);
|
|
||||||
} else if (checkCollision(obs)) {
|
|
||||||
gameOver = true;
|
|
||||||
playSound(deadSound);
|
|
||||||
if (score > highScore) {
|
|
||||||
highScore = score;
|
|
||||||
saveHighScore();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
repaint();
|
repaint();
|
||||||
}
|
}
|
||||||
|
private void updateGameLogic() {
|
||||||
|
frameCount++;
|
||||||
|
updateScoreAndSpeed();
|
||||||
|
updatePlayer();
|
||||||
|
updateEnvironment();
|
||||||
|
spawnAndMoveObstacles();
|
||||||
|
}
|
||||||
|
private void updateScoreAndSpeed() {
|
||||||
|
if (frameCount % 6 == 0) {
|
||||||
|
score++;
|
||||||
|
if (score % 100 == 0) {
|
||||||
|
long currentTime = System.currentTimeMillis();
|
||||||
|
if (currentTime - lastScoreTime > 100) {
|
||||||
|
playSound(scoreSound);
|
||||||
|
lastScoreTime = currentTime;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (frameCount % 900 == 0 && gameSpeed < MAX_SPEED) {
|
||||||
|
gameSpeed += SPEED_INCREMENT;
|
||||||
|
minObstacleDistance = Math.max(40, 60 - ((gameSpeed - INITIAL_SPEED) * 2));
|
||||||
|
maxObstacleDistance = Math.max(80, 120 - ((gameSpeed - INITIAL_SPEED) * 3));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
private void updatePlayer() {
|
||||||
|
if (isJumping) {
|
||||||
|
velocityY += GRAVITY;
|
||||||
|
dinoY += velocityY;
|
||||||
|
|
||||||
|
if (dinoY >= groundY) {
|
||||||
|
dinoY = groundY;
|
||||||
|
isJumping = false;
|
||||||
|
velocityY = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
int animSpeed = Math.max(3, 6 - ((gameSpeed - INITIAL_SPEED) / 2));
|
||||||
|
if (frameCount % animSpeed == 0) {
|
||||||
|
animFrame = (animFrame + 1) % 2;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
private void updateEnvironment() {
|
||||||
|
landX -= gameSpeed;
|
||||||
|
if (land != null && landX <= -land.getWidth()) {
|
||||||
|
landX = 0;
|
||||||
|
}
|
||||||
|
for (Cloud c : clouds) {
|
||||||
|
c.x -= 1;
|
||||||
|
if (cloud != null && c.x < -cloud.getWidth()) {
|
||||||
|
c.x = WIDTH + rand.nextInt(200);
|
||||||
|
c.y = 50 + rand.nextInt(100);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
private void spawnAndMoveObstacles() {
|
||||||
|
obstacleTimer++;
|
||||||
|
int spawnDelay = minObstacleDistance + rand.nextInt(maxObstacleDistance - minObstacleDistance);
|
||||||
|
if (obstacleTimer > spawnDelay) {
|
||||||
|
spawnObstacle();
|
||||||
|
obstacleTimer = 0;
|
||||||
|
}
|
||||||
|
for (int i = obstacles.size() - 1; i >= 0; i--) {
|
||||||
|
Obstacle obs = obstacles.get(i);
|
||||||
|
obs.x -= gameSpeed;
|
||||||
|
|
||||||
|
if (obs.x < -obs.width) {
|
||||||
|
obstacles.remove(i);
|
||||||
|
} else if (checkCollision(obs)) {
|
||||||
|
gameOver = true;
|
||||||
|
playSound(deadSound);
|
||||||
|
if (score > highScore) {
|
||||||
|
highScore = score;
|
||||||
|
saveHighScore();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
private void spawnObstacle() {
|
private void spawnObstacle() {
|
||||||
|
if (!obstacles.isEmpty()) {
|
||||||
|
Obstacle last = obstacles.get(obstacles.size() - 1);
|
||||||
|
if (last.x > WIDTH - 200) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
int birdChance = Math.min(4, ((gameSpeed - INITIAL_SPEED) / 2) + 2);
|
||||||
int type = rand.nextInt(10);
|
int type = rand.nextInt(10);
|
||||||
if (type < 7) {
|
if (type < (10 - birdChance)) {
|
||||||
// Cactus
|
|
||||||
BufferedImage[] cacti = {cactus1, cactus2, cactus3, cactus4, cactus5, cactus6};
|
BufferedImage[] cacti = {cactus1, cactus2, cactus3, cactus4, cactus5, cactus6};
|
||||||
BufferedImage cactusImg = cacti[rand.nextInt(cacti.length)];
|
BufferedImage cactusImg = cacti[rand.nextInt(cacti.length)];
|
||||||
if (cactusImg != null) {
|
if (cactusImg != null) {
|
||||||
@@ -257,38 +301,66 @@ public class DinoGame extends JPanel implements ActionListener, KeyListener {
|
|||||||
cactusImg, false));
|
cactusImg, false));
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// Bird
|
|
||||||
if (birdFly1 != null) {
|
if (birdFly1 != null) {
|
||||||
int birdY = GROUND_Y - 44 - rand.nextInt(2) * 80;
|
int birdHeight = birdFly1.getHeight();
|
||||||
obstacles.add(new Obstacle(WIDTH, birdY, 46, 40, birdFly1, true));
|
int[] heights = {
|
||||||
|
GROUND_Y - birdHeight, // Basso
|
||||||
|
GROUND_Y - birdHeight - 100, // Alto
|
||||||
|
GROUND_Y - birdHeight - 60 // Medio
|
||||||
|
};
|
||||||
|
int birdY = heights[rand.nextInt(heights.length)];
|
||||||
|
obstacles.add(new Obstacle(WIDTH, birdY,
|
||||||
|
birdFly1.getWidth(), birdFly1.getHeight(),
|
||||||
|
birdFly1, true));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
private Rectangle getDinoHitbox() {
|
||||||
|
if (isDucking) {
|
||||||
|
return new Rectangle(
|
||||||
|
dinoX + HITBOX_INSET,
|
||||||
|
GROUND_Y - dinoDuckHeight + HITBOX_INSET,
|
||||||
|
dinoDuckWidth - 2 * HITBOX_INSET,
|
||||||
|
dinoDuckHeight - 2 * HITBOX_INSET
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
return new Rectangle(
|
||||||
|
dinoX + HITBOX_INSET,
|
||||||
|
dinoY + HITBOX_INSET,
|
||||||
|
dinoWidth - 2 * HITBOX_INSET,
|
||||||
|
dinoHeight - 2 * HITBOX_INSET
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private boolean checkCollision(Obstacle obs) {
|
private boolean checkCollision(Obstacle obs) {
|
||||||
int dinoActualHeight = isDucking ? 26 : dinoHeight;
|
Rectangle dinoRect = getDinoHitbox();
|
||||||
int dinoActualY = isDucking ? GROUND_Y - 90 : dinoY;
|
Rectangle obsRect = new Rectangle(
|
||||||
|
obs.x + HITBOX_INSET,
|
||||||
Rectangle dinoRect = new Rectangle(dinoX + 5, dinoActualY + 5,
|
obs.y + HITBOX_INSET,
|
||||||
dinoWidth - 10, dinoActualHeight - 10);
|
obs.width - 2 * HITBOX_INSET,
|
||||||
Rectangle obsRect = new Rectangle(obs.x + 5, obs.y + 5,
|
obs.height - 2 * HITBOX_INSET
|
||||||
obs.width - 10, obs.height - 10);
|
);
|
||||||
|
|
||||||
return dinoRect.intersects(obsRect);
|
return dinoRect.intersects(obsRect);
|
||||||
}
|
}
|
||||||
|
private void resetGameData() {
|
||||||
private void reset() {
|
|
||||||
gameOver = false;
|
|
||||||
gameStarted = true;
|
|
||||||
score = 0;
|
score = 0;
|
||||||
gameSpeed = 12.0;
|
gameSpeed = INITIAL_SPEED;
|
||||||
frameCount = 0;
|
frameCount = 0;
|
||||||
dinoY = GROUND_Y - 90;
|
dinoY = groundY;
|
||||||
velocityY = 0;
|
velocityY = 0;
|
||||||
isJumping = false;
|
isJumping = false;
|
||||||
isDucking = false;
|
isDucking = false;
|
||||||
obstacles.clear();
|
obstacles.clear();
|
||||||
obstacleTimer = 0;
|
obstacleTimer = 0;
|
||||||
|
minObstacleDistance = 60;
|
||||||
|
maxObstacleDistance = 120;
|
||||||
|
}
|
||||||
|
private void reset() {
|
||||||
|
gameOver = false;
|
||||||
|
gameStarted = true;
|
||||||
|
resetGameData();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -296,45 +368,40 @@ public class DinoGame extends JPanel implements ActionListener, KeyListener {
|
|||||||
super.paintComponent(g);
|
super.paintComponent(g);
|
||||||
Graphics2D g2d = (Graphics2D) g;
|
Graphics2D g2d = (Graphics2D) g;
|
||||||
g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
|
g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
|
||||||
|
g2d.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_SPEED);
|
||||||
// Draw clouds
|
drawClouds(g2d);
|
||||||
|
drawGround(g2d);
|
||||||
|
drawDino(g2d);
|
||||||
|
drawObstacles(g2d);
|
||||||
|
drawUI(g2d);
|
||||||
|
drawOverlays(g2d);
|
||||||
|
}
|
||||||
|
private void drawClouds(Graphics2D g2d) {
|
||||||
for (Cloud c : clouds) {
|
for (Cloud c : clouds) {
|
||||||
if (cloud != null) {
|
if (cloud != null) {
|
||||||
g2d.drawImage(cloud, c.x, c.y, null);
|
g2d.drawImage(cloud, c.x, c.y, null);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
// Draw ground
|
private void drawGround(Graphics2D g2d) {
|
||||||
if (land != null) {
|
if (land != null) {
|
||||||
g2d.drawImage(land, landX, GROUND_Y, null);
|
g2d.drawImage(land, landX, GROUND_Y, null);
|
||||||
g2d.drawImage(land, landX + land.getWidth(), GROUND_Y, null);
|
g2d.drawImage(land, landX + land.getWidth(), GROUND_Y, null);
|
||||||
}
|
}
|
||||||
|
g2d.setColor(new Color(83, 83, 83));
|
||||||
// Draw ground line
|
|
||||||
g2d.setColor(Color.BLACK);
|
|
||||||
g2d.fillRect(0, GROUND_Y - 2, WIDTH, 2);
|
g2d.fillRect(0, GROUND_Y - 2, WIDTH, 2);
|
||||||
|
}
|
||||||
// Draw dino
|
private void drawDino(Graphics2D g2d) {
|
||||||
BufferedImage dinoImg;
|
BufferedImage dinoImg = getDinoImage();
|
||||||
if (gameOver) {
|
|
||||||
dinoImg = dinoDead;
|
|
||||||
} else if (isJumping) {
|
|
||||||
dinoImg = dinoJump;
|
|
||||||
} else if (isDucking) {
|
|
||||||
dinoImg = (animFrame == 0) ? dinoDownRun1 : dinoDownRun2;
|
|
||||||
} else {
|
|
||||||
dinoImg = (animFrame == 0) ? dinoRun1 : dinoRun2;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (dinoImg != null) {
|
if (dinoImg != null) {
|
||||||
if (isDucking) {
|
if (isDucking) {
|
||||||
g2d.drawImage(dinoImg, dinoX, GROUND_Y - 26, 59, 26, null);
|
g2d.drawImage(dinoImg, dinoX, GROUND_Y - dinoDuckHeight, null);
|
||||||
} else {
|
} else {
|
||||||
g2d.drawImage(dinoImg, dinoX, dinoY, null);
|
g2d.drawImage(dinoImg, dinoX, dinoY, null);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
// Draw obstacles
|
private void drawObstacles(Graphics2D g2d) {
|
||||||
for (Obstacle obs : obstacles) {
|
for (Obstacle obs : obstacles) {
|
||||||
if (obs.isBird) {
|
if (obs.isBird) {
|
||||||
BufferedImage birdImg = (frameCount % 10 < 5) ? birdFly1 : birdFly2;
|
BufferedImage birdImg = (frameCount % 10 < 5) ? birdFly1 : birdFly2;
|
||||||
@@ -347,17 +414,17 @@ public class DinoGame extends JPanel implements ActionListener, KeyListener {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
// Draw score
|
private void drawUI(Graphics2D g2d) {
|
||||||
g2d.setColor(Color.BLACK);
|
g2d.setColor(new Color(83, 83, 83));
|
||||||
g2d.setFont(new Font("Courier New", Font.BOLD, 16));
|
g2d.setFont(new Font("Courier New", Font.BOLD, 16));
|
||||||
if (hiImg != null && highScore > 0) {
|
if (hiImg != null && highScore > 0) {
|
||||||
g2d.drawImage(hiImg, WIDTH - 200, 20, null);
|
g2d.drawImage(hiImg, WIDTH - 200, 20, null);
|
||||||
g2d.drawString(String.format("%05d", highScore), WIDTH - 150, 35);
|
g2d.drawString(String.format("%05d", highScore), WIDTH - 150, 35);
|
||||||
}
|
}
|
||||||
g2d.drawString(String.format("%05d", score), WIDTH - 80, 35);
|
g2d.drawString(String.format("%05d", score), WIDTH - 80, 35);
|
||||||
|
}
|
||||||
// Draw game over
|
private void drawOverlays(Graphics2D g2d) {
|
||||||
if (gameOver) {
|
if (gameOver) {
|
||||||
if (gameOverImg != null) {
|
if (gameOverImg != null) {
|
||||||
g2d.drawImage(gameOverImg, WIDTH / 2 - gameOverImg.getWidth() / 2, 100, null);
|
g2d.drawImage(gameOverImg, WIDTH / 2 - gameOverImg.getWidth() / 2, 100, null);
|
||||||
@@ -365,25 +432,45 @@ public class DinoGame extends JPanel implements ActionListener, KeyListener {
|
|||||||
if (replayImg != null) {
|
if (replayImg != null) {
|
||||||
g2d.drawImage(replayImg, WIDTH / 2 - replayImg.getWidth() / 2, 150, null);
|
g2d.drawImage(replayImg, WIDTH / 2 - replayImg.getWidth() / 2, 150, null);
|
||||||
}
|
}
|
||||||
}
|
g2d.setFont(new Font("Arial", Font.BOLD, 18));
|
||||||
|
g2d.drawString("Press SPACE to restart", WIDTH / 2 - 110, 200);
|
||||||
|
} else if (!gameStarted) {
|
||||||
|
g2d.setColor(new Color(83, 83, 83));
|
||||||
|
g2d.setFont(new Font("Arial", Font.BOLD, 24));
|
||||||
|
String msg = "Press SPACE to Start";
|
||||||
|
FontMetrics fm = g2d.getFontMetrics();
|
||||||
|
int msgWidth = fm.stringWidth(msg);
|
||||||
|
g2d.drawString(msg, (WIDTH - msgWidth) / 2, HEIGHT / 2);
|
||||||
|
|
||||||
// Draw start message
|
g2d.setFont(new Font("Arial", Font.PLAIN, 14));
|
||||||
if (!gameStarted) {
|
String controls = "↓ to duck • P to pause";
|
||||||
g2d.setFont(new Font("Arial", Font.BOLD, 20));
|
msgWidth = g2d.getFontMetrics().stringWidth(controls);
|
||||||
g2d.drawString("Press SPACE to Start", WIDTH / 2 - 120, HEIGHT / 2);
|
g2d.drawString(controls, (WIDTH - msgWidth) / 2, HEIGHT / 2 + 30);
|
||||||
}
|
} else if (paused) {
|
||||||
|
g2d.setColor(new Color(83, 83, 83));
|
||||||
// Draw pause
|
g2d.setFont(new Font("Arial", Font.BOLD, 36));
|
||||||
if (paused) {
|
String msg = "PAUSED";
|
||||||
g2d.setFont(new Font("Arial", Font.BOLD, 30));
|
FontMetrics fm = g2d.getFontMetrics();
|
||||||
g2d.drawString("PAUSED", WIDTH / 2 - 60, HEIGHT / 2);
|
int msgWidth = fm.stringWidth(msg);
|
||||||
|
g2d.drawString(msg, (WIDTH - msgWidth) / 2, HEIGHT / 2);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private BufferedImage getDinoImage() {
|
||||||
|
if (gameOver) {
|
||||||
|
return dinoDead;
|
||||||
|
} else if (isJumping) {
|
||||||
|
return dinoJump;
|
||||||
|
} else if (isDucking) {
|
||||||
|
return (animFrame == 0) ? dinoDownRun1 : dinoDownRun2;
|
||||||
|
} else {
|
||||||
|
return (animFrame == 0) ? dinoRun1 : dinoRun2;
|
||||||
|
}
|
||||||
|
}
|
||||||
@Override
|
@Override
|
||||||
public void keyPressed(KeyEvent e) {
|
public void keyPressed(KeyEvent e) {
|
||||||
int key = e.getKeyCode();
|
int key = e.getKeyCode();
|
||||||
|
|
||||||
if (key == KeyEvent.VK_SPACE) {
|
if (key == KeyEvent.VK_SPACE) {
|
||||||
if (!gameStarted) {
|
if (!gameStarted) {
|
||||||
gameStarted = true;
|
gameStarted = true;
|
||||||
@@ -395,31 +482,29 @@ public class DinoGame extends JPanel implements ActionListener, KeyListener {
|
|||||||
playSound(jumpSound);
|
playSound(jumpSound);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (key == KeyEvent.VK_DOWN && !isJumping && gameStarted && !gameOver) {
|
if (key == KeyEvent.VK_DOWN && !isJumping && gameStarted && !gameOver) {
|
||||||
isDucking = true;
|
isDucking = true;
|
||||||
}
|
}
|
||||||
|
if (key == KeyEvent.VK_UP && !gameStarted) {
|
||||||
if (key == KeyEvent.VK_P && gameStarted && !gameOver) {
|
gameStarted = true;
|
||||||
|
}
|
||||||
|
if ((key == KeyEvent.VK_P || key == KeyEvent.VK_ESCAPE) && gameStarted && !gameOver) {
|
||||||
paused = !paused;
|
paused = !paused;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void keyReleased(KeyEvent e) {
|
public void keyReleased(KeyEvent e) {
|
||||||
if (e.getKeyCode() == KeyEvent.VK_DOWN) {
|
if (e.getKeyCode() == KeyEvent.VK_DOWN) {
|
||||||
isDucking = false;
|
isDucking = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void keyTyped(KeyEvent e) {}
|
public void keyTyped(KeyEvent e) {}
|
||||||
|
private static class Obstacle {
|
||||||
private class Obstacle {
|
int x;
|
||||||
int x, y, width, height;
|
int y, width, height;
|
||||||
BufferedImage img;
|
BufferedImage img;
|
||||||
boolean isBird;
|
boolean isBird;
|
||||||
|
|
||||||
Obstacle(int x, int y, int width, int height, BufferedImage img, boolean isBird) {
|
Obstacle(int x, int y, int width, int height, BufferedImage img, boolean isBird) {
|
||||||
this.x = x;
|
this.x = x;
|
||||||
this.y = y;
|
this.y = y;
|
||||||
@@ -429,10 +514,9 @@ public class DinoGame extends JPanel implements ActionListener, KeyListener {
|
|||||||
this.isBird = isBird;
|
this.isBird = isBird;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
private static class Cloud {
|
||||||
private class Cloud {
|
int x;
|
||||||
int x, y;
|
int y;
|
||||||
|
|
||||||
Cloud(int x, int y) {
|
Cloud(int x, int y) {
|
||||||
this.x = x;
|
this.x = x;
|
||||||
this.y = y;
|
this.y = y;
|
||||||
@@ -440,27 +524,27 @@ public class DinoGame extends JPanel implements ActionListener, KeyListener {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public static void main(String[] args) {
|
public static void main(String[] args) {
|
||||||
try {
|
SwingUtilities.invokeLater(() -> {
|
||||||
SwingUtilities.invokeLater(() -> {
|
JFrame frame = new JFrame("Java Dino Game");
|
||||||
JFrame frame = new JFrame("Chrome Dino Game");
|
try {
|
||||||
try {
|
InputStream iconStream = DinoGame.class.getResourceAsStream("/dino-jump.png");
|
||||||
BufferedImage icon = ImageIO.read(DinoGame.class.getResourceAsStream("/dino-jump.png"));
|
if (iconStream != null) {
|
||||||
|
BufferedImage icon = ImageIO.read(iconStream);
|
||||||
frame.setIconImage(icon);
|
frame.setIconImage(icon);
|
||||||
} catch (IOException e) {
|
|
||||||
System.err.println("Errore caricamento icona: " + e.getMessage());
|
|
||||||
e.printStackTrace();
|
|
||||||
}
|
}
|
||||||
DinoGame game = new DinoGame();
|
} catch (Exception e) {
|
||||||
frame.add(game);
|
System.err.println("Could not load icon: " + e.getMessage());
|
||||||
frame.pack();
|
}
|
||||||
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
|
|
||||||
frame.setLocationRelativeTo(null);
|
DinoGame game = new DinoGame();
|
||||||
frame.setResizable(false);
|
frame.add(game);
|
||||||
frame.setVisible(true);
|
frame.pack();
|
||||||
});
|
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
|
||||||
} catch (Exception e){
|
frame.setLocationRelativeTo(null);
|
||||||
System.err.println("Error : " + e.getMessage());
|
frame.setResizable(false);
|
||||||
e.printStackTrace();
|
frame.setVisible(true);
|
||||||
}
|
|
||||||
|
game.requestFocusInWindow();
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user