正如在《我的Android进阶之旅------>Android疯狂连连看游戏的实现之状态数据模型(三)》一文中看到的,在AbstractBoard的代码中,当程序需要创建N个Piece对象时,程序会直接调用ImageUtil的getPlayImages()方法去获取图片,该方法会随机从res/drawable目录中取得N张图片。

下面是res/drawable目录视图:

Android疯狂连连看游戏的实现之加载界面图片和实现游戏Activity(四)_第1张图片" style="border:none; max-width:100%;border:1px solid black;" width="325" height="575">

为了让getPlayImages()方法能随机从res/drawable目录中取得N张图片,具体实现分为以下几步:

  1. 通过反射来获取R.drawable的所有Field(Android的每张图片资源都会自动转换为R.drawable的静态Field),并将这些Field值添加到一个List集合中。
  2. 从第一步得到的List集合中随机“抽取”N/2个图片ID。
  3. 将第二步得到的N/2个图片ID全部复制一份,这样就得到了N个图片ID,而且每个图片ID都可以找到与之配对的。
  4. 将第三步得到的N个图片ID再次“随机打乱”,并根据图片ID加载相应的Bitmap对象,最后把图片ID及对应的Bitmap封装成PieceImage对象后返回。
下面是ImageUtil类的代码:cn\oyp\link\utils\ImageUtil .java [java] view plain copy
  1. packagecn.oyp.link.utils;
  2. importjava.lang.reflect.Field;
  3. importjava.util.ArrayList;
  4. importjava.util.Collections;
  5. importjava.util.List;
  6. importjava.util.Random;
  7. importandroid.content.Context;
  8. importandroid.graphics.Bitmap;
  9. importandroid.graphics.BitmapFactory;
  10. importcn.oyp.link.R;
  11. importcn.oyp.link.view.PieceImage;
  12. /**
  13. *图片资源工具类,主要用于读取游戏图片资源值<br/>
  14. *<br/>
  15. *关于本代码介绍可以参考一下博客:<ahref="http://blog.csdn.net/ouyang_peng">欧阳鹏的CSDN博客</a><br/>
  16. */
  17. publicclassImageUtil{
  18. /**
  19. *保存所有连连看图片资源值(int类型)
  20. */
  21. privatestaticList<Integer>imageValues=getImageValues();
  22. /**
  23. *获取连连看所有图片的ID(约定所有图片ID以p_开头)
  24. */
  25. publicstaticList<Integer>getImageValues(){
  26. try{
  27. //得到R.drawable所有的属性,即获取drawable目录下的所有图片
  28. Field[]drawableFields=R.drawable.class.getFields();
  29. List<Integer>resourceValues=newArrayList<Integer>();
  30. for(Fieldfield:drawableFields){
  31. //如果该Field的名称以p_开头
  32. if(field.getName().indexOf("p_")!=-1){
  33. resourceValues.add(field.getInt(R.drawable.class));
  34. }
  35. }
  36. returnresourceValues;
  37. }catch(Exceptione){
  38. returnnull;
  39. }
  40. }
  41. /**
  42. *随机从sourceValues的集合中获取size个图片ID,返回结果为图片ID的集合
  43. *
  44. *@paramsourceValues
  45. *从中获取的集合
  46. *@paramsize
  47. *需要获取的个数
  48. *@returnsize个图片ID的集合
  49. */
  50. publicstaticList<Integer>getRandomValues(List<Integer>sourceValues,
  51. intsize){
  52. //创建一个随机数生成器
  53. Randomrandom=newRandom();
  54. //创建结果集合
  55. List<Integer>result=newArrayList<Integer>();
  56. for(inti=0;i<size;i++){
  57. try{
  58. //随机获取一个数字,大于、小于sourceValues.size()的数值
  59. intindex=random.nextInt(sourceValues.size());
  60. //从图片ID集合中获取该图片对象
  61. Integerimage=sourceValues.get(index);
  62. //添加到结果集中
  63. result.add(image);
  64. }catch(IndexOutOfBoundsExceptione){
  65. returnresult;
  66. }
  67. }
  68. returnresult;
  69. }
  70. /**
  71. *从drawable目录中中获取size个图片资源ID(以p_为前缀的资源名称),其中size为游戏数量
  72. *
  73. *@paramsize
  74. *需要获取的图片ID的数量
  75. *@returnsize个图片ID的集合
  76. */
  77. publicstaticList<Integer>getPlayValues(intsize){
  78. if(size%2!=0){
  79. //如果该数除2有余数,将size加1
  80. size+=1;
  81. }
  82. //再从所有的图片值中随机获取size的一半数量,即N/2张图片
  83. List<Integer>playImageValues=getRandomValues(imageValues,size/2);
  84. //将playImageValues集合的元素增加一倍(保证所有图片都有与之配对的图片),即N张图片
  85. playImageValues.addAll(playImageValues);
  86. //将所有图片ID随机“洗牌”
  87. Collections.shuffle(playImageValues);
  88. returnplayImageValues;
  89. }
  90. /**
  91. *将图片ID集合转换PieceImage对象集合,PieceImage封装了图片ID与图片本身
  92. *
  93. *@paramcontext
  94. *@paramresourceValues
  95. *@returnsize个PieceImage对象的集合
  96. */
  97. publicstaticList<PieceImage>getPlayImages(Contextcontext,intsize){
  98. //获取图片ID组成的集合
  99. List<Integer>resourceValues=getPlayValues(size);
  100. List<PieceImage>result=newArrayList<PieceImage>();
  101. //遍历每个图片ID
  102. for(Integervalue:resourceValues){
  103. //加载图片
  104. Bitmapbm=BitmapFactory.decodeResource(context.getResources(),
  105. value);
  106. //封装图片ID与图片本身
  107. PieceImagepieceImage=newPieceImage(bm,value);
  108. result.add(pieceImage);
  109. }
  110. returnresult;
  111. }
  112. /**
  113. *获取选中标识的图片
  114. *@paramcontext
  115. *@return选中标识的图片
  116. */
  117. publicstaticBitmapgetSelectImage(Contextcontext){
  118. Bitmapbm=BitmapFactory.decodeResource(context.getResources(),
  119. R.drawable.selected);
  120. returnbm;
  121. }
  122. }


前面已经给出了游戏界面的布局文件,该布局文件需要一个Activity来负责显示,除此之外,Activity还需要为游戏界面的按钮、GameView组件的事件提供事件监听器。 尤其是对于GameView组件,程序需要监听用户的触摸动作,当用户触摸屏幕时,程序需要获取用户触摸的是哪个方块,并判断是否需要“消除”该方块。为了判断是否消除该方块,程序需要进行如下判断:
  • 如果程序之前已经选择了某个方块,就判断当前触碰的方块是否能和之前的方块“相连”,如果可以相连,则消除两个方块;如果不能相连,则把当前方块设置为选中方块。
  • 如果程序之前没有选中方块,直接将当前方块设置为选中方块。

下面是Activity的代码:cn\oyp\link\LinkActivity.java [java] view plain copy
  1. packagecn.oyp.link;
  2. importjava.util.Timer;
  3. importjava.util.TimerTask;
  4. importandroid.app.Activity;
  5. importandroid.app.AlertDialog;
  6. importandroid.content.DialogInterface;
  7. importandroid.os.Bundle;
  8. importandroid.os.Handler;
  9. importandroid.os.Message;
  10. importandroid.os.Vibrator;
  11. importandroid.view.MotionEvent;
  12. importandroid.view.View;
  13. importandroid.widget.Button;
  14. importandroid.widget.TextView;
  15. importcn.oyp.link.board.GameService;
  16. importcn.oyp.link.board.impl.GameServiceImpl;
  17. importcn.oyp.link.utils.GameConf;
  18. importcn.oyp.link.utils.LinkInfo;
  19. importcn.oyp.link.view.GameView;
  20. importcn.oyp.link.view.Piece;
  21. /**
  22. *游戏Activity<br/>
  23. *<br/>
  24. *关于本代码介绍可以参考一下博客:<ahref="http://blog.csdn.net/ouyang_peng">欧阳鹏的CSDN博客</a><br/>
  25. */
  26. publicclassLinkActivityextendsActivity{
  27. /**
  28. *游戏配置对象
  29. */
  30. privateGameConfconfig;
  31. /**
  32. *游戏业务逻辑接口
  33. */
  34. privateGameServicegameService;
  35. /**
  36. *游戏界面
  37. */
  38. privateGameViewgameView;
  39. /**
  40. *开始按钮
  41. */
  42. privateButtonstartButton;
  43. /**
  44. *记录剩余时间的TextView
  45. */
  46. privateTextViewtimeTextView;
  47. /**
  48. *失败后弹出的对话框
  49. */
  50. privateAlertDialog.BuilderlostDialog;
  51. /**
  52. *游戏胜利后的对话框
  53. */
  54. privateAlertDialog.BuildersuccessDialog;
  55. /**
  56. *定时器
  57. */
  58. privateTimertimer=newTimer();
  59. /**
  60. *记录游戏的剩余时间
  61. */
  62. privateintgameTime;
  63. /**
  64. *记录是否处于游戏状态
  65. */
  66. privatebooleanisPlaying;
  67. /**
  68. *振动处理类
  69. */
  70. privateVibratorvibrator;
  71. /**
  72. *记录已经选中的方块
  73. */
  74. privatePieceselectedPiece=null;
  75. /**
  76. *Handler类,异步处理
  77. */
  78. privateHandlerhandler=newHandler(){
  79. publicvoidhandleMessage(Messagemsg){
  80. switch(msg.what){
  81. case0x123:
  82. timeTextView.setText("剩余时间:"+gameTime);
  83. gameTime--;//游戏剩余时间减少
  84. //时间小于0,游戏失败
  85. if(gameTime<0){
  86. //停止计时
  87. stopTimer();
  88. //更改游戏的状态
  89. isPlaying=false;
  90. //失败后弹出对话框
  91. lostDialog.show();
  92. return;
  93. }
  94. break;
  95. }
  96. }
  97. };
  98. @Override
  99. publicvoidonCreate(BundlesavedInstanceState){
  100. super.onCreate(savedInstanceState);
  101. setContentView(R.layout.main);
  102. //初始化界面
  103. init();
  104. }
  105. /**
  106. *初始化游戏的方法
  107. */
  108. privatevoidinit(){
  109. config=newGameConf(8,9,2,10,GameConf.DEFAULT_TIME,this);
  110. //得到游戏区域对象
  111. gameView=(GameView)findViewById(R.id.gameView);
  112. //获取显示剩余时间的文本框
  113. timeTextView=(TextView)findViewById(R.id.timeText);
  114. //获取开始按钮
  115. startButton=(Button)this.findViewById(R.id.startButton);
  116. //获取振动器
  117. vibrator=(Vibrator)getSystemService(VIBRATOR_SERVICE);
  118. //初始化游戏业务逻辑接口
  119. gameService=newGameServiceImpl(this.config);
  120. //设置游戏逻辑的实现类
  121. gameView.setGameService(gameService);
  122. //为开始按钮的单击事件绑定事件监听器
  123. startButton.setOnClickListener(newView.OnClickListener(){
  124. @Override
  125. publicvoidonClick(Viewsource){
  126. startGame(GameConf.DEFAULT_TIME);
  127. }
  128. });
  129. //为游戏区域的触碰事件绑定监听器
  130. this.gameView.setOnTouchListener(newView.OnTouchListener(){
  131. publicbooleanonTouch(Viewview,MotionEvente){
  132. if(e.getAction()==MotionEvent.ACTION_DOWN){
  133. gameViewTouchDown(e);
  134. }
  135. if(e.getAction()==MotionEvent.ACTION_UP){
  136. gameViewTouchUp(e);
  137. }
  138. returntrue;
  139. }
  140. });
  141. //初始化游戏失败的对话框
  142. lostDialog=createDialog("Lost","游戏失败!重新开始",R.drawable.lost)
  143. .setPositiveButton("确定",newDialogInterface.OnClickListener(){
  144. publicvoidonClick(DialogInterfacedialog,intwhich){
  145. startGame(GameConf.DEFAULT_TIME);
  146. }
  147. });
  148. //初始化游戏胜利的对话框
  149. successDialog=createDialog("Success","游戏胜利!重新开始",
  150. R.drawable.success).setPositiveButton("确定",
  151. newDialogInterface.OnClickListener(){
  152. publicvoidonClick(DialogInterfacedialog,intwhich){
  153. startGame(GameConf.DEFAULT_TIME);
  154. }
  155. });
  156. }
  157. @Override
  158. protectedvoidonPause(){
  159. //暂停游戏
  160. stopTimer();
  161. super.onPause();
  162. }
  163. @Override
  164. protectedvoidonResume(){
  165. //如果处于游戏状态中
  166. if(isPlaying){
  167. //以剩余时间重新开始游戏
  168. startGame(gameTime);
  169. }
  170. super.onResume();
  171. }
  172. /**
  173. *触碰游戏区域的处理方法
  174. *
  175. *@paramevent
  176. */
  177. privatevoidgameViewTouchDown(MotionEventevent){
  178. //获取GameServiceImpl中的Piece[][]数组
  179. Piece[][]pieces=gameService.getPieces();
  180. //获取用户点击的x座标
  181. floattouchX=event.getX();
  182. //获取用户点击的y座标
  183. floattouchY=event.getY();
  184. //根据用户触碰的座标得到对应的Piece对象
  185. PiececurrentPiece=gameService.findPiece(touchX,touchY);
  186. //如果没有选中任何Piece对象(即鼠标点击的地方没有图片),不再往下执行
  187. if(currentPiece==null)
  188. return;
  189. //将gameView中的选中方块设为当前方块
  190. this.gameView.setSelectedPiece(currentPiece);
  191. //表示之前没有选中任何一个Piece
  192. if(this.selectedPiece==null){
  193. //将当前方块设为已选中的方块,重新将GamePanel绘制,并不再往下执行
  194. this.selectedPiece=currentPiece;
  195. this.gameView.postInvalidate();
  196. return;
  197. }
  198. //表示之前已经选择了一个
  199. if(this.selectedPiece!=null){
  200. //在这里就要对currentPiece和prePiece进行判断并进行连接
  201. LinkInfolinkInfo=this.gameService.link(this.selectedPiece,
  202. currentPiece);
  203. //两个Piece不可连,linkInfo为null
  204. if(linkInfo==null){
  205. //如果连接不成功,将当前方块设为选中方块
  206. this.selectedPiece=currentPiece;
  207. this.gameView.postInvalidate();
  208. }else{
  209. //处理成功连接
  210. handleSuccessLink(linkInfo,this.selectedPiece,currentPiece,
  211. pieces);
  212. }
  213. }
  214. }
  215. /**
  216. *触碰游戏区域的处理方法
  217. *
  218. *@parame
  219. */
  220. privatevoidgameViewTouchUp(MotionEvente){
  221. this.gameView.postInvalidate();
  222. }
  223. /**
  224. *以gameTime作为剩余时间开始或恢复游戏
  225. *
  226. *@paramgameTime
  227. *剩余时间
  228. */
  229. privatevoidstartGame(intgameTime){
  230. //如果之前的timer还未取消,取消timer
  231. if(this.timer!=null){
  232. stopTimer();
  233. }
  234. //重新设置游戏时间
  235. this.gameTime=gameTime;
  236. //如果游戏剩余时间与总游戏时间相等,即为重新开始新游戏
  237. if(gameTime==GameConf.DEFAULT_TIME){
  238. //开始新的游戏游戏
  239. gameView.startGame();
  240. }
  241. isPlaying=true;
  242. this.timer=newTimer();
  243. //启动计时器,每隔1秒发送一次消息
  244. this.timer.schedule(newTimerTask(){
  245. publicvoidrun(){
  246. handler.sendEmptyMessage(0x123);
  247. }
  248. },0,1000);
  249. //将选中方块设为null。
  250. this.selectedPiece=null;
  251. }
  252. /**
  253. *成功连接后处理
  254. *
  255. *@paramlinkInfo
  256. *连接信息
  257. *@paramprePiece
  258. *前一个选中方块
  259. *@paramcurrentPiece
  260. *当前选择方块
  261. *@parampieces
  262. *系统中还剩的全部方块
  263. */
  264. privatevoidhandleSuccessLink(LinkInfolinkInfo,PieceprePiece,
  265. PiececurrentPiece,Piece[][]pieces){
  266. //它们可以相连,让GamePanel处理LinkInfo
  267. this.gameView.setLinkInfo(linkInfo);
  268. //将gameView中的选中方块设为null
  269. this.gameView.setSelectedPiece(null);
  270. this.gameView.postInvalidate();
  271. //将两个Piece对象从数组中删除
  272. pieces[prePiece.getIndexX()][prePiece.getIndexY()]=null;
  273. pieces[currentPiece.getIndexX()][currentPiece.getIndexY()]=null;
  274. //将选中的方块设置null。
  275. this.selectedPiece=null;
  276. //手机振动(100毫秒)
  277. this.vibrator.vibrate(100);
  278. //判断是否还有剩下的方块,如果没有,游戏胜利
  279. if(!this.gameService.hasPieces()){
  280. //游戏胜利
  281. this.successDialog.show();
  282. //停止定时器
  283. stopTimer();
  284. //更改游戏状态
  285. isPlaying=false;
  286. }
  287. }
  288. /**
  289. *创建对话框的工具方法
  290. *
  291. *@paramtitle
  292. *标题
  293. *@parammessage
  294. *内容
  295. *@paramimageResource
  296. *图片
  297. *@return
  298. */
  299. privateAlertDialog.BuildercreateDialog(Stringtitle,Stringmessage,
  300. intimageResource){
  301. returnnewAlertDialog.Builder(this).setTitle(title)
  302. .setMessage(message).setIcon(imageResource);
  303. }
  304. /**
  305. *停止计时
  306. */
  307. privatevoidstopTimer(){
  308. //停止定时器
  309. this.timer.cancel();
  310. this.timer=null;
  311. }
  312. }

该Activity用了两个类,这两个类在下一篇博客中再进行相关描述。
  1. GameConf:负责管理游戏的初始化设置信息。
  2. GameService:负责游戏的逻辑实现。

更多相关文章

  1. GitHub 标星 2.5K+!教你通过玩游戏的方式学习 VIM!
  2. Android侧边导航栏+ListView基础实践
  3. Android实现贪吃蛇游戏一:游戏界面及控制
  4. Android(安卓)button自定义显示
  5. Android(安卓)图片控件ImageView
  6. android中获取手机相机和相册可以传多张图片
  7. 【开源】XPShadow, 用阴影让UWP更有层次感
  8. [Android实例教程] 教你如何拍照+相册选择图片+剪裁图片完整实现
  9. Android(安卓)调用系统相机,解决回调的resultCode一直都是0的问题

随机推荐

  1. 微信小程序菜单实现
  2. [翻译][Pro Android(安卓)4]——Chapter
  3. 框架模式MVC 在Android中的使用
  4. Android(安卓)翻页效果 电子书
  5. Android+OTA+升级之一:编译升级包---make+
  6. MVP+Retrofit2+RxJava2练手项目,玩Android
  7. Android开发心得——网页通过webview调用
  8. Android实现任意分辨率视频编码的思考与
  9. Android(安卓)Kotlin 开发踩坑之旅
  10. Android中使用GreenDao的坑整理