日韩av色在线_av不卡在线观看_亚洲国产精品第一页_在线看日本不卡_色成人综合网_国产丝袜在线视频_国产精成人品localhost_国产91富婆露脸刺激对白_韩国视频一区_国产精品人成电影

當(dāng)前位置: > 華清遠(yuǎn)見教育科技集團(tuán) > 嵌入式學(xué)習(xí) > 講師博文 > 游戲開發(fā)實(shí)例《小魚快跑》
游戲開發(fā)實(shí)例《小魚快跑》
時(shí)間:2016-12-12作者:華清遠(yuǎn)見

本章要點(diǎn):

本章我們將通過在Android平臺(tái)完成一個(gè)小游戲《小魚快跑》來學(xué)習(xí)Android游戲開發(fā)。

6.1. 游戲策劃

在開發(fā)一款游戲時(shí),我們的第一階段是進(jìn)行游戲策劃,這是一項(xiàng)比編寫代碼更為重要的工作。在這個(gè)過程中,我們需要確定這款游戲的基本特征,比如是游戲類型、用戶類型、游戲風(fēng)格以及游戲可玩性等等,這將直接決定該游戲的后期制作和玩家的喜好程度。

游戲的開發(fā)過程已經(jīng)隨著游戲行業(yè)的飛速發(fā)展被賦以更多的元素。在游戲開發(fā)的初期,優(yōu)秀的作品往往只是代碼工作者短期的投入,不需要前期的準(zhǔn)備、調(diào)查等。但是目前游戲行業(yè)的狀況已經(jīng)不同于當(dāng)年,游戲平臺(tái)的多元化、用戶類型的增加以及市場競爭的激烈化等都需要我們將正規(guī)的商業(yè)流程納入游戲的開發(fā)當(dāng)中,如果我們依然按照早期的開發(fā)模式應(yīng)對當(dāng)前的游戲市場,那么開發(fā)出的游戲?qū)?huì)由于缺少一方面或多方面的特性而無法引起用戶的興趣。那么如何才能開發(fā)一款受廣大用戶喜愛的游戲呢?這就需要我們在策劃之前進(jìn)行調(diào)查,首先確定市場的需求和目標(biāo)客戶。在明確了目標(biāo)客戶之后,是否需要考慮游戲類型呢?從大的方面來說,游戲是單機(jī)的還是聯(lián)網(wǎng)的?是單人的還是多人的?是動(dòng)作類型的還是角色扮演類型的?等等。在確定了游戲的類型之后,還需要考慮游戲的可玩性,總不可能說游戲玩家十多分鐘就通關(guān)了,這就需要包括游戲的難度設(shè)置、關(guān)卡控制以及后期的版本控制。現(xiàn)在我們就可以根據(jù)上面的目標(biāo)客戶和游戲類型來定義游戲的風(fēng)格了,比如想以三國為題材做一個(gè)即時(shí)戰(zhàn)略游戲,就不可能定義為現(xiàn)代風(fēng)格,游戲中就不會(huì)出現(xiàn)坦克、飛機(jī)的道具。同時(shí)這也體現(xiàn)了游戲的真實(shí)性,但是游戲本身是一個(gè)虛幻的東西,玩家就是需要將自己放到虛擬的世界中,所以也不能過度真實(shí),讓玩家覺得枯燥乏味。風(fēng)格確定之后,還可以根據(jù)游戲的風(fēng)格來配置游戲音效。由于玩家經(jīng)常會(huì)接觸到很多游戲,所以他在玩游戲時(shí)會(huì)對一些沒有新意的游戲感到厭倦,反正我玩過類似的游戲,沒什么好玩的。如果得到玩家這樣的評論,那么大家都知道這款游戲的成敗了。雖然游戲創(chuàng)新并不是一件很容易的事情,但是為了吸引玩家,我們不得不大膽地創(chuàng)新。

這些問題都解決了,就可以準(zhǔn)備寫策劃案了。在寫策劃案的同時(shí)還需要考慮到美工和程序在技術(shù)上的實(shí)現(xiàn)以及硬件的支持,不能設(shè)計(jì)技術(shù)達(dá)不到的效果。了解了這些問題,開始編寫策劃案。

其實(shí)策劃是一個(gè)非常廣泛的領(lǐng)域,有很多東西需要自己在實(shí)踐中證明,這里只是列舉了常用的、值得注意的地方。

6.2.游戲資源

在策劃階段完成了游戲的前期準(zhǔn)備之后,我們就可以按照策劃文檔開始游戲的實(shí)施。首先準(zhǔn)備游戲的資源,比如音效、界面,這些在公司內(nèi)都由專門的人士負(fù)責(zé),他們需要根據(jù)策劃文檔的描述來發(fā)揮自己的想象力,在保持和策劃文檔一致的情況下,進(jìn)行創(chuàng)新。同樣,為了保證美工的圖片適合程序的要求,還需要多和程序員進(jìn)行交流,以確保程序員能夠很清楚地理解自己的設(shè)計(jì),使游戲效果達(dá)到好。

6.3.游戲開發(fā)

程序員在得到文檔和資源后并不能馬上打開編輯器,新建工程開始寫代碼,而是要仔細(xì)查看文檔和資源,根據(jù)這些來確定所要使用的知識和所要實(shí)現(xiàn)的功能,然后構(gòu)建一個(gè)整體的框架。這個(gè)整體的框架很重要,一個(gè)優(yōu)秀的程序員會(huì)在框架的設(shè)計(jì)上花很多時(shí)間,因?yàn)橐粋(gè)好的框架可以使后面的開發(fā)、調(diào)試等更加簡單,同時(shí)一個(gè)好的框架還能提高游戲的運(yùn)行效率。為了保證質(zhì)量,每個(gè)程序員寫的程序都有Bug,所以我們需要不斷地測試、修改,再測試、再修改,從而給玩家一個(gè)好的體驗(yàn)。

《小魚快跑》包括了游戲開發(fā)中的大部分技術(shù),包括背景、精靈、圖層、音效等。下面是本章將完成的游戲在Android上的運(yùn)行效果。

6.3.1.游戲框架設(shè)計(jì)

前面已經(jīng)介紹了框架在游戲開發(fā)中的重要地位,如何才能實(shí)現(xiàn)一個(gè)適合該游戲的框架呢?首先我們需要了解游戲的內(nèi)容,游戲中包括了地圖、主角、整個(gè)屏幕界面,顯示了地圖和主角的屬性,地圖上還有道具,至少需要一個(gè)視圖來顯示,并且需要更新界面的顯示和一個(gè)控制游戲邏輯及事件的類。下面我們來構(gòu)建該游戲的整體框架。

在Android中要顯示一個(gè)視圖類就必須繼承自View類,在本例中我們使用SurfaceView,SurfaceView可以直接從內(nèi)存或者DMA等硬件接口取得圖像數(shù)據(jù),因此是個(gè)非常重要的繪圖容器類。在Android開發(fā)中,布局資源通過setContentView被設(shè)置在res文件夾下,刷新率通過UI主線來控制。但是在游戲開發(fā)中,這種刷新率遠(yuǎn)遠(yuǎn)不能滿足我們的需求,所以我們應(yīng)該自己掌控刷新率,當(dāng)然Android也想到了這一點(diǎn),所以提供了一個(gè)類SurfaceView,使用SurfaceView我們可以通過自己的線程去控制屏幕的刷新頻率,以達(dá)到游戲中的效果。SurfaceView中包括一主要的繪制方法onDraw和一些事件的處理,本例中我們將所有顯示在屏幕上的對象通過屬性的不同歸類到不同的圖層,并通過哈希表來存儲(chǔ)這些圖層,后通過圖層之間的順序、圖層中對象的順依次在屏幕上繪制。在構(gòu)建這個(gè)類時(shí)還可以加入我們自己的一些方法,比如更新圖層(updatePicLayer)、對圖層的其他操作(removeDrawablePic、updateLayrIds)等。在SurfaceView中,整個(gè)圖層的繪制全部都是基于SurfaceView來實(shí)現(xiàn)的,我們可以從SurfaceView來獲得圖層并對其進(jìn)行繪制。有了這些內(nèi)容,下面構(gòu)建一個(gè)用于顯示游戲界面的視圖類MainSurface。MainSurface類的代碼如代碼清單6-1所示:

代碼清單6-1.MainSurface.java

public class MainSurface extends SurfaceView implements SurfaceHolder.Callback {
        /**
        * 修改圖層的操作定義
        */
        //更新圖層
        private final static int CHANGE_MODE_UPDATE = 0;
        //添加元素到圖層
        private final static int CHANGE_MODE_ADD = 1;
        //刪除元素從圖層
        private final static int CHANGE_MODE_REMOVE = 2;
        // 圖片的圖層分布
        private HashMap<Integer, ArrayList<Drawable>> picLayer =new HashMap<Integer, ArrayList<Drawable>>();
        // 修改后的圖片的圖層分布,這里根據(jù)操作分為了兩個(gè)圖層,分別是添加的元素,和刪除的元素
        private HashMap<Integer, ArrayList<Drawable>> addPicLayer = new HashMap<Integer, ArrayList<Drawable>>(),removePicLayer = new HashMap<Integer, ArrayList<Drawable>>();
        // 是否修改過圖層
        private boolean changeLayer = false;
        private int picLayerId[] = new int[0]; // 定義一個(gè)圖層ID,加速獲取圖層繪制(省去了從map中獲取各個(gè)圖層排序問題)
        private Paint paint; // 畫筆
        private OnDrawThread odt; // 屏幕繪制線程,用于控制繪制幀數(shù),周期性調(diào)用onDraw方法
        private Typeface typeface;
        public MainSurface(Context context) {
        super(context);
        typeface = Typeface.createFromAsset(context.getAssets(),"texttype/WhatsHappened.ttf");
        this.getHolder().addCallback(this);
        paint = new Paint();
        paint.setTypeface(typeface); // 設(shè)置Paint的字體
        paint.setAntiAlias(true);
        paint.setFilterBitmap(true);
        paint.setDither(true);
        paint.setTextSize(15); // 根據(jù)不同分辨率設(shè)置字體大小
        paint.setColor(Color.WHITE);
        odt = new OnDrawThread(this);
        }
         public void surfaceChanged(SurfaceHolder arg0, int arg1, int arg2, int arg3) {
        // TODO Auto-generated method stub
        }
        public void surfaceCreated(SurfaceHolder arg0) {
        // TODO Auto-generated method stub
         odt.start();
       nbsp; }
        public void surfaceDestroyed(SurfaceHolder arg0) {
        paint = null;
        typeface = null;
        picLayerId = null;
        picLayer = null;
        addPicLayer = null;
        removePicLayer = null;
        }
        @Override
        /**
        * 繪圖方法,這個(gè)方法是由線程控制,周期性調(diào)用的
        */
        public void onDraw(Canvas canvas) {
        //更新圖層內(nèi)容
        updatePicLayer(CHANGE_MODE_UPDATE,0,null);
        // 遍歷所有圖層,按圖層先后順序繪制
        for (int id : picLayerId) {
        for (Drawable drawable : picLayer.get(id)) {
        drawable.onDraw(canvas, paint);
            }
        }
        //繪制LOGO
        canvas.drawText("farsight android game demo. by XiloerFan", 0, 20, paint);
        }
        /**
        * 更新圖層,這里分為三種操作,分別是更新臨時(shí)圖層中的內(nèi)容到繪制圖層中,刪除繪制圖層中的元素,添加繪制圖層中的元素
        * 這里加了個(gè)線程鎖,保證多線程下操作圖層的安全性
        * @param mode 對繪制圖層的操作類型,對應(yīng)當(dāng)前類的CHANGE_MODE常量
        * @param layerId 操作的圖層ID
        * @param draw 操作的圖層元素
        */
        private synchronized void updatePicLayer(int mode,int layerId,Drawable draw){
        switch(mode){
        //將臨時(shí)圖層中的內(nèi)容更新至繪制圖層中
        case CHANGE_MODE_UPDATE:
        //如果有修改
        if(changeLayer){
        //向圖層添加新的元素
        for(Integer id:addPicLayer.keySet()){
        for(Drawable d:addPicLayer.get(id)){
        //如果要添加的元素所處圖層不存在,則創(chuàng)建這個(gè)圖層,并更新圖層ID數(shù)組
        if(this.picLayer.get(id)==null){
        this.picLayer.put(id, new ArrayList<Drawable>());
        updateLayerIds(id);
                }
        this.picLayer.get(id).add(d);
            }
        }
        addPicLayer.clear();
        //刪除圖層中的元素
        for(Integer id:removePicLayer.keySet()){
        for(Drawable d:removePicLayer.get(id)){
        try {
        this.picLayer.get(id).remove(d);
            } catch (Exception e) {
        System.out.println("圖層內(nèi)容不存在:"+id);
            }
            }
        }
        removePicLayer.clear();
        changeLayer = false;
        }
        break;
        /**
        * 無論是向繪圖圖層中添加還是刪除元素,都不是直接操作繪制圖層,都是存放在對應(yīng)的臨時(shí)圖層中,等待繪制方法繪制周期中將變化的內(nèi)容更新到繪制圖層中
        * 保證多線程操作情況下的安全性
        */
        //添加一個(gè)元素
        case CHANGE_MODE_ADD:
        ArrayList<Drawable> al = addPicLayer.get(layerId);
        if(al==null){
        al = new ArrayList<Drawable>();
        addPicLayer.put(layerId, al);
        }
        al.add(draw);
        changeLayer = true;
        break;
        //刪除一個(gè)元素
      nbsp;  case CHANGE_MODE_REMOVE:
        ArrayList<Drawable> al1 = removePicLayer.get(layerId);
        if(al1==null){
        al1 = new ArrayList<Drawable>();
            removePicLayer.put(layerId, al1);
        }
        al1.add(draw);
        changeLayer = true;
        break;
        }
        }
        /**
        * 將一個(gè)可繪制的圖放入圖層中
        *
        * @param layer
        *圖層號 圖層號雖然是int,但是實(shí)際上只支持到byte,原因是圖層沒有必要那么多
        * @param pic
   &nbnbsp;    *可繪制的圖
        */
        public void putDrawablePic(int layer, Drawable pic) {
        if(pic==null){
        System.out.println("圖層內(nèi)容不能為空:對應(yīng)圖層:"+layer);
        return;
        }
        updatePicLayer(CHANGE_MODE_ADD,layer,pic);
        }
        /**
        * 將一個(gè)可繪制的圖從圖層中移除
        *
        * @param layer
        * @param pic
        */
        public void removeDrawablePic(int layer, Drawable pic) {
        if(pic==null){
       &nbsnbsp;System.out.println("圖層內(nèi)容不能為空:對應(yīng)圖層:"+layer);
        return;
        }
        updatePicLayer(CHANGE_MODE_REMOVE,layer,pic);
        }
        /**
        * 更新圖層Id
        *
        * @param newLayerId
        */
        private void updateLayerIds(int newLayerId) {
        // 初始化圖層
        if (picLayerId.length == 0) {
        picLayerId = new int[1];
        picLayerId[0] = newLayerId; // 將新的圖層ID添加到初始化的圖層ID數(shù)組中
        } else {
        // 創(chuàng)建一個(gè)新的圖層數(shù)組,長度比原來的大1位
        int picLayerIdFlag[] = new int[picLayerId.length + 1];
        for (int i = 0; i < picLayerId.length; i++) {
        // 排序操作,如果新的圖層ID小于當(dāng)前圖層ID,講新的圖層ID插入其中
        if (picLayerId[i] > newLayerId) {
        for (int f = picLayerIdFlag.length - 1; f > i; f--) {
        picLayerIdFlag[f] = picLayerId[f - 1];
        }
        picLayerIdFlag[i] = newLayerId;
        break;
        } else {
        picLayerIdFlag[i] = picLayerId[i];
        }
        // 如果到了后,都沒有比新圖層ID大的,就將新的圖層ID存入后
        if (i == picLayerId.length - 1) {
        picLayerIdFlag[picLayerIdFlag.length - 1] = newLayerId;
            }
        }
        // 將新的圖層ID數(shù)組覆蓋原有的
        this.picLayerId = picLayerIdFlag;
                }
            }
        }

在創(chuàng)建和控制了圖層顯示之后,要讓游戲能夠動(dòng)起來,需要開啟一個(gè)線程來實(shí)時(shí)更新圖層顯示界面并刷新。下面我們將為游戲創(chuàng)建一個(gè)繪圖線程,可以通過sh.lockCanvas(null)方法來取得當(dāng)前顯示的圖層,然后根據(jù)不同的圖層來進(jìn)行游戲更新。線程的開啟在MainSurface繼承的接口SurfaceHolder.CallBack中體現(xiàn):SurfaceCreated圖層創(chuàng)建時(shí)開啟。代碼清單6-2所示為繪制圖層的線程:

代碼清單6-2. 控制繪圖的線程

public class OnDrawThread extends Thread{
        private MainSurface surface;
        private SurfaceHolder sh;
        private int drawSpeed; &nbnbsp;  //每次繪制后的休息毫秒數(shù),這個(gè)值是根據(jù)常量中的繪制幀數(shù)決定的
        public OnDrawThread(MainSurface surface){
        super();
        this.surface = surface;
        sh = surface.getHolder();
        drawSpeed = 1000/Constant.ON_DRAW_SLEEP;
        }
        public void run(){
        super.run();
        Canvas canvas = null;
        while(GamingInfo.getGamingInfo().isGaming()){
        try{
        canvas = sh.lockCanvas(null);
        if(canvas!=null){
        surface.onDraw(canvas);
        }
        }catch(Exception e){
        Log.e(this.getName(), e.toString());
        e.printStackTrace();
        }finally{
        try{
        if(sh!=null){
        sh.unlockCanvasAndPost(canvas);
            }
        }catch(Exception e){
        Log.e(this.getName(), e.toString());
            }
        }
        try{
        Thread.sleep(drawSpeed);
        }catch(Exception e){
                    }
                }
    nbsp;        }
        }

在完成了這些模塊之后,就需要通知一個(gè)Activity來控制游戲的運(yùn)行,游戲開始(onCreate)、游戲重置(onResume)、游戲暫停(onPause)、事件處理(onTouchEvent)等。整個(gè)游戲界面的顯示通過繪圖線程來控制:當(dāng)創(chuàng)建一個(gè)MainSurface對象時(shí),在MainSurface的構(gòu)造方法里面創(chuàng)建一個(gè)繪圖線程odt,同時(shí)SurfaceHolder.Callback監(jiān)聽到MainSurface的創(chuàng)建時(shí)自動(dòng)調(diào)用surfaceCreate方法,在surfaceCreate中開啟線程,開始以雙緩沖模式繪制圖層。代碼清單6-3為GameActivity類的處理:

代碼清單6-3. GameActivity.java

public class GameActivity extends Activity {
        private MainSurface surface;
        /** Called when the activity is first created. */
        @Override
        public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        this.requestWindowFeature(Window.FEATURE_NO_TITLE);//設(shè)置屏幕顯示沒有title
        this.getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, WindowManager.LayoutParams.FLAG_FULLSCREEN);
        getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
        }
        @Override
        protected void onResume() {
        super.onResume();
        init();    //開始初始化
        }
        @Override
        public void onWindowFocusChanged(boolean hasFocus) {
        // TODO Auto-generated method stub
        super.onWindowFocusChanged(hasFocus);
        if(hasFocus){
        /**
        * 創(chuàng)建一個(gè)線程來異步初始化游戲內(nèi)容
        */
  &nbsnbsp;     new Thread(new Runnable(){
        public void run() {
        //使用游戲初始化管理器初始化游戲
        GameInitManager.getGameInitManager().init();
                }
            }).start();
        }
        }
        /**
        * 初始化操作
        */
      &nbsnbsp; private void init(){
        /**
        * 初始化繪圖層
        */
        GamingInfo.clearGameInfo();
        GamingInfo.getGamingInfo().setGaming(true);
        GamingInfo.getGamingInfo().setActivity(this);
        //獲得手機(jī)的寬度和高度像素單位為px
        DisplayMetrics dm = new DisplayMetrics();
        this.getWindowManager().getDefaultDisplay().getMetrics(dm);
        if(dm.widthPixels<dm.heightPixels){
        GamingInfo.getGamingInfo().setScreenWidth(dm.heightPixels);
        GamingInfo.getGamingInfo().setScreenHeight(dm.widthPixels);
        }else{
        GamingInfo.getGamingInfo().setScreenWidth(dm.widthPixels);
        GamingInfo.getGamingInfo().setScreenHeight(dm.heightPixels);
            }
        surface = new MainSurface(this);
        GamingInfo.getGamingInfo().setSurface(surface);
        this.setContentView(surface);
        }
        @Override
        protected void onPause() {
        //停止游戲相關(guān)活動(dòng)
        GameInitManager.getGameInitManager().stop();
        //清除共享數(shù)據(jù)對象
        GamingInfo.clearGameInfo();
        super.onPause();
        }
        @Override
        public boolean onTouchEvent(MotionEvent event) {
        if(GameInitManager.getGameInitManager().isIniting()){
        return super.onTouchEvent(event);
        }
        //屏幕被觸摸
        if(event.getAction()==MotionEvent.ACTION_DOWN){
        //先看布局管理器是否有相應(yīng)
        if(LayoutManager.getLayoutManager().onClick(event.getRawX(), event.getRawY())){
        return true;
        }
        //發(fā)射子彈
        CannonManager.getCannonManager().shot(event.getRawX(), event.getRawY());
        return true;
                }
        return super.onTouchEvent(event);
            }
        }

到這里我們基本完成了一個(gè)游戲的整體框架,值得一提的是,整個(gè)程序采用MVC架構(gòu),后面的所有游戲?qū)ο蠖贾恍枰^承自我們自定義的實(shí)體類DrawableAdapter,然后在相應(yīng)的管理類中判斷和更改當(dāng)前的游戲狀態(tài),程序便自動(dòng)找到我們需要更新和釋放的游戲?qū)ο筮M(jìn)行操作。

6.3.2.背景設(shè)計(jì)

小魚快跑的游戲背景設(shè)計(jì)比較簡單,僅僅是一張背景圖的顯示,沒有參與游戲邏輯的處理。代碼清單6-4為游戲背景的實(shí)體類:

代碼清單6-4. BackGround.java

public class BackGround extends DrawableAdapter{
        private Bitmap background;
        public void setCurrentPic(Bitmap background){
        this.background = background;
        }
        public Bitmap getCurrentPic() {
        // TODO Auto-generated method stub
        return background;
        }
        public int getPicWidth() {
        // TODO Auto-generated method stub
        return background.getWidth();
        }
        public int getPicHeight() {
        // TODO Auto-generated method stub
       nbsp; return background.getHeight();
            }
        }

6.3.3.精靈設(shè)計(jì)

游戲中的對象稱為精靈,當(dāng)然精靈的范圍很廣,包括NPC、道具等。既然是精靈,必然有很多動(dòng)畫,比如,小魚在游動(dòng)和被捕時(shí)應(yīng)該有不同的動(dòng)畫,動(dòng)畫本身就是將圖片一幀一幀地連接起來,循環(huán)地播放每一幀形成的。一般做游戲會(huì)把精靈作為一個(gè)單獨(dú)的類,本例中由于我們使用MVC架構(gòu),因此精靈的屬性和活動(dòng)分開操作。我們定義一個(gè)可繪制接口Drawable,用一個(gè)實(shí)體類DrawableAdapter來實(shí)現(xiàn)該接口,表示精靈的實(shí)體,后用相應(yīng)的管理類來實(shí)現(xiàn)精靈的活動(dòng)。Drawable是個(gè)用來顯示圖像的類,是由一個(gè)圖像(可以有好幾幀,但是一次只有一個(gè)顯示)組成的(當(dāng)然DrawableAdapter還有其他的特性,每次只能使用一個(gè)圖像而不是多個(gè)圖像來填充屏幕是它的主要特征)。下面我們看看Drawable和DrawableAdapter類:

代碼清單6-5.Drawable.java

public interface Drawable {
        public Matrix getPicMatrix();//獲取圖片旋轉(zhuǎn)的矩陣表示
        public Bitmap getCurrentPic();//獲取當(dāng)前動(dòng)作圖片的資源
        public int getPicWidth();//返回圖片的寬度
        public int getPicHeight();//返回圖片的高度
        public void onDraw(Canvas canvas,Paint paint);//繪制的回調(diào)方法
        }

代碼清單6-6.DrawableAdapter.java

public abstract class DrawableAdapter implements Drawable{
        private Matrix matrix = new Matrix();
        public Matrix getPicMatrix() {
        // TODO Auto-generated method stub
        return matrix;
        }
        public void onDraw(Canvas canvas, Paint paint) {
        canvas.drawBitmap(this.getCurrentPic(),
        this.getPicMatrix(), paint);
                }
        }

在Drawable類中,getCurrentPic()得到游戲?qū)ο螽?dāng)前的動(dòng)作圖片,getPicMatrix()得到處理游戲?qū)ο蟮木仃嚒T贒rawableAdapter類中,onDraw方法用來實(shí)現(xiàn)游戲?qū)ο蟮睦L制。

6.3.3.1.游戲?qū)ο髽?gòu)造

本游戲中的對象包括小魚、子彈、金幣等。下面我們以小魚為例介紹游戲?qū)ο蟮臉?gòu)造:

1. 常量、屬性定義

代碼清單6-7. Fish類常量、屬性定義

/**
        * 常量定義
*/
        public static final int ROTATE_DIRECTION_LEFT = 1; //左轉(zhuǎn)
        public static final int ROTATE_DIRECTION_RIGHT = 2; //右轉(zhuǎn)
        /**
        * 引用類型屬性定義
        */
        private FishInfo fishInfo;        //當(dāng)前魚的細(xì)節(jié)配置信息
        private Bitmap[] fishActs;        //當(dāng)前魚的所有動(dòng)作
        private Bitmap[] fishCatchActs;         //當(dāng)前魚的所有被捕獲動(dòng)作
        private PicActThread picActThread;         // 創(chuàng)建當(dāng)前魚的動(dòng)作線程
        /**
        * 簡單類型屬性定義
        */
        private int currentPicAct = 0;        //當(dāng)前動(dòng)作索引值
        private int currentCatchPicAct = 0;         //當(dāng)前被捕捉動(dòng)作索引值
        private boolean isAlive = true;        //當(dāng)前魚是否活著
        private float distanceHeadFishX;        //距領(lǐng)頭魚X偏移量
        private float distanceHeadFishY;        //距領(lǐng)頭魚Y偏移量
        private HeadFish headFish;        //領(lǐng)頭魚
        private boolean canRun;        //魚是否可以移動(dòng)
        private int[] fishOutlinePoint = new int[4];          //魚的外接矩形,x的小值,大值,Y的小值,大值

常量定義中定義小魚游動(dòng)的方向——左、右;引用類型中,定義小魚的活動(dòng)信息——當(dāng)前魚的細(xì)節(jié)配置信息、當(dāng)前魚的所有動(dòng)作、當(dāng)前與的所有被捕動(dòng)作、控制當(dāng)前魚動(dòng)作的線程。

2. 事件處理

當(dāng)小魚被捕時(shí)觸發(fā)一個(gè)事件,程序由此做出響應(yīng)的處理——捕捉動(dòng)作變換、小魚總數(shù)變換等。

代碼清單6-8.Fish類事件處理

/**
        * 觸發(fā)已被捕捉事件的響應(yīng)方法
        * 當(dāng)調(diào)用了這個(gè)方法,說明這條魚已經(jīng)被捕捉了
        */
        public void onCatched(Ammo ammo,final float targetX,final float targetY){
        this.setAlive(false);
        new Thread(new Runnable() {
        public void run() {
        try{
        float fishX = getHeadFish().getFish_X()-getDistanceHeadFishX();
        float fishY = getHeadFish().getFish_Y()-getDistanceHeadFishY();
        GamingInfo.getGamingInfo().getFish().remove(Fish.this);
        Thread.sleep(1800);
        //調(diào)用增加分?jǐn)?shù)方法
        ScoreManager.getScoreManager().addScore(getFishInfo().getWorth(), fishX, fishY);
        Thread.sleep(200);
        Fish.this.getPicActThread().stopPlay();
        GamingInfo.getGamingInfo().getSurface().removeDrawablePic(Fish.this.getFishInfo().getFishInLayer(), Fish.this);
        }catch(Exception e){
        Log.e("Fish_onCatched", e.toString());
                }
        }
        }).start();
        }

3. 基本信息處理

代碼清單6-9.Fish類基本信息處理

public Fish(){
        }
        public Fish(Bitmap[] fishActs,Bitmap[] fishCatchActs,FishInfo fishInfo){
        this.fishActs = fishActs;
        this.fishCatchActs = fishCatchActs;
        this.fishInfo = fishInfo;
        this.getPicMatrix().setTranslate(-500, -500);
        }
        //是否處于活動(dòng)狀態(tài)(在屏幕中游著)
        public boolean isAlive() {
        // TODO Auto-generated method stub
        return isAlive;
        }
        //設(shè)置是否處于活動(dòng)狀態(tài)
        public void setAlive(boolean isAlive) {
        // TODO Auto-generated method stub
        this.isAlive = isAlive;
        }
        /**
        * 魚旋轉(zhuǎn)點(diǎn)的X坐標(biāo)
        */
        public int getFishRotatePoint_X() {
        return getCurrentPic().getWidth()/2;
        }
        /**
        * 魚旋轉(zhuǎn)點(diǎn)的Y坐標(biāo)
        */
        public int getFishRotatePoint_Y() {
        return getCurrentPic().getHeight()/2;
        }
        public PicActThread getPicActThread() {
        return picActThread;
        }
        public void setPicActThread(PicActThread picActThread) {
        this.picActThread = picActThread;
        }
        public float getDistanceHeadFishX() {
        return distanceHeadFishX;
        }
        public void setDistanceHeadFishX(float distanceHeadFishX) {
        this.distanceHeadFishX = distanceHeadFishX;
        }
        public float getDistanceHeadFishY() {
        return distanceHeadFishY;
        }
        public void setDistanceHeadFishY(float distanceHeadFishY) {
        this.distanceHeadFishY = distanceHeadFishY;
        }
        /**
        * 獲取所有的動(dòng)作數(shù)量
        * @return
        */
        public int getFishActs() {
        // TODO Auto-generated method stub
        if(isAlive()){
        return fishActs.length;
        }else{
        return fishCatchActs.length;
        }
        }
        /**
        * 設(shè)置當(dāng)前動(dòng)作圖片的資源ID
        * @param picId
        */
        public void setCurrentPicId(int picId) {
        if(isAlive()){
        this.currentPicAct = picId;
        }else{
        this.currentCatchPicAct = picId;
                }
        }
        public int getCurrentPicId() {
        if(isAlive()){
        return currentPicAct;
        }else{
        return currentCatchPicAct;
                }
        }
        public Bitmap getCurrentPic() {
        // TODO Auto-generated method stub
        if(isAlive()){
        return fishActs[currentPicAct];
        }else{
        return fishCatchActs[currentCatchPicAct];
                }
        }
        public int getPicWidth() {
        // TODO Auto-generated method stub
        return getCurrentPic().getWidth();
        }
        public int getPicHeight() {
        // TODO Auto-generated method stub
        return getCurrentPic().getHeight();
        }
        /**
        * 設(shè)置魚的所有動(dòng)作
        * @param fishActs
        */
        public void setFishActs(Bitmap[] fishActs) {
        // TODO Auto-generated method stub
        this.fishActs = fishActs;
        }
        /**
        * 設(shè)置魚的所有被捕獲動(dòng)作
        * @param fishCatchActs
        */
        public void setFishCatchActs(Bitmap[] fishCatchActs) {
        // TODO Auto-generated method stub
        this.fishCatchActs = fishCatchActs;
        }
        public FishInfo getFishInfo() {
        return fishInfo;
        }
        public void setFishInfo(FishInfo fishInfo) {
        this.fishInfo = fishInfo;
        }
        /**
        * 根據(jù)當(dāng)前魚獲取同類魚實(shí)例
        * @return
        */
        public Fish getFish(){
        return new Fish(this.fishActs,this.fishCatchActs,this.fishInfo);
        }
        /**
        * 觸發(fā)捕捉事件的響應(yīng)方法
        */
        public void onCatch(Ammo ammo,final float targetX,final float targetY){
        // System.out.println("魚被捕捉了,但是沒有捕捉到");
        }
        public HeadFish getHeadFish() {
        return headFish;
        }
   &nbsnbsp;    public void setHeadFish(HeadFish headFish) {
        this.headFish = headFish;
        }
        public int[] getFishOutlinePoint() {
        return fishOutlinePoint;
        }
        public boolean isCanRun() {
        return canRun;
        }
        public void setCanRun(boolean canRun) {
        this.canRun = canRun;
        }

6.3.3.2.游戲?qū)ο蠊芾?/p>

仍然以小魚為例,我們使用單例模式對魚對象進(jìn)行管理。以下只列出管理類的屬性和方法的定義:

代碼清單6-10.FishManager.java

/**
        * 魚的管理器
        * @author Xiloerfan
        *
        */
        public class FishManager {
        /**
        * 單利模式
        */
        private static FishManager fishManager;
        private FishManager();
        public static FishManager getFishMananger();
        /**
        * 根據(jù)名字保存所有魚的配置信息
        */
        private HashMap<String,FishInfo> allFishConfig = new HashMap<String,FishInfo>();
        /**
        * 根據(jù)名字保存所有魚的動(dòng)作配置信息
        */
        private HashMap<String,ActConfig[]> allFishActConfigs = new HashMap<String,ActConfig[]>();
        /**
        * 根據(jù)名字保存所有魚的捕獲動(dòng)作配置信息
        */
        private HashMap<String,ActConfig[]> allFishCatchActConfigs = new HashMap<String,ActConfig[]>();
        /**
        * 根據(jù)名字緩存的魚的動(dòng)作圖片
        */
        private HashMap<String,Bitmap[]> allFishActs = new HashMap<String,Bitmap[]>();
        /**
        * 根據(jù)名字緩存的魚的捕獲動(dòng)作圖片
        */
        private HashMap<String,Bitmap[]> allFishCatchActs = new HashMap<String,Bitmap[]>();
        /**
        * 魚的種類
        */
    &nbsnbsp;   private ArrayList<String> allFish = new ArrayList<String>();
        /**
        * 根據(jù)XML配置文件,初始化所有魚
        * 這里的配置文件還沒定義,只是寫在代碼里了,以后可以改成通過讀取配置文件來加載不同
        * 資源的魚
        * @param initXml
        */
        /**
        * 是否可以創(chuàng)建新的魚
        * 這個(gè)值的改變在以下會(huì)發(fā)生:
        * 每當(dāng)調(diào)用updateFish方法時(shí),會(huì)將這個(gè)值設(shè)置為false
        * updateFish方法執(zhí)行完畢時(shí),會(huì)將這個(gè)值在改變回true
        */
        private boolean createable = false;
        /**
        * 初始化管理器
        * 這里會(huì)讀取fish文件夾下的FishConfig.plist文件,來加載所有其他配置信息
        */
        public void initFish();
        /**
        * 根據(jù)魚的名字獲取一條魚的實(shí)例
        * @param fishName
        * @return
    nbsp;    */
        public Fish birthFishByFishName(String fishName);
        /**
        * 更新加載的魚
        * @param fish
        */
        public void updateFish(String []fish);
        /**
        * 獲取所有魚的名字
        * @return
        */
&nnbsp;       public ArrayList<String> getAllFishName();
        /**
        * 銷毀釋放資源
        */
        public static void destroy();
        /**
        * 設(shè)置魚的動(dòng)作到管理器魚動(dòng)作結(jié)構(gòu)中
        * @param fishName
        * @param fishActs
        * @return true:放置成功 false:放置失敗
        */
private boolean getFishByName(String fishName,HashMap<String,ActConfig> configs);
        /**
        * 獲取魚的游動(dòng)圖片集
        * @param fishName
        * @return
        */
        private Bitmap[] getFishActByFishName(String fishName);
        /**
        * 獲取魚的被捕獲圖片集
        * @param fishName
        * @return
        */
        private Bitmap[] getFishCatchActsByFishName(String fishName);
        /**
        * 初始化魚的配置信息
        * @param config
        */
        private void initFishInfo(String config);
        /**
        * 初始化魚的動(dòng)作信息
        * @param configs 將解析出來的每個(gè)配置文件放入這個(gè)Map中
        * @param fishActConfiges 所有的配置文件名稱
        */
     &nbnbsp;  private void initFishAct(HashMap<String,ActConfig> configs,String fishActConfiges[]);

6.3.3.3.游戲?qū)ο筮壿嬏幚?/p>

游戲核心的部分是玩家體驗(yàn)的過程,而對這一過程的處理屬于游戲的邏輯部分。我們在Android游戲開發(fā)中通常使用線程來實(shí)現(xiàn)。

1.移動(dòng)魚群

a) 移動(dòng)

魚群的移動(dòng)在FishRunThread中實(shí)現(xiàn),下面是實(shí)現(xiàn)該功能的代碼:

代碼清單6-11.移動(dòng)魚群

/**
        * 移動(dòng)魚群
        */
        private void moveShoal(){
        try{
        if(fish.getShoal()==null){
        return;
        }
        for(Fish fishFlag:fish.getShoal()){
        if(!fishFlag.isCanRun()||!fishFlag.isAlive()){
        continue;
        }
        fishFlag.getFishOutlinePoint()[0] = (int)(fish.getFishOutlinePoint()[0]-fishFlag.getDistanceHeadFishX());
        fishFlag.getFishOutlinePoint()[1] = (int)(fish.getFishOutlinePoint()[1]-fishFlag.getDistanceHeadFishX());
        fishFlag.getFishOutlinePoint()[2] = (int)(fish.getFishOutlinePoint()[2]-fishFlag.getDistanceHeadFishY());
        fishFlag.getFishOutlinePoint()[3] = (int)(fish.getFishOutlinePoint()[3]-fishFlag.getDistanceHeadFishY());
        fishFlag.getPicMatrix().setTranslate(fish.getFish_X()-fishFlag.getDistanceHeadFishX(), fish.getFish_Y()-fishFlag.getDistanceHeadFishY());
        fishFlag.getPicMatrix().preRotate(fish.getCurrentRotate(),fishFlag.getFishRotatePoint_X(),fishFlag.getFishRotatePoint_Y());
                        }
                }catch(Exception e){
              nbsp;  }
        }

對魚群移動(dòng)的處理包括根據(jù)給定長度走直線(goStraight)、旋轉(zhuǎn)魚的角度并移動(dòng)(rotateRightFish、rotateLeftFish)、設(shè)置魚的外接矩形(setFishOutlinePoint),具體實(shí)現(xiàn)見工程。

b)碰撞檢測

當(dāng)魚群超出屏幕邊界時(shí),程序也作出相應(yīng)的操作:isAtOut和checkFishAtOut用于檢測,isAtOut判斷魚是否部分在屏幕外,checkFishAtOut檢測魚是否完全在屏幕外。setFishAtOut用于進(jìn)行當(dāng)魚群處于邊界外的操作,以下給出setFishOut函數(shù)的代碼:

代碼清單6-12. setFishAtOut

/**
        * 處理魚出了邊界后的操作
        */
        private void setFishAtOut() {
        fishIsOut = true;
        new Thread(new Runnable(){
        public void run() {
        try{
        // TODO Auto-generated method stub
        //如果領(lǐng)頭魚有魚群
        for(Fish fishFlag:fish.getShoal()){
        while(GamingInfo.getGamingInfo().isGaming()){
        if(checkFishAtOut(fish,fishFlag)){
        GamingInfo.getGamingInfo().getFish().remove(fishFlag);
        GamingInfo.getGamingInfo().getSurface().removeDrawablePic(fishFlag.getFishInfo().getFishInLayer(), fishFlag);
        fishFlag.getPicActThread().stopPlay();//停止動(dòng)作
        break;
        }
        try{
        Thread.sleep(10);
        }catch(Exception e){
        e.printStackTrace();
                        }
                }
     nbsp;   }
        //讓魚群移動(dòng)線程停掉
        setRun(false);
        //通知魚群管理器,這條魚已經(jīng)離開屏幕
        if(GamingInfo.getGamingInfo().isGaming()){
        GamingInfo.getGamingInfo().getShoalManager().notifyFishIsOutOfScreen();
                        }
                }catch(Exception e){
                        LogTools.doLogForException(e);
                }
        }
        }).start();
        }

2.子彈

子彈的處理在ShotTread類中實(shí)現(xiàn):

代碼清單6-13.ShotTread.java

public class ShotThread extends Thread {
        private float targetX;
        private float targetY;
        private float currentX;
        private float currentY;
        private float ammoRotateX;
        private float ammoRotateY;
        private float speed_x;        // 取一個(gè)近似值,代表每幀移動(dòng)的像素?cái)?shù)
        private float speed_y;
        private int ammo_speed = 1000 / Constant.ON_DRAW_SLEEP;        // 子彈繪制速度,這個(gè)與屏幕刷新速度一樣
        private Ammo ammo;        //子彈
        private boolean ammoActIsRun;        //子彈動(dòng)畫是否播放
  &nbsnbsp;     public ShotThread(float targetX, float targetY, Ammo ammo,float fromX,float fromY) {
        this.ammo = ammo;
        currentX = fromX;
        currentY = fromY;
        ammoRotateX = ammo.getPicWidth()/2;
        ammoRotateY = ammo.getPicHeight()/2;
        this.targetX = targetX;
        this.targetY = targetY;
        float x = Math.abs(this.targetX - fromX);        // 獲取目標(biāo)距離子彈始發(fā)的X坐標(biāo)長度
        float y = Math.abs(this.targetY - fromY);        // 獲取目標(biāo)距離子彈始發(fā)的Y坐標(biāo)長度
        float len = (float) Math.sqrt(x * x + y * y);        // 目標(biāo)和始發(fā)點(diǎn)之間的距離
        float time = len / (Constant.AMMO_SPEED / Constant.ON_DRAW_SLEEP);    &nbnbsp;    // 計(jì)算目標(biāo)與始發(fā)之間子彈需要行走的幀數(shù)
        speed_x = x / time;         // 計(jì)算子彈沿X軸行進(jìn)的增量
        speed_y = y / time;         // 計(jì)算子彈沿Y軸行進(jìn)的增量
        if (targetX < fromX) {
        speed_x = -speed_x;
        }
        if (targetY < fromY) {
        speed_y = -speed_y;
                }
        }
        public void run() {
        try{
        //如果子彈幀數(shù)多于1,就播放子彈動(dòng)畫
        if(ammo.getAmmoPicLenght()>1){
        new Thread(this.playAmmoAct()).start();
        }
        // 計(jì)算子彈需要的旋轉(zhuǎn)角度
        float angle = Tool.getAngle(targetX, targetY, currentX, currentY);
        AmmoParticleEffect effect = ParticleEffectManager.getParticleEffectManager().getAmmoEffect();
        int ammoRedius = ammo.getPicHeight()/2;//這個(gè)半徑的作用是用于計(jì)算子彈尾巴處出現(xiàn)粒子使用
        effect.playEffect((float)(ammoRedius*Math.cos(Math.toRadians(angle+180)))+ammoRotateX,-(float)(ammoRedius*Math.sin(Math.toRadians(angle+180)))+ammoRotateY,currentX, currentY, speed_x, speed_y);
        // 計(jì)算子彈的旋轉(zhuǎn)(原理與大炮一樣)
        if (angle >= 90) {
        angle = -(angle - 90);
        } else {
        angle = 90 - angle;
        }
        // 創(chuàng)建變換矩陣
     nbsp;   Matrix matrix = ammo.getPicMatrix();
        matrix.setTranslate(currentX, currentY);
        matrix.preRotate(angle,ammoRotateX,ammoRotateY);
        GamingInfo.getGamingInfo().getSurface()
        .putDrawablePic(Constant.AMMO_LAYER, ammo); // 將子彈放入圖層,等待被繪制
        // 根據(jù)增量移動(dòng)子彈
        while (GamingInfo.getGamingInfo().isGaming()) {
        while(!GamingInfo.getGamingInfo().isPause()){
        matrix.reset();
        matrix.setTranslate(currentX, currentY);
        matrix.preRotate(angle,ammoRotateX,ammoRotateY);
        currentX += speed_x;
        currentY += speed_y;
        effect.setEffectMatrix(currentX,currentY);
        if (checkHit()) {
        effect.stopEffect();
        // 命中后刪除這個(gè)子彈
        GamingInfo.getGamingInfo().getSurface()
        .removeDrawablePic(Constant.AMMO_LAYER, ammo);
        CatchFishManager.getCatchFishManager().catchFishByAmmo(currentX, currentY, ammo);
        // 如果超出屏幕,從圖層中刪除子彈
        GamingInfo.getGamingInfo().getSurface()
        .removeDrawablePic(Constant.AMMO_LAYER, ammo);
        this.ammoActIsRun = false;//停止子彈動(dòng)畫
        break;
        } else if (currentX - 100 >= GamingInfo.getGamingInfo().getScreenWidth()
        || currentX + 100 <= 0 || currentY + 100 <= 0) {
        // 如果超出屏幕,從圖層中刪除子彈
        effect.stopEffect();
        GamingInfo.getGamingInfo().getSurface()
        .removeDrawablePic(Constant.AMMO_LAYER, ammo);
        this.ammoActIsRun = false;//停止子彈動(dòng)畫
        break;
        }
        try {
        Thread.sleep(ammo_speed);
        } catch (Exception e) {
                }
        }
        break;
                }
        }catch(Exception e){
        LogTools.doLogForException(e);
                }
        }
        private Runnable playAmmoAct(){
        Runnable runnable = new Runnable(){
        public void run() {
        ammoActIsRun = true;
        int picIndex = 0;
        try {
        while(GamingInfo.getGamingInfo().isGaming()){
        while(!GamingInfo.getGamingInfo().isPause()&&ammoActIsRun){
        ammo.setCurrentId(picIndex);
        picIndex++;
        if(picIndex==ammo.getAmmoPicLenght()){
        picIndex=0;
        }
        Thread.sleep(200);
        }
        break;
                }
        } catch (Exception e) {
        // TODO: handle exception
                        }
                }
        };
        return runnable;
        }
        private boolean checkHit() {
        try{
        ArrayList<Fish> allFish = (ArrayList<Fish>)GamingInfo.getGamingInfo().getFish().clone();
        for (Fish fish : allFish) {
        if (currentX > fish.getFishOutlinePoint()[0]
        && currentX < fish.getFishOutlinePoint()[1]
        && currentY >gt; fish.getFishOutlinePoint()[2]
        && currentY < fish.getFishOutlinePoint()[3]) {
        return true;
                        }
                }
        }catch(Exception e){
        LogTools.doLogForException(e);
        }
        return false;
                }
        }

6.3.4. 游戲特效

除了游戲?qū)ο蟮奶幚恚覀冞添加了簡單的粒子系統(tǒng)、水紋效果來增強(qiáng)玩家的游戲體驗(yàn)。

6.3.4.1.粒子系統(tǒng)

粒子系統(tǒng)主要體現(xiàn)子彈的粒子效果、漁網(wǎng)的粒子效果、金幣的粒子效果,子彈的粒子效果在AmmoParticleEffect類中實(shí)現(xiàn),漁網(wǎng)的粒子效果在在NetParticleEffect類中實(shí)現(xiàn),金幣粒子效果在GoldParticleEffect中實(shí)現(xiàn),粒子管理器是ParticleEffectManager類,以下只給出子彈粒子效果的實(shí)現(xiàn)代碼:

代碼清單6-14.NetParticleEffect.java

/**
        * 子彈粒子效果
        * @author Xiloer
        *
        */
        public class AmmoParticleEffect extends DrawableAdapter{
        private static byte ADD = 1;
        private static byte REMOVE = 2;
        private static byte UPDATE = 3;
        //粒子彩色圖
        private Bitmap effectImgs[];
        private ArrayList<Particle> effects = new ArrayList<Particle>();
        private ArrayList<Particle> news = new ArrayList<Particle>();
        private ArrayList<Particle> removes = new ArrayList<Particle>();
        private int indexByDraw;//這個(gè)值用于繪制方法循環(huán)使用
        private Particle particle;//這個(gè)值用于繪制方法循環(huán)使用
        private boolean isPlay;//是否播放粒子效果
        private float targetOffsetX,targetOffsetY;//距離當(dāng)前坐標(biāo)的偏移量,這兩個(gè)值加上currentX,currentY來得到粒子初始位置
        private float currentX,currentY;
        public AmmoParticleEffect(Bitmap effectImgs[]){
        this.effectImgs = effectImgs;
        }
        /**
        * 播放一次粒子效果
        * @param x 粒子的生成位置X
        * @param y 粒子的生成位置Y
        * @param offX 粒子偏移量X 這兩個(gè)值是生成粒子時(shí)的行動(dòng)路線,這個(gè)應(yīng)該和給定的物體的偏移量相反
        * @param offY 粒子偏移量Y
        */
        public void playEffect(float targetOffsetX,float targetOffsetY,float x,float y,float offX,float offY){
        try{
        isPlay = true;
        this.targetOffsetX = targetOffsetX;
        this.targetOffsetY = targetOffsetY;
        startCreateEffectThread(x,y,offX,offY);
        GamingInfo.getGamingInfo().getSurface().putDrawablePic(Constant.PARTICLE_EFFECT_LAYER, this);
        }catch(Exception e){
        LogTools.doLogForException(e);
                }
        }
        private void updateEffect(byte mode,Particle p){
        if(mode==ADD){
        news.add(p);
        }else if(mode==REMOVE){
        removes.add(p);
        }else if(mode == UPDATE){
        if(news.size()>0){
        effects.addAll(news);
        news.clear();
        }
        if(removes.size()>0){
        effects.removeAll(removes);
        removes.clear();
                        }
                }
        }
        /**
        * 啟動(dòng)產(chǎn)生粒子的線程
        */
        private void startCreateEffectThread(final float x,final float y,final float offX,final float offY){
        this.currentX = x;
        this.currentY = y;
        new Thread(new Runnable() {
        public void run() {
        try{
        while(GamingInfo.getGamingInfo().isGaming()){
        while(!GamingInfo.getGamingInfo().isPause()&&isPlay){
        updateEffect(ADD,new Particle(currentX,currentY,offX,offY,0.5f,effectImgs[(int)(Math.random()*effectImgs.length)]));
        Thread.sleep((long)(Math.random()*201));
        }
        break;
                }
        }catch(Exception e){
        LogTools.doLogForException(e);
                }
       nbsp;         }
        }).start();
        }
        @Override
        public void onDraw(Canvas canvas, Paint paint) {
        updateEffect(UPDATE,null);
        indexByDraw = 0;
        while(GamingInfo.getGamingInfo().isGaming()){
        while(!GamingInfo.getGamingInfo().isPause()&&isPlay&&indexByDraw<effects.size()){
        particle = effects.get(indexByDraw);
        canvas.drawBitmap(particle.effect, particle.matrix, paint);
        indexByDraw++;
        }
        break;
                }
        }
        /**
        * 停止播放粒子
        */
        public void stopEffect(){
        this.isPlay = false;
        GamingInfo.getGamingInfo().getSurface().removeDrawablePic(Constant.PARTICLE_EFFECT_LAYER, this);
        }
        /**
        * 設(shè)置粒子位置
        */
        public void setEffectMatrix(float currentX,float currentY){
        this.currentX = currentX;
        this.currentY = currentY;
        Particle particle;
        for(int i =0;i<effects.size();i++){
        particle = effects.get(i);
        particle.offX -=particle.offX*0.05f;
        particle.offY -=particle.offY*0.05f;
        particle.scale -=particle.scale*0.05f;
        particle.currentX = particle.currentX+particle.offX;
        particle.currentY = particle.currentY+particle.offY;
        particle.matrix.setTranslate(particle.currentX, particle.currentY);
        particle.matrix.preScale(particle.scale, particle.scale);
        if(particle.scale<0.1){
        updateEffect(REMOVE,particle);
                        }
                }
        }
        public Bitmap getCurrentPic() {
        // TODO Auto-generated method stub
        return null;
        }
        public int getPicWidth() {
        // TODO Auto-generated method stub
        return 0;
        }
        public int getPicHeight() {
        // TODO Auto-generated method stub
        return 0;
        }
        /**
        * 粒子對象
        * @author Xiloer
        *
        */
        private class Particle{
        private Bitmap effect;
        /**
        * 當(dāng)前粒子坐在坐標(biāo)X
        */
        public float currentX;
        /**
        * 當(dāng)前粒子坐在坐標(biāo)Y
        */
        public float currentY;
        /**
        * 偏移量X
        */
        public float offX;
        /**
        * 偏移量Y
        */
        public float offY;
        /**
     nbsp;   * 縮放
        */
        public float scale;//縮放基數(shù)
        /**
        * 粒子矩陣
        */
        public Matrix matrix = new Matrix();
        /**
        *
        * @param currentX
        * @param currentY
        * @param offX
        * @param offY
        */
        public Particle(float currentX,float currentY,float offX,float offY,float scale,Bitmap effect){
        this.offX = offX;
        this.offY = offY;
        this.scale = scale;
        this.currentX = currentX-effect.getWidth()/2*scale+targetOffsetX;
        this.currentY = currentY-effect.getHeight()/2*scale+targetOffsetY;
        this.matrix.setTranslate(this.currentX, this.currentY);
        this.matrix.preScale(scale, scale);
        this.effect = effect;
                        }
                }
        }

6.3.4.2. 水紋效果

水波紋的實(shí)現(xiàn)在3D游戲中比較復(fù)雜,涉及到各種光線的處理。在2D游戲中相對簡單,我們只需要對圖片進(jìn)行簡單的處理即可。小魚快跑中的水波紋在WaterRipper類中實(shí)現(xiàn):

代碼清單6-15.水波紋

/**
        * 水波紋
        * @author Xiloer
        *
        */
        public class WaterRipple extends DrawableAdapter{
        private Bitmap[] ripple;
        private int currentId;
        public WaterRipple(Bitmap[] ripple){
        this.ripple = ripple;
        }
        public void setCurrentId(int currentId) {
        this.currentId = currentId;
        }
        public Bitmap getCurrentPic() {
        // TODO Auto-generated method stub
        return ripple[currentId];
        }
        public int getPicWidth() {
        // TODO Auto-generated method stub
  nbsp;      return getCurrentPic().getWidth();
        }
        public int getPicHeight() {
        // TODO Auto-generated method stub
        return getCurrentPic().getHeight();
        }
        }

6.3.5. 游戲音效

游戲中不可或缺的另一個(gè)方面就是音效,音效容易讓玩家將自己融入其中,隨著游戲的節(jié)奏喜怒哀樂。游戲開發(fā)的高境界就是能帶動(dòng)玩家的情緒,如果游戲沒有音效,會(huì)是一個(gè)什么樣的情況呢?可能總是感覺缺少什么一樣,玩家不會(huì)如此輕易地進(jìn)入游戲的情節(jié)。好的游戲音效和音樂可以使玩家融入游戲世界,產(chǎn)生共鳴。音效的作用還不僅限于此。如果沒有高超的游戲音效的映襯,再好的圖像技巧也無法使游戲的表現(xiàn)擺脫平庸,對玩家也沒有足夠的吸引力。開發(fā)游戲時(shí),人們常常忽視游戲的音效。開發(fā)者往往把主要精力花費(fèi)在游戲的圖像和動(dòng)畫等方面,而忽視了背景音樂和聲音效果。當(dāng)他們意識到這一點(diǎn)時(shí),通常為時(shí)已晚,這種做法顯然是不正確的。

游戲中的音效可分為如下幾類:背景音樂、劇情音樂、音效(動(dòng)作的音效、使用道具音效、輔助音效)等。背景音樂一般需要一直播放,而劇情音樂則只需要在劇情需要的時(shí)候播放,音效則是很短小的一段,比如揮刀的聲音、怪物叫聲等。

《小魚快跑》中的音效存儲(chǔ)在res文件夾的raw下,管理類分別在soundManager和musicManager實(shí)現(xiàn)。

6.4.小結(jié)

本章我們通過實(shí)現(xiàn)一個(gè)Android平臺(tái)下的小游戲《小魚快跑》學(xué)習(xí)了Android平臺(tái)游戲開發(fā)的相關(guān)知識。本章所講述的內(nèi)容基本上包括了游戲開發(fā)中經(jīng)常使用的技術(shù),由于在前面的章節(jié)中我們介紹了有關(guān)圖形繪制和操作的一些知識,我們把重點(diǎn)放在游戲框架、如何實(shí)現(xiàn)以及游戲開發(fā)流程上,關(guān)于該游戲的具體實(shí)現(xiàn)可以參考所附源代碼。

發(fā)表評論
評論列表(網(wǎng)友評論僅供網(wǎng)友表達(dá)個(gè)人看法,并不表明本站同意其觀點(diǎn)或證實(shí)其描述)
69堂免费精品视频在线播放| 99精品视频免费在线观看| 26uuu亚洲电影在线观看| 亚洲女人天堂a在线播放| 日韩av在线免播放器| 亚洲视频日韩| 久久人妻无码aⅴ毛片a片app | 亚洲精品伦理在线| 欧美日韩国产欧| 国产精品久久乐| 18免费在线视频| 多人啪嗒啪嗒在线观看免费| 国产黄色av网站| 欧美极品少妇xxxxx| 欧美性生活一区| 26uuu国产一区二区三区| 日韩亚洲国产欧美| 亚洲三级网页| 视频一区在线免费看| av在线三区| 成年人视频在线| 精品久久亚洲一级α| 97人妻一区二区精品免费视频| 天天操夜夜操av| 99久久久无码国产精品性波多| 精品久久久无码人妻字幂| 成人免费视频网站| 91精品国产777在线观看| 精品国产网站在线观看| 欧美日韩在线另类| 日韩理论片网站| 91丝袜国产在线播放| 视频一区欧美精品| 真实国产乱子伦精品一区二区三区| 色播一区二区| 精品众筹模特私拍视频| 2019中文字幕在线视频| 黄色污网站在线免费观看| 免费偷拍视频网站| 翔田千里精品久久一区二| 国产美女永久免费| 国产无遮挡又黄又爽又色视频| 国产情侣高潮对白| 91亚洲精品在线观看| 久久精品99久久久久久久久| 91精品国产91热久久久做人人| 一卡二卡欧美日韩| 国产欧美日韩在线视频| 国产精品88av| 蜜臀久久99精品久久久画质超高清 | 亚洲欧美综合国产精品一区| 网红女主播少妇精品视频| 99视频有精品高清视频| 国产在线a视频| 久久www视频| 欧美精品v日韩精品v国产精品| 国产女人精品视频| 日韩免费中文字幕| 欧美中文字幕视频在线观看| 美日韩精品免费视频| 在线观看日韩专区| 亚洲色图综合久久| 国产午夜精品视频| 亚洲深夜福利视频| 国产亚洲精品91在线| 亚洲欧美日韩国产中文| 亚洲欧美日韩天堂一区二区| 亚洲精品福利在线| 日韩中文字幕av在线| 秋霞成人午夜鲁丝一区二区三区| 色999日韩欧美国产| 日韩精品高清在线观看| 日韩精品免费在线视频观看| 精品视频久久久久久久| 日韩h在线观看| 国产一区二区三区三区在线观看| 亚洲欧美国产va在线影院| 亚洲人成网在线播放| 亚洲夜晚福利在线观看| 深夜福利日韩在线看| 久久综合伊人77777蜜臀| 九九久久久久99精品| 久久久免费精品视频| 日本一区二区在线播放| 成人免费福利在线| 久久66热这里只有精品| 先锋影音网一区| 人妻激情另类乱人伦人妻| 高清av一区二区| 四虎影视精品成人| 美女被人操视频在线观看| av777777| 在线观看高清av| 国产视频精品久久| 秋霞午夜在线观看| 97se综合| 精品人人人人| 91精品高清| 乱人伦精品视频在线观看| 免费xxxx性欧美18vr| 国产成人精品免费一区二区| 国产欧美日产一区| 一区二区三区精密机械公司| 日韩成人精品一区二区三区| 成人精品动漫一区二区三区| 色婷婷亚洲mv天堂mv在影片| 99精品国产在热久久| 国产精品中文字幕一区二区三区| 久久综合久久综合久久| 一二三区精品福利视频| 91精品国产色综合久久| 亚洲天堂av在线免费观看| 久久91精品国产91久久久| 国产欧美精品xxxx另类| 亚洲国产精品视频一区| 日韩有码免费视频| 草草影院第一页| xxxx.国产| 亚洲av成人无码网天堂| 青青草娱乐视频| 手机福利在线| 欧美大片免费高清观看| 曰本一区二区三区视频| 午夜在线一区二区| 久久久亚洲精品石原莉奈| 欧美午夜视频一区二区| 亚洲香蕉成人av网站在线观看 | 一区二区三区精品视频在线| 在线播放亚洲一区| 欧美成人午夜激情在线| 国产精品嫩草影院桃色| 欧美日韩系列| 久色视频在线播放| fc2成人免费视频| 日韩一区二区三区四区在线| 中文字幕人妻一区二区三区视频| 性xxxfllreexxx少妇| 18成人免费观看视频漫画| 福利视频在线看| 国产日韩欧美中文在线| aa级大片欧美三级| 国产精品国产三级国产aⅴ中文| 欧美一区二区三区免费| 欧美亚洲国产成人精品| 色一情一区二区三区四区 | 色网综合在线观看| 亚洲精品一区二区三区在线观看| 欧美激情乱人伦| 天天干,天天操,天天射| 国产成人在线免费看| 9.1在线观看免费| 国产精品变态另类虐交| 四虎永久在线观看| 福利h视频在线| 超级碰碰久久| 综合av在线| 国产三级精品视频| 欧美mv日韩mv| 国产精品午夜一区二区欲梦| 欧美极品少妇无套实战| 国产真实乱人偷精品人妻| 国产不卡精品视频| 免费av网页| 希岛爱理中文字幕| 亚洲精品一区二区三区不卡| 成人免费网址在线| 理论片午夜视频在线观看| 97精品国产| 中文字幕巨乱亚洲| 杨幂一区二区三区免费看视频| 亚洲伦理精品| 一区二区不卡在线视频 午夜欧美不卡在| 欧美日韩国产精选| 国产激情视频一区| 中国丰满人妻videoshd| 国产奶水涨喷在线播放| 日本成址在线观看| 三级在线看中文字幕完整版| 欧美午夜视频| 一区二区三区蜜桃网| 欧美精品在线免费| 国内精品视频一区二区三区| 久久久久久久麻豆| 高清国语自产拍免费视频国产 | 91美女片黄在线观看游戏| 蜜臀视频一区二区三区| 欧美性猛交bbbbb精品| 97操碰视频| 视频一区视频二区欧美| 成人一级视频在线观看| 亚洲高清免费观看高清完整版| 国产精选在线观看91| aaaaaav| 欧美最猛性xxxxx喷水| 日本在线视频网址| 亚洲理论在线| 在线播放/欧美激情| 亚洲成人手机在线| 久久久久免费精品国产| 欧美视频第一区| 国产精品无码AV| 午夜免费视频在线国产| 欧美午夜影院| 欧美日韩和欧美的一区二区| 亚洲综合自拍一区| 先锋影音av在线| 九九热视频免费在线观看| 亚洲精品伊人| www久久久久| 欧美极品欧美精品欧美视频| www.天天射.com| 人妻偷人精品一区二区三区| 宅男在线观看免费高清网站| 久久午夜视频| 日韩av在线免费看| 男人添女人下部视频免费| 波多野结衣电车| 91精品国产综合久久久久久豆腐| 精品成人久久| 日韩女优毛片在线| 一区二区免费在线视频| 免费黄色av片| 免费a级毛片在线播放| 久久久久久久波多野高潮日日| 日韩欧美一级精品久久| 少妇熟女一区二区| 888奇米影视| caopeng在线| 99精品视频免费观看视频| 91福利视频在线| 国产精品一区二区a| 午夜爱爱毛片xxxx视频免费看| 电影天堂av在线| 国产精品美女久久久久久不卡| 亚洲成人7777| 精品日本一区二区三区| www日韩精品| 1024免费在线视频| 免费看黄色91| 精品国产拍在线观看| 最新免费av网址| 日日干夜夜骑| 日韩免费一区| 国产精品久久国产精麻豆99网站| 日韩福利视频一区| 亚洲乱码国产乱码精品精98午夜| 国产精品亚洲视频在线观看| 超碰97人人干| 色偷偷7777www人| 精品免费视频| 欧美日韩一二区| 国产四区在线观看| 日韩久久久久久久久久| 天堂av在线电影| 99re8在线精品视频免费播放| 精品剧情在线观看| xxxx18hd亚洲hd捆绑| 黄色一级大片在线免费看国产| 岛国av在线网站| 中文字幕欧美国产| 欧美日韩成人激情| 免费黄色国产视频| 天天操天天摸天天爽| 国产精品久久777777换脸| 青青在线视频| 久久嫩草精品久久久精品| 日本高清不卡在线| 国产xxxxxxxxx| 美女视频黄a视频全免费观看| 在线欧美三区| 久久精品国产亚洲7777| 在线xxxxx| 一级网站免费观看| 2022国产麻豆剧果冻传媒剧情| 国产自产视频| 盗摄系列偷拍视频精品tp| 亚洲1区2区3区视频| 先锋影音欧美| 极品销魂一区二区三区| 国产精品宾馆| 911精品产国品一二三产区| 国产一区二区网| 夜夜摸夜夜操| 影音先锋中文字幕一区| 日韩一二三在线视频播| 国产精品第七页| 毛片网站在线| www.亚洲色图.com| 亚洲一区二区三区毛片| 中文字幕乱码在线观看| 91精品国产色综合久久久蜜香臀| 黑人精品xxx一区一二区| 日本高清久久一区二区三区| 国产精品国产三级国产普通话对白| 免费看男女www网站入口在线 | va天堂va亚洲va影视| 精品福利在线视频| www.av中文字幕| 美女被搞网站| 日本午夜一本久久久综合| 国产成人极品视频| 无码人妻精品一区二区三区蜜桃91| 欧美成人精品三级网站| 欧美日韩亚洲国产一区| 少妇性饥渴无码a区免费| jizzjizzjizzjizz日本| 日本免费在线视频不卡一不卡二| 国产91丝袜在线18| 国产精品揄拍500视频| 日韩 国产 欧美| 国产精品一区二区精品| 日韩手机在线导航| 亚洲av无码一区二区三区网址 | 男人天堂欧美日韩| 日本亚洲欧洲色α| 国产精品欧美久久久久天天影视 | 国产成人综合一区| 日本欧美亚洲| 国产99久久久国产精品潘金网站| 国内成+人亚洲| 一区二区不卡久久精品| 亚洲综合丁香| 91蜜桃网站免费观看| 亚洲色偷精品一区二区三区| 一区二区在线影院| 热re99久久精品国产66热| 日本妇乱大交xxxxx| 少妇精品久久久| 欧美日韩国产成人在线| 久久亚洲精品石原莉奈| 欧美午夜18电影| 欧美成人性色生活仑片| 国产一级一片免费播放放a| 国产亚洲观看| 亚洲人成电影在线观看天堂色| 久久久久无码国产精品| 国产精品va视频| 一区二区欧美激情| 一区二区三区视频免费看| 精品欧美午夜寂寞影院| 美女福利精品视频| 99er热精品视频| 合欧美一区二区三区| 国产精品自拍偷拍| 日韩黄色成人| 国产丶欧美丶日本不卡视频| 欧美高清视频一区| 成人满18在线观看网站免费| 99热99精品| 久久国产成人精品国产成人亚洲| 日韩一区av| 一本一道久久a久久精品| 中文字幕一区二区三区乱码不卡| 菠萝蜜视频在线观看www入口| 91精品欧美综合在线观看最新| 久久精品日韩无码| 综合激情久久| 国自产精品手机在线观看视频| www.看毛片| 日韩av在线播放中文字幕| 久久久久久九九九九| av影音资源网| 国产精品伦一区| 久久无码人妻一区二区三区| 日本蜜桃在线观看视频| 日韩精品免费电影| 免费观看日批视频| 欧美韩日精品| 激情小说综合网| 国产字幕中文| 欧美日韩另类视频| 精品人妻无码一区| 超碰97久久国产精品牛牛| 久久久久五月天| 日本一本久久| 不卡影院免费观看| 波多结衣在线观看| 小草在线视频免费播放| 中文字幕免费精品一区高清| 国产精品爽爽久久久久久| 日日摸夜夜添夜夜添国产精品| 亚洲一区三区视频在线观看| 四虎影视在线播放| 91精品国产91久久久久久一区二区 | 日本成人中文字幕在线视频| 中文字幕日韩精品久久| 国产永久免费高清在线观看| 欧美日韩一区成人| 五月天综合在线| 69p69国产精品| 成熟丰满熟妇高潮xxxxx视频| 国产黄色免费在线观看| 91麻豆精品国产91| 国产手机在线视频| 欧美日韩国产一区精品一区| 欧美日韩国产一二| 一区二区三区高清在线视频| 欧美日韩国产综合一区二区三区 | 国产精品高潮久久久久无| 白嫩情侣偷拍呻吟刺激|