关于上一次问的为什么用某一种方法播放音频的时候为什么会卡,可能原因如下
while ((len = audioInputStream.read(b)) > 0) {
sourceDataLine.write(b, 0, len);
}
使用如上方法的时候是以字节流读取文件的,每次读取一个字节然后进行相应操作,每一次读取一个字节的时候都是从外存读取没有缓冲区,导致卡顿。
这次介绍一下Bird这个类吧,数据成员如下:
public class Bird {
//小鸟初始坐标x值
private int x=132;
//小鸟初始坐标y值
private int y=275;
//在旋转绘图时旋转的角度
private double angle;
//用于储存小鸟的一组图片
private BufferedImage[] images;
//当前应该绘制的小鸟图片
private BufferedImage image;
//配合绘图调节动画更新频率使用的一个数字
private int index = 0;
//小鸟的模拟重力值
private double g=3;
//每次刷新图片之间小鸟已经飞行的时间
private final double t=0.25;
//小鸟的初始上飞的垂直速度
private double v0=24;
//小鸟当前的速度
private double speed;
//小鸟向上飞行的垂直距离
private double s;
//小鸟的宽度
private final int width=41;
//小鸟的高度
private final int height=37;
}
public Bird() throws IOException {images = new BufferedImage[8];for(int i=0; i<8; i++){images[i] = ImageIO.read(new File("img/"+i+".png"));}image = images[0];}
构造方法没什么说的
//设置难度的方法
public void setrate(int n) {
if(n==1) {
t=0.25;
}else if(n==2) {
t=0.245;
}else if(n==3){
t=0.22;
}
}
setrate方法是为了设置难度,在World类里调用此方法传入一个表示难度int参数(1最简单,3最困难),可能有人会问为什么从v0和g的设置上来看应该是更简单了,为什么我却说是更难了?答案是因为我在World类中同时把画面的刷新速度调快了。但是我也发现当我调快了刷新速度(也就是管道和地面的前进速度)而不改变t的值的话,在难度为3的游戏中,由于每刷新一次画面,小鸟就会下落0.25秒,导致小鸟下落过快,用户根本没有时间反应,游戏直接GAMEOVER,所以要调节一下小鸟的下落时间,但是由于我游戏水平太渣,自己编的游戏也过不了几个,所以所谓的不同难度的t的设置是我瞎填的,我也不清楚到底哪个更难(主要是对我来说都挺难的,在我的体验中难的游戏和更难的游戏没有差别)。
//计算下一次绘图的时候小鸟的位置
public void step() {
double v1 = speed;
double v = v1 - g*t;
speed = v;
s = v1*t - 0.5 * g * t * t;
y = y - (int)s;
angle = -Math.atan(s/8);
}
step方法是为了计算下一次绘图的时候小鸟的位置,当然小鸟的横向位置是不变的,计算的公式就是物理竖直上抛运动的公式,还要计算小鸟的仰角。
//刷新应该绘制的小鸟的图片
public void fly() {
index++;
image = images[(index/8)%images.length];
}
fly方法是用于刷新当前应该绘制的小鸟的图片的。在小鸟的8张图片中,每一张小鸟的位置和翅膀的位置稍有不同,不断变换小鸟的图片然后重绘可以实现小鸟扇翅膀的效果。但是这也是有缺点的,由于每一张图片小鸟的位置稍有改变(十几像素)又因为检测碰撞时使用的是两个固定的值,再因为要实现小鸟一跳一跳的效果要旋转绘图,导致实际游戏的时候有时候图像显示小鸟已经和管道发生了碰撞,但是系统却没有判定碰撞,而又有的时候图像上没有显示碰撞,系统却判定碰撞了。这是游戏的一个小瑕疵,但是由于写这个游戏的主要目的是为我这个java初始学者能获得一些学习的乐趣,这个小瑕疵我就没有深究。有兴趣的同学可以建立小鸟图片中小鸟的抖动像素个数和小鸟飞行的仰角和width和height之间的数学关系,将这两个数据变成动态变化的应该就可以解决这个问题(目测不简单)。如果你不想麻烦,也可以通过调节width和height这两个参数使此现象发生的频率降低。可能有人会想把小鸟图片裁一下只让小鸟的翅膀动不就行了,然而我在绘制游戏的准备界面的时候还是需要这个位置抖动的。
//将小鸟的上飞的垂直速度重置
public void up() {
speed = v0;
}
up方法只是为了当小鸟已经向上飞的同时,如果用户点击鼠标,给与小鸟一个新的向上飞的速度。
//小鸟的绘制方法
public void paint(Graphics g) {
Graphics2D g2 = (Graphics2D)g;
g2.rotate(angle, this.x, this.y);
int x = this.x-image.getWidth()/2;
int y = this.y-image.getHeight()/2;
g.drawImage(image, x, y, null);
g2.rotate(-angle, this.x, this.y);
}
重写paint方法,旋转绘制小鸟图片。
//判断小鸟是否通过了管道
public boolean pass(Pipe pipe1,Pipe pipe2) {
return pipe1.x+(pipe1.width/2)+width/2==x || pipe2.x+(pipe2.width/2)+width/2==x;
}
这个方法与得分计算有关,只要小鸟尾巴的坐标通过管道最右侧的坐标就算通过。pipe.x和pipe.y是管道缝隙中心坐标。
//判断小鸟是否和管道或者地面发生碰撞
public boolean hit(Pipe pipe1,Pipe pipe2,Ground ground) {
if(y+height/2 >= ground.y){
return true;
}
return hit(pipe1) || hit(pipe2);
}
此方法直接判断小鸟是否与地面碰撞,间接调用本类hit方法检测是否与地面碰撞,ground.y是地面的纵坐标。为什么有两个pipe类参数是因为这个游戏的界面里只会同时出现两个管道。
private boolean hit(Pipe pipe) {
if( x>pipe.x-pipe.width/2-width/2 && x<pipe.x+pipe.width/2+width/2){
if(y>pipe.y-pipe.gap/2+height/2 && y<pipe.y+pipe.gap/2-height/2 ){
return false;
}
return true;
}
return false;
}
此方法判断小鸟是否撞击管道,当小鸟在小鸟的头刚进入管道缝隙和小鸟的尾刚出管道的x轴范围内时小鸟的最上端和最下端不与管道碰撞就是没有发生碰撞,此外都碰撞。
Bird类的全部数据成员和成员方法就是这些了。下次讲一讲能使地面动态滚动的Ground类。
之前一直没有上游戏截图,现在一起发
![](https://i-blog.csdnimg.cn/blog_migrate/669e954e9eb066571a244d7d72aa1c9a.png)
开始界面,左上角显示难度级别,点击rate切换难度级别,点击开始图案进入准备界面,点击排行榜界面显示最高记录(这三个图案都是图片里有的,我就没另设JButton),这个界面里地面和小鸟都会动。
![](https://i-blog.csdnimg.cn/blog_migrate/44c033f309953d4a17aa29354b8a11fd.png)
最高记录界面(我知道有点简陋),不过简洁也是一种美么。在这个界面在此点击返回开始界面
![](https://i-blog.csdnimg.cn/blog_migrate/b581f9d094bcfe2a9779ee5b8b612b06.png)
准备界面,小鸟和地面是会动的。在此点击就开始游戏了。
![](https://i-blog.csdnimg.cn/blog_migrate/1a7549a8e2ec614821f10bb9917f4509.png)
游戏界面,左上角显示的是当前分数,此界面点击鼠标小鸟向上飞。
![](https://i-blog.csdnimg.cn/blog_migrate/07c947f3dcb33840fb4c4fb40ef7de88.png)
GAMEOVER界面,点击restart图案,返回准备界面。