色yeye在线视频观看_亚洲人亚洲精品成人网站_一级毛片免费播放_91精品一区二区中文字幕_一区二区三区日本视频_成人性生交大免费看

當前位置: > 華清遠見教育科技集團 > 嵌入式學習 > 講師博文 > 游戲開發實例《小魚快跑》
游戲開發實例《小魚快跑》
時間:2016-12-12作者:華清遠見

本章要點:

本章我們將通過在Android平臺完成一個小游戲《小魚快跑》來學習Android游戲開發。

6.1. 游戲策劃

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

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

這些問題都解決了,就可以準備寫策劃案了。在寫策劃案的同時還需要考慮到美工和程序在技術上的實現以及硬件的支持,不能設計技術達不到的效果。了解了這些問題,開始編寫策劃案。

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

6.2.游戲資源

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

6.3.游戲開發

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

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

6.3.1.游戲框架設計

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

在Android中要顯示一個視圖類就必須繼承自View類,在本例中我們使用SurfaceView,SurfaceView可以直接從內存或者DMA等硬件接口取得圖像數據,因此是個非常重要的繪圖容器類。在Android開發中,布局資源通過setContentView被設置在res文件夾下,刷新率通過UI主線來控制。但是在游戲開發中,這種刷新率遠遠不能滿足我們的需求,所以我們應該自己掌控刷新率,當然Android也想到了這一點,所以提供了一個類SurfaceView,使用SurfaceView我們可以通過自己的線程去控制屏幕的刷新頻率,以達到游戲中的效果。SurfaceView中包括一主要的繪制方法onDraw和一些事件的處理,本例中我們將所有顯示在屏幕上的對象通過屬性的不同歸類到不同的圖層,并通過哈希表來存儲這些圖層,后通過圖層之間的順序、圖層中對象的順依次在屏幕上繪制。在構建這個類時還可以加入我們自己的一些方法,比如更新圖層(updatePicLayer)、對圖層的其他操作(removeDrawablePic、updateLayrIds)等。在SurfaceView中,整個圖層的繪制全部都是基于SurfaceView來實現的,我們可以從SurfaceView來獲得圖層并對其進行繪制。有了這些內容,下面構建一個用于顯示游戲界面的視圖類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>>();
        // 修改后的圖片的圖層分布,這里根據操作分為了兩個圖層,分別是添加的元素,和刪除的元素
        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]; // 定義一個圖層ID,加速獲取圖層繪制(省去了從map中獲取各個圖層排序問題)
        private Paint paint; // 畫筆
        private OnDrawThread odt; // 屏幕繪制線程,用于控制繪制幀數,周期性調用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); // 設置Paint的字體
        paint.setAntiAlias(true);
        paint.setFilterBitmap(true);
        paint.setDither(true);
        paint.setTextSize(15); // 根據不同分辨率設置字體大小
        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
        /**
        * 繪圖方法,這個方法是由線程控制,周期性調用的
        */
        public void onDraw(Canvas canvas) {
        //更新圖層內容
        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);
        }
        /**
        * 更新圖層,這里分為三種操作,分別是更新臨時圖層中的內容到繪制圖層中,刪除繪制圖層中的元素,添加繪制圖層中的元素
        * 這里加了個線程鎖,保證多線程下操作圖層的安全性
        * @param mode 對繪制圖層的操作類型,對應當前類的CHANGE_MODE常量
        * @param layerId 操作的圖層ID
        * @param draw 操作的圖層元素
        */
        private synchronized void updatePicLayer(int mode,int layerId,Drawable draw){
        switch(mode){
        //將臨時圖層中的內容更新至繪制圖層中
        case CHANGE_MODE_UPDATE:
        //如果有修改
        if(changeLayer){
        //向圖層添加新的元素
        for(Integer id:addPicLayer.keySet()){
        for(Drawable d:addPicLayer.get(id)){
        //如果要添加的元素所處圖層不存在,則創建這個圖層,并更新圖層ID數組
        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("圖層內容不存在:"+id);
            }
            }
        }
        removePicLayer.clear();
        changeLayer = false;
        }
        break;
        /**
        * 無論是向繪圖圖層中添加還是刪除元素,都不是直接操作繪制圖層,都是存放在對應的臨時圖層中,等待繪制方法繪制周期中將變化的內容更新到繪制圖層中
        * 保證多線程操作情況下的安全性
        */
        //添加一個元素
        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;
        //刪除一個元素
      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;
        }
        }
        /**
        * 將一個可繪制的圖放入圖層中
        *
        * @param layer
        *圖層號 圖層號雖然是int,但是實際上只支持到byte,原因是圖層沒有必要那么多
        * @param pic
   &nbnbsp;    *可繪制的圖
        */
        public void putDrawablePic(int layer, Drawable pic) {
        if(pic==null){
        System.out.println("圖層內容不能為空:對應圖層:"+layer);
        return;
        }
        updatePicLayer(CHANGE_MODE_ADD,layer,pic);
        }
        /**
        * 將一個可繪制的圖從圖層中移除
        *
        * @param layer
        * @param pic
        */
        public void removeDrawablePic(int layer, Drawable pic) {
        if(pic==null){
       &nbsnbsp;System.out.println("圖層內容不能為空:對應圖層:"+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數組中
        } else {
        // 創建一個新的圖層數組,長度比原來的大1位
        int picLayerIdFlag[] = new int[picLayerId.length + 1];
        for (int i = 0; i < picLayerId.length; i++) {
        // 排序操作,如果新的圖層ID小于當前圖層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數組覆蓋原有的
        this.picLayerId = picLayerIdFlag;
                }
            }
        }

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

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

public class OnDrawThread extends Thread{
        private MainSurface surface;
        private SurfaceHolder sh;
        private int drawSpeed; &nbnbsp;  //每次繪制后的休息毫秒數,這個值是根據常量中的繪制幀數決定的
        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;        }
        }

在完成了這些模塊之后,就需要通知一個Activity來控制游戲的運行,游戲開始(onCreate)、游戲重置(onResume)、游戲暫停(onPause)、事件處理(onTouchEvent)等。整個游戲界面的顯示通過繪圖線程來控制:當創建一個MainSurface對象時,在MainSurface的構造方法里面創建一個繪圖線程odt,同時SurfaceHolder.Callback監聽到MainSurface的創建時自動調用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);//設置屏幕顯示沒有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){
        /**
        * 創建一個線程來異步初始化游戲內容
        */
  &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);
        //獲得手機的寬度和高度像素單位為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() {
        //停止游戲相關活動
        GameInitManager.getGameInitManager().stop();
        //清除共享數據對象
        GamingInfo.clearGameInfo();
        super.onPause();
        }
        @Override
        public boolean onTouchEvent(MotionEvent event) {
        if(GameInitManager.getGameInitManager().isIniting()){
        return super.onTouchEvent(event);
        }
        //屏幕被觸摸
        if(event.getAction()==MotionEvent.ACTION_DOWN){
        //先看布局管理器是否有相應
        if(LayoutManager.getLayoutManager().onClick(event.getRawX(), event.getRawY())){
        return true;
        }
        //發射子彈
        CannonManager.getCannonManager().shot(event.getRawX(), event.getRawY());
        return true;
                }
        return super.onTouchEvent(event);
            }
        }

到這里我們基本完成了一個游戲的整體框架,值得一提的是,整個程序采用MVC架構,后面的所有游戲對象都只需要繼承自我們自定義的實體類DrawableAdapter,然后在相應的管理類中判斷和更改當前的游戲狀態,程序便自動找到我們需要更新和釋放的游戲對象進行操作。

6.3.2.背景設計

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

代碼清單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.精靈設計

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

代碼清單6-5.Drawable.java

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

代碼清單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()得到游戲對象當前的動作圖片,getPicMatrix()得到處理游戲對象的矩陣。在DrawableAdapter類中,onDraw方法用來實現游戲對象的繪制。

6.3.3.1.游戲對象構造

本游戲中的對象包括小魚、子彈、金幣等。下面我們以小魚為例介紹游戲對象的構造:

1. 常量、屬性定義

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

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

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

2. 事件處理

當小魚被捕時觸發一個事件,程序由此做出響應的處理——捕捉動作變換、小魚總數變換等。

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

/**
        * 觸發已被捕捉事件的響應方法
        * 當調用了這個方法,說明這條魚已經被捕捉了
        */
        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);
        //調用增加分數方法
        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);
        }
        //是否處于活動狀態(在屏幕中游著)
        public boolean isAlive() {
        // TODO Auto-generated method stub
        return isAlive;
        }
        //設置是否處于活動狀態
        public void setAlive(boolean isAlive) {
        // TODO Auto-generated method stub
        this.isAlive = isAlive;
        }
        /**
        * 魚旋轉點的X坐標
        */
        public int getFishRotatePoint_X() {
        return getCurrentPic().getWidth()/2;
        }
        /**
        * 魚旋轉點的Y坐標
        */
        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;
        }
        /**
        * 獲取所有的動作數量
        * @return
        */
        public int getFishActs() {
        // TODO Auto-generated method stub
        if(isAlive()){
        return fishActs.length;
        }else{
        return fishCatchActs.length;
        }
        }
        /**
        * 設置當前動作圖片的資源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();
        }
        /**
        * 設置魚的所有動作
        * @param fishActs
        */
        public void setFishActs(Bitmap[] fishActs) {
        // TODO Auto-generated method stub
        this.fishActs = fishActs;
        }
        /**
        * 設置魚的所有被捕獲動作
        * @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;
        }
        /**
        * 根據當前魚獲取同類魚實例
        * @return
        */
        public Fish getFish(){
        return new Fish(this.fishActs,this.fishCatchActs,this.fishInfo);
        }
        /**
        * 觸發捕捉事件的響應方法
        */
        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.游戲對象管理

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

代碼清單6-10.FishManager.java

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

6.3.3.3.游戲對象邏輯處理

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

1.移動魚群

a) 移動

魚群的移動在FishRunThread中實現,下面是實現該功能的代碼:

代碼清單6-11.移動魚群

/**
        * 移動魚群
        */
        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;  }
        }

對魚群移動的處理包括根據給定長度走直線(goStraight)、旋轉魚的角度并移動(rotateRightFish、rotateLeftFish)、設置魚的外接矩形(setFishOutlinePoint),具體實現見工程。

b)碰撞檢測

當魚群超出屏幕邊界時,程序也作出相應的操作:isAtOut和checkFishAtOut用于檢測,isAtOut判斷魚是否部分在屏幕外,checkFishAtOut檢測魚是否完全在屏幕外。setFishAtOut用于進行當魚群處于邊界外的操作,以下給出setFishOut函數的代碼:

代碼清單6-12. setFishAtOut

/**
        * 處理魚出了邊界后的操作
        */
        private void setFishAtOut() {
        fishIsOut = true;
        new Thread(new Runnable(){
        public void run() {
        try{
        // TODO Auto-generated method stub
        //如果領頭魚有魚群
        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();//停止動作
        break;
        }
        try{
        Thread.sleep(10);
        }catch(Exception e){
        e.printStackTrace();
                        }
                }
     nbsp;   }
        //讓魚群移動線程停掉
        setRun(false);
        //通知魚群管理器,這條魚已經離開屏幕
        if(GamingInfo.getGamingInfo().isGaming()){
        GamingInfo.getGamingInfo().getShoalManager().notifyFishIsOutOfScreen();
                        }
                }catch(Exception e){
                        LogTools.doLogForException(e);
                }
        }
        }).start();
        }

2.子彈

子彈的處理在ShotTread類中實現:

代碼清單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;        // 取一個近似值,代表每幀移動的像素數
        private float speed_y;
        private int ammo_speed = 1000 / Constant.ON_DRAW_SLEEP;        // 子彈繪制速度,這個與屏幕刷新速度一樣
        private Ammo ammo;        //子彈
        private boolean ammoActIsRun;        //子彈動畫是否播放
  &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);        // 獲取目標距離子彈始發的X坐標長度
        float y = Math.abs(this.targetY - fromY);        // 獲取目標距離子彈始發的Y坐標長度
        float len = (float) Math.sqrt(x * x + y * y);        // 目標和始發點之間的距離
        float time = len / (Constant.AMMO_SPEED / Constant.ON_DRAW_SLEEP);    &nbnbsp;    // 計算目標與始發之間子彈需要行走的幀數
        speed_x = x / time;         // 計算子彈沿X軸行進的增量
        speed_y = y / time;         // 計算子彈沿Y軸行進的增量
        if (targetX < fromX) {
        speed_x = -speed_x;
        }
        if (targetY < fromY) {
        speed_y = -speed_y;
                }
        }
        public void run() {
        try{
        //如果子彈幀數多于1,就播放子彈動畫
        if(ammo.getAmmoPicLenght()>1){
        new Thread(this.playAmmoAct()).start();
        }
        // 計算子彈需要的旋轉角度
        float angle = Tool.getAngle(targetX, targetY, currentX, currentY);
        AmmoParticleEffect effect = ParticleEffectManager.getParticleEffectManager().getAmmoEffect();
        int ammoRedius = ammo.getPicHeight()/2;//這個半徑的作用是用于計算子彈尾巴處出現粒子使用
        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);
        // 計算子彈的旋轉(原理與大炮一樣)
        if (angle >= 90) {
        angle = -(angle - 90);
        } else {
        angle = 90 - angle;
        }
        // 創建變換矩陣
     nbsp;   Matrix matrix = ammo.getPicMatrix();
        matrix.setTranslate(currentX, currentY);
        matrix.preRotate(angle,ammoRotateX,ammoRotateY);
        GamingInfo.getGamingInfo().getSurface()
        .putDrawablePic(Constant.AMMO_LAYER, ammo); // 將子彈放入圖層,等待被繪制
        // 根據增量移動子彈
        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();
        // 命中后刪除這個子彈
        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;//停止子彈動畫
        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;//停止子彈動畫
        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. 游戲特效

除了游戲對象的處理,我們還添加了簡單的粒子系統、水紋效果來增強玩家的游戲體驗。

6.3.4.1.粒子系統

粒子系統主要體現子彈的粒子效果、漁網的粒子效果、金幣的粒子效果,子彈的粒子效果在AmmoParticleEffect類中實現,漁網的粒子效果在在NetParticleEffect類中實現,金幣粒子效果在GoldParticleEffect中實現,粒子管理器是ParticleEffectManager類,以下只給出子彈粒子效果的實現代碼:

代碼清單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;//這個值用于繪制方法循環使用
        private Particle particle;//這個值用于繪制方法循環使用
        private boolean isPlay;//是否播放粒子效果
        private float targetOffsetX,targetOffsetY;//距離當前坐標的偏移量,這兩個值加上currentX,currentY來得到粒子初始位置
        private float currentX,currentY;
        public AmmoParticleEffect(Bitmap effectImgs[]){
        this.effectImgs = effectImgs;
        }
        /**
        * 播放一次粒子效果
        * @param x 粒子的生成位置X
        * @param y 粒子的生成位置Y
        * @param offX 粒子偏移量X 這兩個值是生成粒子時的行動路線,這個應該和給定的物體的偏移量相反
        * @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();
                        }
                }
        }
        /**
        * 啟動產生粒子的線程
        */
        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);
        }
        /**
        * 設置粒子位置
        */
        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;
        /**
        * 當前粒子坐在坐標X
        */
        public float currentX;
        /**
        * 當前粒子坐在坐標Y
        */
        public float currentY;
        /**
        * 偏移量X
        */
        public float offX;
        /**
        * 偏移量Y
        */
        public float offY;
        /**
     nbsp;   * 縮放
        */
        public float scale;//縮放基數
        /**
        * 粒子矩陣
        */
        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. 水紋效果

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

代碼清單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. 游戲音效

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

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

《小魚快跑》中的音效存儲在res文件夾的raw下,管理類分別在soundManager和musicManager實現。

6.4.小結

本章我們通過實現一個Android平臺下的小游戲《小魚快跑》學習了Android平臺游戲開發的相關知識。本章所講述的內容基本上包括了游戲開發中經常使用的技術,由于在前面的章節中我們介紹了有關圖形繪制和操作的一些知識,我們把重點放在游戲框架、如何實現以及游戲開發流程上,關于該游戲的具體實現可以參考所附源代碼。

發表評論
評論列表(網友評論僅供網友表達個人看法,并不表明本站同意其觀點或證實其描述)
主站蜘蛛池模板: 国语精品自产拍在线观看网站 | 在线精品免费视频无码的 | 亚洲成年看片在线观看 | 人妻中文字幕乱人伦在线 | 性色a∨精品高清在线观看 国语一级片 | 欧美精产国品一二三产品区别在哪 | 国产精品乱码久久久久久小说 | 国产婷婷一区二区三区 | 18禁止进入1000部高潮网站 | 国产香蕉一区二区三区在线视频 | 久久精品国产99 | 变态另类AV天堂手机版 | 久久综合久久久久88 | 欧美成人极品 | 人妻被黑人粗大的猛烈进出 | 欧美日韩网站在线观看 | 国产精品地址 | 激情五月天操女人 | 啪啪无码人妻丰满熟妇 | 国产亚洲精品综合一区91555 | 色情久久久AV熟女人妻网站 | 中文字幕乱码亚洲无线码 | 色狠狠色噜噜AV一区 | 国产精品久久久久久久久绿色 | 欧美人与动性XXXXX杂性 | 2017最新高清无码网站 | 免费国产欧美国日产A | 麻豆影视在线免费观看 | 欧美aaaaxxxxx精品 | 中文精品久久久久鬼色 | 日韩在线看片免费人成视频播放 | 日本高清一级片 | 国产综合色在线精品 | JULIA无码人妻中文字幕在线 | 爆乳美女午夜福利视频 | 亚洲色无码A片一区二区潘甜甜 | 国产熟妇久久777777 | 无码人妻丰满熟妇区毛片18 | 三年片在线观看大全国语 | 中文字幕精品亚洲无线码二区 | 性奴老师穿乳环上锁野外调教 |