这次也是最后一次我们来说一说world类,world类继承了JPanel实现了Runnable接口
数据成员如下
private static boolean flag;
private Thread th;
private boolean diepoint;
private static final int gamestart=0;
private static final int gamerecord=4;
private static final int gameready=1;
private static final int gameplaying=2;
private static final int gameover=3;
private static int gamestate;
public static final int gameW=448,gameH=685;
private int score;
private BufferedImage background;
private BufferedImage readyImg;
private BufferedImage gameoverImg;
private Num num;
private Start start;
private Bird bird;
private Ground ground;
private Pipe pipe1;
private Pipe pipe2;
private AudioPlay wing;
private AudioPlay point;
private AudioPlay die;
private AudioPlay hit;
private int rateindex=1;
private int sleeptime=15;
flag是线程运行的标志,当我们需要关闭线程的时候只要将flag置为false就行了。
gamestate是当前游戏状态标志,值有gamestart,gamerecord,gameready,gameplaying和gameover几类。
gameW和gameH是窗口宽高,score分数,background背景图片,readyImg准备界面图片,gameoverImg是gameover界面图片。diepoint是用来指示是否在gameover界面播放过die音效的指示器,rateindex是游戏难度级别默认为1,sleeptime是线程休眠时间,用来控制画面刷新速度。
public World() throws IOException {
wing=new AudioPlay("wav/wing.wav");
point=new AudioPlay("wav/point.wav");
die=new AudioPlay("wav/die.wav");
hit=new AudioPlay("wav/hit.wav");
readyImg = ImageIO.read(new File("img/readyImg.png"));
background = ImageIO.read(new File("img/bg.png"));
gameoverImg = ImageIO.read(new File("img/gameover.png"));
gamestate=gamestart;
start();
}
构造方法如上,没什么好说的。
private void start() throws IOException {
score=0;
diepoint=false;
flag=true;
num=new Num();
start=new Start();
bird=new Bird();
ground=new Ground();
pipe1=new Pipe(1);
pipe2=new Pipe(2);
}
start方法是游戏由gameover重新开始的入口,执行的也是一些初始化操作。
private void doInit() throws IOException {
addMouseListener(new MouseAdapter() {
public void mousePressed(MouseEvent e) {
switch(gamestate) {
case gamestart:
if(e.getX()>40&&e.getX()<195&&e.getY()>470&&e.getY()<560) {
gamestate=gameready;
}else if(e.getX()>240&&e.getX()<390&&e.getY()>470&&e.getY()<560) {
gamestate=gamerecord;
}else if(e.getX()>170&&e.getX()<260&&e.getY()>350&&e.getY()<410) {
if(rateindex==3) {
rateindex=0;
}
rateindex++;
bird.setrate(rateindex);
setrate(rateindex);
}
break;
case gamerecord:
gamestate=gamestart;
break;
case gameready:
gamestate=gameplaying;
break;
case gameplaying:
bird.up();
wing.play();
break;
case gameover:
if(e.getX()>130&&e.getX()<285&&e.getY()>330&&e.getY()<410) {
flag=false;
gamestate=gameready;
try {
start();
} catch (IOException e1) {
e1.printStackTrace();
}
bird.setrate(rateindex);
setrate(rateindex);
}
}
}
});
th=new Thread(this);
th.start();
}
doInit是第一次进入游戏的入口,用来添加鼠标监听并在鼠标监听里添加游戏流程控制代码,也是不用按钮实现一部分按钮功能的窍门所在。
游戏的流程全靠gamestate控制。
当gamestate==gamestart时也就是在开始界面,在rate图案范围内点击鼠标调用bird类和本类的setrate方法设置难度gamestate不变。在start图案范围内点击鼠标设置gamestate为gameready,也就是进入准备状态。在record图案范围内点击鼠标设置gamestate为gamerecord,也就是进入显示最高记录的状态。
当gamestate==gamerecord时也就是在record界面点击任意位置,置gamestate为gamestart,也就是恢复游戏的开始状态。
当gamestate==gameready时也就是在准备界面点击任意位置使gamestate=gameplaying表示游戏正在进行。
在游戏进行界面任意位置点击鼠标就是控制小鸟向上飞了。此外还会条用play方法播放小鸟挥动翅膀的音效。
当gamestate==gameover时,点击restart图案所在位置就会设gamestate=gameready重新回到准备界面。此外还要调用两个setrate方法使游戏难度保持一致。
doInit方法末尾是启动游戏的线程。
public void run() {
while(flag) {
long starttime = System.currentTimeMillis();
if(gamestate==gamestart) start.refresh();
if(gamestate==gameplaying) {
bird.step();
pipe1.step();
pipe2.step();
if(bird.pass(pipe1,pipe2)) {
point.play();
score++;
}
if(bird.hit(pipe1,pipe2,ground)) {
hit.play();
gamestate=gameover;
}
}
if(gamestate!=gameover&&gamestate!=gamestart&&gamestate!=gamerecord) bird.fly();
if(gamestate==gameover) {
BufferedReader fin=null;
try {
fin = new BufferedReader(new InputStreamReader(new FileInputStream("rec/record.txt")));
} catch (FileNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
String recordline=null;
try {
recordline = fin.readLine();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
int record=Integer.valueOf(recordline);
if(score>record) {
BufferedWriter fout=null;
try {
fout = new BufferedWriter(new FileWriter("rec/record.txt"));
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
String scoreline=String.valueOf(score);
try {
fout.write(scoreline);
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
try {
fout.flush();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
try {
fout.close();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
run方法里starttime、endtime和sleeptime是用来控制图案刷新速率的。代码的大部分是先判断当前的游戏状态是否是bird、pipe等对象需要刷新的状态,如果是则刷新对象的位置,然后调用repaint方法重绘整个画面。其中调用的bird类的pass方法判断是否得分,调用hit方法判断是否碰撞,如果碰撞设gamestate为gameover并判断最高记录和得分的大小,如果得分更大,则以覆盖的方式写入record.txt文件。
public void setrate(int n) {
if(n==1) {
sleeptime=15;
}else if(n==2) {
sleeptime=9;
}else if(n==3){
sleeptime=5;
}
}
设置难度的方法。
public void paint(Graphics g) {
if(gamestate!=gamestart) {
g.drawImage(background,0,0,null);
}
if(gamestate!=gamestart&&gamestate!=gameready&&gamestate!=gamerecord) {
pipe1.paint(g);
pipe2.paint(g);
if(score<=999) {
num.paint(g, score,0);
}else {
num.paint(g, -1,0);
}
}
if(gamestate!=gamestart) {
ground.paint(g);
}
if(gamestate!=gamestart&&gamestate!=gamerecord) {
bird.paint(g);
}
if(gamestate==gamestart) {
start.paint(g);
num.paint(g, rateindex, 2);
}
if(gamestate==gameready) {
g.drawImage(readyImg, 0, 0, null);
}
if(gamestate==gamerecord) {
BufferedReader fin=null;
try {
fin = new BufferedReader(new InputStreamReader(new FileInputStream("rec/record.txt")));
} catch (FileNotFoundException e1) {
e1.printStackTrace();
}
String recordline=null;
try {
recordline = fin.readLine();
} catch (IOException e) {
e.printStackTrace();
}
int record=Integer.valueOf(recordline);
if(record<=999) {
num.paint(g, record, 1);
}else {
num.paint(g, -1, 1);
}
try {
fin.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if(gamestate==gameover) {
if(!diepoint) {
die.play();
diepoint=true;
}
g.drawImage(gameoverImg, 0, 0, null);
}
}
paint方法跟run方法类似,也是先判断当前状态是否是对象需要重绘的状态,如果是则重绘图像。另外当gamestate==gamerecord的时候读取record.txt中的记录并绘制。
JFrame frame=new JFrame("Flappy Bird");
World world=new World();
frame.add(world);
frame.setBounds(20, 40, gameW, gameH);
frame.setResizable(false);
frame.setVisible(true);
frame.addWindowListener(new WindowAdapter() {
public void windowClosing(WindowEvent e) {
flag = false;
frame.dispose();
System.exit(0);
}
});
world.doInit();
}
最后main方法就是建立新的框架和面板设置相应参数,并进入游戏。
至此一个简单的flappybird游戏就编好了。
相关图片和音频资源链接如下:
点击打开链接
相关图片和音频资源链接如下: