日本搞逼视频_黄色一级片免费在线观看_色99久久_性明星video另类hd_欧美77_综合在线视频

國(guó)內(nèi)最全I(xiàn)T社區(qū)平臺(tái) 聯(lián)系我們 | 收藏本站
阿里云優(yōu)惠2
您當(dāng)前位置:首頁(yè) > php開源 > 綜合技術(shù) > 寫個(gè)簡(jiǎn)單的飛機(jī)游戲玩玩

寫個(gè)簡(jiǎn)單的飛機(jī)游戲玩玩

來源:程序員人生   發(fā)布時(shí)間:2015-01-16 08:57:10 閱讀次數(shù):2616次

寫個(gè)簡(jiǎn)單的飛機(jī)游戲玩玩

 

侯亮

 

 

1      概述

        前些天看了《Android游戲編程之從零開始》1書中1個(gè)簡(jiǎn)單飛機(jī)游戲的實(shí)現(xiàn)代碼,1時(shí)手癢,也寫了1個(gè)練練手。雖然我的本職工作其實(shí)不是寫游戲,不進(jìn)程序員或多或少都有編寫游戲的情結(jié),那就寫吧,Just for fun!游戲的代碼部份我基本上全部重寫了,至于游戲的圖片資源嘛,我老實(shí)不客氣地全拿來復(fù)用了1下,呵呵,希望李華明先生不要見怪啊。

 

        在Android平臺(tái)上,SurfaceView就足以應(yīng)付所有簡(jiǎn)單游戲了。固然我說的是簡(jiǎn)單游戲,如果要寫復(fù)雜游戲,恐怕還得使用各種游戲引擎,不過游戲引擎不是本文關(guān)心的重點(diǎn),對(duì)我寫的簡(jiǎn)單游戲來講,用SurfaceView就能夠了。

 

        飛機(jī)游戲的1個(gè)小特點(diǎn)是,畫面總是在變動(dòng)的,這固然是句空話,不過卻能引出1個(gè)關(guān)鍵的設(shè)計(jì)核心,那就是“幀流”。幀流的最典型例子大概就是電影啦,我們知道,只要膠片按每秒鐘24幀(或更高)的速率播放,人眼就會(huì)誤以為看到了連續(xù)的運(yùn)動(dòng)畫面。飛機(jī)游戲中的運(yùn)動(dòng)畫面大體也是這樣顯現(xiàn)的,因此游戲設(shè)計(jì)者必須設(shè)計(jì)出1條平滑的幀流,并且?guī)室銐蚩臁?/p>

 

        從技術(shù)上說,我們可以在1個(gè)線程中,構(gòu)造1個(gè)不斷繪制“幀”的while循環(huán),并在每次畫好幀后,調(diào)用Thread.sleep()睡眠適合的時(shí)間,這樣就能夠?qū)崿F(xiàn)1個(gè)相對(duì)平滑的幀流了。

 

        另外一方面,游戲的邏輯也是可以融入到幀流里的,也就是說,每次畫好幀后,我們可以調(diào)用1個(gè)類似execLogic()的函數(shù)來履行游戲邏輯,從而(間接)產(chǎn)生新的幀。而游戲邏輯又可以劃分成多個(gè)子邏輯,比如關(guān)卡背景邏輯、敵人行動(dòng)邏輯、玩家飛機(jī)邏輯、子彈行動(dòng)邏輯、碰撞邏輯等等,這個(gè)我們后文再細(xì)說。

 

        大概說起來就是這么多了,現(xiàn)在我們逐一來看游戲設(shè)計(jì)中的細(xì)節(jié)。

 

2      平滑的幀流

        我們先寫個(gè)全屏顯示的Activity:

public class HLPlaneGameActivity extends Activity { @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); this.getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, WindowManager.LayoutParams.FLAG_FULLSCREEN); requestWindowFeature(Window.FEATURE_NO_TITLE); setContentView(new PlaneGameView(this)); } }

這個(gè)Activity的主視圖是PlaneGameView類,它繼承于SurfaceView。

public class PlaneGameView extends SurfaceView implements Callback, Runnable

 

         1旦surface創(chuàng)建成功,我們就啟動(dòng)1個(gè)線程,這個(gè)線程負(fù)責(zé)運(yùn)作幀流。

@Override public void surfaceCreated(SurfaceHolder holder) { GlobalInfo.screenW = getWidth(); GlobalInfo.screenH = getHeight(); mSurfaceWorking = true; mGameManager = new GameManager(getContext()); mGameThread = new Thread(this); mGameThread.start(); }

         mGameThread線程的核心run()函數(shù)的代碼以下:

@Override public void run() { while (mSurfaceWorking) { long start = System.currentTimeMillis(); drawFrame(); // 畫幀! execLogic(); // 履行所有游戲邏輯! long end = System.currentTimeMillis(); try { if (end - start < 50) { Thread.sleep(50 - (end - start)); // 睡眠適合的時(shí)間! } } catch (InterruptedException e) { e.printStackTrace(); } } }

畫幀、游戲邏輯、適合的sleep,1氣呵成。為了便于計(jì)算,此處我采取了每秒20幀的幀率,所以每幀平均50毫秒,而且由于畫幀和履行游戲邏輯都是需要消耗時(shí)間的,所以適合的sleep()動(dòng)作應(yīng)當(dāng)寫成:Thread.sleep(50 - (end - start))。

 

3      GameManager

3.1  整合游戲中所有元素

        為了便于管理,我設(shè)計(jì)了1個(gè)GameManager管理類。這個(gè)類究竟是干甚么的呢?簡(jiǎn)單地說,它整合了游戲中的所有元素,目前有:

  • 繪制關(guān)卡背景;
  • 所有敵人;
  • 爆炸殊效;
  • 所有子彈、炮彈;
  • 玩家(player)飛機(jī);
  • 游戲信息面板;

固然,以后還可以再擴(kuò)大1些東西,它們的機(jī)理是接近的。

 

        GameManager的代碼截選以下:

public class GameManager { private Context mContext = null; private GameStage mCurStage = null; private Player mPlayer = null; private EnemyManager mEnemyMgr = null; private BulletsManager mPlayerBulletsMgr = new BulletsManager(); private BulletsManager mEnemyBulletsMgr = new BulletsManager(); private ExplodeManager mExplodeMgr = null; private GameInfoPanel mGameInfoPanel = null;
 

        GameManager的總模塊關(guān)系示意圖以下:


 

既然在“幀流”線程里最重要的動(dòng)作是drawFrame()和execLogic(),那末GameManager類也必須提供這兩個(gè)成員函數(shù),這樣幀流線程只需直接調(diào)用GameManager的同名函數(shù)便可。

 

3.2  GameManager的畫幀動(dòng)作

        幀流線程的drawFrame()函數(shù),其代碼以下:

public void drawFrame() { Canvas canvas = null; try { canvas = mSfcHolder.lockCanvas(); if (canvas == null) { return; } mGameManager.drawFrame(canvas); } catch (Exception e) { // TODO: handle exception } finally { if (canvas != null) { mSfcHolder.unlockCanvasAndPost(canvas); } } }

其中GameManager的drawFrame()函數(shù)以下:

public void drawFrame(Canvas canvas) { mCurStage.drawFrame(canvas); mEnemyMgr.drawFrame(canvas); mExplodeMgr.drawFrame(canvas); mPlayerBulletsMgr.drawFrame(canvas); mEnemyBulletsMgr.drawFrame(canvas); mPlayer.drawFrame(canvas); mGameInfoPanel.drawFrame(canvas); }

不過是調(diào)用所有游戲角色的drawFrame()而已。

 

         每一個(gè)游戲角色有自己的存活期,在其存活期中,可以通過drawFrame()向canvas中的適合位置繪制相應(yīng)的圖片。示意圖以下:


在上面的示意圖中,兩個(gè)enemy的生存期都只有5幀,當(dāng)幀流繪制到上圖的紫色幀時(shí),會(huì)先繪制enemy_1的第1幀,而后繪制enemy_2的第5幀,最后繪制player確當(dāng)前幀。(固然,這里我們只是簡(jiǎn)單論述原理,大家如有興趣,可以再在這張圖上添加其他的游戲元素。)繪制終了后的終究效果,就是屏幕展現(xiàn)給用戶的終究畫面。

 

         每一個(gè)游戲角色都非常清楚自己當(dāng)前應(yīng)當(dāng)如何繪制,而且它通過履行自己的子邏輯,決定出下1幀該如何繪制,這就是游戲中最重要的畫幀流程。

 

3.3  GameManager管理所有的子邏輯

        其實(shí),游戲的整體運(yùn)作是由兩個(gè)方面帶動(dòng)的,1個(gè)是“軟件內(nèi)部控制”,主要控制所有“非player角色”的移動(dòng)和動(dòng)作,比如每一個(gè)enemy下1步移動(dòng)到哪里,如何發(fā)射子彈等等;另外一個(gè)是“用戶操作”,主要控制“player角色”的移動(dòng)和動(dòng)作(這部份我們放在后文再說)。在前文所說的幀流線程里,是通過調(diào)用GameManager的execLogic()來完成所有“軟件內(nèi)部控制”的,其代碼以下:

public void execLogic() { mCurStage.execLogic(); mEnemyMgr.execLogic(); mPlayer.execLogic(); mPlayerBulletsMgr.execLogic(); mEnemyBulletsMgr.execLogic(); mExplodeMgr.execLogic(); mGameInfoPanel.execLogic(); execCollsionLogic(); // 碰撞邏輯 }

         從上面代碼就能夠看出,GameManager所管理的子邏輯大概有以下幾個(gè):

  • 關(guān)卡運(yùn)作子邏輯
  • 所有敵人的運(yùn)作子邏輯
  • 玩家角色的子邏輯
  • 玩家發(fā)射的子彈的子邏輯
  • 敵人發(fā)射的子彈的子邏輯
  • 管理爆炸效果的子邏輯
  • 游戲信息面板的子邏輯
  • 碰撞子邏輯

 

4      游戲子邏輯

4.1  關(guān)卡運(yùn)作子邏輯――GameStage

        我們先看前面execLogic()函數(shù)里的第1句:mCurState.execLogic(),這個(gè)mCurState是GameStage類型的,這個(gè)類主要保護(hù)當(dāng)前關(guān)卡的相干數(shù)據(jù)。目前這個(gè)類非常簡(jiǎn)單,只保護(hù)了關(guān)卡背景圖和本關(guān)enemy的出現(xiàn)順序表。

 

4.1.1   關(guān)卡背景圖由StageBg類處理

1般來講,飛機(jī)游戲的背景是不斷轉(zhuǎn)動(dòng)的。為了實(shí)現(xiàn)轉(zhuǎn)動(dòng)效果,我們可以繪制1張比屏幕長(zhǎng)度更長(zhǎng)的圖片,并首尾相接地循環(huán)繪制它。

 

 

         在StageBg里,mBackGroundBmp1和mBackGroundBmp2這兩個(gè)域其實(shí)指向的是同1個(gè)位圖對(duì)象,之所以寫成兩個(gè)域,是為了代碼更容易于瀏覽。另外,mBgScrollSpeed用于表示背景轉(zhuǎn)動(dòng)的速度,我們可以通過修改它,來體現(xiàn)飛行的速度。

 

4.1.2   關(guān)卡中的敵人的出場(chǎng)安排

GameStage的另外一個(gè)重要職責(zé)是向游戲的主控制器(GameManager)提供1張表示敵人出場(chǎng)順序的表,為此它提供了getEnemyMap()函數(shù):

public int[][] getEnemyMap() { // ENEMY_TYPE_NONE = 0; // ENEMY_TYPE_DUCK = 1; // ENEMY_TYPE_FLY = 2; // ENEMY_TYPE_PIG = 3; int[][] map = new int[][] { {0, 0, 0, 0, 1, 0, 0, 0, 0}, {0, 0, 0, 1, 1, 1, 0, 0, 0}, {0, 0, 0, 1, 0, 1, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 2, 1, 0, 0, 0, 1, 2, 0}, {0, 2, 2, 1, 0, 1, 2, 2, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 1, 1}, {0, 0, 0, 0, 0, 0, 1, 1, 1}, {0, 2, 2, 0, 0, 0, 2, 2, 0}, {0, 2, 2, 0, 0, 0, 2, 2, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 3, 0, 0, 0, 0}, }; return map; }

該函數(shù)返回的2維數(shù)組,表達(dá)的就是敵人的出場(chǎng)順序和出場(chǎng)位置。我們目前是這樣安排的,將屏幕均分為9列,每列的特定位置對(duì)應(yīng)2維數(shù)組中的1個(gè)整數(shù),當(dāng)數(shù)值為0時(shí),表示此處沒有敵人;當(dāng)數(shù)值為1到3之間的整數(shù)時(shí),分別代表此處將出現(xiàn)哪一種敵人。現(xiàn)在我們只有3種敵人:DUCK,F(xiàn)LY,PIG。

  

          這1關(guān)卡只有1個(gè)BOSS,其類型為3型,對(duì)應(yīng)上面的PIG。我們可以看到,它只會(huì)在上面出場(chǎng)表的最后1行出現(xiàn)1次。

 

4.2  EnemyManager

關(guān)卡里的所有敵人最好能統(tǒng)1管理,所以我編寫了EnemyManager類。EnemyManager的定義截選以下:

public class EnemyManager implements IGameElement { private ArrayList<Enemy> mEnemyList = new ArrayList<Enemy>(); private int[][] mEnemyMap = null; private int mCurLine = 0; private int mEnemyCounter = 0; private Context mContext = null; private EnemyFactory mEnemyFactory = null; private BulletsManager mBulletsMgr = null; private ExplodeManager mExplodeMgr = null; private Player mPlayer = null;
其中mEnemyList列表中會(huì)記錄關(guān)卡里產(chǎn)生的所有敵人,當(dāng)敵人被擊斃以后,程序會(huì)把相應(yīng)的Enemy對(duì)象從這張表中刪除。mEnemyMap記錄的其實(shí)就是前文所說的敵人的出場(chǎng)順序表。另外,為了便于創(chuàng)建Enemy對(duì)象,我們可以先創(chuàng)建1個(gè)EnemyFactory對(duì)象,并記入mEnemyFactory域。

 

         另外,我們還需要管理所有Enemy發(fā)出的子彈,我們?yōu)镋nemyManager添加了mBulletsMgr域,意思很簡(jiǎn)單,往后每一個(gè)Enemy發(fā)射子彈時(shí),其實(shí)都是向這個(gè)BulletsManager添加子彈對(duì)象。與此同理,我們還需要1個(gè)記錄爆炸效果的爆炸管理器,那就是mExplodeMgr域。每當(dāng)1個(gè)Enemy被擊斃時(shí),它會(huì)向爆炸管理器中添加1個(gè)爆炸效果對(duì)象。

 

4.2.1   drawFrame()

EnemyManager的繪制動(dòng)作很簡(jiǎn)單,只需遍歷1下所記錄的Enemy列表,調(diào)用每一個(gè)Enemy對(duì)象的drawFrame()函數(shù)便可。

@Override public void drawFrame(Canvas canvas) { Iterator<Enemy> itor = mEnemyList.iterator(); while (itor.hasNext()) { Enemy b = itor.next(); b.drawFrame(canvas); } }<span style="font-family: Arial, Helvetica, sans-serif; background-color: rgb(255, 255, 255);"> </span>

4.2.2   execLogic()

        履行邏輯的動(dòng)作也差不多,都需要遍歷Enemy列表:

@Override public void execLogic() { execAddEnemyLogic(); // 添加enemy的地方! Iterator<Enemy> itor = mEnemyList.iterator(); while (itor.hasNext()) { Enemy b = itor.next(); b.execLogic(); // 履行每一個(gè)enemy的execLogic。 } // EnemyManager還需要負(fù)責(zé)清算“已死亡”的enemy itor = mEnemyList.iterator(); while (itor.hasNext()) { Enemy b = itor.next(); if (b.isDead()) { itor.remove(); } } }

         請(qǐng)注意,EnemyManager的execLogic()在1開始會(huì)調(diào)用execAddEnemyLogic()函數(shù),由于我們總需要1個(gè)地方添加關(guān)卡里的enemy吧。

private void execAddEnemyLogic() { mEnemyCounter++; if (mEnemyCounter % 24 == 0) { if (mCurLine < mEnemyMap.length) { for (int i = 0; i < mEnemyMap[mCurLine].length; i++) { addEnemy(mEnemyMap[mCurLine][i], i, mEnemyMap[mCurLine].length); } } mCurLine++; } }

我們用1個(gè)mEnemyCounter計(jì)數(shù)器,來控制添加enemy的頻率。幀流里每活動(dòng)1幀,耗時(shí)大概50毫秒(由于我們?cè)O(shè)的幀率是20幀/秒),那末24幀大概會(huì)耗時(shí)24 * 50 = 1200毫秒。也就是說,每過1.2秒,我們就會(huì)向EnemyManager里添加1行enemy。至于這1行里具體有甚么類型的enemy,是由mEnemyMap[ ]數(shù)組決定的。

 

         addEnemy的代碼以下:

private void addEnemy(int enemyType, int colIdx, int colCount) { Enemy enemy = null; int enemyCenterX, enemyCenterY; enemy = mEnemyFactory.createEnemy(enemyType); if (null == enemy) { return; } enemy.setBulletsManager(mBulletsMgr); enemy.setExplodeManager(mExplodeMgr); enemy.setTarget(mPlayer); mEnemyList.add(enemy); switch (enemyType) { case EnemyFactory.ENEMY_TYPE_DUCK: case EnemyFactory.ENEMY_TYPE_FLY: int colWidth = (int)((double)GlobalInfo.screenW / colCount); enemyCenterX = colWidth * colIdx + colWidth / 2; enemyCenterY = ⑴ * enemy.getHeight(); enemy.setInitInfo(enemyCenterX, enemyCenterY, 8); break; case EnemyFactory.ENEMY_TYPE_PIG: enemyCenterX = GlobalInfo.screenW / 2; enemyCenterY = ⑴ * enemy.getHeight(); enemy.setInitInfo(enemyCenterX, enemyCenterY, 8); break; default: break; } }
代碼很簡(jiǎn)單,先利用EnemyFactory根據(jù)不同的enemyType,創(chuàng)建相應(yīng)的enemy對(duì)象。然后為每一個(gè)enemy設(shè)置重要的關(guān)聯(lián)對(duì)象,比如mBulletsMgr、mExplodeMgr、mPlayer。這是由于enemy總是要發(fā)子彈的嘛,那末它每發(fā)1顆子彈,都要向“子彈管理器”里添加子彈對(duì)象。同理,當(dāng)enemy爆炸時(shí),它也會(huì)向“爆炸管理器”里添加1個(gè)爆炸效果對(duì)象。又由于enemy常常需要瞄準(zhǔn)玩家發(fā)射子彈,那末它就需要知道玩家的位置信息,因此setTarget(mPlayer)也是必要的。

 

        接著我們將enemy對(duì)象添加進(jìn)EnemyManager的mEnemyList列表中。另外還需要為不同enemy設(shè)置不同的初始信息,比如初始位置、運(yùn)行速度等等。

 

4.3  BulletsManager

        游戲中所有的子彈,不論是enemy發(fā)射的,還是玩家發(fā)射的,都必須添加進(jìn)“子彈管理器”加以保護(hù)。只不過為了便于處理,我們把enemy和玩家發(fā)射的子彈分別放在了不同的BulletsManager里。這就是為何在GameManager里,會(huì)有兩個(gè)BulletsManager的緣由:

private BulletsManager mPlayerBulletsMgr = new BulletsManager(); private BulletsManager mEnemyBulletsMgr = new BulletsManager();


        BulletsManager的代碼以下:

public class BulletsManager { private ArrayList<Bullet> mBulletsList = new ArrayList<Bullet>(); public void addBullet(Bullet bullet) { mBulletsList.add(bullet); } public void drawFrame(Canvas canvas) { Iterator<Bullet> itor = mBulletsList.iterator(); while (itor.hasNext()) { Bullet b = itor.next(); b.drawFrame(canvas); } } public void execLogic() { Iterator<Bullet> itor = mBulletsList.iterator(); while (itor.hasNext()) { Bullet b = itor.next(); b.execLogic(); } itor = mBulletsList.iterator(); while (itor.hasNext()) { Bullet b = itor.next(); if (b.isDead()) { itor.remove(); } } } public ArrayList<Bullet> getBullets() { ArrayList<Bullet> bullets = (ArrayList<Bullet>)mBulletsList.clone(); return bullets; } }

從代碼上看,它的drawFrame()和execLogic()和EnemyManager的同名函數(shù)很像。在execLogic()中,每當(dāng)發(fā)現(xiàn)1顆子彈已報(bào)廢了,就會(huì)把它從mBulletsList列表里刪除。嗯,用isDead()來表達(dá)子彈是不是報(bào)廢了好像不太貼切,不過大家應(yīng)當(dāng)都能夠理解吧,呵呵。
 

         BulletsManager還得向外提供1個(gè)getBullets()函數(shù),以便外界進(jìn)行碰撞判斷。這個(gè)我們?cè)诤笪脑偌?xì)說。

 

4.4  ExplodeManager

        爆炸效果管理器和子彈管理器的邏輯代碼差不多,所以我們就不貼它的execLogic()和drawFrame()的代碼了。

 

         每一個(gè)爆炸效果會(huì)對(duì)應(yīng)1個(gè)Explode對(duì)象。由于爆炸效果1般都會(huì)表現(xiàn)為動(dòng)畫,所以Explode內(nèi)部必須記錄下自己當(dāng)前該繪制哪1張圖片了。在我們的程序里,爆炸資源圖以下:


這張爆炸圖會(huì)在Explode對(duì)象構(gòu)造之時(shí)傳入,而且外界會(huì)告知Explode對(duì)象,爆炸圖中總共有幾幀。Explode的構(gòu)造函數(shù)以下:

public Explode(int explodeType, Rect rect, Bitmap explodeBmp, int totalFrame) { mType = explodeType; mCurRect = new Rect(rect); mExplodeBmp = explodeBmp; mTotalFrame = totalFrame; mFrameWidth = mExplodeBmp.getWidth() / mTotalFrame; mFrameHeight = mExplodeBmp.getHeight(); }

         每當(dāng)ExplodeManager遍歷履行每一個(gè)Explode對(duì)象的execLogic()時(shí),會(huì)改變當(dāng)前應(yīng)當(dāng)繪制的幀號(hào)。這樣當(dāng)游戲總幀流活動(dòng)時(shí),爆炸效果也就動(dòng)起來了。Explode的execLogic()函數(shù)以下:

public void execLogic() { mCurFrameIdx++; if (mCurFrameIdx >= mTotalFrame) { mState = STATE_DEAD; } }

         具體繪制爆炸幀時(shí),我們只需把爆炸圖中與mCurFrameIdx對(duì)應(yīng)的那1部份畫出來就能夠了,這就必須用到clipRect()。Explode的drawFrame()函數(shù)以下:

public void drawFrame(Canvas canvas) { Rect srcRect = new Rect(mCurFrameIdx * mFrameWidth, 0, (mCurFrameIdx + 1)*mFrameWidth, mFrameHeight); canvas.save(); canvas.clipRect(mCurRect); canvas.drawBitmap(mExplodeBmp, srcRect, mCurRect, null); canvas.restore(); }

1開始計(jì)算的srcRect,表示的就是和mCurFrameIdx對(duì)應(yīng)的繪制部份。

 

         其實(shí),不光是爆炸效果,我們的每類Enemy都是具有自己的動(dòng)畫的。它們的繪制機(jī)理和爆炸效果1致,我們就不贅述了。下面只貼出3類Enemy的角色動(dòng)畫圖:



 

4.5  Player

        現(xiàn)在我們來看玩家控制的角色――Player類。它和Enemy最大的不同是,它是直接由玩家控制的。玩家想把它移到甚么地方,他就得乖乖地移到那個(gè)地方去,為此它必須能夠處理MotionEvent。

 

4.5.1   doWithTouchEvent()

public boolean doWithTouchEvent(MotionEvent event) { int x = (int)event.getX(); int y = (int)event.getY(); switch (event.getAction()) { case MotionEvent.ACTION_DOWN: mOffsetX = x - mCurRect.left; mOffsetY = y - mCurRect.top; return true; case MotionEvent.ACTION_UP: mOffsetX = mOffsetY = 0; return true; case MotionEvent.ACTION_MOVE: int curX = x - mOffsetX; int curY = y - mOffsetY; if (curX < 0) { curX = 0; } if (curY < 0) { curY = 0; } if (curX + mWidth > GlobalInfo.screenW) { curX = GlobalInfo.screenW - mWidth; } if (curY + mHeight > GlobalInfo.screenH) { curY = GlobalInfo.screenH - mHeight; } mCurRect.set(curX, curY, curX+mWidth, curY+mHeight); return true; default: break; } return false; }

         注意,為了保證良好的用戶體驗(yàn),我們需要在用戶點(diǎn)擊屏幕之時(shí),先計(jì)算1下手指導(dǎo)擊處和Player對(duì)象當(dāng)前所在位置之間的偏移量,以后在處理ACTION_MOVE時(shí),還需用x、y減去偏移量。這樣,就不會(huì)出現(xiàn)Player對(duì)象從舊位置直接跳變得手指導(dǎo)擊處的情況。

 

4.5.2   碰撞判斷

        現(xiàn)在我們來講說碰撞處理。在飛機(jī)游戲里,1種典型的碰撞情況就是被子彈擊中啦。對(duì)Player來講,它必須逐一判斷敵人發(fā)出的子彈,看自己是不是已和某個(gè)子彈密切接觸,如果是的話,那末Player就得減血,如果沒血可減了,就算被擊斃了。

 

        對(duì)簡(jiǎn)單的游戲而言,我們只需判斷子彈所占的Rect范圍是不是和Player所占的Rect范圍有交集,如果是的話,就能夠認(rèn)為產(chǎn)生碰撞了。固然,為了增加1點(diǎn)兒趣味性,我們是用1個(gè)比Player Rect更小的矩形來和子彈Rect比對(duì)的,這樣可以出現(xiàn)1點(diǎn)兒子彈和Player擦身而過的驚險(xiǎn)效果。

 

         在GameManager的execLogic()的最后1步,會(huì)調(diào)用execCollsionLogic()函數(shù)。該函數(shù)的代碼以下:

private void execCollsionLogic() { mPlayer.doWithCollision(mEnemyBulletsMgr); mEnemyMgr.doWithCollision(mPlayerBulletsMgr); }

意思很簡(jiǎn)單,Player需要和所有enemy發(fā)出的子彈進(jìn)行比對(duì),而每一個(gè)enemy需要和Player發(fā)出的子彈比對(duì)。我們只看Player的doWithCollision()函數(shù),代碼以下:

public void doWithCollision(BulletsManager bulletsMgr) { if (mState == STATE_EXPLODE || mState == STATE_DEAD) { return; } ArrayList<Bullet> bullets = bulletsMgr.getBullets(); Iterator<Bullet> itor = bullets.iterator(); int insetWidth = (int)((mCurRect.right - mCurRect.left) * 0.2); int insetHeight = (int)((mCurRect.bottom - mCurRect.top) * 0.15); Rect effectRect = new Rect(mCurRect); effectRect.inset(insetWidth, insetHeight); while (itor.hasNext()) { Bullet b = itor.next(); Rect bulletRect = b.getRect(); if (effectRect.intersect(bulletRect)) { b.doCollide(); doCollide(b.getPower()); } } }

其中那個(gè)effectRect就是比Player所占矩形更小1點(diǎn)兒的矩形啦。我們遍歷BulletsManager中的每一個(gè)子彈,1旦發(fā)現(xiàn)哪一個(gè)子彈和effectRect有交集,就履行doCollide()。

private void doCollide(int power) { if (mState == STATE_ADJUST || mState == STATE_EXPLODE || mState == STATE_DEAD) { return; } if (power < 0) { // kill me directly mState = STATE_EXPLODE; } else if (power > 0) { mMyHP -= power; if (mMyHP <= 0) { mMyHP = 0; mState = STATE_EXPLODE; } else { mState = STATE_ADJUST; mAdjustCounter = 0; } } }

         如果寫得復(fù)雜1點(diǎn)兒的話,不同enemy發(fā)出的子彈的威力應(yīng)當(dāng)是不1樣的。不過在本游戲中,每顆子彈的威力都定為1了。也就是說,傳入doCollide()的power參數(shù)的值總為1。每次碰撞時(shí),Player就減1滴血(mMyHP -= power),然后立即跳變到STATE_ADJUST狀態(tài)或STATE_EXPLODE狀態(tài)。

 

         另外一方面,enemy和Player發(fā)出的子彈也有類似的判斷,只是判斷條件更加寬松1些,這樣可以給玩家增加1點(diǎn)兒射擊的爽快感,呵呵。關(guān)于這部份的代碼我們就不重復(fù)貼了。

 

4.5.3   被擊中后的閃爍效果

        Player需要完成的另外一個(gè)效果是被擊中后,閃爍1段很短的時(shí)間,在這段時(shí)間內(nèi),它會(huì)暫時(shí)處于無敵狀態(tài),這樣做可以免玩家出現(xiàn)被多顆子彈同時(shí)擊中而被瞬殺的情況。為此我們?cè)O(shè)計(jì)了1個(gè)“調(diào)劑狀態(tài)”,就是我們剛剛看到的STATE_ADJUST狀態(tài)啦。

 

         1旦Player被擊中,只要它的mMyHP(血值)沒有減到0,那末它立即跳變到STATE_ADJUST。在這類狀態(tài)下,我們不再每次都繪制Player圖片了,而是隔1幀繪制1次,這樣就能夠到達(dá)閃爍的效果了。固然這個(gè)狀態(tài)的保持時(shí)間很短,我們會(huì)記錄1個(gè)mAdjustCounter計(jì)數(shù)變量,每次履行execLogic()會(huì)給這個(gè)計(jì)數(shù)器加1,直到加到6,我們就從STATE_ADJUST狀態(tài),跳變回普通狀態(tài)(STATE_ALIVE狀態(tài))。

public void execLogic() { if (mState == STATE_ALIVE) { doFireBulletLogic(); } else if (mState == STATE_EXPLODE) { doExplode(); } else if (mState == STATE_ADJUST) { doFireBulletLogic(); mAdjustCounter++; if (mAdjustCounter > 6) { mState = STATE_ALIVE; mAdjustCounter = 0; } } }


public void drawFrame(Canvas canvas) { boolean shouldDraw = true; if (mState == STATE_DEAD) { Log.d("Player", "mState == STATE_DEAD"); return; } else if (mState == STATE_ADJUST) { if (mAdjustCounter % 2 == 0) { shouldDraw = false; } } else if (mState == STATE_EXPLODE) { // should draw } Log.d("Player", "mState == " + mState); if (shouldDraw) { Rect src = new Rect(0, 0, mPlayerBmp.getWidth(), mPlayerBmp.getHeight()); canvas.drawBitmap(mPlayerBmp, src, mCurRect, null); } }<span style="font-family: Arial, Helvetica, sans-serif; background-color: rgb(255, 255, 255);"> </span>

4.6  GameInfoPanel

        飛機(jī)游戲還需要1個(gè)簡(jiǎn)單的“信息顯示板”,來顯示1些重要的信息。在本游戲中,我只顯示了Player的剩余血量(每滴血用1個(gè)紅心表示),大家有興趣可以再添加玩家分?jǐn)?shù)等信息。

 

         我們?cè)O(shè)計(jì)的信息顯示板是GameInfoPanel,它的邏輯非常簡(jiǎn)單:

public void execLogic() { mPlayerHP = mPlayer.getHP(); }

只是簡(jiǎn)單地記錄1下Player的血量而已。

 

         繪制時(shí),它根據(jù)所記錄的血量值繪制相應(yīng)的紅心圖片就能夠了:

public void drawFrame(Canvas canvas) { Rect src = new Rect(0, 0, mHPBmp.getWidth(), mHPBmp.getHeight()); Rect dest = new Rect(); for (int i = 0; i < mPlayerHP; i++) { dest.left = mRect.left + i * mHPiconWidth; dest.top = mRect.top; dest.right = dest.left + mHPiconWidth; dest.bottom = dest.top + mHPiconHeight; canvas.drawBitmap(mHPBmp, src, dest, null); } }

5      尾聲

至此,我們已把這個(gè)小游戲的主要設(shè)計(jì)方面都講到了。固然,由于這個(gè)游戲只是我為了好玩而寫的1個(gè)demo程序,所以肯定有很多地方其實(shí)不完備,這個(gè)我想大家也是可以理解的。那末就先說這么多吧。最后讓我們來貼兩張游戲截圖,樂呵1下。

        

生活不易,碼農(nóng)辛苦
如果您覺得本網(wǎng)站對(duì)您的學(xué)習(xí)有所幫助,可以手機(jī)掃描二維碼進(jìn)行捐贈(zèng)
程序員人生
------分隔線----------------------------
分享到:
------分隔線----------------------------
關(guān)閉
程序員人生
主站蜘蛛池模板: 一区二区三区中文字幕 | 天天爱综合 | 国产91精品一区二区 | 日韩成人在线播放 | 国产精品日韩一区二区 | 尤物在线| 亚洲三区四区 | 欧美成人精品一区二区男人看 | 亚洲精品久久久久久久久久久 | 亚洲专区欧美专区 | 欧美日韩亚洲国产 | 中文字幕高清视频 | 美女又黄又免费的视频 | 99久久久国产精品 | 日韩欧美国产高清 | 欧美日韩高清在线一区 | 欧美日本在线播放 | 中文字幕一区二区三区四区 | av免费不卡 | 国产不卡视频一区二区三区 | 欧美二区在线观看 | 91国偷自产一区二区使用方法 | 欧美日韩精品一区二区三区蜜桃 | av在线资源网 | 日韩中文一区二区 | 亚洲精品视频二区 | 国产精品久久久久久久久久不蜜月 | 天堂在线中文 | 久久久www成人免费无遮挡大片 | 超碰在线久 | 国产日韩欧美 | 国产日产亚洲精品 | 免费大片黄在线观看视频网站 | 国产精品久久久久久久免费大片 | 亚洲精品99久久久久中文字幕 | 国产精品一区在线观看 | 久久99视频 | 国产精品一区二区三区免费看 | 国产在线观看 | 成人小视频在线 | 中文字幕国产日韩 |