微信经典飞机大战是微信5.0推出的一款手机游戏,该游戏在的画面并非美轮美奂,功能上也并非十分高大上,整个游戏的复杂程度可谓“简单得可怕”。但是,游戏支持用户与微信(QQ)好友进行分数对比,大大的满足玩家的攀比心理,因此,微信经典飞机大战在国内牵起浪潮——全名打飞机。
OGEngine官网:http://www.ogengine.com
源码下载:http://www.ogengine.com/download/resources.jsp
简单得说一下游戏的玩法:玩家的任务是控制一部飞机,消灭敌方飞机,尽可能的取得更高的分数。
游戏资源(各个图片以及声音效果)的加载就不再累述。
一、首先来一个开发结果图:
二、游戏场景类的设计:
游戏场景(GameScene)是游戏的主体部分,包括了游戏中所有的元素:
(1) 我方飞机
(2) 敌方飞机
(3) 子弹
(4) 炸要包
(5) 背景
(6) 声音
游戏场景控制的操作:
(1) 飞机、炸要包、子弹的生成
(2) 各种碰撞的检测
(3) 使用炸要
(4) 分数的实时统计
(5) 拖动屏幕,我方飞机的移动
(6) 背景的移动
三、创建各个类
1. 创建敌机类(Enemy)
敌方飞机分为三种(大中小),分别对应的血量(blood)、分值(score)、速度(speed)均不一样。同时我们定义了一个变量(IsPass)判断敌机飞过屏幕下端,以方便在游戏场景层遍历所有敌机。
我们利用PhysicsHandle处理敌机飞行问题,不同种类的敌方飞机以不同的速度向屏幕的下端匀速飞行。
请看下面代码:
-
public Enemy(float pX, float pY, StringpTextureRegionName,int type, VertexBufferObjectManagerpVertexBufferObjectManager) { super(pX,pY, pTextureRegionName, pVertexBufferObjectManager); this.type=type; this.IsPass=false; switch(type) { case 0 : this.blood= 25; this.Score=100; this.speed=300; break; case 1: this.blood= 100; this.Score=500; this.speed=200; break; case 2: this.blood= 300; this.Score=2000; this.speed=150; break; } mPhysicsHandler= new PhysicsHandler(this); this.registerUpdateHandler(mPhysicsHandler); //设置移动方向以及速度 mPhysicsHandler.setVelocityY(this.speed); } /** * 停止移动 */ public voidstopMove(){ this.unregisterUpdateHandler(mPhysicsHandler); }
2. 创建子弹类(Bullet)
子弹分为两种,一种是普通子弹(一倍伤害),一种是高效子弹(两倍伤害),定义两个变量记录子弹是否超出屏幕上端(IsPass)以及子弹是否已经击中飞机(IsHit)。
同样的,我们用PhysicsHandle处理子弹飞行问题,所有子弹以相同速度向屏幕的上端匀速飞行。
请看代码:
-
public Bullet(float pX, float pY, String pTextureRegionName,inttype, VertexBufferObjectManagerpVertexBufferObjectManager) { super(pX,pY, pTextureRegionName, pVertexBufferObjectManager); this.type=type; mPhysicsHandler= new PhysicsHandler(this); this.registerUpdateHandler(mPhysicsHandler); //设置移动方向以及速度 mPhysicsHandler.setVelocityY(-450); } /** * 停止移动 */ public voidstopMove(){ this.unregisterUpdateHandler(mPhysicsHandler); }
3. 创建炸要包类(BoomBulletPackage)
炸要包分为两种,一种是高效子弹炸要包,飞机“吃”后可以在一定时间内发出高效炮弹。另一种是炸要包,飞机“吃”后可以点击按钮使用炸要,一次将屏幕上所有敌机消灭掉,注意炸要上线是3。同样的,我们设定一个变量记录炸要包是否飞出屏幕
请看下面代码:
-
public BoomBulletPackage(float pX, float pY, StringpTextureRegionName,int type, VertexBufferObjectManagerpVertexBufferObjectManager) { super(pX,pY, pTextureRegionName, pVertexBufferObjectManager); this.setPass(false); this.type=type; mPhysicsHandler= new PhysicsHandler(this); this.registerUpdateHandler(mPhysicsHandler); //设置移动方向以及速度 mPhysicsHandler.setVelocityY(80); } /** * 停止移动 */ public voidstopMove(){ this.unregisterUpdateHandler(mPhysicsHandler); }
4. 创建游戏结束类(OverLayer)
当玩家的飞机碰撞到任意一架敌机,游戏失败,进入游戏结束层。游戏结束层包含了一个游戏结束提示图,以及重新开始游戏按钮。
设置onClick函数监听按钮是否被点击。
请看下面代码:
private ButtonSprite startBtn; public OverLayer(floatpWidth, float pHeight, Scene pScene) { super(pWidth,pHeight, pScene); pVertexBufferObjectManager= getVertexBufferObjectManager(); initView(); } private voidinitView() { gameOverTip= new AnimatedSprite(0, 150, Res.GAME_OVER, pVertexBufferObjectManager); gameOverTip.setCentrePositionX(this.getCentreX()); this.attachChild(gameOverTip); startBtn =new ButtonSprite(0, 250, Res.GAME_START, pVertexBufferObjectManager); startBtn.setCentrePositionX(this.getCentreX()); startBtn.setIgnoreTouch(false); startBtn.setOnClickListener(this); this.attachChild(startBtn); } @Override public voidonClick(ButtonSprite pButtonSprite, float pTouchAreaLocalX, floatpTouchAreaLocalY) { if(pButtonSprite== startBtn){ ((GameScene)getScene()).restartGame(); } }
四、游戏场景类的编写
1. 不急,首先添加各个变量的声明先。
//敌机列表 private List<Enemy> EnemyList; //子弹列表 privateList<Bullet> BulletList; //弹要包列表 privateList<BoomBulletPackage> packageList; //当前分数 private intcurrScore; //当前得分文本 private TexttCurrScore; //暂停按钮 private ButtonSpritepause; //使用炸要按钮 privateButtonSprite useBomb; //当前炸要数 private intcurrBombNum; //当前炸要数文本 private TexttCurrBombNum; //敌机生成注册表 privateTimerHandler mTimerHandler_enemy; //子弹生成注册表 privateTimerHandler mTimerHandler_bullet_creat; //子弹变换注册表 privateTimerHandler mTimerHandler_bullet_change; //炸要包生成注册表 privateTimerHandler mTimerHandler_package_creat; //敌机生成时间 private floatpTimerSeconds_enemy = 2.0f; //子弹生成时间 private floatpTimerSeconds_bullet_create = 0.2f; //强效子弹持续时间 private floatpTimerSeconds_bullet_change = 10.0f; //弹要包生成时间 private floatpTimerSeconds_package_creat = 20.0f; //按概率生成敌机 private intenemyChance; // 游戏结束层 private OverLayermOverLayer; //子弹种类 private intbullet_type=0; //我方飞机精灵 AnimatedSpritemyPlane; //第一背景精灵 AnimatedSpritegame_bg_1; //第二背景精灵 AnimatedSpritegame_bg_2; //屏幕滑动监听 SurfaceScrollDetectormScrollDetector; //背景移动速度 float bg_speed = 120;
2. 初始化GameSence类。
主要完成如下工作,①加载各个图片、文字、按钮精灵、游戏结束层至游戏场景层。②初始化各个列表。③注册各个handle。
@Override public voidonSceneCreate(SceneBundle bundle) { super.onSceneCreate(bundle); this.setIgnoreTouch(false); mScrollDetector= new SurfaceScrollDetector(this); this.setWidth(this.getCameraWidth()); this.setHeight(this.getCameraHeight()); this.EnemyList= new ArrayList<Enemy>(); this.BulletList=new ArrayList<Bullet>(); this.packageList=newArrayList<BoomBulletPackage>(); initTimerHander(); initView(); this.registerUpdateHandler(updateHandler); this.registerUpdateHandler(mTimerHandler_bullet_creat); this.registerUpdateHandler(mTimerHandler_package_creat); this.registerUpdateHandler(mTimerHandler_enemy); } private void initView() { game_bg_1= new AnimatedSprite(0, 0, Res.BG, getVertexBufferObjectManager()); game_bg_1.setRightPositionX(this.getRightX()); game_bg_1.setBottomPositionY(this.getBottomY()); this.attachChild(game_bg_1); game_bg_2= new AnimatedSprite(0, 0, Res.BG, getVertexBufferObjectManager()); game_bg_2.setRightPositionX(this.getRightX()); game_bg_2.setBottomPositionY(this.getTopY()+10); this.attachChild(game_bg_2); myPlane = newAnimatedSprite(200, 500, Res.PLANE, getVertexBufferObjectManager()); this.attachChild(myPlane); tCurrScore= new Text(0, 20, FontRes.getFont(ConstantUtil.FONT_SCORE_NUM), "0",9, getVertexBufferObjectManager()); tCurrScore.setCentrePositionX(this.getCentreX()); tCurrScore.setColor(0,0,0); currScore=0; this.attachChild(tCurrScore); tCurrBombNum= new Text(100,0,FontRes.getFont(ConstantUtil.FONT_SCORE_NUM), "0",9, getVertexBufferObjectManager()); tCurrBombNum.setBottomPositionY(this.getBottomY()-20); currBombNum=0; this.attachChild(tCurrBombNum); pause=newButtonSprite(0, 0, Res.STOP, getVertexBufferObjectManager()); pause.setTopPositionY(this.getTopY()+20); pause.setIgnoreTouch(false); pause.setOnClickListener(onClickListener); this.attachChild(pause); useBomb=newButtonSprite(0,0,Res.BT_BOMB,getVertexBufferObjectManager()); useBomb.setBottomPositionY(this.getBottomY()-20); useBomb.setIgnoreTouch(false); useBomb.setOnClickListener(onClickListener); this.attachChild(useBomb); mOverLayer= new OverLayer(this.getCameraWidth(), this.getCameraHeight(), this); MusicRes.playMusic(ConstantUtil.SOUND_GAME_MUSIC,true); }
3. 定义各个timerhandle。
由上可知,一共有4个TimeHandle等待我们定义。
代码如下:
private void initTimerHander() { // TODOAuto-generated method stub mTimerHandler_enemy= new TimerHandler(pTimerSeconds_enemy, true, newITimerCallback() { @Override publicvoid onTimePassed(TimerHandler pTimerHandler) { enemyChance=(int)(Math.random()*50); intpX = new Random().nextInt(480); if(0 <=enemyChance && enemyChance <20) createEnemy((int)(pX%(431)+25), 0); elseif (20 <=enemyChance && enemyChance <40) createEnemy((int)(pX%(411)+35), 1); else createEnemy((int)(pX%(396)+85), 2); } }); mTimerHandler_bullet_creat= new TimerHandler(pTimerSeconds_bullet_create, true, newITimerCallback() { @Override public voidonTimePassed(TimerHandler pTimerHandler) { createBullet(); } }); mTimerHandler_bullet_change= new TimerHandler(pTimerSeconds_bullet_change, false, newITimerCallback() { @Override publicvoid onTimePassed(TimerHandler pTimerHandler) { bullet_type=0; } }); mTimerHandler_package_creat= new TimerHandler(pTimerSeconds_package_creat, true, newITimerCallback() { @Override publicvoid onTimePassed(TimerHandler pTimerHandler) { creatPackage(); } }); }
4. 上述的注册时间表对应的函数:
包括四个函数:敌机按概率论生成、子弹生成、弹要包生成。
代码如下:
private void createEnemy(int pX,int type) { float pY =0; Enemyenemy = null; switch(type) { case0: enemy= new Enemy(pX, pY, Res.SENEMY, 0, getVertexBufferObjectManager()); enemy.setType(0); enemy.setPass(false); break; case1: enemy= new Enemy(pX, pY, Res.MENEMY, 1, getVertexBufferObjectManager()); enemy.setType(1); enemy.setPass(false); break; case2: enemy= new Enemy(pX, pY, Res.BENEMY, 2, getVertexBufferObjectManager()); enemy.setType(2); enemy.setPass(false); break; } this.attachChild(enemy); EnemyList.add(enemy); }
5. 实现滑动屏幕使得我方飞机位置移动。
利用onScroll方法使得屏幕能够监听滑动,并且得出滑动距离,最终计算出飞机最终位置。
代码如下:
@Override public voidonScroll(ScrollDetector pScollDetector, int pPointerID, floatpDistanceX, float pDistanceY) { myPlane.setPosition(myPlane.getX()+pDistanceX,myPlane.getY()+pDistanceY); //边界检测,如果滑动到屏幕外边则飞机移动到边界 if(myPlane.getRightX()>this.getWidth()) myPlane.setRightPositionX(this.getWidth()); if(myPlane.getLeftX()<0) myPlane.setLeftPositionX(0); if(myPlane.getTopY()<0) myPlane.setTopPositionY(0); if(myPlane.getBottomY()>this.getHeight()) myPlane.setBottomPositionY(this.getHeight()); }
6. 定义UpdateHandle,检测界面
该handle主要负责如下工作:背景移动、碰撞检测、越界检测。
(1) 背景移动
背景移动的原理是初始时两张相同的背景图片上下相连(屏幕显示下边图片),然后将两张图片以同样速度向下移动,如果检测到上边 的图片已经完全在屏幕中,那么将两张图片移回至初始位置。
在UpdateHandle直接调用bgmove(pSecondsElapsed);
下面是背景移动函数的定义:
private void bgmove(floatpSecondsElapsed) { game_bg_1.setPositionY(game_bg_1.getY()+bg_speed*pSecondsElapsed); game_bg_2.setPositionY(game_bg_2.getY()+bg_speed*pSecondsElapsed); if(game_bg_1.getTopY()>=this.getBottomY()){ game_bg_1.setBottomPositionY(this.getBottomY()); game_bg_2.setBottomPositionY(this.getTopY()+10); } }
(2)碰撞检测
碰撞一共分为三种:①我机与炸要包发生碰撞(吃掉炸要包)。②我机与敌机发生碰撞。③敌机与子弹发生碰撞。
① 遍历整个炸要包列表, 如果我机与炸要包列表中元素发生碰撞,则认为吃掉炸要包。注意下面代码并不完整,必须与②、③、④、⑤按顺序整合才 可以运行。
代码如下:
/** 弹要包碰撞检测 **/ for(inti=0;i<packageList.size();i++) { boombulletpackageSprite= packageList.get(i); if(myPlane.collidesWith(boombulletpackageSprite)){ switch(boombulletpackageSprite.getType()){ case0: SoundRes.playSound(ConstantUtil.SOUND_GET_DOUBLE_LASER); //高效弹要包 bullet_type=1; mTimerHandler_bullet_change.reset(); registerUpdateHandler(mTimerHandler_bullet_change); break; case1: SoundRes.playSound(ConstantUtil.SOUND_GET_BOMB); //炸要包,炸要的最高上限为3 if(currBombNum < 3) currBombNum++; tCurrBombNum.setText(currBombNum+""); break; } //删除吃掉的炸要包 detachChild(boombulletpackageSprite); packageList.remove(i); } }
② 我机与敌机发生碰撞
遍历整个敌机列表,如果有敌机与我机发生碰撞,那么游戏失败,跳转至游戏结束层。
/** 敌机与我机碰撞检测以及子弹与敌机的碰撞检测**/ for(inti=0;i<EnemyList.size();i++) { enemySprite=EnemyList.get(i); if(!enemySprite.IsPass()){ if(myPlane.collidesWith(enemySprite)){ PlaneBoom(myPlane,1); PlaneBoom(enemySprite,2); EnemyList.remove(i); enemySprite.PlaySound(); SoundRes.playSound(ConstantUtil.SOUND_GAME_OVER); unregisterUpdateHandler(mTimerHandler_enemy); unregisterUpdateHandler(mTimerHandler_package_creat); unregisterUpdateHandler(mTimerHandler_bullet_creat); if(!mOverLayer.hasParent()){ attachChild(mOverLayer); } i--;//下一架敌机已经移动到列表第i个位置 //已经碰撞的飞机不在做子弹碰撞检测与边界检测 continue; } }
③ 敌机与子弹发生碰撞
遍历敌机列表(上面已经进行),遍历子弹列表,一旦子弹与敌机发生碰撞,则消除子弹,并且降低敌机的生命值。如果飞机生 命值低于0则消除飞机。
代码如下:
for (int j=0;j<BulletList.size();j++) { bulletSprite= BulletList.get(j); if(!bulletSprite.IsPass()&& !bulletSprite.IsHit()) { if(bulletSprite.collidesWith(enemySprite)){ bulletSprite.setHit(true); detachChild(bulletSprite); bulletSprite.dispose(); BulletList.remove(j); j--;//下一颗子弹已经移动到第j个位置 switch(bulletSprite.getType()) { case0: enemySprite.setBlood(enemySprite.getBlood()-25); break; case1: enemySprite.setBlood(enemySprite.getBlood()-50); break; } if(enemySprite.getBlood()<=0){ updateScore(enemySprite.getScore()); PlaneBoom(enemySprite,2); enemySprite.PlaySound(); EnemyList.remove(i); i--;//下一架飞机已经移动到第i个位置 } }
(3)越界检测
越界是指飞行物已经飞离了屏幕显示区域,对这些元素我们要在器列表中删除其位置,并且在屏幕不再显示。分为两种情况:④子弹越 界⑤敌机越界
④ 子弹越界
//已经碰撞的子弹不在做边界检测 if(bulletSprite.IsHit()) continue; //没有碰撞的子弹做碰撞检测 if(bulletSprite.getBottomY()<0){ bulletSprite.setPass(true); detachChild(bulletSprite); bulletSprite.dispose(); BulletList.remove(j); j--;//下一颗子弹已经移动到第j个位置 }
⑤ 飞机越界
//已经爆炸的飞机不在做边界检测 if(enemySprite.getBlood()<=0) continue; //将移出了下边镜头的敌方飞机删除 if(enemySprite.getBottomY() > getCameraHeight()) { enemySprite.setPass(true); detachChild(enemySprite); enemySprite.dispose(); EnemyList.remove(i); i--;//下一架飞机已经移动到第i个位置 }
7. 爆炸处理
飞机爆炸时,必须播放爆炸效果,在爆炸结束后将飞机从场景类中删除或者隐藏。具体代码如下:
<p style="text-indent: 2em;">private void PlaneBoom (final AnimatedSprite Plane,finalint type) {</p> <p style="text-indent: 2em;"> Plane.animate(100,false, new IAnimationListener(){</p> @Override <p style="text-indent: 2em;"> publicvoid onAnimationFinished(AnimatedSprite arg0) {</p><p style="text-indent: 2em;"> //TODO Auto-generated method stub</p><p style="text-indent: 2em;"> if(type==2){</p> //敌机爆炸 detachChild(Plane); Plane.dispose(); Plane.setVisible(false); } <p style="text-indent: 2em;"> else{</p> //我机爆炸 Plane.setVisible(false); } } @Override <p style="text-indent: 2em;"> publicvoid onAnimationFrameChanged(AnimatedSprite arg0, int arg1,</p><p style="text-indent: 2em;"> intarg2) {</p><p style="text-indent: 2em;"> //TODO Auto-generated method stub</p> } @Override <p style="text-indent: 2em;"> publicvoid onAnimationLoopFinished(AnimatedSprite arg0, int arg1,</p><p style="text-indent: 2em;"> intarg2) {</p><p style="text-indent: 2em;"> //TODO Auto-generated method stub</p> } @Override <p style="text-indent: 2em;"> publicvoid onAnimationStarted(AnimatedSprite arg0, int arg1) {</p><p style="text-indent: 2em;"> //TODO Auto-generated method stub</p> }}); }
8. 重新开始游戏。
如果在结束层点击了重新开始按钮,则重新开启游戏。重新开启游戏时必须将分数等信息重置。
代码如下:
public void restartGame() { SoundRes.playSound(ConstantUtil.SOUND_COUNTDOWN); this.registerUpdateHandler(mTimerHandler_bullet_creat); this.registerUpdateHandler(mTimerHandler_package_creat); this.registerUpdateHandler(mTimerHandler_enemy); myPlane.setVisible(true);; this.detachChild(mOverLayer); this.currScore= 0; updateScore(currScore); this.currBombNum= 0; this.bullet_type=0; tCurrBombNum.setText(currBombNum+""); this.myPlane.stopAnimation(0); //清空原先敌机 for (Enemyenemy : EnemyList) { enemy.detachSelf(); enemy.dispose(); } this.EnemyList.clear(); // 播放音效 MusicRes.playMusic(ConstantUtil.SOUND_GAME_MUSIC,true); }
9. 使用炸要
当点击炸要按钮时候,必须将敌机列表中所有飞机删除,并且播放敌机爆炸效果。具体代码如下:
private void usebomb() { SoundRes.playSound(ConstantUtil.SOUND_USE_BOMB); EnemyenemySprite_boom = null; for(inti=0;i<EnemyList.size();i++) { enemySprite_boom= EnemyList.get(i); PlaneBoom(enemySprite_boom,2); updateScore(enemySprite_boom.getScore()); EnemyList.remove(i); i--;//下一架飞机已经移动到列表的第i个位置 } }
10. 退出游戏提示框以及使用炸要
当点击暂停按钮,以及点击手机回退键,屏幕将会弹出退出游戏提示框。当点击使用炸要按钮,调用usebomb函数,使用炸要
具体代码:
private OnClickListener onClickListener = newOnClickListener() { @Override publicvoid onClick(ButtonSprite pButtonSprite, float pTouchAreaLocalX, floatpTouchAreaLocalY) { if(pButtonSprite == pause) { //点击了帮助按钮 showDialog(); } elseif(pButtonSprite == useBomb) { //点击使用炸要按钮 if(currBombNum>0) { currBombNum--; tCurrBombNum.setText(currBombNum+""); usebomb(); } } else ; } }; //退出游戏提示框 private voidshowDialog() { getActivity().runOnUiThread(newRunnable() { @Override publicvoid run() { newAlertDialog.Builder(getActivity()) .setTitle("退出游戏") .setMessage("是否要退出游戏!") .setPositiveButton("确定", newDialogInterface.OnClickListener() { @Override publicvoid onClick(DialogInterface dialog, intwhich) { SoundRes.playSound(ConstantUtil.SOUND_BUTTON); getActivity().finish(); System.exit(0); } }).setNegativeButton("取消",null).show(); } }); } @Override public booleanonKeyDown(int keyCode, KeyEvent event) { if(keyCode ==KeyEvent.KEYCODE_BACK){ showDialog(); returntrue; } returnsuper.onKeyDown(keyCode, event); }
呼,讲解就到这里,详情请看代码~
http://www.ogengine.com/download/resources.jsp