刚毕业时候写的一个小游戏,今天突然看到了,想起以前的工作,同事,好多事情还历历在目,愿大家都过得开心。
步入正题:因为当时很菜,所以写的很简单,很粗陋,但是勉强能用,先看下效果,附带一个贪吃蛇的效果图,凑合着看了:
游戏,比较简单,功能有:最高分记录,背景音乐播放,地鼠出现个数随机,出现位置随机;主要采用了异步任务来实现打地鼠,看网上好多都是直接在主线程来操作,大多都是固定生成一个地鼠,但是如果生成地鼠个数多的话,主线程可能就会卡死,或者不流畅,因此本文采用了异步任务。设计思路:通过对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;
}
}
##总结
虽然游戏很简单,实现方式也很简单,但是在处理一些异常操作时还是需要用心处理的。写这个小游戏的过程中加深了自己对异步任务的理解
不经常登陆,有需要让我发源码到邮箱,看到时已经隔了一两天了。因此需要的加QQ826969531留言即可。
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
如需转载请保留出处:http://bianchenghao.cn/38580.html