Commit d7b64a56 authored by kingyu's avatar kingyu Committed by kingyuluk
Browse files

feat: 具备原版的游戏功能

parent b9468532
package com.bird.app;
import com.bird.main.GameFrame;
/**
* 程序入口类
*
* @author Kingyu
*
*/
public class GameApp {
public static void main(String[] args) {
new GameFrame();
}
}
package com.bird.main;
import java.awt.Color;
import java.awt.Graphics;
import java.awt.Rectangle;
import java.awt.image.BufferedImage;
import com.bird.util.Constant;
import com.bird.util.GameUtil;
import com.bird.util.MusicUtil;
/**
* 小鸟类,小鸟的绘制与飞行逻辑都在此类
*
* @author Kingyu
*
*/
public class Bird {
public static final int IMG_COUNT = 8; // 图片数量
public static final int STATE_COUNT = 4; // 状态数
private BufferedImage[][] birdImgs; // 小鸟的图片数组对象
private int x, y; // 小鸟的坐标
int wingState; // 翅膀状态
// 图片资源
private BufferedImage image; // 实时的小鸟图片
private BufferedImage scoreImg; // 计分牌
private BufferedImage overImg; // 结束标志
private BufferedImage againImg; // 继续标志
// 小鸟的状态
private int state;
public static final int STATE_NORMAL = 0;
public static final int STATE_UP = 1;
public static final int STATE_DOWN = 2;
public static final int STATE_FALL = 3;
public static final int STATE_DEAD = 4;
private Rectangle birdRect; // 碰撞矩形
public static final int RECT_DESCALE = 2; // 补偿碰撞矩形宽高的参数
private GameTime timing; // 飞行时间
// 在构造器中对资源初始化
public Bird() {
timing = new GameTime(); // 计时器
// 读取小鸟图片资源
birdImgs = new BufferedImage[STATE_COUNT][IMG_COUNT];
for (int j = 0; j < STATE_COUNT; j++) {
for (int i = 0; i < IMG_COUNT; i++) {
birdImgs[j][i] = GameUtil.loadBUfferedImage(Constant.BIRDS_IMG_PATH[j][i]);
}
}
// 初始化小鸟的坐标
x = Constant.FRAME_WIDTH >> 2;
y = Constant.FRAME_HEIGHT >> 1;
int ImgWidth = birdImgs[state][0].getWidth();
int ImgHeight = birdImgs[state][0].getHeight();
// 初始化碰撞矩形
int rectX = x - ImgWidth / 2;
int rectY = y - ImgHeight / 2;
int rectWidth = ImgWidth;
int rectHeight = ImgHeight;
birdRect = new Rectangle(rectX + RECT_DESCALE, rectY + RECT_DESCALE * 2, rectWidth - RECT_DESCALE * 3,
rectHeight - RECT_DESCALE * 4); // 碰撞矩形的坐标与小鸟相同
}
// 绘制方法
public void draw(Graphics g) {
Fly();
int state_index = state > STATE_FALL ? STATE_FALL : state; // 图片资源索引
// 小鸟中心点计算
int halfImgWidth = birdImgs[state_index][0].getWidth() >> 1;
int halfImgHeight = birdImgs[state_index][0].getHeight() >> 1;
if (speed > 0)
image = birdImgs[STATE_UP][0];
g.drawImage(image, x - halfImgWidth, y - halfImgHeight, null); // x坐标于窗口1/4处,y坐标位窗口中心
if (state == STATE_DEAD) {
drawGameover(g);
} else if (state == STATE_FALL) {
} else {
drawTime(g);
}
timing.TimeToScore();
// 绘制矩形
// g.setColor(Color.black);
// g.drawRect((int) birdRect.getX(), (int) birdRect.getY(), (int) birdRect.getWidth(), (int) birdRect.getHeight());
}
public static final int SPEED_UP = 32; // 小鸟向上的速度
public static final double g = 9.8; // 重力加速度
public static final double T = 0.2; // 小鸟的下落函数执行的时间
private double h; // 小鸟y轴的位移量
private double speed = 0; // 小鸟的初速度
private boolean keyFlag = true; // 按键状态,true为已释放,使当按住按键时不会重复调用方法
public void keyPressed() {
keyFlag = false;
}
public void keyReleased() {
keyFlag = true;
}
public boolean keyIsReleased() {
return keyFlag;
}
// 小鸟的飞行逻辑
private void Fly() {
// 翅膀状态,实现小鸟振翅飞行
wingState++;
image = birdImgs[state > STATE_FALL ? STATE_FALL : state][wingState / 10 % IMG_COUNT];
switch (state) {
case STATE_NORMAL:
break;
case STATE_UP:
// 控制上边界
break;
case STATE_DOWN:
// 物理公式
speed -= g * T;
h = speed * T - g * T * T / 2;
y = (int) (y - h);
birdRect.y = (int) (birdRect.y - h);
// 控制边界,死亡条件
break;
case STATE_FALL:
// 鸟死亡,自由落体
speed -= g * T;
h = speed * T - g * T * T / 2;
y = (int) (y - h);
birdRect.y = (int) (birdRect.y - h);
// 控制坠落的边界
if (birdRect.y > Constant.FRAME_HEIGHT - Constant.GROUND_HEIGHT - (birdImgs[state][0].getHeight() >> 1)) {
y = Constant.FRAME_HEIGHT - Constant.GROUND_HEIGHT - (birdImgs[state][0].getHeight() >> 1);
birdRect.y = Constant.FRAME_HEIGHT - Constant.GROUND_HEIGHT - (birdImgs[state][0].getHeight() >> 1);
GameFrame.setGameState(GameFrame.STATE_OVER);
BirdDead();
}
break;
case STATE_DEAD:
break;
}
// 控制上方边界
if (birdRect.y < -1 * Constant.TOP_PIPE_LENGTHENING / 2) {
birdRect.y = -1 * Constant.TOP_PIPE_LENGTHENING / 2;
y = -1 * Constant.TOP_PIPE_LENGTHENING / 2;
}
if (birdRect.y > Constant.FRAME_HEIGHT - Constant.GROUND_HEIGHT - (image.getHeight() >> 1)) {
BirdFall();
}
}
// 鸟的状态
public void BirdUp() {
if (keyIsReleased()) { // 如果按键已释放
if (state == STATE_DEAD || state == STATE_FALL || state == STATE_UP)
return;
state = STATE_UP;
speed = SPEED_UP;
MusicUtil.playFly(); // 播放音效
wingState = 0; // 重置翅膀状态
keyPressed();
}
}
public void BirdDown() {
if (state == STATE_DEAD || state == STATE_FALL)
return;
state = STATE_DOWN;
}
public void BirdFall() {
state = STATE_FALL;
MusicUtil.playCrash(); // 播放音效
// 结束计时
timing.endTiming();
}
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);
}
}
public boolean isDead() {
return state == STATE_FALL || state == STATE_DEAD;
}
// 开始计时的方法
public void startTiming() {
if (timing.isReadyTiming())
timing.startTiming();
}
// 绘制实时分数
private void drawTime(Graphics g) {
g.setColor(Color.white);
g.setFont(Constant.TIME_FONT);
// String str = Long.toString(timing.getTimeInSeconds()); 时间分数
String str = Long.toString(timing.TimeToScore());
int x = Constant.FRAME_WIDTH - GameUtil.getStringWidth(Constant.TIME_FONT, str) >> 1;
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(timing.TimeToScore());
x -= GameUtil.getStringWidth(Constant.SCORE_FONT, str) >> 1;
y += GameUtil.getStringHeight(Constant.SCORE_FONT, str);
g.drawString(str, x, y);
// 绘制最高分数
if (timing.getBestScore() > 0) {
str = Long.toString(timing.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) {
x = Constant.FRAME_WIDTH - againImg.getWidth() >> 1;
y = Constant.FRAME_HEIGHT / 5 * 3;
g.drawImage(againImg, x, y, null);
if (flash == COUNT * 2)
flash = 0;
}
}
// 重置小鸟
public void reset() {
state = STATE_NORMAL; // 小鸟状态
y = Constant.FRAME_HEIGHT >> 1; // 小鸟坐标
speed = 0; // 小鸟速度
int ImgHeight = birdImgs[state][0].getHeight();
birdRect.y = y - ImgHeight / 2 + RECT_DESCALE * 2; // 小鸟碰撞矩形坐标
timing.reset(); // 计时器
flash = 0;
}
// 获取小鸟的碰撞矩形
public Rectangle getBirdRect() {
return birdRect;
}
}
\ No newline at end of file
package com.bird.main;
import java.awt.Graphics;
import java.awt.image.BufferedImage;
import com.bird.util.Constant;
/**
* 云朵类,在屏幕的上半部飘动
*
* @author Kingyu
*
*/
public class Cloud {
private int dir; // 方向
public static final int DIR_NONE = 0;
public static final int DIR_LEFT = 1;
public static final int DIR_RIGHT = 2;
private int speed; // 速度
private int x, y; // 坐标
private BufferedImage img;
// 云朵图片缩放的比例 1.0~2.0
private double scale;
private int scaleImageWidth;
private int scaleImageHeight;
// 构造器
public Cloud(BufferedImage img, int dir, int x, int y) {
super();
this.img = img;
this.dir = dir;
this.x = x;
this.y = y;
this.speed = 2; //云朵的速度
scale = 1 + Math.random(); // Math.random()返回0.0~1.0的随机值
// 缩放云朵图片
scaleImageWidth = (int) (scale * img.getWidth());
scaleImageHeight = (int) (scale * img.getWidth());
}
// 绘制方法
public void draw(Graphics g, Bird bird) {
int speed = this.speed;
if(dir == DIR_NONE) //云彩不动
speed = 0;
x = (dir == DIR_LEFT) ? x - speed : x + speed; // 方向逻辑
g.drawImage(img, x, y, scaleImageWidth, scaleImageHeight, null);
}
/**
* 判断云朵是否飞出屏幕
*
* @return 飞出则返回true,否则返回false
*/
public boolean isOutFrame() {
boolean result = false;
if (dir == DIR_LEFT) {
if (x < -1 * scaleImageWidth) {
return true;
}
} else if (dir == DIR_RIGHT) {
if (x > Constant.FRAME_WIDTH) {
return true;
}
}
return result;
}
// 改变方向
public void setDir(int dir) {
this.dir = dir;
}
}
package com.bird.main;
import java.awt.Graphics;
import java.awt.image.BufferedImage;
import com.bird.util.Constant;
import com.bird.util.GameUtil;
/**
* 游戏背景类,绘制游戏背景的内容都在此类
*
* @author Kingyu
*
*/
public class GameBackground {
private static BufferedImage BackgroundImg;// 背景图片
private int speed; // 背景层的速度
private int layerX; // 背景层的坐标
// 在构造器中初始化
public GameBackground() {
this.speed = 1;
this.layerX = 0;
}
static { //读取背景图片
BackgroundImg = GameUtil.loadBUfferedImage(Constant.BG_IMG_PATH);
}
public static final int BG_IMAGE_HEIGHT = BackgroundImg.getHeight(); //图片的高度
// 绘制方法
public void draw(Graphics g, Bird bird) {
// 绘制背景色
g.setColor(Constant.BG_COLOR);
g.fillRect(0, 0, Constant.FRAME_WIDTH, Constant.FRAME_HEIGHT);
// 获得背景图片的尺寸
int imgWidth = BackgroundImg.getWidth();
int imgHeight = BackgroundImg.getHeight();
int count = Constant.FRAME_WIDTH / imgWidth + 2; // 根据窗口宽度得到图片的绘制次数
for (int i = 0; i < count; i++) {
g.drawImage(BackgroundImg, imgWidth * i - layerX, Constant.FRAME_HEIGHT - imgHeight, null);
}
if(bird.isDead()) { //小鸟死亡则不再绘制
return;
}
moveLogic();
}
// 背景层的运动逻辑
private void moveLogic() {
layerX += speed;
if (layerX > BackgroundImg.getWidth())
layerX = 0;
}
}
\ No newline at end of file
package com.bird.main;
import java.awt.Graphics;
import java.util.ArrayList;
import java.util.List;
import com.bird.util.Constant;
import com.bird.util.GameUtil;
/**
* 游戏中各种元素层的类
*
* @author Kingyu
*
*/
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++) {
Pipe pipe = pipes.get(i);
if (pipe.isVisible()) {
pipe.draw(g, bird);
} else {
Pipe remove = pipes.remove(i);
PipePool.giveBack(remove);
i--;
}
}
// 碰撞检测
isCollideBird(bird);
pipeBornLogic(bird);
}
/**
* 添加水管的逻辑: 当容器中添加的最后一个元素完全显示到屏幕后,添加下一对; 水管成对地相对地出现,空隙高度为窗口高度的1/6;
* 每对水管的间隔距离为屏幕高度的1/4; 水管的高度的取值范围为窗口的[1/8~5/8]
*/
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 MAX_HEIGHT = ((Constant.FRAME_HEIGHT) >> 3) * 5;
private void pipeBornLogic(Bird bird) {
if (bird.isDead()) {
// 鸟死后不再添加水管
return;
}
if (pipes.size() == 0) {
// 若容器为空,则添加一对水管
int topHeight = GameUtil.getRandomNumber(MIN_HEIGHT, MAX_HEIGHT + 1); // 随机生成水管高度
Pipe top = PipePool.get();
top.setAttribute(Constant.FRAME_WIDTH, -Constant.TOP_PIPE_LENGTHENING,
topHeight + Constant.TOP_PIPE_LENGTHENING, Pipe.TYPE_TOP_NORMAL, true);
Pipe bottom = PipePool.get();
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坐标 + 水管的间隔
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);
}
}
}
/**
* 判断元素和小鸟是否发生碰撞,若发生碰撞返回true,否则返回false
*
* @param bird
* @return
*/
public boolean isCollideBird(Bird bird) {
// 若鸟已死则不再判断
if (bird.isDead()) {
return false;
}
//遍历水管容器
for (int i = 0; i < pipes.size(); i++) {
Pipe pipe = pipes.get(i);
// 判断碰撞矩形是否有交集
if (pipe.getPipeRect().intersects(bird.getBirdRect())) {
bird.BirdFall();
return true;
}
}
return false;
}
// 重置元素层
public void reset() {
for (int i = 0; i < pipes.size(); i++) {
Pipe pipe = pipes.get(i);
PipePool.giveBack(pipe);
}
pipes.clear();
}
}
\ No newline at end of file
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