Unverified Commit 5676508a authored by kingyuluk's avatar kingyuluk
Browse files

refactor: 简化部分代码

parent 4cc7c4e2
# Flappy Bird # Flappy Bird
![](https://img.shields.io/badge/JDK-1.8.0-a7742f.svg) ![](https://img.shields.io/badge/jdk-1.8.0-FFB6C1.svg?&logo=github)
![](https://img.shields.io/badge/platform-MacOS%20%7C%20Windows-yellow.svg) ![](https://img.shields.io/github/v/release/kingyuluk/FlappyBird?color=FFB6C1&logo=github)
![](https://img.shields.io/github/license/kingyuluk/FlappyBird) ![](https://img.shields.io/github/license/kingyuluk/FlappyBird?color=FFB6C1&logo=github)
![](https://img.shields.io/github/v/release/kingyuluk/FlappyBird) ![](https://img.shields.io/github/repo-size/kingyuluk/FlappyBird?color=FFB6C1&logo=github)
![](https://img.shields.io/github/repo-size/kingyuluk/FlappyBird?color=ff69b4)
## Overview ## Overview
...@@ -12,8 +10,10 @@ ...@@ -12,8 +10,10 @@
具备原版所有功能,相较原版加入了移动型水管、优化了难度梯度。 具备原版所有功能,相较原版加入了移动型水管、优化了难度梯度。
[RL FlappyBird](https://github.com/kingyuluk/RL-FlappyBird)
基于本项目集成了Amazon的Deep Java Library (DJL),可以使用强化学习(DQN)训练Flappy Bird
## How to play ## How to play
直接运行FlappyBird.jar即可开始游戏。 程序入口:app/GameApp.java
游戏使用空格键操作。 游戏使用空格键操作。
...@@ -43,11 +43,11 @@ ...@@ -43,11 +43,11 @@
* 图片与音效资源皆来源于网络,仅供学习交流 * 图片与音效资源皆来源于网络,仅供学习交流
## Package Contents ## Package Contents
* com.bird.app 游戏的入口 * com.kingyu.flappybird.app 游戏的入口
* com.bird.main 游戏的内容 * com.kingyu.flappybird.game 游戏的主体
* com.bird.util 自定义的工具 * com.kingyu.flappybird.util 自定义的工具
## [Change Log](https://github.com/kingyuluk/FlappyBird/blob/master/CHANGELOG.md) ## [Change Log](https://github.com/kingyuluk/FlappyBird/blob/master/CHANGELOG.md)
......
package com.bird.main;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import com.bird.util.Constant;
import com.bird.util.MusicUtil;
/**
* 游戏计时类, 单例类,方便调用
*
* @author Kingyu
*
*/
public class GameScore {
private static final GameScore GAME_SCORE = new GameScore();
private long score = 0; // 分数
private long bestScore; // 最高分数
private GameScore() {
bestScore = -1;
try {
loadBestScore();
} catch (Exception e) {
e.printStackTrace();
}
}
public static GameScore getInstance() {
return GAME_SCORE;
}
// 装载最高纪录
private void loadBestScore() throws Exception {
File file = new File(Constant.SCORE_FILE_PATH);
if (file.exists()) {
DataInputStream dis = new DataInputStream(new FileInputStream(file));
bestScore = dis.readLong();
dis.close();
}
}
// 保存最高纪录
public void saveBestScore(long score) throws Exception {
File file = new File(Constant.SCORE_FILE_PATH);
DataOutputStream dos = new DataOutputStream(new FileOutputStream(file));
dos.writeLong(score);
dos.close();
}
public long getBestScore() {
return bestScore;
}
public long getScore() {
return score;
}
public void setScore(Bird bird) {
if(!bird.isDead()){
MusicUtil.playScore(); //每次得分播放音效
score += 1;
//小鸟没死时记分
}
}
// 判断是否为最高纪录
public void isSaveScore() {
long score = getScore();
if (bestScore < score)
bestScore = score;
try {
saveBestScore(bestScore);
} catch (Exception e) {
e.printStackTrace();
}
}
//重置计分器
public void reset() {
score = 0;
}
// private int timeState; // 计时器的状态
// public static final int STATE_READY = 0; // 计时就绪
// public static final int STATE_START = 1; // 计时开始
// public static final int STATE_OVER = 2; // 计时结束
// 以下为游戏计时的相关方法,因改进了记分方式目前无用
// /**
// * 游戏用时,毫秒
// *
// * @return
// */
// public long getTime() {
// if (timeState == STATE_READY) {
// return startTime;
// } else if (timeState == STATE_START) {
// return (System.currentTimeMillis() - startTime);
// } else {
// return (endTime - startTime);
// }
// }
//
// //游戏时间转换为秒
// public long getTimeInSeconds() {
// return getTime() / 1000;
// }
//
// // 计时器是否就绪
// public boolean isReadyTiming() {
// return timeState == STATE_READY;
// }
//
// // 开始计时
// public void startTiming() {
// startTime = System.currentTimeMillis();
// timeState = STATE_START;
// }
//
// // 结束计时
// public void endTiming() {
// endTime = System.currentTimeMillis();
// timeState = STATE_OVER;
// }
//
// private static final int FIRST_SCORE_TIME = 6600; // 从游戏开始到通过第一根水管的所需时间
// private static final int PER_SCORE_TIME = 2900; // 通过后续每一根水管的间隔的所需时间
//
// //将游戏时间转换为通过水管的数量
// public long TimeToScore() {
// long time = getTime();
// long temp = score;
// if (time >= FIRST_SCORE_TIME && time < FIRST_SCORE_TIME + PER_SCORE_TIME) {
// score = 1; //time大于FIRST_SCORE_TIME且未到第二对水管
// } else if (time >= FIRST_SCORE_TIME + PER_SCORE_TIME) {
// score = (int) (time - FIRST_SCORE_TIME) / PER_SCORE_TIME + 1;
// }
// if (score - temp > 0) {
// MusicUtil.playScore(); //每次得分播放音效
// }
// return score;
// }
//
// /**
// * 是否正在计时
// *
// * @return
// */
// public boolean isTiming() {
// return timeState == STATE_START;
// }
}
package com.bird.app; package com.kingyu.flappybird.app;
import com.bird.main.GameFrame; import com.kingyu.flappybird.game.Game;
/** /**
* 程序入口类 * 程序入口类
...@@ -12,6 +12,6 @@ import com.bird.main.GameFrame; ...@@ -12,6 +12,6 @@ import com.bird.main.GameFrame;
public class GameApp { public class GameApp {
public static void main(String[] args) { public static void main(String[] args) {
new GameFrame(); new Game();
} }
} }
package com.bird.main; package com.kingyu.flappybird.game;
import java.awt.Color; import java.awt.Color;
import java.awt.Graphics; import java.awt.Graphics;
import java.awt.Rectangle; import java.awt.Rectangle;
import java.awt.image.BufferedImage; import java.awt.image.BufferedImage;
import com.bird.util.Constant; import com.kingyu.flappybird.util.Constant;
import com.bird.util.GameUtil; import com.kingyu.flappybird.util.GameUtil;
import com.bird.util.MusicUtil; import com.kingyu.flappybird.util.MusicUtil;
/** /**
* 小鸟类,小鸟的绘制与飞行逻辑都在此类 * 小鸟类,小鸟的绘制与飞行逻辑都在此类
...@@ -25,26 +25,28 @@ public class Bird { ...@@ -25,26 +25,28 @@ public class Bird {
// 图片资源 // 图片资源
private BufferedImage image; // 实时的小鸟图片 private BufferedImage image; // 实时的小鸟图片
private BufferedImage scoreImg; // 计分牌
private BufferedImage overImg; // 结束标志
private BufferedImage againImg; // 继续标志
// 小鸟的状态 // 小鸟的状态
private int state; private int state;
public static final int STATE_NORMAL = 0; public static final int BIRD_NORMAL = 0;
public static final int STATE_UP = 1; public static final int BIRD_UP = 1;
public static final int STATE_DOWN = 2; public static final int BIRD_FALL = 2;
public static final int STATE_FALL = 3; public static final int BIRD_DEAD_FALL = 3;
public static final int STATE_DEAD = 4; public static final int BIRD_DEAD = 4;
private final Rectangle birdRect; // 碰撞矩形 private final Rectangle birdRect; // 碰撞矩形
public static final int RECT_DESCALE = 2; // 补偿碰撞矩形宽高的参数 public static final int RECT_DESCALE = 2; // 补偿碰撞矩形宽高的参数
private final GameScore countScore; // 计分器 private final ScoreCounter counter; // 计分器
private final GameOverAnimation gameOverAnimation;
public static int BIRD_WIDTH;
public static int BIRD_HEIGHT;
// 在构造器中对资源初始化 // 在构造器中对资源初始化
public Bird() { public Bird() {
countScore = GameScore.getInstance(); // 计分器 counter = ScoreCounter.getInstance(); // 计分器
gameOverAnimation = new GameOverAnimation();
// 读取小鸟图片资源 // 读取小鸟图片资源
birdImages = new BufferedImage[STATE_COUNT][IMG_COUNT]; birdImages = new BufferedImage[STATE_COUNT][IMG_COUNT];
...@@ -54,45 +56,47 @@ public class Bird { ...@@ -54,45 +56,47 @@ public class Bird {
} }
} }
assert birdImages[0][0] != null;
BIRD_WIDTH = birdImages[0][0].getWidth();
BIRD_HEIGHT = birdImages[0][0].getHeight();
// 初始化小鸟的坐标 // 初始化小鸟的坐标
x = Constant.FRAME_WIDTH >> 2; x = Constant.FRAME_WIDTH >> 2;
y = Constant.FRAME_HEIGHT >> 1; y = Constant.FRAME_HEIGHT >> 1;
int ImgWidth = birdImages[state][0].getWidth();
int ImgHeight = birdImages[state][0].getHeight();
// 初始化碰撞矩形 // 初始化碰撞矩形
int rectX = x - ImgWidth / 2; int rectX = x - BIRD_WIDTH / 2;
int rectY = y - ImgHeight / 2; int rectY = y - BIRD_HEIGHT / 2;
birdRect = new Rectangle(rectX + RECT_DESCALE, rectY + RECT_DESCALE * 2, ImgWidth - RECT_DESCALE * 3, birdRect = new Rectangle(rectX + RECT_DESCALE, rectY + RECT_DESCALE * 2, BIRD_WIDTH - RECT_DESCALE * 3,
ImgHeight - RECT_DESCALE * 4); // 碰撞矩形的坐标与小鸟相同 BIRD_WIDTH - RECT_DESCALE * 4); // 碰撞矩形的坐标与小鸟相同
} }
// 绘制方法 // 绘制方法
public void draw(Graphics g) { public void draw(Graphics g) {
fly(); movement();
int state_index = Math.min(state, STATE_FALL); // 图片资源索引 int state_index = Math.min(state, BIRD_DEAD_FALL); // 图片资源索引
// 小鸟中心点计算 // 小鸟中心点计算
int halfImgWidth = birdImages[state_index][0].getWidth() >> 1; int halfImgWidth = birdImages[state_index][0].getWidth() >> 1;
int halfImgHeight = birdImages[state_index][0].getHeight() >> 1; int halfImgHeight = birdImages[state_index][0].getHeight() >> 1;
if (speed > 0) if (velocity > 0)
image = birdImages[STATE_UP][0]; image = birdImages[BIRD_UP][0];
g.drawImage(image, x - halfImgWidth, y - halfImgHeight, null); // x坐标于窗口1/4处,y坐标位窗口中心 g.drawImage(image, x - halfImgWidth, y - halfImgHeight, null); // x坐标于窗口1/4处,y坐标位窗口中心
if (state == STATE_DEAD) if (state == BIRD_DEAD)
drawGameOver(g); gameOverAnimation.draw(g, this);
else if (state != STATE_FALL) else if (state != BIRD_DEAD_FALL)
drawScore(g); drawScore(g);
// 绘制矩形 // 绘制矩形
// g.setColor(Color.black); // g.setColor(Color.black);
// g.drawRect((int) birdRect.getX(), (int) birdRect.getY(), (int) birdRect.getWidth(), (int) birdRect.getHeight()); // g.drawRect((int) birdRect.getX(), (int) birdRect.getY(), (int) birdRect.getWidth(), (int) birdRect.getHeight());
} }
public static final int SPEED_UP = 32; // 小鸟向上的速度 public static final int ACC_FLAP = 14; // players speed on flapping
public static final double g = 9.8; // 重力加速度 public static final double ACC_Y = 2; // players downward acceleration
public static final double T = 0.2; // 小鸟的下落函数执行的时间 public static final int MAX_VEL_Y = 15; // max vel along Y, max descend speed
private double speed = 0; // 小鸟的初速度 private int velocity = 0; // bird's velocity along Y, default same as playerFlapped
private boolean keyFlag = true; // 按键状态,true为已释放,使当按住按键时不会重复调用方法 private boolean keyFlag = true; // 按键状态,true为已释放,使当按住按键时不会重复调用方法
...@@ -108,81 +112,83 @@ public class Bird { ...@@ -108,81 +112,83 @@ public class Bird {
return keyFlag; return keyFlag;
} }
private final int BOTTOM_BOUNDARY = Constant.FRAME_HEIGHT - GameBackground.GROUND_HEIGHT - (BIRD_HEIGHT / 2);
int TOP_BOUNDARY = 30;
// 小鸟的飞行逻辑 // 小鸟的飞行逻辑
private void fly() { private void movement() {
// 翅膀状态,实现小鸟振翅飞行 // 翅膀状态,实现小鸟振翅飞行
wingState++; wingState++;
image = birdImages[Math.min(state, STATE_FALL)][wingState / 10 % IMG_COUNT]; image = birdImages[Math.min(state, BIRD_DEAD_FALL)][wingState / 10 % IMG_COUNT];
// 下方边界: 窗口的高度 - 地面的高度 - 小鸟图片的高度
final int bottomBoundary = Constant.FRAME_HEIGHT - Constant.GROUND_HEIGHT - (birdImages[0][0].getHeight() >> 1);
final int topBoundary = -50;
switch (state) { switch (state) {
case STATE_DOWN: case BIRD_FALL:
// 自由落体 // 自由落体
speed -= g * T; if (velocity < MAX_VEL_Y)
double h = speed * T - g * T * T / 2; velocity -= ACC_Y;
y = Math.min((int) (y - h), bottomBoundary); y = Math.min((y - velocity), BOTTOM_BOUNDARY);
birdRect.y = Math.min((int) (birdRect.y - h), bottomBoundary); birdRect.y = birdRect.y - velocity;
if (birdRect.y == bottomBoundary) { if (birdRect.y > BOTTOM_BOUNDARY) {
MusicUtil.playCrash(); MusicUtil.playCrash();
birdDead(); die();
} }
break; break;
case STATE_FALL: case BIRD_DEAD_FALL:
// 自由落体 // 自由落体
speed -= g * T; if (velocity < MAX_VEL_Y)
h = speed * T - g * T * T / 2; velocity -= ACC_Y;
y = Math.min((int) (y - h), bottomBoundary); y = Math.min((y - velocity), BOTTOM_BOUNDARY);
birdRect.y = Math.min((int) (birdRect.y - h), bottomBoundary); birdRect.y = birdRect.y - velocity;
if (birdRect.y == bottomBoundary) if (birdRect.y > BOTTOM_BOUNDARY) {
birdDead(); die();
}
break; break;
case STATE_DEAD: case BIRD_DEAD:
GameFrame.setGameState(GameFrame.STATE_OVER); Game.setGameState(Game.STATE_OVER);
break; break;
case STATE_NORMAL: case BIRD_NORMAL:
case STATE_UP: case BIRD_UP:
break; break;
} }
// 控制上方边界
if (birdRect.y < topBoundary) {
birdRect.y = topBoundary;
y = topBoundary;
}
} }
// 小鸟振翅 // 小鸟振翅
public void birdUp() { public void birdFlap() {
if (keyIsReleased()) { // 如果按键已释放 if (keyIsReleased()) { // 如果按键已释放
if (state == STATE_DEAD || state == STATE_UP || state == STATE_FALL) if (state == BIRD_DEAD || state == BIRD_UP || state == BIRD_DEAD_FALL)
return; // 小鸟死亡或坠落时返回 return; // 小鸟死亡或坠落时返回
MusicUtil.playFly(); // 播放音效 MusicUtil.playFly(); // 播放音效
state = STATE_UP; state = BIRD_UP;
speed = SPEED_UP; // 每次振翅将速度改为上升速度 if (birdRect.y > TOP_BOUNDARY) {
wingState = 0; // 重置翅膀状态 velocity = ACC_FLAP; // 每次振翅将速度改为上升速度
wingState = 0; // 重置翅膀状态
}
keyPressed(); keyPressed();
} }
} }
// 小鸟下降 // 小鸟下降
public void birdDown() { public void birdFall() {
if (state == STATE_DEAD || state == STATE_FALL) if (state == BIRD_DEAD || state == BIRD_DEAD_FALL)
return; // 小鸟死亡或坠落时返回 return; // 小鸟死亡或坠落时返回
state = STATE_DOWN; state = BIRD_FALL;
}
public void die(){
counter.saveScore();
state = BIRD_DEAD;
Game.setGameState(Game.STATE_OVER);
} }
// 小鸟坠落(已死) // 小鸟坠落(已死)
public void birdFall() { public void deadBirdFall() {
state = STATE_FALL; state = BIRD_DEAD_FALL;
MusicUtil.playCrash(); // 播放音效 MusicUtil.playCrash(); // 播放音效
speed = 0; // 速度置0,防止小鸟继续上升与水管重叠 velocity = 0; // 速度置0,防止小鸟继续上升与水管重叠
// 死后画面静止片刻 // 死后画面静止片刻
try { try {
Thread.sleep(200); Thread.sleep(200);
...@@ -191,85 +197,42 @@ public class Bird { ...@@ -191,85 +197,42 @@ public class Bird {
} }
} }
// 小鸟死亡
public void birdDead() {
state = STATE_DEAD;
// 加载游戏结束的资源
if (overImg == null) {
overImg = GameUtil.loadBufferedImage(Constant.OVER_IMG_PATH);
scoreImg = GameUtil.loadBufferedImage(Constant.SCORE_IMG_PATH);
againImg = GameUtil.loadBufferedImage(Constant.AGAIN_IMG_PATH);
}
countScore.isSaveScore(); // 判断是否保存纪录
}
// 判断小鸟是否死亡 // 判断小鸟是否死亡
public boolean isDead() { public boolean isDead() {
return state == STATE_FALL || state == STATE_DEAD; return state == BIRD_DEAD_FALL || state == BIRD_DEAD;
} }
// 绘制实时分数 // 绘制实时分数
private void drawScore(Graphics g) { private void drawScore(Graphics g) {
g.setColor(Color.white); g.setColor(Color.white);
g.setFont(Constant.CURRENT_SCORE_FONT); g.setFont(Constant.CURRENT_SCORE_FONT);
String str = Long.toString(countScore.getScore()); String str = Long.toString(counter.getCurrentScore());
int x = Constant.FRAME_WIDTH - GameUtil.getStringWidth(Constant.CURRENT_SCORE_FONT, str) >> 1; int x = Constant.FRAME_WIDTH - GameUtil.getStringWidth(Constant.CURRENT_SCORE_FONT, str) >> 1;
g.drawString(str, x, Constant.FRAME_HEIGHT / 10); g.drawString(str, x, Constant.FRAME_HEIGHT / 10);
} }
private static final int SCORE_LOCATE = 5; // 位置补偿参数
private int flash = 0; // 图片闪烁参数
// 绘制游戏结束的显示
private void drawGameOver(Graphics g) {
// 绘制结束标志
int x = Constant.FRAME_WIDTH - overImg.getWidth() >> 1;
int y = Constant.FRAME_HEIGHT / 4;
g.drawImage(overImg, x, y, null);
// 绘制计分牌
x = Constant.FRAME_WIDTH - scoreImg.getWidth() >> 1;
y = Constant.FRAME_HEIGHT / 3;
g.drawImage(scoreImg, x, y, null);
// 绘制本局的分数
g.setColor(Color.white);
g.setFont(Constant.SCORE_FONT);
x = (Constant.FRAME_WIDTH - scoreImg.getWidth() / 2 >> 1) + SCORE_LOCATE;// 位置补偿
y += scoreImg.getHeight() >> 1;
String str = Long.toString(countScore.getScore());
x -= GameUtil.getStringWidth(Constant.SCORE_FONT, str) >> 1;
y += GameUtil.getStringHeight(Constant.SCORE_FONT, str);
g.drawString(str, x, y);
// 绘制最高分数
if (countScore.getBestScore() > 0) {
str = Long.toString(countScore.getBestScore());
x = (Constant.FRAME_WIDTH + scoreImg.getWidth() / 2 >> 1) - SCORE_LOCATE;// 位置补偿
x -= GameUtil.getStringWidth(Constant.SCORE_FONT, str) >> 1;
g.drawString(str, x, y);
}
// 绘制继续游戏,使图像闪烁
final int COUNT = 30; // 闪烁周期
if (flash++ > COUNT)
GameUtil.drawImage(againImg,Constant.FRAME_WIDTH - againImg.getWidth() >> 1, Constant.FRAME_HEIGHT / 5 * 3, g);
if (flash == COUNT * 2) // 重置闪烁参数
flash = 0;
}
// 重置小鸟 // 重置小鸟
public void reset() { public void reset() {
state = STATE_NORMAL; // 小鸟状态 state = BIRD_NORMAL; // 小鸟状态
y = Constant.FRAME_HEIGHT >> 1; // 小鸟坐标 y = Constant.FRAME_HEIGHT >> 1; // 小鸟坐标
speed = 0; // 小鸟速度 velocity = 0; // 小鸟速度
int ImgHeight = birdImages[state][0].getHeight(); int ImgHeight = birdImages[state][0].getHeight();
birdRect.y = y - ImgHeight / 2 + RECT_DESCALE * 2; // 小鸟碰撞矩形坐标 birdRect.y = y - ImgHeight / 2 + RECT_DESCALE * 2; // 小鸟碰撞矩形坐标
countScore.reset(); // 重置计分器 counter.reset(); // 重置计分器
flash = 0; }
public long getCurrentScore() {
return counter.getCurrentScore();
}
public long getBestScore() {
return counter.getBestScore();
}
public int getBirdX() {
return x;
} }
// 获取小鸟的碰撞矩形 // 获取小鸟的碰撞矩形
......
package com.bird.main; package com.kingyu.flappybird.game;
import com.kingyu.flappybird.util.Constant;
import java.awt.Graphics; import java.awt.Graphics;
import java.awt.image.BufferedImage; import java.awt.image.BufferedImage;
...@@ -25,7 +27,7 @@ public class Cloud { ...@@ -25,7 +27,7 @@ public class Cloud {
this.img = img; this.img = img;
this.x = x; this.x = x;
this.y = y; this.y = y;
this.speed = 2; //云朵的速度 this.speed = Constant.GAME_SPEED * 2; //云朵的速度
// 云朵图片缩放的比例 1.0~2.0 // 云朵图片缩放的比例 1.0~2.0
double scale = 1 + Math.random(); // Math.random()返回0.0~1.0的随机值 double scale = 1 + Math.random(); // Math.random()返回0.0~1.0的随机值
// 缩放云朵图片 // 缩放云朵图片
......
package com.bird.main; package com.kingyu.flappybird.game;
import static com.bird.util.Constant.FRAME_HEIGHT; import static com.kingyu.flappybird.util.Constant.FRAME_HEIGHT;
import static com.bird.util.Constant.FRAME_WIDTH; import static com.kingyu.flappybird.util.Constant.FRAME_WIDTH;
import static com.bird.util.Constant.FRAME_X; import static com.kingyu.flappybird.util.Constant.FRAME_X;
import static com.bird.util.Constant.FRAME_Y; import static com.kingyu.flappybird.util.Constant.FRAME_Y;
import static com.bird.util.Constant.GAME_INTERVAL; import static com.kingyu.flappybird.util.Constant.GAME_INTERVAL;
import static com.bird.util.Constant.GAME_TITLE; import static com.kingyu.flappybird.util.Constant.GAME_TITLE;
import java.awt.Frame; import java.awt.Frame;
import java.awt.Graphics; import java.awt.Graphics;
...@@ -22,22 +22,22 @@ import java.awt.image.BufferedImage; ...@@ -22,22 +22,22 @@ import java.awt.image.BufferedImage;
* @author Kingyu * @author Kingyu
*/ */
public class GameFrame extends Frame implements Runnable { public class Game extends Frame implements Runnable {
private static final long serialVersionUID = 1L; // 保持版本的兼容性 private static final long serialVersionUID = 1L; // 保持版本的兼容性
private static int gameState; // 游戏状态 private static int gameState; // 游戏状态
public static final int STATE_READY = 0; // 游戏未开始 public static final int GAME_READY = 0; // 游戏未开始
public static final int STATE_START = 1; // 游戏开始 public static final int GAME_START = 1; // 游戏开始
public static final int STATE_OVER = 2; // 游戏结束 public static final int STATE_OVER = 2; // 游戏结束
private GameBackground background; // 游戏背景对象 private GameBackground background; // 游戏背景对象
private GameForeground foreground; // 游戏前景对象 private GameForeground foreground; // 游戏前景对象
private Bird bird; // 小鸟对象 private Bird bird; // 小鸟对象
private GameElementLayer gameElement; // 游戏元素对象 private GameElementLayer gameElement; // 游戏元素对象
private GameReady ready; // 游戏未开始时对象 private WelcomeAnimation welcomeAnimation; // 游戏未开始时对象
// 在构造器中初始化 // 在构造器中初始化
public GameFrame() { public Game() {
initFrame(); // 初始化游戏窗口 initFrame(); // 初始化游戏窗口
setVisible(true); // 窗口默认为不可见,设置为可见 setVisible(true); // 窗口默认为不可见,设置为可见
initGame(); // 初始化游戏对象 initGame(); // 初始化游戏对象
...@@ -65,19 +65,19 @@ public class GameFrame extends Frame implements Runnable { ...@@ -65,19 +65,19 @@ public class GameFrame extends Frame implements Runnable {
public void keyPressed(KeyEvent e) { public void keyPressed(KeyEvent e) {
int keycode = e.getKeyChar(); int keycode = e.getKeyChar();
switch (gameState) { switch (gameState) {
case STATE_READY: case GAME_READY:
if (keycode == KeyEvent.VK_SPACE) { if (keycode == KeyEvent.VK_SPACE) {
// 游戏启动界面时按下空格,小鸟振翅一次并开始受重力影响 // 游戏启动界面时按下空格,小鸟振翅一次并开始受重力影响
bird.birdUp(); bird.birdFlap();
bird.birdDown(); bird.birdFall();
setGameState(STATE_START); // 游戏状态改变 setGameState(GAME_START); // 游戏状态改变
} }
break; break;
case STATE_START: case GAME_START:
if (keycode == KeyEvent.VK_SPACE) { if (keycode == KeyEvent.VK_SPACE) {
//游戏过程中按下空格则振翅一次,并持续受重力影响 //游戏过程中按下空格则振翅一次,并持续受重力影响
bird.birdUp(); bird.birdFlap();
bird.birdDown(); bird.birdFall();
} }
break; break;
case STATE_OVER: case STATE_OVER:
...@@ -91,7 +91,7 @@ public class GameFrame extends Frame implements Runnable { ...@@ -91,7 +91,7 @@ public class GameFrame extends Frame implements Runnable {
// 重新开始游戏 // 重新开始游戏
private void resetGame() { private void resetGame() {
setGameState(STATE_READY); setGameState(GAME_READY);
gameElement.reset(); gameElement.reset();
bird.reset(); bird.reset();
} }
...@@ -113,9 +113,9 @@ public class GameFrame extends Frame implements Runnable { ...@@ -113,9 +113,9 @@ public class GameFrame extends Frame implements Runnable {
background = new GameBackground(); background = new GameBackground();
gameElement = new GameElementLayer(); gameElement = new GameElementLayer();
foreground = new GameForeground(); foreground = new GameForeground();
ready = new GameReady(); welcomeAnimation = new WelcomeAnimation();
bird = new Bird(); bird = new Bird();
setGameState(STATE_READY); setGameState(GAME_READY);
// 启动用于刷新窗口的线程 // 启动用于刷新窗口的线程
new Thread(this).start(); new Thread(this).start();
...@@ -139,8 +139,8 @@ public class GameFrame extends Frame implements Runnable { ...@@ -139,8 +139,8 @@ public class GameFrame extends Frame implements Runnable {
foreground.draw(bufG, bird); // 前景层 foreground.draw(bufG, bird); // 前景层
// 鸟 // 鸟
if (gameState == STATE_READY) { // 游戏未开始 if (gameState == GAME_READY) { // 游戏未开始
ready.draw(bufG); welcomeAnimation.draw(bufG);
} else { // 游戏结束 } else { // 游戏结束
gameElement.draw(bufG, bird); // 游戏元素层 gameElement.draw(bufG, bird); // 游戏元素层
} }
...@@ -148,7 +148,6 @@ public class GameFrame extends Frame implements Runnable { ...@@ -148,7 +148,6 @@ public class GameFrame extends Frame implements Runnable {
g.drawImage(bufImg, 0, 0, null); // 一次性将图片绘制到屏幕上 g.drawImage(bufImg, 0, 0, null); // 一次性将图片绘制到屏幕上
} }
// TODO: 不建议在while循环中使用sleep
@SuppressWarnings("InfiniteLoopStatement") @SuppressWarnings("InfiniteLoopStatement")
@Override @Override
public void run() { public void run() {
...@@ -162,8 +161,10 @@ public class GameFrame extends Frame implements Runnable { ...@@ -162,8 +161,10 @@ public class GameFrame extends Frame implements Runnable {
} }
} }
public static int getGameState(){ return gameState;};
public static void setGameState(int gameState) { public static void setGameState(int gameState) {
GameFrame.gameState = gameState; Game.gameState = gameState;
} }
} }
package com.bird.main; package com.kingyu.flappybird.game;
import java.awt.Graphics; import java.awt.Graphics;
import java.awt.image.BufferedImage; import java.awt.image.BufferedImage;
import com.bird.util.Constant; import com.kingyu.flappybird.util.Constant;
import com.bird.util.GameUtil; import com.kingyu.flappybird.util.GameUtil;
/** /**
* 游戏背景类,绘制游戏背景的内容都在此类 * 游戏背景类,绘制游戏背景的内容都在此类
...@@ -19,15 +19,19 @@ public class GameBackground { ...@@ -19,15 +19,19 @@ public class GameBackground {
private final int speed; // 背景层的速度 private final int speed; // 背景层的速度
private int layerX; // 背景层的坐标 private int layerX; // 背景层的坐标
public static final int GROUND_HEIGHT;
static {
BackgroundImg = GameUtil.loadBufferedImage(Constant.BG_IMG_PATH);
assert BackgroundImg != null;
GROUND_HEIGHT = BackgroundImg.getHeight() / 2;
}
// 在构造器中初始化 // 在构造器中初始化
public GameBackground() { public GameBackground() {
this.speed = 1; this.speed = Constant.GAME_SPEED;
this.layerX = 0; this.layerX = 0;
} }
static { //读取背景图片
BackgroundImg = GameUtil.loadBufferedImage(Constant.BG_IMG_PATH);
}
// 绘制方法 // 绘制方法
public void draw(Graphics g, Bird bird) { public void draw(Graphics g, Bird bird) {
......
package com.bird.main; package com.kingyu.flappybird.game;
import java.awt.Graphics; import java.awt.Graphics;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import com.bird.util.Constant; import com.kingyu.flappybird.util.Constant;
import com.bird.util.GameUtil; import com.kingyu.flappybird.util.GameUtil;
/** /**
* 游戏中各种元素层的类 * 游戏中各种元素层的类
...@@ -70,11 +70,14 @@ public class GameElementLayer { ...@@ -70,11 +70,14 @@ public class GameElementLayer {
} else { } else {
// 判断最后一对水管是否完全进入游戏窗口,若进入则添加水管 // 判断最后一对水管是否完全进入游戏窗口,若进入则添加水管
Pipe lastPipe = pipes.get(pipes.size() - 1); // 获得容器中最后一个水管 Pipe lastPipe = pipes.get(pipes.size() - 1); // 获得容器中最后一个水管
int currentDistance = lastPipe.getX() - bird.getBirdX() + Bird.BIRD_WIDTH / 2; // 小鸟和最后一根水管的距离
final int SCORE_DISTANCE = Pipe.PIPE_WIDTH * 2 + HORIZONTAL_INTERVAL; // 小于得分距离则得分
if (lastPipe.isInFrame()) { if (lastPipe.isInFrame()) {
if (pipes.size() >= Constant.FULL_PIPE - 2)// 若窗口中可容纳的水管已满,说明小鸟已飞到第一对水管的位置,开始记分 if (pipes.size() >= PipePool.FULL_PIPE - 2) {
GameScore.getInstance().setScore(bird); ScoreCounter.getInstance().score(bird);
}
try { try {
int currentScore = (int) GameScore.getInstance().getScore() + 1; // 获取当前分数 int currentScore = (int) ScoreCounter.getInstance().getCurrentScore() + 1; // 获取当前分数
// 移动水管刷新的概率随当前分数递增,当得分大于19后全部刷新移动水管 // 移动水管刷新的概率随当前分数递增,当得分大于19后全部刷新移动水管
if (GameUtil.isInProbability(currentScore, 20)) { if (GameUtil.isInProbability(currentScore, 20)) {
if (GameUtil.isInProbability(1, 4)) // 生成移动水管和移动悬浮水管的概率 if (GameUtil.isInProbability(1, 4)) // 生成移动水管和移动悬浮水管的概率
...@@ -209,7 +212,7 @@ public class GameElementLayer { ...@@ -209,7 +212,7 @@ public class GameElementLayer {
for (Pipe pipe : pipes) { for (Pipe pipe : pipes) {
// 判断碰撞矩形是否有交集 // 判断碰撞矩形是否有交集
if (pipe.getPipeRect().intersects(bird.getBirdRect())) { if (pipe.getPipeRect().intersects(bird.getBirdRect())) {
bird.birdFall(); // 有交集则小鸟坠落 bird.deadBirdFall();
return; return;
} }
} }
......
package com.bird.main; package com.kingyu.flappybird.game;
import java.awt.Graphics; import java.awt.Graphics;
import java.awt.image.BufferedImage; import java.awt.image.BufferedImage;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import com.bird.util.Constant; import com.kingyu.flappybird.util.Constant;
import com.bird.util.GameUtil; import com.kingyu.flappybird.util.GameUtil;
/** /**
* 前景类, 游戏中的遮挡层 包含多朵云 * 前景类, 游戏中的遮挡层 包含多朵云
......
package com.kingyu.flappybird.game;
import com.kingyu.flappybird.util.Constant;
import com.kingyu.flappybird.util.GameUtil;
import java.awt.*;
import java.awt.image.BufferedImage;
/**
* 游戏结束界面
*
* @author Kingyu
*
*/
public class GameOverAnimation {
private final BufferedImage scoreImg; // 计分牌
private final BufferedImage overImg; // 结束标志
private final BufferedImage againImg; // 继续标志
public GameOverAnimation(){
overImg = GameUtil.loadBufferedImage(Constant.OVER_IMG_PATH);
scoreImg = GameUtil.loadBufferedImage(Constant.SCORE_IMG_PATH);
againImg = GameUtil.loadBufferedImage(Constant.AGAIN_IMG_PATH);
}
private static final int SCORE_LOCATE = 5; // 计分牌位置补偿参数
private int flash = 0; // 图片闪烁参数
public void draw(Graphics g, Bird bird) {
int x = Constant.FRAME_WIDTH - overImg.getWidth() >> 1;
int y = Constant.FRAME_HEIGHT / 4;
g.drawImage(overImg, x, y, null);
// 绘制计分牌
x = Constant.FRAME_WIDTH - scoreImg.getWidth() >> 1;
y = Constant.FRAME_HEIGHT / 3;
g.drawImage(scoreImg, x, y, null);
// 绘制本局的分数
g.setColor(Color.white);
g.setFont(Constant.SCORE_FONT);
x = (Constant.FRAME_WIDTH - scoreImg.getWidth() / 2 >> 1) + SCORE_LOCATE;// 位置补偿
y += scoreImg.getHeight() >> 1;
String str = Long.toString(bird.getCurrentScore());
x -= GameUtil.getStringWidth(Constant.SCORE_FONT, str) >> 1;
y += GameUtil.getStringHeight(Constant.SCORE_FONT, str);
g.drawString(str, x, y);
// 绘制最高分数
if (bird.getBestScore() > 0) {
str = Long.toString(bird.getBestScore());
x = (Constant.FRAME_WIDTH + scoreImg.getWidth() / 2 >> 1) - SCORE_LOCATE;// 位置补偿
x -= GameUtil.getStringWidth(Constant.SCORE_FONT, str) >> 1;
g.drawString(str, x, y);
}
// 绘制继续游戏,图像闪烁
final int COUNT = 30; // 闪烁周期
if (flash++ > COUNT)
GameUtil.drawImage(againImg,Constant.FRAME_WIDTH - againImg.getWidth() >> 1, Constant.FRAME_HEIGHT / 5 * 3, g);
if (flash == COUNT * 2) // 重置闪烁参数
flash = 0;
}
}
package com.bird.main; package com.kingyu.flappybird.game;
import java.awt.Graphics; import java.awt.Graphics;
import com.bird.util.Constant; import com.kingyu.flappybird.util.Constant;
/** /**
* 移动水管类,继承Pipe类 * 移动水管类,继承Pipe类
......
package com.bird.main; package com.kingyu.flappybird.game;
import java.awt.Graphics; import java.awt.*;
import java.awt.Rectangle;
import java.awt.image.BufferedImage; import java.awt.image.BufferedImage;
import com.bird.util.Constant; import com.kingyu.flappybird.util.Constant;
import com.bird.util.GameUtil; import com.kingyu.flappybird.util.GameUtil;
/** /**
* 水管类 * 水管类
...@@ -43,14 +42,13 @@ public class Pipe { ...@@ -43,14 +42,13 @@ public class Pipe {
public static final int TYPE_HOVER_HARD = 5; public static final int TYPE_HOVER_HARD = 5;
// 水管的速度 // 水管的速度
public static final int SPEED = 1;
int speed; int speed;
Rectangle pipeRect; // 水管的碰撞矩形 Rectangle pipeRect; // 水管的碰撞矩形
// 构造器 // 构造器
public Pipe() { public Pipe() {
this.speed = SPEED; this.speed = Constant.GAME_SPEED;
this.width = PIPE_WIDTH; this.width = PIPE_WIDTH;
pipeRect = new Rectangle(); pipeRect = new Rectangle();
...@@ -184,25 +182,4 @@ public class Pipe { ...@@ -184,25 +182,4 @@ public class Pipe {
return pipeRect; return pipeRect;
} }
// 各参数的写入与获取
// public void setX(int x) {
// this.x = x;
// }
//
// public void setY(int y) {
// this.y = y;
// }
//
// public void setType(int type) {
// this.type = type;
// }
//
// public void setHeight(int height) {
// this.height = height;
// }
//
// public void setVisible(boolean visible) {
// this.visible = visible;
// }
} }
package com.bird.main; package com.kingyu.flappybird.game;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import com.bird.util.Constant; import com.kingyu.flappybird.util.Constant;
/** /**
* 为了避免反复地创建和销毁对象,使用对象池来提前创建好一些对象,使用时从对象池中获得,使用完后归还 * 为了避免反复地创建和销毁对象,使用对象池来提前创建好一些对象,使用时从对象池中获得,使用完后归还
...@@ -16,13 +16,14 @@ public class PipePool { ...@@ -16,13 +16,14 @@ public class PipePool {
private static final List<MovingPipe> movingPool = new ArrayList<>(); // 池中对象的容器 private static final List<MovingPipe> movingPool = new ArrayList<>(); // 池中对象的容器
public static final int MAX_PIPE_COUNT = 30; // 对象池中对象的最大个数,自行定义 public static final int MAX_PIPE_COUNT = 30; // 对象池中对象的最大个数,自行定义
public static final int FULL_PIPE = (Constant.FRAME_WIDTH
/ (Pipe.PIPE_HEAD_WIDTH + GameElementLayer.HORIZONTAL_INTERVAL) + 2) * 2;
// 初始化水管容器,初始化水管的数量的计算方式见常量类中的注释 // 初始化水管容器,初始化水管的数量的计算方式见常量类中的注释
static { static {
for (int i = 0; i < Constant.FULL_PIPE; i++) { for (int i = 0; i < PipePool.FULL_PIPE; i++) {
pool.add(new Pipe()); pool.add(new Pipe());
} }
for (int i = 0; i < Constant.FULL_PIPE; i++) { for (int i = 0; i < PipePool.FULL_PIPE; i++) {
movingPool.add(new MovingPipe()); movingPool.add(new MovingPipe());
} }
} }
......
package com.kingyu.flappybird.game;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import com.kingyu.flappybird.util.Constant;
import com.kingyu.flappybird.util.MusicUtil;
/**
* 游戏计时类, 单例类,方便调用
*
* @author Kingyu
*
*/
public class ScoreCounter {
private static final ScoreCounter scoreCounter = new ScoreCounter();
private long score = 0; // 分数
private long bestScore; // 最高分数
private ScoreCounter() {
bestScore = -1;
try {
loadBestScore();
} catch (Exception e) {
e.printStackTrace();
}
}
public static ScoreCounter getInstance() {
return scoreCounter;
}
// 装载最高纪录
private void loadBestScore() throws Exception {
File file = new File(Constant.SCORE_FILE_PATH);
if (file.exists()) {
DataInputStream dis = new DataInputStream(new FileInputStream(file));
bestScore = dis.readLong();
dis.close();
}
}
public void saveScore() {
bestScore = Math.max(bestScore, getCurrentScore());
try {
File file = new File(Constant.SCORE_FILE_PATH);
DataOutputStream dos = new DataOutputStream(new FileOutputStream(file));
dos.writeLong(bestScore);
dos.close();
} catch (Exception e) {
e.printStackTrace();
}
}
public void score(Bird bird) {
if (!bird.isDead()) {
MusicUtil.playScore();
score += 1;
}
}
public long getBestScore() {
return bestScore;
}
public long getCurrentScore() {
return score;
}
public void reset() {
score = 0;
}
}
\ No newline at end of file
package com.bird.main; package com.kingyu.flappybird.game;
import java.awt.Graphics; import com.kingyu.flappybird.util.Constant;
import com.kingyu.flappybird.util.GameUtil;
import java.awt.*;
import java.awt.image.BufferedImage; import java.awt.image.BufferedImage;
import com.bird.util.Constant;
import com.bird.util.GameUtil;
/** /**
* 游戏启动界面类 * 游戏启动界面类
* *
* @author Kingyu * @author Kingyu
* *
*/ */
public class GameReady { public class WelcomeAnimation {
private final BufferedImage titleImg; private final BufferedImage titleImg;
private final BufferedImage noticeImg; private final BufferedImage noticeImg;
private int flash = 0; // 图像闪烁参数 private int flashCount = 0; // 图像闪烁参数
// 构造器中进行初始化,装载图像资源 public WelcomeAnimation() {
public GameReady() {
titleImg = GameUtil.loadBufferedImage(Constant.TITLE_IMG_PATH); titleImg = GameUtil.loadBufferedImage(Constant.TITLE_IMG_PATH);
noticeImg = GameUtil.loadBufferedImage(Constant.NOTICE_IMG_PATH); noticeImg = GameUtil.loadBufferedImage(Constant.NOTICE_IMG_PATH);
} }
public void draw(Graphics g) { public void draw(Graphics g) {
// 计算title图像的x、y坐标 int x = (Constant.FRAME_WIDTH - titleImg.getWidth()) >> 1;
int x = Constant.FRAME_WIDTH - titleImg.getWidth() >> 1; //x坐标为窗口中央 int y = Constant.FRAME_HEIGHT / 3;
int y = Constant.FRAME_HEIGHT / 3; //y坐标为游戏窗口的1/3处 g.drawImage(titleImg, x, y, null);
g.drawImage(titleImg, x, y, null); // 绘制
// 使notice的图像闪烁 // 使notice的图像闪烁
final int COUNT = 30; // 闪烁周期 final int CYCLE = 30; // 闪烁周期
if (flash++ > COUNT) if (flashCount++ > CYCLE)
GameUtil.drawImage(noticeImg, Constant.FRAME_WIDTH - noticeImg.getWidth() >> 1, Constant.FRAME_HEIGHT / 5 * 3, g); GameUtil.drawImage(noticeImg, Constant.FRAME_WIDTH - noticeImg.getWidth() >> 1, Constant.FRAME_HEIGHT / 5 * 3, g);
if (flash == COUNT * 2) // 重置闪烁参数 if (flashCount == CYCLE * 2)
flash = 0; flashCount = 0;
} }
} }
package com.bird.util; package com.kingyu.flappybird.util;
import java.awt.Color; import java.awt.Color;
import java.awt.Font; import java.awt.Font;
import com.bird.main.GameElementLayer;
import com.bird.main.Pipe;
/** /**
* 常量类 * 常量类
* *
...@@ -55,11 +52,14 @@ public class Constant { ...@@ -55,11 +52,14 @@ public class Constant {
public static final String SCORE_FILE_PATH = "resources/score"; // 分数文件路径 public static final String SCORE_FILE_PATH = "resources/score"; // 分数文件路径
// 游戏速度(水管及背景层的移动速度)
public static final int GAME_SPEED = 4;
// 游戏背景色 // 游戏背景色
public static final Color BG_COLOR = new Color(0x4bc4cf); public static final Color BG_COLOR = new Color(0x4bc4cf);
// 游戏刷新率 // 游戏刷新率
public static final int GAME_INTERVAL = 1000 / 60; public static final int GAME_INTERVAL = 1000 / 30;
// 标题栏高度 // 标题栏高度
public static final int TOP_BAR_HEIGHT = 20; public static final int TOP_BAR_HEIGHT = 20;
...@@ -77,7 +77,4 @@ public class Constant { ...@@ -77,7 +77,4 @@ public class Constant {
public static final Font CURRENT_SCORE_FONT = new Font("华文琥珀", Font.BOLD, 32);// 字体 public static final Font CURRENT_SCORE_FONT = new Font("华文琥珀", Font.BOLD, 32);// 字体
public static final Font SCORE_FONT = new Font("华文琥珀", Font.BOLD, 24);// 字体 public static final Font SCORE_FONT = new Font("华文琥珀", Font.BOLD, 24);// 字体
// 窗口可容纳的水管数量+2, 由窗口宽度、水管宽度、水管间距算得
public static final int FULL_PIPE = (Constant.FRAME_WIDTH
/ (Pipe.PIPE_HEAD_WIDTH + GameElementLayer.HORIZONTAL_INTERVAL) + 2) * 2;
} }
package com.bird.util; package com.kingyu.flappybird.util;
import java.awt.*; import java.awt.*;
import java.awt.image.BufferedImage; import java.awt.image.BufferedImage;
......
package com.bird.util; package com.kingyu.flappybird.util;
import java.io.FileInputStream; import java.io.FileInputStream;
import java.io.IOException; import java.io.IOException;
......
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment