Unverified Commit eb44ec32 authored by kingyuluk's avatar kingyuluk
Browse files

refactor: 优化代码

parent 5676508a
No preview for this file type
......@@ -12,8 +12,12 @@
[RL FlappyBird](https://github.com/kingyuluk/RL-FlappyBird)
基于本项目集成了Amazon的Deep Java Library (DJL),可以使用强化学习(DQN)训练Flappy Bird
## How to play
程序入口:app/GameApp.java
* 通过 ```java -jar FlappyBird.jar```直接运行
* 运行源码中的 ```App:main``` 方法
游戏使用空格键操作。
......@@ -43,11 +47,11 @@
* 图片与音效资源皆来源于网络,仅供学习交流
## Package Contents
* com.kingyu.flappybird.app 游戏的入口
* com.kingyu.flappybird.app 游戏主体
* com.kingyu.flappybird.game 游戏的主体
* com.kingyu.flappybird.component 游戏的组件
* com.kingyu.flappybird.util 自定义的工具
* com.kingyu.flappybird.util 工具
## [Change Log](https://github.com/kingyuluk/FlappyBird/blob/master/CHANGELOG.md)
......
package com.kingyu.flappybird.app;
import com.kingyu.flappybird.game.Game;
/**
* 程序入口
* 游戏入口
*
* @author Kingyu
*
*/
public class GameApp {
public class App {
public static void main(String[] args) {
new Game();
}
......
package com.kingyu.flappybird.game;
package com.kingyu.flappybird.app;
import com.kingyu.flappybird.component.GameElementLayer;
import com.kingyu.flappybird.component.Bird;
import com.kingyu.flappybird.component.GameBackground;
import com.kingyu.flappybird.component.GameForeground;
import com.kingyu.flappybird.component.WelcomeAnimation;
import static com.kingyu.flappybird.util.Constant.FRAME_HEIGHT;
import static com.kingyu.flappybird.util.Constant.FRAME_WIDTH;
import static com.kingyu.flappybird.util.Constant.FRAME_X;
import static com.kingyu.flappybird.util.Constant.FRAME_Y;
import static com.kingyu.flappybird.util.Constant.GAME_INTERVAL;
import static com.kingyu.flappybird.util.Constant.FPS;
import static com.kingyu.flappybird.util.Constant.GAME_TITLE;
import java.awt.Frame;
import java.awt.Graphics;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.awt.event.*;
import java.awt.image.BufferedImage;
/**
* 主窗口类,游戏窗口绘制的相关内容
* 游戏主体,管理游戏的组件和窗口绘制
*
* @author Kingyu
*/
public class Game extends Frame implements Runnable {
public class Game extends Frame {
private static final long serialVersionUID = 1L; // 保持版本的兼容性
private static int gameState; // 游戏状态
......@@ -118,7 +121,16 @@ public class Game extends Frame implements Runnable {
setGameState(GAME_READY);
// 启动用于刷新窗口的线程
new Thread(this).start();
new Thread(() ->{
while (true) {
repaint(); // 通过调用repaint(),让JVM调用update()
try {
Thread.sleep(FPS);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}).start();
}
// 项目中存在两个线程:系统线程,自定义的线程:调用repaint()。
......@@ -134,35 +146,17 @@ public class Game extends Frame implements Runnable {
public void update(Graphics g) {
Graphics bufG = bufImg.getGraphics(); // 获得图片画笔
// 使用图片画笔将需要绘制的内容绘制到图片
background.draw(bufG, bird); // 背景层
foreground.draw(bufG, bird); // 前景层
// 鸟
if (gameState == GAME_READY) { // 游戏未开始
welcomeAnimation.draw(bufG);
} else { // 游戏结束
gameElement.draw(bufG, bird); // 游戏元素层
}
bird.draw(bufG); // 鸟
bird.draw(bufG);
g.drawImage(bufImg, 0, 0, null); // 一次性将图片绘制到屏幕上
}
@SuppressWarnings("InfiniteLoopStatement")
@Override
public void run() {
while (true) {
repaint(); // 通过调用repaint(),让JVM调用update()
try {
Thread.sleep(GAME_INTERVAL);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public static int getGameState(){ return gameState;};
public static void setGameState(int gameState) {
Game.gameState = gameState;
}
......
package com.kingyu.flappybird.game;
package com.kingyu.flappybird.component;
import java.awt.Color;
import java.awt.Graphics;
import java.awt.Rectangle;
import java.awt.image.BufferedImage;
import com.kingyu.flappybird.app.Game;
import com.kingyu.flappybird.util.Constant;
import com.kingyu.flappybird.util.GameUtil;
import com.kingyu.flappybird.util.MusicUtil;
/**
* 小鸟类,小鸟的绘制与飞行逻辑都在此类
* 小鸟类,实现小鸟的绘制与飞行逻辑
*
* @author Kingyu
*/
......@@ -20,8 +21,7 @@ public class Bird {
private final BufferedImage[][] birdImages; // 小鸟的图片数组对象
private final int x;
private int y; // 小鸟的坐标
int wingState; // 翅膀状态
private int wingState; // 翅膀状态
// 图片资源
private BufferedImage image; // 实时的小鸟图片
......@@ -34,7 +34,7 @@ public class Bird {
public static final int BIRD_DEAD_FALL = 3;
public static final int BIRD_DEAD = 4;
private final Rectangle birdRect; // 碰撞矩形
private final Rectangle birdCollisionRect; // 碰撞矩形
public static final int RECT_DESCALE = 2; // 补偿碰撞矩形宽高的参数
private final ScoreCounter counter; // 计分器
......@@ -67,7 +67,7 @@ public class Bird {
// 初始化碰撞矩形
int rectX = x - BIRD_WIDTH / 2;
int rectY = y - BIRD_HEIGHT / 2;
birdRect = new Rectangle(rectX + RECT_DESCALE, rectY + RECT_DESCALE * 2, BIRD_WIDTH - RECT_DESCALE * 3,
birdCollisionRect = new Rectangle(rectX + RECT_DESCALE, rectY + RECT_DESCALE * 2, BIRD_WIDTH - RECT_DESCALE * 3,
BIRD_WIDTH - RECT_DESCALE * 4); // 碰撞矩形的坐标与小鸟相同
}
......@@ -86,7 +86,7 @@ public class Bird {
gameOverAnimation.draw(g, this);
else if (state != BIRD_DEAD_FALL)
drawScore(g);
// 绘制矩形
// 绘制碰撞矩形
// g.setColor(Color.black);
// g.drawRect((int) birdRect.getX(), (int) birdRect.getY(), (int) birdRect.getWidth(), (int) birdRect.getHeight());
}
......@@ -94,76 +94,46 @@ public class Bird {
public static final int ACC_FLAP = 14; // players speed on flapping
public static final double ACC_Y = 2; // players downward acceleration
public static final int MAX_VEL_Y = 15; // max vel along Y, max descend speed
private int velocity = 0; // bird's velocity along Y, default same as playerFlapped
private boolean keyFlag = true; // 按键状态,true为已释放,使当按住按键时不会重复调用方法
public void keyPressed() {
keyFlag = false;
}
public void keyReleased() {
keyFlag = true;
}
public boolean keyIsReleased() {
return keyFlag;
}
private final int BOTTOM_BOUNDARY = Constant.FRAME_HEIGHT - GameBackground.GROUND_HEIGHT - (BIRD_HEIGHT / 2);
int TOP_BOUNDARY = 30;
// 小鸟的飞行逻辑
private void movement() {
// 翅膀状态,实现小鸟振翅飞行
wingState++;
image = birdImages[Math.min(state, BIRD_DEAD_FALL)][wingState / 10 % IMG_COUNT];
switch (state) {
case BIRD_FALL:
// 自由落体
if (velocity < MAX_VEL_Y)
velocity -= ACC_Y;
y = Math.min((y - velocity), BOTTOM_BOUNDARY);
birdRect.y = birdRect.y - velocity;
if (birdRect.y > BOTTOM_BOUNDARY) {
if (state == BIRD_FALL || state == BIRD_DEAD_FALL) {
freeFall();
if (birdCollisionRect.y > BOTTOM_BOUNDARY) {
if (state == BIRD_FALL) {
MusicUtil.playCrash();
die();
}
break;
case BIRD_DEAD_FALL:
// 自由落体
if (velocity < MAX_VEL_Y)
velocity -= ACC_Y;
y = Math.min((y - velocity), BOTTOM_BOUNDARY);
birdRect.y = birdRect.y - velocity;
if (birdRect.y > BOTTOM_BOUNDARY) {
die();
}
break;
case BIRD_DEAD:
Game.setGameState(Game.STATE_OVER);
break;
case BIRD_NORMAL:
case BIRD_UP:
break;
die();
}
}
}
private void freeFall() {
if (velocity < MAX_VEL_Y)
velocity -= ACC_Y;
y = Math.min((y - velocity), BOTTOM_BOUNDARY);
birdCollisionRect.y = birdCollisionRect.y - velocity;
}
private void die() {
counter.saveScore();
state = BIRD_DEAD;
Game.setGameState(Game.STATE_OVER);
}
// 小鸟振翅
public void birdFlap() {
if (keyIsReleased()) { // 如果按键已释放
if (state == BIRD_DEAD || state == BIRD_UP || state == BIRD_DEAD_FALL)
return; // 小鸟死亡或坠落时返回
if (keyIsReleased()) {
if (isDead())
return;
MusicUtil.playFly(); // 播放音效
state = BIRD_UP;
if (birdRect.y > TOP_BOUNDARY) {
if (birdCollisionRect.y > Constant.TOP_BAR_HEIGHT) {
velocity = ACC_FLAP; // 每次振翅将速度改为上升速度
wingState = 0; // 重置翅膀状态
}
......@@ -173,28 +143,16 @@ public class Bird {
// 小鸟下降
public void birdFall() {
if (state == BIRD_DEAD || state == BIRD_DEAD_FALL)
return; // 小鸟死亡或坠落时返回
if (isDead())
return;
state = BIRD_FALL;
}
public void die(){
counter.saveScore();
state = BIRD_DEAD;
Game.setGameState(Game.STATE_OVER);
}
// 小鸟坠落(已死)
public void deadBirdFall() {
state = BIRD_DEAD_FALL;
MusicUtil.playCrash(); // 播放音效
velocity = 0; // 速度置0,防止小鸟继续上升与水管重叠
// 死后画面静止片刻
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
// 判断小鸟是否死亡
......@@ -218,11 +176,25 @@ public class Bird {
velocity = 0; // 小鸟速度
int ImgHeight = birdImages[state][0].getHeight();
birdRect.y = y - ImgHeight / 2 + RECT_DESCALE * 2; // 小鸟碰撞矩形坐标
birdCollisionRect.y = y - ImgHeight / 2 + RECT_DESCALE * 2; // 小鸟碰撞矩形坐标
counter.reset(); // 重置计分器
}
private boolean keyFlag = true; // 按键状态,true为已释放,使当按住按键时不会重复调用方法
public void keyPressed() {
keyFlag = false;
}
public void keyReleased() {
keyFlag = true;
}
public boolean keyIsReleased() {
return keyFlag;
}
public long getCurrentScore() {
return counter.getCurrentScore();
}
......@@ -236,7 +208,7 @@ public class Bird {
}
// 获取小鸟的碰撞矩形
public Rectangle getBirdRect() {
return birdRect;
public Rectangle getBirdCollisionRect() {
return birdCollisionRect;
}
}
package com.kingyu.flappybird.game;
package com.kingyu.flappybird.component;
import com.kingyu.flappybird.util.Constant;
......@@ -6,7 +6,7 @@ import java.awt.Graphics;
import java.awt.image.BufferedImage;
/**
* 云朵类
* 云朵类,实现云朵的绘制和运动逻辑
*
* @author Kingyu
*/
......
package com.kingyu.flappybird.game;
package com.kingyu.flappybird.component;
import java.awt.Graphics;
import java.awt.image.BufferedImage;
......@@ -7,7 +7,7 @@ import com.kingyu.flappybird.util.Constant;
import com.kingyu.flappybird.util.GameUtil;
/**
* 游戏背景类,绘制游戏背景的内容都在此类
* 游戏背景类,实现游戏背景的绘制
*
* @author Kingyu
*
......@@ -51,11 +51,11 @@ public class GameBackground {
if(bird.isDead()) { //小鸟死亡则不再绘制
return;
}
moveLogic();
movement();
}
// 背景层的运动逻辑
private void moveLogic() {
private void movement() {
layerX += speed;
if (layerX > BackgroundImg.getWidth())
layerX = 0;
......
package com.kingyu.flappybird.game;
package com.kingyu.flappybird.component;
import java.awt.Graphics;
import java.util.ArrayList;
......@@ -8,7 +8,7 @@ import com.kingyu.flappybird.util.Constant;
import com.kingyu.flappybird.util.GameUtil;
/**
* 游戏中各种元素层的类
* 游戏元素层,目前管理水管的生成逻辑并绘制容器中的水管
*
* @author Kingyu
*/
......@@ -73,7 +73,8 @@ public class GameElementLayer {
int currentDistance = lastPipe.getX() - bird.getBirdX() + Bird.BIRD_WIDTH / 2; // 小鸟和最后一根水管的距离
final int SCORE_DISTANCE = Pipe.PIPE_WIDTH * 2 + HORIZONTAL_INTERVAL; // 小于得分距离则得分
if (lastPipe.isInFrame()) {
if (pipes.size() >= PipePool.FULL_PIPE - 2) {
if (pipes.size() >= PipePool.FULL_PIPE - 2
&& currentDistance <= SCORE_DISTANCE + Pipe.PIPE_WIDTH * 3 / 2) {
ScoreCounter.getInstance().score(bird);
}
try {
......@@ -211,7 +212,7 @@ public class GameElementLayer {
// 遍历水管容器
for (Pipe pipe : pipes) {
// 判断碰撞矩形是否有交集
if (pipe.getPipeRect().intersects(bird.getBirdRect())) {
if (pipe.getPipeRect().intersects(bird.getBirdCollisionRect())) {
bird.deadBirdFall();
return;
}
......
package com.kingyu.flappybird.game;
package com.kingyu.flappybird.component;
import java.awt.Graphics;
import java.awt.image.BufferedImage;
......@@ -9,21 +9,18 @@ import com.kingyu.flappybird.util.Constant;
import com.kingyu.flappybird.util.GameUtil;
/**
* 前景类, 游戏中的遮挡层 包含多朵云
* 前景层,目前管理云朵的生成逻辑并绘制容器中的云朵
*
* @author Kingyu
*/
public class GameForeground {
private final List<Cloud> clouds; // 云朵的容器
private final BufferedImage[] cloudImages; // 图片资源
private long time; // 控制云的逻辑运算周期
public static final int CLOUD_INTERVAL = 100; //云朵刷新的逻辑运算的周期
public GameForeground() {
clouds = new ArrayList<>(); //云朵的容器
// 读入图片资源
cloudImages = new BufferedImage[Constant.CLOUD_IMAGE_COUNT];
for (int i = 0; i < Constant.CLOUD_IMAGE_COUNT; i++) {
......@@ -34,14 +31,14 @@ public class GameForeground {
// 绘制方法
public void draw(Graphics g, Bird bird) {
cloudLogic();
cloudBornLogic();
for (Cloud cloud : clouds) {
cloud.draw(g, bird);
}
}
// 云朵的控制
private void cloudLogic() {
private void cloudBornLogic() {
// 100ms运算一次
if (System.currentTimeMillis() - time > CLOUD_INTERVAL) {
time = System.currentTimeMillis(); // 重置time
......@@ -74,7 +71,6 @@ public class GameForeground {
i--;
}
}
}
}
}
package com.kingyu.flappybird.game;
package com.kingyu.flappybird.component;
import com.kingyu.flappybird.util.Constant;
import com.kingyu.flappybird.util.GameUtil;
......
package com.kingyu.flappybird.component;
import java.awt.Graphics;
import com.kingyu.flappybird.util.Constant;
/**
* 移动水管类,继承Pipe类
*
* @author Kingyu
*/
public class MovingPipe extends Pipe {
private int dealtY; // 移动水管的坐标
public static final int MAX_DELTA = 50; // 最大移动距离
private int direction;
public static final int DIR_UP = 0;
public static final int DIR_DOWN = 1;
// 构造器
public MovingPipe() {
super();
}
/**
* 设置水管参数
*
* @param x:x坐标
* @param y:y坐标
* @param height:水管高度
* @param type:水管类型
* @param visible:水管可见性
*/
public void setAttribute(int x, int y, int height, int type, boolean visible) {
super.setAttribute(x, y, height, type, visible);
dealtY = 0;
direction = DIR_DOWN;
if (type == TYPE_TOP_HARD) {
direction = 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;
}
movement();
// 绘制碰撞矩形
// 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 movement() {
//x坐标的运动逻辑与普通水管相同
x -= speed;
pipeRect.x -= speed;
if (x < -1 * PIPE_HEAD_WIDTH) {// 水管完全离开了窗口
visible = false;
}
//水管上下移动的逻辑
if (direction == DIR_DOWN) {
dealtY++;
if (dealtY > MAX_DELTA) {
direction = DIR_UP;
}
} else {
dealtY--;
if (dealtY <= 0) {
direction = DIR_DOWN;
}
}
pipeRect.y = this.y + dealtY;
}
}
package com.kingyu.flappybird.game;
package com.kingyu.flappybird.component;
import java.awt.*;
import java.awt.image.BufferedImage;
......@@ -7,7 +7,7 @@ import com.kingyu.flappybird.util.Constant;
import com.kingyu.flappybird.util.GameUtil;
/**
* 水管类
* 水管类,实现水管的绘制与运动逻辑
*
* @author Kingyu
*/
......@@ -108,7 +108,7 @@ public class Pipe {
if (bird.isDead()) {
return;
}
pipeLogic();
movement();
}
// 绘制从上往下的普通水管
......@@ -155,7 +155,7 @@ public class Pipe {
/**
* 普通水管的运动逻辑
*/
private void pipeLogic() {
private void movement() {
x -= speed;
pipeRect.x -= speed;
if (x < -1 * PIPE_HEAD_WIDTH) {// 水管完全离开了窗口
......
package com.kingyu.flappybird.game;
package com.kingyu.flappybird.component;
import java.util.ArrayList;
import java.util.List;
......@@ -6,6 +6,7 @@ import java.util.List;
import com.kingyu.flappybird.util.Constant;
/**
* 水管对象池
* 为了避免反复地创建和销毁对象,使用对象池来提前创建好一些对象,使用时从对象池中获得,使用完后归还
*
* @author Kingyu
......@@ -14,11 +15,10 @@ import com.kingyu.flappybird.util.Constant;
public class PipePool {
private static final List<Pipe> pool = new ArrayList<>(); // 池中对象的容器
private static final List<MovingPipe> movingPool = new ArrayList<>(); // 池中对象的容器
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 {
for (int i = 0; i < PipePool.FULL_PIPE; i++) {
pool.add(new Pipe());
......
package com.kingyu.flappybird.game;
package com.kingyu.flappybird.component;
import java.io.DataInputStream;
import java.io.DataOutputStream;
......@@ -10,13 +10,20 @@ 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 static class ScoreCounterHolder {
private static final ScoreCounter scoreCounter = new ScoreCounter();
}
public static ScoreCounter getInstance() {
return ScoreCounterHolder.scoreCounter;
}
private long score = 0; // 分数
private long bestScore; // 最高分数
......@@ -30,10 +37,6 @@ public class ScoreCounter {
}
}
public static ScoreCounter getInstance() {
return scoreCounter;
}
// 装载最高纪录
private void loadBestScore() throws Exception {
File file = new File(Constant.SCORE_FILE_PATH);
......
package com.kingyu.flappybird.game;
package com.kingyu.flappybird.component;
import com.kingyu.flappybird.util.Constant;
import com.kingyu.flappybird.util.GameUtil;
......@@ -7,7 +7,7 @@ import java.awt.*;
import java.awt.image.BufferedImage;
/**
* 游戏启动界面
* 游戏启动界面
*
* @author Kingyu
*
......
package com.kingyu.flappybird.game;
import java.awt.Graphics;
import com.kingyu.flappybird.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:x坐标
* @param y: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坐标的运动逻辑与普通水管相同
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;
}
}
......@@ -59,7 +59,7 @@ public class Constant {
public static final Color BG_COLOR = new Color(0x4bc4cf);
// 游戏刷新率
public static final int GAME_INTERVAL = 1000 / 30;
public static final int FPS = 1000 / 30;
// 标题栏高度
public static final int TOP_BAR_HEIGHT = 20;
......
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