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;
    }

}

总结

虽然游戏很简单,实现方式也很简单,但是在处理一些异常操作时还是需要用心处理的。写这个小游戏的过程中加深了自己对异步任务的理解
可直接运行的源码

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