Android小遊戲 打地鼠

剛畢業時候寫的一個小遊戲,今天突然看到了,想起以前的工作,同事,好多事情還歷歷在目,願大家都過得開心。 

步入正題:因爲當時很菜,所以寫的很簡單,很粗陋,但是勉強能用,先看下效果不會插動圖,只能湊合着看了:
主界面

進入遊戲界面

遊戲開始

地鼠被揍

遊戲,比較簡單,功能有:最高分記錄,背景音樂播放,地鼠出現個數隨機,出現位置隨機;主要採用了異步任務來實現打地鼠,看網上好多都是直接在主線程來操作,大多都是固定生成一個地鼠,但是如果生成地鼠個數多的話,主線程可能就會卡死,或者不流暢,因此本文采用了異步任務。設計思路:通過對imageview切換不同的背景圖片達到地鼠出現,地鼠被打,地鼠消失的效果。

主界面實現

-太簡單了,不說了,就放了兩個button

遊戲邏輯實現

雖然也很簡單,但是不說就沒得說了,先看下整個代碼(不完整):
遊戲中的幾個地鼠洞用一維數組保存(第一次寫的時候我竟然使用了二維數組,不知道咋想的),具體操作可以看註釋,後面也會對需要注意的地方進行解釋


public class GameView extends Activity implements View.OnClickListener {
       //遊戲狀態
    static final int GAME_START = 0;
    static final int GAME_RUN = 1;
    static final int GAME_OVER = -1;


    @Override
    protected void onCreate(Bundle savedInstanceState) {

        super.onCreate(savedInstanceState);
        setContentView(R.layout.view_game);
        this.init();
        Intent intent = new Intent(GameView.this,MusicService.class);
        startService(intent);//啓動播放背景音樂的service
    }

    public void init() {

        mHighScores = myHighestScore.getScore(this);
        tvHighScore.setText(""+this.mHighScores);
        //初始化遊戲界面9個區域顯示爲地鼠洞
        for (int i = 0; i<ivView.length; i++) {
                ivView[i].setBackgroundResource(R.drawable.img_game_no);
                this.ivView[i].setClickable(false);
        }
    }
    //點擊地鼠的事件
    public void hit(View view) {

        view.setBackgroundResource(R.drawable.img_game_hit);
        view.setClickable(false);
        this.mScores++;
        this.mTempTime = this.mTime - mScores * 20;
        this.tvScore.setText(""+this.mScores);
   }
 // 底部開始  結束遊戲的事件
    public void onClick(View v) {

        if (v.getId() == R.id.new_game_btn) {
        //開始計時
            chTime.setBase(SystemClock.elapsedRealtime());
            chTime.start();
            chTime.setOnChronometerTickListener(new Chronometer.OnChronometerTickListener() {
                @Override
                public void onChronometerTick(Chronometer chronometer) {
                    if (mFlag != GAME_OVER){
                //顯示30s倒計時
                        tvTime.setText(((30 * 1000 + 500 -(curTime+SystemClock.elapsedRealtime() - chTime.getBase())) / 1000) + "s");}
                }
            });

            this.btnStart.setClickable(false);
            this.btnStop.setClickable(true);
            this.mFlag = GAME_RUN;
            this.mScores = 0;
            this.curTime = 0;
            this.tvScore.setText("");
            //開啓異步任務
            myAsyncTask = new MyAsyncTask();
            myAsyncTask.execute();
        }else if (v.getId() == R.id.stop_btn) {
            this.btnStart.setClickable(true);
            this.btnStop.setClickable(false);
            chTime.stop();
            gameOver();
        }
    }

    public void writeScore() {

        if (mScores > mHighScores) {

            myHighestScore.saveScore(this, mScores);
            mHighScores = myHighestScore.getScore(this);
            tvHighScore.setText("" + this.mHighScores);
        }else {
            tvHighScore.setText("" + this.mHighScores);
        }
    }

    public void gameOver() {

        this.mFlag = GAME_OVER;
        writeScore();
        this.mScores = 0;
        this.mTempTime = 1000;
        btnStart.setClickable(true);
        Toast.makeText(GameView.this, R.string.toast_game_over,Toast.LENGTH_LONG).show();
    }

 //計算地鼠出現個數和位置的異步任務
    class MyAsyncTask extends AsyncTask<String,Integer,String> {

        @Override
        protected String doInBackground(String... param) {

            while (mFlag == GAME_RUN) {
                if (isCancelled()) {
                    break;
                }

                int mouseNum = (int) ((Math.random() * 10) % 3);
                int[] position = getRandom();
                try {
                    Thread.sleep(mTempTime);
                }catch (InterruptedException e) {
                    e.printStackTrace();
                }
                switch (mouseNum +1){
                    case 1:
                        this.publishProgress(position[0]);
                        break;
                    case 2:
                        this.publishProgress(position[0],position[1]);
                        break;
                    case 3:
                        this.publishProgress(position[0],position[1],position[2]);
                        break;
                }
            }
            return null;
        }

        @Override
        protected void onProgressUpdate(Integer... values) {

           if (isCancelled()) {
               return;
           } else {

               if (mFlag == GAME_RUN) {

                   ivView[positionX].setBackgroundResource(R.drawable.img_game_no);
                   ivView[positionX].setClickable(false);
                   ivView[positionY].setBackgroundResource(R.drawable.img_game_no);
                   ivView[positionY].setClickable(false);
                   ivView[positionZ].setBackgroundResource(R.drawable.img_game_no);
                   ivView[positionZ].setClickable(false);
                   if (values.length >= 1){
                        ivView[values[0]].setBackgroundResource(R.drawable.img_game_have);
                        ivView[values[0]].setClickable(true);
                       positionX = values[0];

                   }
                   if (values.length >= 2){
                        ivView[values[1]].setBackgroundResource(R.drawable.img_game_have);
                        ivView[values[1]].setClickable(true);
                        positionY = values[1];
                   }
                   if (values.length >= 3){
                       ivView[values[2]].setBackgroundResource(R.drawable.img_game_have);
                       ivView[values[2]].setClickable(true);
                       positionZ = values[2];
                   }
                   if (curTime + SystemClock.elapsedRealtime() - chTime.getBase() > 30 * 1000+300) {

                       chTime.stop();
                       mTempTime = 1000;
                       mFlag = GAME_OVER;
                       btnStart.setClickable(true);
                       writeScore();
                       Toast.makeText(GameView.this, R.string.toast_game_over, Toast.LENGTH_SHORT).show();
                   }

               }
               if (mFlag == GAME_OVER) {
                   ivView[positionX].setClickable(false);
                   ivView[positionX].setBackgroundResource(R.drawable.img_game_no);
                   ivView[positionY].setClickable(false);
                   ivView[positionY].setBackgroundResource(R.drawable.img_game_no);
                   ivView[positionZ].setClickable(false);
                   ivView[positionZ].setBackgroundResource(R.drawable.img_game_no);
                   if(myAsyncTask != null && myAsyncTask.getStatus() == AsyncTask.Status.RUNNING) {
                       myAsyncTask.cancel(true);
                   }

               }
           }
        }

    }

    @Override
    public void onBackPressed() {

        if(mIsFirst){
            Toast.makeText(this,R.string.toast_game_back,Toast.LENGTH_SHORT).show();
            mLastTime=System.currentTimeMillis();
            mIsFirst=false;
        }else {
            if ((System.currentTimeMillis()-mLastTime)<2000){
                this.finish();

            }else {
                Toast.makeText(this, R.string.toast_game_back,Toast.LENGTH_SHORT).show();
                mLastTime=System.currentTimeMillis();
            }
        }
    }

    @Override
    protected void onPause() {
        super.onPause();
        //必須這樣做,因爲異步任務只能執行一次
        if(myAsyncTask != null && myAsyncTask.getStatus() == AsyncTask.Status.RUNNING) {
            myAsyncTask.cancel(true);
        }
    }

    @Override
    protected void onStart() {
        super.onStart();
    }

    @Override
    protected void onResume() {
        super.onResume();
        Intent intent = new Intent(GameView.this,MusicService.class);
        startService(intent);
      //activity生命週期發生變化時,重啓異步任務
        myAsyncTask = new MyAsyncTask();
        myAsyncTask.execute();
    }

    @Override
    protected void onStop() {
        super.onStop();
        Intent intent = new Intent(GameView.this,MusicService.class);
        stopService(intent);
       //activity生命週期發生變化時,保存當前已經消耗的時間
        chTime.stop();
        curTime=curTime+ SystemClock.elapsedRealtime() - chTime.getBase();
    }

    @Override
    protected void onRestart() {
        super.onRestart();
        chTime.setBase(SystemClock.elapsedRealtime());
        chTime.start();
    }

    @Override
    protected void onDestroy() {
        Intent intent = new Intent(GameView.this,MusicService.class);
        stopService(intent);
        super.onDestroy();
    }
}

寫的過程遇到的坑有以下需要注意的:

  • 倒計時的實現:剛開始想使用Chronometer 控件來實現,但是效果不好,這個控件實現正計時比較好,所以採用了Chronometer + TextView ,倒計時通過Chronometer的 onChronometerTick方法 用TextView來顯示,Chronometer設置爲gone 。
            //設置起始時間
            chTime.setBase(SystemClock.elapsedRealtime());
            chTime.start();
            chTime.setOnChronometerTickListener(new 
                Chronometer.OnChronometerTickListener() {
                @Override
                public void onChronometerTick(Chronometer chronometer) {
                    if (mFlag != GAME_OVER){
        //顯示30s倒計時,用30s - 當前已經消耗的時間 - 計時器的時間 = 剩餘時間
                        tvTime.setText(((30 * 1000 + 500 -(curTime+SystemClock.elapsedRealtime() - chTime.getBase())) / 1000) + "s");}
                }
            });

其中使用當前已用時間是爲了防止activity異常退出時計時混亂,在onStop方法中對其進行改變

 @Override
    protected void onStop() {
        super.onStop();
        chTime.stop();
        curTime=curTime+ SystemClock.elapsedRealtime() - chTime.getBase();
    }

在onRestart方法中重新給計時器設置起始時間

 @Override
    protected void onRestart() {
        super.onRestart();
        chTime.setBase(SystemClock.elapsedRealtime());
        chTime.start();
    }

以上三個地方相互配合才能比較好的實現倒計時這個功能
- 第二個坑是隨機數的計算,計算隨機數是爲了讓地鼠出現在不同的位置上,可以比較下面兩處代碼,一個是我第一次寫的,一個是後面改的

//第一次寫的兩組隨機數生成,如果相等,則重新生成,可以看到,這個方法簡直是效率太低,理論上是可能會出現卡死的,
                do {
                    double r = Math.random();
                     x = ((int) (r*10))%3;
                     r = Math.random();
                     y = ((int) (r*10))%3;
                     r = Math.random();
                     i = ((int) (r*10))%3;
                     r = Math.random();
                     j = ((int) (r*10))%3;
                } while (x == i && y == j);

改進後的算法,感覺可以通用,所以直接寫成了一個方法,這個方法返回一個數組,數組裏存放的就是兩個不等的int值,想生成別的隨機數,可以對該方法稍微改動,就可以。

public int[] getRandom() {
        int startArray[] = {0,1,2,3,4,5,6,7,8};//預期隨機數的值,打地鼠需要隨機生成0-8這9個數字,因此該數組長這樣
        int N = 3;//隨機數個數,你想生成幾個隨機數,就定義爲幾
        int[] resArray = new int[N];
        Random r = new Random();
        for(int i = 0; i < N; i++)
        {
            int seed = r.nextInt(startArray.length-i);//從剩下的隨機數裏生成
            resArray[i] = startArray[seed];//賦值給結果數組
            startArray[seed] = startArray[startArray.length - i-1];//把隨機數產生過的位置替換爲未被選中的值。
        }
        return resArray;
    }

該方法明顯效率很高,因爲預選數組的某個位置被選中後,就把它替換爲未選中的值,然後從剩下的隨機數裏生成新的,這樣就可以保證每次生成的隨機數絕對不一樣。不理解的可以跑一遍算法。

  • 第三個坑:AsyncTask 的使用注意

    1. AsyncTask 的實例必須在UI線程中創建
    2. execute(Params… params)方法必須在UI線程中調用
    3. 不要手動調用onPreExecute(),doInBackground(Params… params),onProgressUpdate(Progress… values),onPostExecute(Result result)這幾個方法
    4. 不能在doInBackground(Params… params)方法更新UI
    5. 一個任務實例只能執行一次,如果執行第二次將會拋出異常。 這個最重要

    在打地鼠中,如果遊戲正在運行,用戶點擊了home鍵,那麼遊戲應該處於暫停狀態,計時器暫停、音樂暫停、地鼠出現暫停。 這個該怎麼實現呢,處理這一塊的時候因爲AsyncTask 一個實例只能執行一次,調試了很多次。我們把對AsyncTask 的操作單獨拿出來講一下

        //點擊開始遊戲,啓動異步任務
        if (v.getId() == R.id.new_game_btn) {
            myAsyncTask = new MyAsyncTask();
            myAsyncTask.execute();
// 如果收到遊戲結束的信號,先檢查AsyncTask的狀態,如果RUNNING,則取消異步任務
        if (mFlag == GAME_OVER) {
                 if(myAsyncTask != null && myAsyncTask.getStatus() == AsyncTask.Status.RUNNING) {
                       myAsyncTask.cancel(true);
                   }

               }
//遊戲界面被遮擋或不可見時就要取消異步任務,在onPause方法中檢查AsyncTask的狀態,如果RUNNING,則取消異步任務

 @Override
    protected void onPause() {
        super.onPause();
        if(myAsyncTask != null && myAsyncTask.getStatus() == AsyncTask.Status.RUNNING) {
            myAsyncTask.cancel(true);
        }
    }
//用戶又返回了遊戲界面,則要開啓異步任務,出現地鼠,在onResume方法中開啓AsyncTask

 @Override
    protected void onResume() {
        super.onResume();
        myAsyncTask = new MyAsyncTask();
        myAsyncTask.execute();
    }

背景音樂播放

這塊代碼很簡單,就是開啓一個service去播放循環播放

package com.example.shen.myapplication;

import android.app.Service;
import android.content.Intent;
import android.media.MediaPlayer;
import android.os.IBinder;
import java.io.IOException;

public class MusicService extends Service {
    private MediaPlayer mMediaPlayer;

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {

        mMediaPlayer.start();
        mMediaPlayer.setOnCompletionListener(new MediaPlayer.OnCompletionListener() {

            @Override
            public void onCompletion(MediaPlayer mediaplayer) {

                try {
                    mediaplayer.start();
                } catch (IllegalStateException e) {
                    e.printStackTrace();
                }
            }
        });

        mMediaPlayer.setOnErrorListener(new MediaPlayer.OnErrorListener() {

            public boolean onError(MediaPlayer mediaplayer, int what, int extra) {

                try {
                    mediaplayer.release();
                } catch (Exception e) {
                    e.printStackTrace();
                }

                return false;
            }
        });

        return super.onStartCommand(intent, flags, startId);
    }

    @Override
    public void onCreate() {

        try {
            mMediaPlayer = new MediaPlayer();
            mMediaPlayer = MediaPlayer.create(MusicService.this, R.raw.game_bgmusic);
            mMediaPlayer.prepare();
        } catch (IllegalStateException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
        super.onCreate();
    }

    @Override
    public void onDestroy() {

        mMediaPlayer.stop();
        mMediaPlayer.release();
        super.onDestroy();
    }

    @Override
    public IBinder onBind(Intent intent) {
        return null;
    }

}

總結

雖然遊戲很簡單,實現方式也很簡單,但是在處理一些異常操作時還是需要用心處理的。寫這個小遊戲的過程中加深了自己對異步任務的理解
可直接運行的源碼

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章