Commit 5e5ba4bf authored by kingyu's avatar kingyu Committed by kingyuluk
Browse files

feat: 添加悬浮型水管

parent d7b64a56
......@@ -2,6 +2,15 @@
All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines.
## 1.1.0 (2020-07-11)
### Features
* 添加悬浮型水管 ([cc80ec9](https://github.com/kingyuluk/FlappyBird/commit/cc80ec9845194dc5326a8d70799b847b6821f751))
* 具备原版的游戏功能 ([264a7c3](https://github.com/kingyuluk/FlappyBird/commit/264a7c320c894851aa3e6c8c30ffcddf3ce1f78a))
## 1.0.0 (2020-07-10)
......
......@@ -17,8 +17,14 @@ com.bird.util 自定义的工具
# Version History
1.0.0 - July 11, 2020
1.0.0 - July 10, 2020
* 具备完整的游戏功能
1.1.0 - July 11, 2020
* 添加了悬浮型的水管
# Notes
文本编码格式为UTF-8,若注释出现乱码请修改编译器的文本编码格式
# Contact
* email: <kingyuluk@hotmail.com>
......@@ -44,7 +44,7 @@ public class Bird {
// 在构造器中对资源初始化
public Bird() {
timing = new GameTime(); // 计时器
timing = GameTime.getInstance(); // 计时器
// 读取小鸟图片资源
birdImgs = new BufferedImage[STATE_COUNT][IMG_COUNT];
......
......@@ -17,12 +17,12 @@ import com.bird.util.GameUtil;
public class GameElementLayer {
private List<Pipe> pipes; // 水管的容器
//构造器
// 构造器
public GameElementLayer() {
pipes = new ArrayList<>();
}
//绘制方法
// 绘制方法
public void draw(Graphics g, Bird bird) {
// 遍历水管容器,如果可见则绘制,不可见则归还
for (int i = 0; i < pipes.size(); i++) {
......@@ -46,7 +46,7 @@ public class GameElementLayer {
*/
public static final int VERTICAL_INTERVAL = Constant.FRAME_HEIGHT / 5;
public static final int HORIZONTAL_INTERVAL = Constant.FRAME_HEIGHT >> 2;
public static final int MIN_HEIGHT = (Constant.FRAME_HEIGHT) >> 3;
public static final int MIN_HEIGHT = Constant.FRAME_HEIGHT >> 3;
public static final int MAX_HEIGHT = ((Constant.FRAME_HEIGHT) >> 3) * 5;
private void pipeBornLogic(Bird bird) {
......@@ -58,37 +58,152 @@ public class GameElementLayer {
// 若容器为空,则添加一对水管
int topHeight = GameUtil.getRandomNumber(MIN_HEIGHT, MAX_HEIGHT + 1); // 随机生成水管高度
Pipe top = PipePool.get();
Pipe top = PipePool.get("Pipe");
top.setAttribute(Constant.FRAME_WIDTH, -Constant.TOP_PIPE_LENGTHENING,
topHeight + Constant.TOP_PIPE_LENGTHENING, Pipe.TYPE_TOP_NORMAL, true);
Pipe bottom = PipePool.get();
Pipe bottom = PipePool.get("Pipe");
bottom.setAttribute(Constant.FRAME_WIDTH, topHeight + VERTICAL_INTERVAL,
Constant.FRAME_HEIGHT - topHeight - VERTICAL_INTERVAL, Pipe.TYPE_BOTTOM_NORMAL, true);
pipes.add(top);
pipes.add(bottom);
} else {
// 判断最后一对水管是否完全进入游戏窗口
// 判断最后一对水管是否完全进入游戏窗口,若进入则添加水管
Pipe lastPipe = pipes.get(pipes.size() - 1); // 获得容器中最后一个水管
if (lastPipe.isInFrame()) {
int topHeight = GameUtil.getRandomNumber(MIN_HEIGHT, MAX_HEIGHT + 1); // 随机生成水管高度
int x = lastPipe.getX() + HORIZONTAL_INTERVAL; //新水管的x坐标 = 最后一对水管的x坐标 + 水管的间隔
if (lastPipe.isInFrame()) { // 根据游戏分数难度递增
if (GameTime.getInstance().TimeToScore() < Constant.HOVER_MOVING_SCORE) {
try {
if (GameUtil.isInProbability(2, 5)) { // 40%的概率生成悬浮的普通水管
addHoverPipe(lastPipe);
} else {
addNormalPipe(lastPipe);
}
} catch (Exception e) {
e.printStackTrace();
}
} else {
try {
if (GameUtil.isInProbability(1, 4)) { // 1/4的概率生成普通水管
if(GameUtil.isInProbability(1, 2)) // 生成普通水管和悬浮水管的概率
addNormalPipe(lastPipe);
else
addHoverPipe(lastPipe);
} else {
if(GameUtil.isInProbability(1, 3)) // 生成移动水管和移动悬浮水管的概率
addMovingHoverPipe(lastPipe);
else
addMovingNormalPipe(lastPipe);
}
} catch (Exception e) {
e.printStackTrace();
}
}
Pipe top = PipePool.get();
top.setAttribute(x, -Constant.TOP_PIPE_LENGTHENING, topHeight + Constant.TOP_PIPE_LENGTHENING,
Pipe.TYPE_TOP_NORMAL, true);
Pipe bottom = PipePool.get();
bottom.setAttribute(x, topHeight + VERTICAL_INTERVAL,
Constant.FRAME_HEIGHT - topHeight - VERTICAL_INTERVAL, Pipe.TYPE_BOTTOM_NORMAL, true);
pipes.add(top);
pipes.add(bottom);
}
}
}
/**
* 添加普通水管
*
* @param lastPipe
*/
private void addNormalPipe(Pipe lastPipe) {
int topHeight = GameUtil.getRandomNumber(MIN_HEIGHT, MAX_HEIGHT + 1); // 随机生成水管高度
int x = lastPipe.getX() + HORIZONTAL_INTERVAL; // 新水管的x坐标 = 最后一对水管的x坐标 + 水管的间隔
// 概率生成移动的水管
Pipe top = PipePool.get("Pipe");
top.setAttribute(x, -Constant.TOP_PIPE_LENGTHENING, topHeight + Constant.TOP_PIPE_LENGTHENING,
Pipe.TYPE_TOP_NORMAL, true);
Pipe bottom = PipePool.get("Pipe");
bottom.setAttribute(x, topHeight + VERTICAL_INTERVAL, Constant.FRAME_HEIGHT - topHeight - VERTICAL_INTERVAL,
Pipe.TYPE_BOTTOM_NORMAL, true);
pipes.add(top);
pipes.add(bottom);
}
/**
* 添加悬浮水管
*
* @param lastPipe
*/
private void addHoverPipe(Pipe lastPipe) {
// 随机生成水管高度,屏幕高度的[1/4,1/6]
int topHoverHeight = GameUtil.getRandomNumber(Constant.FRAME_HEIGHT / 6, Constant.FRAME_HEIGHT / 4);
int x = lastPipe.getX() + HORIZONTAL_INTERVAL; // 新水管的x坐标 = 最后一对水管的x坐标 + 水管的间隔
int y = GameUtil.getRandomNumber(Constant.FRAME_HEIGHT / 12, Constant.FRAME_HEIGHT / 6); // 随机水管的y坐标,窗口的[1/6,1/12]
int type = Pipe.TYPE_HOVER_NORMAL;
// 生成上部的悬浮水管
Pipe topHover = PipePool.get("Pipe");
topHover.setAttribute(x, y, topHoverHeight, type, true);
// 生成下部的悬浮水管
int bottomHoverHeight = Constant.FRAME_HEIGHT - 2 * y - topHoverHeight - VERTICAL_INTERVAL;
Pipe bottomHover = PipePool.get("Pipe");
bottomHover.setAttribute(x, y + topHoverHeight + VERTICAL_INTERVAL, bottomHoverHeight, type, true);
pipes.add(topHover);
pipes.add(bottomHover);
}
/**
* 添加移动的悬浮水管
*
* @param lastPipe
*/
private void addMovingHoverPipe(Pipe lastPipe) {
// 随机生成水管高度,屏幕高度的[1/4,1/6]
int topHoverHeight = GameUtil.getRandomNumber(Constant.FRAME_HEIGHT / 6, Constant.FRAME_HEIGHT / 4);
int x = lastPipe.getX() + HORIZONTAL_INTERVAL; // 新水管的x坐标 = 最后一对水管的x坐标 + 水管的间隔
int y = GameUtil.getRandomNumber(Constant.FRAME_HEIGHT / 12, Constant.FRAME_HEIGHT / 6); // 随机水管的y坐标,窗口的[1/6,1/12]
int type = Pipe.TYPE_HOVER_HARD;
// 生成上部的悬浮水管
Pipe topHover = PipePool.get("MovingPipe");
topHover.setAttribute(x, y, topHoverHeight, type, true);
// 生成下部的悬浮水管
int bottomHoverHeight = Constant.FRAME_HEIGHT - 2 * y - topHoverHeight - VERTICAL_INTERVAL;
Pipe bottomHover = PipePool.get("MovingPipe");
bottomHover.setAttribute(x, y + topHoverHeight + VERTICAL_INTERVAL, bottomHoverHeight, type, true);
pipes.add(topHover);
pipes.add(bottomHover);
}
/**
* 添加移动的普通水管
*
* @param lastPipe
*/
private void addMovingNormalPipe(Pipe lastPipe) {
int topHeight = GameUtil.getRandomNumber(MIN_HEIGHT, MAX_HEIGHT + 1); // 随机生成水管高度
int x = lastPipe.getX() + HORIZONTAL_INTERVAL; // 新水管的x坐标 = 最后一对水管的x坐标 + 水管的间隔
Pipe top = PipePool.get("MovingPipe");
top.setAttribute(x, -Constant.TOP_PIPE_LENGTHENING, topHeight + Constant.TOP_PIPE_LENGTHENING,
Pipe.TYPE_TOP_HARD, true);
Pipe bottom = PipePool.get("MovingPipe");
bottom.setAttribute(x, topHeight + VERTICAL_INTERVAL, Constant.FRAME_HEIGHT - topHeight - VERTICAL_INTERVAL,
Pipe.TYPE_BOTTOM_HARD, true);
pipes.add(top);
pipes.add(bottom);
}
/**
* 判断元素和小鸟是否发生碰撞,若发生碰撞返回true,否则返回false
*
......@@ -100,8 +215,8 @@ public class GameElementLayer {
if (bird.isDead()) {
return false;
}
//遍历水管容器
// 遍历水管容器
for (int i = 0; i < pipes.size(); i++) {
Pipe pipe = pipes.get(i);
// 判断碰撞矩形是否有交集
......
......@@ -10,16 +10,14 @@ import com.bird.util.Constant;
import com.bird.util.MusicUtil;
/**
* 游戏计时类
* 游戏计时类,单例类,方便调用
*
* @author Kingyu
*
*/
public class GameTime {
public static final int HOVER_BARRIER_TIME = 10; // 出现悬浮管道的时间
public static final int MOVING_BARRIER_TIME = 20; // 出现移动管道的时间
private static final GameTime GAME_TIME = new GameTime();
private int timeState; // 计时器的状态
public static final int STATE_READY = 0; // 计时就绪
public static final int STATE_START = 1; // 计时开始
......@@ -30,7 +28,7 @@ public class GameTime {
private long score = 0; // 分数
private long bestScore; // 最高分数
public GameTime() {
private GameTime() {
timeState = STATE_READY;
bestScore = -1;
......@@ -40,6 +38,10 @@ public class GameTime {
e.printStackTrace();
}
}
public static GameTime getInstance() {
return GAME_TIME;
}
// 装载最高纪录
private void loadBestTime() throws Exception {
......@@ -117,8 +119,8 @@ public class GameTime {
}
}
private static final int FIRST_SCORE_TIME = 6700; // 从游戏开始到通过第一根水管的所需时间
private static final int PER_SCORE_TIME = 2850; // 通过后续每一根水管的间隔的所需时间
private static final int FIRST_SCORE_TIME = 6600; // 从游戏开始到通过第一根水管的所需时间
private static final int PER_SCORE_TIME = 2880; // 通过后续每一根水管的间隔的所需时间
//将游戏时间转换为通过水管的数量
public long TimeToScore() {
......@@ -149,6 +151,7 @@ public class GameTime {
timeState = STATE_READY;
startTime = 0;
endTime = 0;
score = 0;
}
}
package com.bird.main;
import java.awt.Color;
import java.awt.Graphics;
import com.bird.util.Constant;
/**
* 移动水管类,继承Pipe类
*
* @author Kingyu
*
*/
public class MovingPipe extends Pipe {
private int dealtY; // 移动水管的坐标
public static final int MAX_DEALY = 50; // 最大移动距离
private int dir;
public static final int DIR_UP = 0;
public static final int DIR_DOWN = 1;
// 构造器
public MovingPipe() {
super();
}
/**
* 设置水管参数
*
* @param x
* @param y
* @param height
* @param type
* @param visible
*/
public void setAttribute(int x, int y, int height, int type, boolean visible) {
this.x = x;
this.y = y;
this.height = height;
this.type = type;
this.visible = visible;
setRectangle(this.x, this.y, this.height);
dealtY = 0;
dir = DIR_DOWN;
if (type == TYPE_TOP_HARD) {
dir = DIR_UP;
}
}
// 绘制方法
public void draw(Graphics g, Bird bird) {
switch (type) {
case TYPE_HOVER_HARD:
drawHoverHard(g);
break;
case TYPE_TOP_HARD:
drawTopHard(g);
break;
case TYPE_BOTTOM_HARD:
drawBottomHard(g);
break;
}
// 鸟死后水管停止移动
if (bird.isDead()) {
return;
}
pipeLogic();
// 绘制碰撞矩形
g.setColor(Color.black);
g.drawRect((int) pipeRect.getX(), (int) pipeRect.getY(), (int) pipeRect.getWidth(), (int) pipeRect.getHeight());
}
// 绘制移动的悬浮水管
private void drawHoverHard(Graphics g) {
// 拼接的个数
int count = (height - 2 * PIPE_HEAD_HEIGHT) / PIPE_HEIGHT + 1;
// 绘制水管的上顶部
g.drawImage(imgs[2], x - ((PIPE_HEAD_WIDTH - width) >> 1), y + dealtY, null);
// 绘制水管的主体
for (int i = 0; i < count; i++) {
g.drawImage(imgs[0], x, y + dealtY + i * PIPE_HEIGHT + PIPE_HEAD_HEIGHT, null);
}
// 绘制水管的下底部
int y = this.y + height - PIPE_HEAD_HEIGHT;
g.drawImage(imgs[1], x - ((PIPE_HEAD_WIDTH - width) >> 1), y + dealtY, null);
}
// 绘制从上往下的普通水管
private void drawTopHard(Graphics g) {
// 拼接的个数
int count = (height - PIPE_HEAD_HEIGHT) / PIPE_HEIGHT + 1; // 取整+1
// 绘制水管的主体
for (int i = 0; i < count; i++) {
g.drawImage(imgs[0], x, y + dealtY + i * PIPE_HEIGHT, null);
}
// 绘制水管的顶部
g.drawImage(imgs[1], x - ((PIPE_HEAD_WIDTH - width) >> 1),
height - Constant.TOP_PIPE_LENGTHENING - PIPE_HEAD_HEIGHT + dealtY, null);
}
// 绘制从下往上的普通水管
private void drawBottomHard(Graphics g) {
// 拼接的个数
int count = (height - PIPE_HEAD_HEIGHT) / PIPE_HEIGHT + 1;
// 绘制水管的主体
for (int i = 0; i < count; i++) {
g.drawImage(imgs[0], x, Constant.FRAME_HEIGHT - PIPE_HEIGHT - i * PIPE_HEIGHT + dealtY, null);
}
// 绘制水管的顶部
g.drawImage(imgs[2], x - ((PIPE_HEAD_WIDTH - width) >> 1), Constant.FRAME_HEIGHT - height + dealtY, null);
}
/**
* 可动水管的运动逻辑
*/
private void pipeLogic() {
x -= speed;
pipeRect.x -= speed;
if (x < -1 * PIPE_HEAD_WIDTH) {// 水管完全离开了窗口
visible = false;
}
//水管上下移动
if (dir == DIR_DOWN) {
dealtY++;
if (dealtY > MAX_DEALY) {
dir = DIR_UP;
}
} else {
dealtY--;
if (dealtY <= 0) {
dir = DIR_DOWN;
}
}
pipeRect.y = this.y + dealtY;
}
}
......@@ -14,7 +14,7 @@ import com.bird.util.GameUtil;
*
*/
public class Pipe {
private static BufferedImage[] imgs; // 水管的图片,static保证图片只加载一次
static BufferedImage[] imgs; // 水管的图片,static保证图片只加载一次
static {// 静态代码块,类加载的时候,初始化图片
final int PIPE_IMAGE_COUNT = 3;
imgs = new BufferedImage[PIPE_IMAGE_COUNT];
......@@ -29,12 +29,12 @@ public class Pipe {
public static final int PIPE_HEAD_WIDTH = imgs[1].getWidth();
public static final int PIPE_HEAD_HEIGHT = imgs[1].getHeight();
private int x, y; // 水管的坐标,相对于元素层
private int width, height; // 水管的宽,高
int x, y; // 水管的坐标,相对于元素层
int width, height; // 水管的宽,高
private boolean visible; // 水管可见状态,true为可见,false表示可归还至对象池
boolean visible; // 水管可见状态,true为可见,false表示可归还至对象池
// 水管的类型
private int type;
int type;
public static final int TYPE_TOP_NORMAL = 0;
public static final int TYPE_TOP_HARD = 1;
public static final int TYPE_BOTTOM_NORMAL = 2;
......@@ -45,9 +45,9 @@ public class Pipe {
// 水管的速度
public static final int MIN_SPEED = 1;
public static final int MAX_SPEED = 2;
private int speed;
int speed;
private Rectangle pipeRect; // 水管的碰撞矩形
Rectangle pipeRect; // 水管的碰撞矩形
// 构造器
public Pipe() {
......@@ -106,10 +106,13 @@ public class Pipe {
case TYPE_HOVER_NORMAL:
drawHoverNormal(g);
break;
}
// //绘制碰撞矩形
// g.setColor(Color.black);
// g.drawRect((int) pipeRect.getX(), (int) pipeRect.getY(), (int) pipeRect.getWidth(), (int) pipeRect.getHeight());
//鸟死后水管停止移动
if (bird.isDead()) {
return;
}
......@@ -156,9 +159,9 @@ public class Pipe {
int y = this.y + height - PIPE_HEAD_HEIGHT;
g.drawImage(imgs[1], x - ((PIPE_HEAD_WIDTH - width) >> 1), y, null);
}
/**
* 水管的运动逻辑
* 普通水管的运动逻辑
*/
private void pipeLogic() {
x -= speed;
......@@ -166,7 +169,6 @@ public class Pipe {
if (x < -1 * PIPE_HEAD_WIDTH) {// 水管完全离开了窗口
visible = false;
}
}
/**
......
......@@ -12,16 +12,21 @@ import com.bird.util.Constant;
*
*/
public class PipePool {
private static List<Pipe> pool = new ArrayList<>(); // 用于管理池中所有对象的容器
private static List<Pipe> pool = new ArrayList<Pipe>(); // 池中对象的容器
private static List<MovingPipe> movingPool = new ArrayList<MovingPipe>(); // 池中对象的容器
public static final int INIT_PIPE_COUNT = (Constant.FRAME_WIDTH
/ (Pipe.PIPE_HEAD_WIDTH + GameElementLayer.HORIZONTAL_INTERVAL) + 2) * 2; // 根据窗口宽度算得对象池中对象的初始个数
public static final int MAX_PIPE_COUNT = 50; // 对象池中对象的最大个数,自行定义
public static final int MAX_PIPE_COUNT = 30; // 对象池中对象的最大个数,自行定义
//初始化水管容器
// 初始化水管容器
static {
for (int i = 0; i < INIT_PIPE_COUNT; i++) {
pool.add(new Pipe());
}
for (int i = 0; i < INIT_PIPE_COUNT; i++) {
movingPool.add(new MovingPipe());
}
}
/**
......@@ -29,12 +34,21 @@ public class PipePool {
*
* @return
*/
public static Pipe get() {
int size = pool.size();
if (size > 0) {
return pool.remove(size - 1); // 移除并返回最后一个
public static Pipe get(String className) {
if ("Pipe".equals(className)) {
int size = pool.size();
if (size > 0) {
return pool.remove(size - 1); // 移除并返回最后一个
} else {
return new Pipe(); // 空对象池,返回一个新对象
}
} else {
return new Pipe(); // 空对象池,返回一个新对象
int size = movingPool.size();
if (size > 0) {
return movingPool.remove(size - 1); // 移除并返回最后一个
} else {
return new MovingPipe(); // 空对象池,返回一个新对象
}
}
}
......@@ -44,8 +58,15 @@ public class PipePool {
* @param pipe
*/
public static void giveBack(Pipe pipe) {
if (pool.size() < MAX_PIPE_COUNT) {
pool.add(pipe);
}
//判断类的类型
if(pipe.getClass() == Pipe.class) {
if (pool.size() < MAX_PIPE_COUNT) {
pool.add(pipe);
}
}else {
if (movingPool.size() < MAX_PIPE_COUNT) {
movingPool.add((MovingPipe)pipe);
}
}
}
}
......@@ -23,6 +23,8 @@ public class Constant {
// 图像资源路径
public static final String BG_IMG_PATH = "sources/img/background.png"; // 背景图片
public static final int HOVER_MOVING_SCORE = 4; //出现移动管道的分数
// 小鸟图片
public static final String[][] BIRDS_IMG_PATH = {
......
package com.bird.util;
import java.awt.Font;
import java.awt.FontMetrics;
import java.awt.image.BufferedImage;
import java.io.FileInputStream;
import java.io.IOException;
import java.awt.font.FontRenderContext;
import java.awt.geom.AffineTransform;
import javax.imageio.ImageIO;
import sun.font.FontDesignMetrics;
/**
* 工具类,游戏中用到的工具都在此类
*
......@@ -71,12 +70,17 @@ public class GameUtil {
* 获得指定字符串在指定字体的宽高
*/
public static int getStringWidth(Font font, String str) {
FontMetrics fm = FontDesignMetrics.getMetrics(font);
return fm.stringWidth(str);
AffineTransform affinetransform = new AffineTransform();
FontRenderContext frc = new FontRenderContext(affinetransform,true,true);
int textHeight = (int)(font.getStringBounds(str, frc).getWidth());
return textHeight;
}
public static int getStringHeight(Font font, String str) {
FontMetrics fm = FontDesignMetrics.getMetrics(font);
return fm.getHeight();
AffineTransform affinetransform = new AffineTransform();
FontRenderContext frc = new FontRenderContext(affinetransform,true,true);
int textHeight = (int)(font.getStringBounds(str, frc).getHeight());
return textHeight;
}
}
......@@ -18,6 +18,7 @@ public class MusicUtil {
private static AudioClip score;
// 装载音乐资源
@SuppressWarnings("deprecation")
public static void load() {
try {
fly = Applet.newAudioClip(new File("sources/wav/fly.wav").toURL());
......
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