一.飞机大战的整体思路:
飞机大战的主要使用的方法大纲:
1.概略:
Android的飞机大战用的是SurfaceView()来写,那么首先我们要继承SurfaceView这个类,然后我们还需要用多线程的来运行,那么还要实现Runnable以及SurfaceHolder.CallBack接口。实现多线程就必须要用到run()方法。
由于飞机大战的大部分工作是需要画图片到app里,那么我们又会用到Canvas这个类,这个类是画布类,所有的图片都需要画在Canvas的对象上。
然后,我们需要把图片转换成Bitmap格式。
那么,总结一下:飞机大战使用的最重要的一些类,接口和方法分别是
SurfaceView();Runnable;SurfaceHolder.CallBack;Canvas;Bitmap以及run()方法。
2.规划大概要写的类以及要实现的效果
(1)进入界面的类(Start):
构建一个Start类,这个界面上会有三张图片:背景图,logo图,开始游戏的按钮图。
那么,我们就要定义三张图片的X,Y坐标变量,还有Bitmap变量,这些变量都用private进行修饰,这是用了Java的封装,提高了代码的安全性。
然后,开始游戏按钮需要点击屏幕然后进入游戏界面,这里就用到了onTouchEvent()这个方法,那么我们就需要在SurfaceView的子类里复写这个方法,然后把start类的touchEvent()方法调用过去。
(2)进入游戏界面(需要多个类)
在进入到游戏界面时,我们需要建背景类(Background),我的飞机类(MyPlan),
boss飞机类(BossPlan),子弹类(MyBullet),爆炸类(Boom),
音效类(GameSoundPool) ,一共6个类。
首先,游戏界面的背景需要滚动,那么要在背景类(Background)将图片进行无限的循环。
在游戏界面,我们要移动自己的飞机和Boss飞机对战,那么自己飞机就需要可移动,那么同样要在MyPlan类里写一个touchEvent()方法。
boss飞机会进入疯狂模式,那么要在boss飞机类(BossPlan)写一个方法来判断它什么时候进入这个模式以及持续的时间。
子弹发射出去有它的速度和频率,那么在子弹类(MyBullet)里要对这些进行判断。
子弹击中飞机以及飞机撞到飞机会产生爆炸效果,我们需要在MyPlan和BossPlan类里分别判断飞机是否被击中或者是否被撞到。
子弹发射会有声音,以及爆炸时的爆炸声,这时我们要在音效类(GameSoundPool)里使用load()方法调用音效,然后再用play()方法播放。在使用音效前要先实例化SoundPool的对象,SoundPool是返回一个int类型的值,那么要先int一个整数来接受它,这些变量的声明都用private修饰。
3.分步进行详细操作
(1)如何绘制循环滚动背景图片
当第一张图片的y轴大于屏幕的高度时,说明第一张图片滚动结束了,用第二张图片的y轴减去图片高度,这时第一张图片下一次的y轴就在这。
滚动逻辑代码:
public void logic() {
y1 += 3;
y2 += 3;
if (y1 > MySurfaceView.height) {
y1 = y2 - bitmap1.getHeight(); //当y轴大于手机屏幕高度,下一张图的y轴减去图片高度,第一张图片下一次的y轴就在这
}
if (y2 > MySurfaceView.height) {
y2 = y1 - bitmap1.getHeight();
}
}
(2)如何绘制飞机
绘制飞机的图片用Canvas的对象调用draw()方法就行了,由于飞机需要根据手指移动,那么就要判断当手指的坐标在飞机内移动,飞机才会跟随移动,否则飞机不动。
触摸事件就要用toTouchEveny()方法来写,boss飞机有疯狂模式,要定义一个计数器来判断它进入疯狂模式的时间
触摸事件逻辑代码:
public void touchEvent(MotionEvent event) { //移动飞机
if (event.getAction() == MotionEvent.ACTION_MOVE) {
float ex = event.getX();//手指触摸的坐标
float ey = event.getY();
if (ex > x && ex < x + width && ey > y && ey < y + height) {
x = (int) ex - width / 2;//起始坐标(0,0)在左上角,移动的话会向反方向,把起始坐标调整到它中间
y = (int) ey - height / 2;
if (y < 0) {
y = 0;
}
if (y > MySurfaceView.height - height) {
y = MySurfaceView.height - height;
}
if (x < 0) {
x = 0;
}
if (x > MySurfaceView.width - width) {
x = MySurfaceView.width - width;
}
}
}
}
判断boss飞机是否进入疯狂模式代码:
private int count; //疯狂模式计数器
private int crazySpeed = 45; //疯狂模式速读
private int time = 200; //疯狂模式时间
private boolean isCrazy; //是否进入疯狂模式
public void logic(){
count++;
if(isCrazy){ //判断boss飞机是否进入疯狂模式
y = y+crazySpeed;
crazySpeed--;
if(y==0){
isCrazy = false;
crazySpeed = 50;
}
}else{
if(count%time==0){
isCrazy = true;
}
x = x+speed;
if(x < 0){
speed = -speed;
}
if(x > MySurfaceView.width - oneW){
speed = -speed;
}
}
}
(3)如何绘制子弹
子弹是连续发射的,那么不光要将它画出来,在MysurfaceView里面还要定义两个Vector数组,分别用来添加我的飞机和Boss飞机射出的子弹,将每一发子弹都add进去,然后将子弹画到界面里,而且要判断子弹是否飞出界面,一旦飞出界面,立马将它删除。
定义数组代码:
private Vector<MyBullet> bulletVector = new Vector<>(); //用Vector数组定义一个子弹类数组
private Vector<MyBullet> bossBulletVector = new Vector<>(); //用Vector数组定义一个boss子弹类数组
绘制boss子弹代码:
if (count % 50 == 0) {
MyBullet bossBullet = new MyBullet(BitmapFactory.decodeResource(getResources(), R.mipmap.bossbullet), bossPlane.getX(), bossPlane.getY() + bossPlane.getOneH(), 1);
MyBullet bossBullet1 = new MyBullet(BitmapFactory.decodeResource(getResources(), R.mipmap.bossbullet), bossPlane.getX() + bossPlane.getOneW(), bossPlane.getY() + bossPlane.getOneH(), 1);
bossBulletVector.add(bossBullet); //把boss子弹对象加到boss子弹数组里
bossBulletVector.add(bossBullet1);
}
for (int i = 0; i < bossBulletVector.size(); i++) {
bossBulletVector.elementAt(i).draw(canvas, paint);
myPlane.isCollision(bossBulletVector.elementAt(i));
}
绘制我的子弹代码:
if (count % 10 == 0) {
MyBullet myBullet = new MyBullet(BitmapFactory.decodeResource(getResources(), R.mipmap.mybullet), myPlane.getX(), myPlane.getY(), 0);
MyBullet myBullet1 = new MyBullet(BitmapFactory.decodeResource(getResources(), R.mipmap.mybullet), myPlane.getX() + myPlane.getWidth(), myPlane.getY(), 0);
bulletVector.add(myBullet);
bulletVector.add(myBullet1);
gameSoundPool.playSound(1);//添加射击音效
}
for (int i = 0; i < bulletVector.size(); i++) {
bulletVector.elementAt(i).draw(canvas, paint);
if (bossPlane.isCollision(bulletVector.elementAt(i))){ //判断我的子弹是否击中boss飞机,击中就画一张爆炸图
Boom boom = new Boom(BitmapFactory.decodeResource(getResources(),R.mipmap.boom),bossPlane.getX(),bossPlane.getY(),7);
boomVector.add(boom);
gameSoundPool.playSound(2); //在爆炸时添加爆炸音效
}
}
移除子弹代码:
//移除消失的boss子弹
for (int i = 0; i < bossBulletVector.size(); i++) {
if (bossBulletVector.elementAt(i).isDead()) {
bossBulletVector.remove(i);
}
}
//移除消失我的的子弹
for (int i = 0; i < bulletVector.size(); i++) {
if (bulletVector.elementAt(i).isDead()) {
bulletVector.remove(i);
}
}
判断子弹是否超出边界代码:
public void logic(){
switch (temp){
case 0:
y -= speed + 3; //我的子弹的坐标
if(,y < 0){ //对我的子弹是否超过屏幕边界判断
isDead = true;
}
break;
case 1:
y += Bspeed + 3; //boss子弹的坐标
if(y > MySurfaceView.height){ //对boss子弹是否超过屏幕边界判断
isDead = true;
}
break;
}
}
(4)如何判断碰撞(子弹与飞机碰撞,飞机与飞机碰撞)
我的飞机如果受到了碰撞,那么会进入一段时间的无敌状态,要先定义一个boolean值,然后进行判断,当碰撞到时,让飞机闪烁,不然就不闪烁,这里要定义一个计数器。
判断飞机是否碰撞也是先定义一个Boolean值,然后对坐标是否重合进行判断,
判断我的飞机是否是无敌时间代码:
private boolean noCollision; //判断是否进入无敌时间
private int noCollisionCount; //无敌时间计数
public void draw(Canvas canvas, Paint paint) {
if (hp <= 0){
MySurfaceView.GameStart = 3;
}
if (noCollision) {
noCollisionCount++;
if (noCollisionCount % 10 == 0) {
canvas.drawBitmap(bitmap, x, y, paint); //飞机闪烁
}
if (noCollisionCount > 100) { //无敌时间
noCollision = false;
noCollisionCount = 0;
}
} else {
//非无敌时间
canvas.drawBitmap(bitmap, x, y, paint);
}
for (int i = 0; i < hp; i++) {
canvas.drawBitmap(bitmapHp, i * bitmapHp.getWidth(), MySurfaceView.height - bitmapHp.getHeight(), paint);
}
}
判断我的飞机是否被碰撞代码:
/**
* 判断子弹是否击中飞机
*
* @param
* @return
*/
public boolean isCollision(MyBullet bullet) {
if (noCollision) {
return false;
} else {
if (bullet.getX() > x && bullet.getX() < x + width && bullet.getY() > y && bullet.getY() < y + height) {
noCollision = true;
if (hp > 0) {
hp--;
}
return true;
}
}
return false;
}
/**
* 判断我的飞机是否撞上boss飞机
*/
public boolean isCollision(BossPlane bossPlane) {
if (noCollision){
return false;
}else {
if(y > bossPlane.getY()+bossPlane.getOneH()||y+height < bossPlane.getY()||x+width < bossPlane.getX()||x >bossPlane.getX()+bossPlane.getOneW()){
}else {
noCollision = true;
if (hp>0){
hp--;
}
return true;
}
}
return false;
}
判断boss飞机是否被我的子弹击中代码:
public boolean isCollision(MyBullet bullet){
if (bullet.getX() > x&&bullet.getX()+bullet.getBitmap().getWidth() < x+oneW&&bullet.getY() > y&&bullet.getY() < y+oneH){
bossHp--;
bullet.setDead(true);
if (bossHp < 0){
MySurfaceView.GameStart = 2;
}
return true;
}
return false;
}
(5)如何绘制爆炸效果
首先,爆炸的图片不止一帧,要一帧一帧依次播放,先要定义两个整数 帧数和播放第几帧,
然后再用Vector数组实例化一个爆炸数组,把一帧一帧的图片add进去,然后在绘制我的子弹时,进行判断,如果我的子弹击中boss飞机,那么就将爆炸数组里的图片画一次,然后将结束爆炸的图删去。
显示爆炸图片的逻辑代码:
public void logic() {
if (currentFrame < totalFrame) {
currentFrame++;
} else {
isEnd = true;
}
}
定义爆炸数组:
private Vector<Boom>boomVector = new Vector<>(); //爆炸图的数组,显示图片的每一帧画面
判断我的子弹是否击中boss飞机:
if (bossPlane.isCollision(bulletVector.elementAt(i))){ //判断我的子弹是否击中boss飞机,击中就画一张爆炸图
Boom boom = new Boom(BitmapFactory.decodeResource(getResources(),R.mipmap.boom),bossPlane.getX(),bossPlane.getY(),7);
boomVector.add(boom);
}
(6)如何添加音效
这里音效有两种,一种是游戏内的子弹声和爆炸声,还有一种是背景音乐,游戏内声音用的是
SoundPool来写,背景音乐用MediaPlayer写。游戏内的音乐建一个类来写,
先用load方法添加,再用play方法播放,最后用这个类的对象来调用play方法,再将它插入需要播放的地方,
背景音乐直接在 主类的run方法里写。
游戏内音效代码:
import android.content.Context;
import android.media.AudioManager;
import android.media.SoundPool;
public class GameSoundPool {
private SoundPool soundPool;
private int s1;
private int s2;
private int s3;
public GameSoundPool(Context context){
this.soundPool = new SoundPool(2, AudioManager.STREAM_MUSIC,0);
s1 = soundPool.load(context,R.raw.shoot,1); //第二个参数是播放的音频资源名,第三个参数是播放优先级
s2 = soundPool.load(context,R.raw.explosion3,1);
s3 = soundPool.load(context,R.raw.bgm_zhuxuanlv,1);
}
public void playSound(int s){
switch (s){
case 1:
/**
* 第一个参数是 音效对象,第二和第三个参数是左,右声道音量,第四个是优先级,第五个是是否循环,第六个是播放倍速
*/
soundPool.play(s1,1,1,2,0,2f);//
break;
case 2:
soundPool.play(s2,1,1,1,0,1f);
break;
case 3:
soundPool.play(s3,1,1,3,0,2f);
}
}
}
将游戏内音效添加到需要的地方
射击声:在绘制子弹里添加
if (count % 10 == 0) {
MyBullet myBullet = new MyBullet(BitmapFactory.decodeResource(getResources(), R.mipmap.mybullet), myPlane.getX(), myPlane.getY(), 0);
MyBullet myBullet1 = new MyBullet(BitmapFactory.decodeResource(getResources(), R.mipmap.mybullet), myPlane.getX() + myPlane.getWidth(), myPlane.getY(), 0);
bulletVector.add(myBullet);
bulletVector.add(myBullet1);
gameSoundPool.playSound(1);//添加射击音效
}
爆炸声:在画出爆炸图时
for (int i = 0; i < bulletVector.size(); i++) {
bulletVector.elementAt(i).draw(canvas, paint);
if (bossPlane.isCollision(bulletVector.elementAt(i))){ //判断我的子弹是否击中boss飞机,击中就画一张爆炸图
Boom boom = new Boom(BitmapFactory.decodeResource(getResources(),R.mipmap.boom),bossPlane.getX(),bossPlane.getY(),7);
boomVector.add(boom);
gameSoundPool.playSound(2); //在爆炸时添加爆炸音效
}
}
背景音乐代码:
/**
* 添加背景音乐
*/
mediaPlayer = MediaPlayer.create(getContext(),R.raw.balu);
mediaPlayer.setOnPreparedListener(new MediaPlayer.OnPreparedListener() {
@Override
public void onPrepared(MediaPlayer mp) {
mediaPlayer.start();
mediaPlayer.setVolume(1,1);
}
});
(7)有哪些地方使用了封装,继承,多态,方法重载等
方法重载:这里判断我的飞机是被子弹击中,还是被boss飞机撞击用了方法重载
代码
/**
* 判断子弹是否击中飞机
*
* @param
* @return
*/
public boolean isCollision(MyBullet bullet) {
if (noCollision) {
return false;
} else {
if (bullet.getX() > x && bullet.getX() < x + width && bullet.getY() > y && bullet.getY() < y + height) {
noCollision = true;
if (hp > 0) {
hp--;
}
return true;
}
}
return false;
}
/**
* 判断我的飞机是否撞上boss飞机
*/
public boolean isCollision(BossPlane bossPlane) {
if (noCollision){
return false;
}else {
if(y > bossPlane.getY()+bossPlane.getOneH()||y+height < bossPlane.getY()||x+width < bossPlane.getX()||x >bossPlane.getX()+bossPlane.getOneW()){
}else {
noCollision = true;
if (hp>0){
hp--;
}
return true;
}
}
return false;
}
继承和接口:主类继承了SurfaceView,实现了Runnable和SurfaceHolder.Callback接口。
封装:所有的类里定义的变量都是用private修饰,都用到了封装,体现了Java的安全性
比如:我的飞机类里定义的变量都是private型。
代码:
private int x;
private int y;
private Bitmap bitmap;
private Bitmap bitmapHp;
private int height, width;
private boolean noCollision; //判断是否进入无敌时间
private int noCollisionCount; //无敌时间计数
private int hp = 3; //血量初始值为三
如果别的类想要调用封装的变量,可以用set,get’方法,比如:
get,set方法代码:
public int getX() {
return x;
}
public int getY() {
return y;
}
public int getWidth() {
return width;
}
}
二.对于飞机大战的收获与感悟
经过一个月的学习,对以前学习过的和没有学习过的Java知识进行了全面的复习和总结,在前几周,没有写飞机大战这个项目之前,我以为自己的基础学习的还可以,对于基本的概念以经可以流畅的应用了,但是我也知道我自己的缺点在于不能把这些知识点串联起来使用,我只时在每一小节的运用上比较熟练,但是当项目复杂以及涉及的内容和知识点过多时,就会产生一种力不从心的感觉,特别是让我自己去写一个项目的时候,常常是没有任何的头绪,不知道该从哪写起,程序报错了,也不会学着去看错误提示,不会自己试着改正。
通过这个项目的编写,我学会了自我学习的习惯,知道要自己先思考问题,自己先解决错误,到最后在和别人讨论,这样才能收获到更多的东西,才能吸取到精华,如果只是单单把别人的东西拿过来抄一遍,效果是没什么用。一定是自己思考过后。再讨论才有更多自己的想法,而且有时候我觉得我不行,我写不了,但是多想,多思考,然后再到网上学 ,这个问题说不定就能被我自己给解决了。这时,一种满足感油然而生,这种满足感会促使着我不断的去学习,然后不断地进步。
这一次写飞机大战这个项目,我有学会了使用AndroidStudio这个编译工具,说起AndroidStudio的安装,我还真费了好大的劲,最后重置了系统才把它搞定,但是有好多的报错都是我自己一个一个去网上百度,自己解决的,这也加深了我对这些错误的理解,以后遇到了可以更好的去解决它们。
这一次的学习中,不光学到了技术,最重要的是还学到了一些态度,对待学习的态度,一定要多学,多思考,多问。不光是学习,很多方面都是如此。
最后,希望自己在接下来的时间里,能把这个飞机大战的项目多写几遍,把各种知识的串联使用的熟练一点,然后再写一些小项目来提升自己的实力。