剛畢業時候寫的一個小遊戲,今天突然看到了,想起以前的工作,同事,好多事情還歷歷在目,願大家都過得開心。
步入正題:因爲當時很菜,所以寫的很簡單,很粗陋,但是勉強能用,先看下效果不會插動圖,只能湊合着看了:
遊戲,比較簡單,功能有:最高分記錄,背景音樂播放,地鼠出現個數隨機,出現位置隨機;主要採用了異步任務來實現打地鼠,看網上好多都是直接在主線程來操作,大多都是固定生成一個地鼠,但是如果生成地鼠個數多的話,主線程可能就會卡死,或者不流暢,因此本文采用了異步任務。設計思路:通過對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 的使用注意
- AsyncTask 的實例必須在UI線程中創建
- execute(Params… params)方法必須在UI線程中調用
- 不要手動調用onPreExecute(),doInBackground(Params… params),onProgressUpdate(Progress… values),onPostExecute(Result result)這幾個方法
- 不能在doInBackground(Params… params)方法更新UI
- 一個任務實例只能執行一次,如果執行第二次將會拋出異常。 這個最重要
在打地鼠中,如果遊戲正在運行,用戶點擊了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;
}
}
總結
雖然遊戲很簡單,實現方式也很簡單,但是在處理一些異常操作時還是需要用心處理的。寫這個小遊戲的過程中加深了自己對異步任務的理解
可直接運行的源碼