微信经典飞机大战是微信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