先看看基本的效果(背景图片及老鼠图片来源于网络)
上面贴出的这几张就是该APP的游戏界面,下面谈谈一开始设计的基本思路
在布局方面,整体是一个线性布局,最下方是由两个按钮和一个文本组成
关于上方的游戏画面,有两种设计思想
1.最外层采用帧布局,设置背景为有9个空洞的那张图片,在帧布局中使用相对布局放置9个ImageView,分别放置在洞的位置。在画面左上角,设置一个CheckBox用于开关背景音乐
2.最外层采用帧布局,设置背景为有9个空洞的那张图片,在帧布局中使用相对布局放置9个按钮,分别放置在洞的位置。在画面左上角,设置一个CheckBox用于开关背景音乐
关于代码,主要的实现在于背景音乐的控制、老鼠的弹出、下方游戏时间的控制
背景音乐控制:采用了Service的基本用法,根据用户在游戏界面CheckBox的选中与否,分别开启和关闭服务,在服务中使用MediaPlayer播放背景音乐
老鼠的弹出:这个与游戏画面的不同设计有关。 对于第1种,是采用一个数组存放9个ImageView的对象,同时在线程中每隔1s生成一次随机数(0~8),刚好与ImageView的对象在数组中的序号对应,然后将对应的ImageView背景设置为那张老鼠。 对于第2种,是制作9张老鼠与背景的合成图分别作为背景,即每个洞分别与老鼠进行合成,然后将这9张图用一个数组存放起来,同时在线程中每隔1s生成一次随机数(0~8),刚好与9张合成图在数组中的序号对应,然后将包裹9个按钮的相对布局的背景设置为这张图片,由于图片的覆盖,所以看上去老鼠像是弹出的
下方游戏时间控制:当用户点击开始后,开启线程控制总时间的线程、弹出老鼠的线程和计时线程,总时间设定为60s,控制总时间的线程即sleep共60s,时间到后关闭点弹老鼠的线程和计时线程,在其sleep期间,计时线程每隔1s改变一次文本显示的内容(从60到0)
相比上次的Android简易老虎机 ,这次也是使用线程的相关知识,不同的就是添加了服务用于后台控制音乐的播放,使用了帧布局,而且解决了上次的一个小bug,就是在点击开始游戏后(开启各种线程),再次重复点击开始会造成线程重复启动,使老虎机中的转动絮乱,解决方法是使用一个标识flag,初始化为true,而且只有在flag为true时点击按钮才有效果,所以当点击开始后,会设置flag为false,同时启动一个线程,让该线程sleep一定时间,在sleep结束后设置flag为true,所以在该线程sleep期间,flag一直为flase,那么点击开始按钮是无效的
更为详细的解释都放在代码的注释中,这里贴出实现该游戏的主要代码,完整代码已上传到GitHub
public class GameActivity extends Activity{
private Button playGame,overGame;
private TextView times;
private Button btn1,btn2,btn3,btn4,btn5,btn6,btn7,btn8,btn9;
private LinearLayout ll_bg_show;
private CheckBox cb_sound;
private intbgAtrr[] =new int[9];//保存九张带有老鼠的背景图
private intbtnAtrr[] =new int[9];//保存九个洞对应的按钮
private MyHandler myHandler=newMyHandler();
private SumTime sumTime;
private GameTime gameTime;
private GoTime goTime;
private ClickTime clickTime;
private intt=59;
private intsumMouse=0;//弹出老鼠总个数
private intclickMouse=0;//用户点中的老鼠个数
private intmouseCheckedId=0;//当前弹出图片在数组中的序号
private booleanflag=true;//用于限制开始按钮的有效点击次数
@Override
protected void onCreate(BundlesavedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.game_layout);
initView();//初始化控件
initAtrr();//添加图片id到数组bgAtrr中
initListener();//初始化监听器,将监听器与开始和结束按钮进行绑定
judgeMusic();//播放背景音乐
}
private void judgeMusic() {
Intent intent Service=newIntent(GameActivity.this,MyMusicService.class);
if(cb_sound.isChecked()) {
stopService(intentService);
}else{
startService(intentService);
}
}
/**
* 计时器,游戏开始,该线程sleep一分钟,时间到后,终止弹老鼠进程和计时进程并弹出提示框,传入MyHander的参数为0
*/
class SumTime extendsThread{
@Override
public voidrun() {
super.run();
try{
sleep(60000);
}catch(InterruptedExceptione) {
e.printStackTrace();
}
gameTime.isStop();//调用gameTime的isStop方法,使其停止弹出老鼠
goTime.isStop();//调用goTime的isStop方法,结束该线程
Message message=Message.obtain();
message.what=0;
myHandler.sendMessage(message);
}
}
/**
* 随机弹出老鼠图片,传入到MyHander的参数为1
*/
class GameTime extends Thread{
private boolean isStoped=false;
private void isStop() {
isStoped=true;
gameTime.interrupt();
}
@Override
public voidrun() {
super.run();
while(!isStoped) {
try{
sleep(1000);
}catch(InterruptedExceptione) {
e.printStackTrace();
}
Message message=Message.obtain();//获取Message对象
message.what=1;
myHandler.sendMessage(message);
}
}
}
/**
* 时间计数,传入到MyHander的参数为2
*/
class GoTime extends Thread{
private boolean isStoped=false;
private void isStop() {
isStoped=true;
goTime.interrupt();
}
@Override
public void run() {
super.run();
while(!isStoped) {
try{
sleep(1000);
}catch(InterruptedExceptione) {
e.printStackTrace();
}
Message message=Message.obtain();
message.what=2;
myHandler.sendMessage(message);
}
}
}
class ClickTime implements Runnable{
@Override
public voidrun() {
try{
Thread.sleep(60000);
}catch(InterruptedExceptione) {
e.printStackTrace();
}
flag=true;
}
}
/**
* 根据传入的参数进行处理,传入为0则表示游戏结束,传入为1表示开始游戏,传入2是倒时器开始
*/
class MyHandler extends Handler{
@Override
public void handleMessage(Messagemsg) {
super.handleMessage(msg);
switch(msg.what) {
case0:
sumTime.interrupt();
AlertDialog.Builderdialog=newAlertDialog.Builder(GameActivity.this);
dialog.setTitle("游戏结束");
dialog.setMessage("本轮地鼠总数为: "+sumMouse+" 只\n"+
"您逮住的地鼠共:"+clickMouse+" 只\n"+
"捕获率:"+clickMouse*100/sumMouse+"%"
);
dialog.setCancelable(false);
dialog.setPositiveButton("再试一次",newDialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterfacedialog,intwhich) {
times.setText(60+"");
t=60;
sumMouse=0;
clickMouse=0;
onRestart();
}
});
dialog.show();
break;
case1:
Random random=newRandom();
int index=random.nextInt(9);
ll_bg_show.setBackgroundResource(bgAtrr[index]);
sumMouse++;
if(btnAtrr[index] ==mouseCheckedId) {
clickMouse++;//判断当前老鼠所在的RadioButton的id与用户点击的是否一致,若一致则为打中地鼠
}
break;
case2:
times.setText(t+"");
t--;
break;
}
}
}
class ButtonListener implementsView.OnClickListener{
@Override
public voidonClick(Viewv) {
mouseCheckedId=v.getId();
switch(v.getId()) {
caseR.id.bt_play:
if(flag) {
sumTime=newSumTime();
sumTime.start();
gameTime=newGameTime();
gameTime.start();
goTime=newGoTime();
goTime.start();
}
flag=false;
clickTime=newClickTime();
newThread(clickTime).start();
break;
caseR.id.bt_over:
finish();
Intent intent=newIntent(GameActivity.this,MyMusicService.class);
stopService(intent);
break;
caseR.id.cb_sound:
judgeMusic();
break;
}
}
}
下面列出这次开发中的一些小知识点:
设置手机强制横屏
在LInearLayout中设置 orientation属性为vertical时,layout_gravity只能够使用横向的如:left, right, center_horizontal 设置 orientation属性为horizontal时,layout_gravity只能够使用纵向的,如:top, bottom, center_vertical
在src中放置的是原图,是不会被拉伸的,如果在水平或者垂直方向需要拉伸,分别使用scaleX和scaleY,参数为缩放比例
在Activity中调用onRestart方法可以使当前Activity回到原始状态
使用Service,需要写一个类继承自Service,在需要使用服务的Activity中使用 Intent intent = new Intent(this, 定义的类.class); 使用startService(intent)开启服务 ,记得使用stopService来关闭服务,整体操作类似Activity的跳转
音频文件放在raw文件夹中(可能需要自己创建),然后使用 MediaPlayer mediaPlayer = MediaPlayer.create(this, R.raw.music) 创建播放器对象并指定音频位置,然后使用mediaPlayer.setLooping(true)设置循环播放,最后使用mediaPlayer.start()开始播放,在退出时使用mediaPlayer.release()释放资源