我的Android进阶之旅------>Android疯狂连连看游戏的实现之加载界面图片和实现游戏Activity(四)

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

下面是res/drawable目录视图:

为了让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. package cn.oyp.link.utils;  
  2.   
  3. import java.lang.reflect.Field;  
  4. import java.util.ArrayList;  
  5. import java.util.Collections;  
  6. import java.util.List;  
  7. import java.util.Random;  
  8.   
  9. import android.content.Context;  
  10. import android.graphics.Bitmap;  
  11. import android.graphics.BitmapFactory;  
  12. import cn.oyp.link.R;  
  13. import cn.oyp.link.view.PieceImage;  
  14.   
  15. /** 
  16.  * 图片资源工具类, 主要用于读取游戏图片资源值<br/> 
  17.  * <br/> 
  18.  * 关于本代码介绍可以参考一下博客: <a href="http://blog.csdn.net/ouyang_peng">欧阳鹏的CSDN博客</a> <br/> 
  19.  */  
  20. public class ImageUtil {  
  21.     /** 
  22.      *  保存所有连连看图片资源值(int类型) 
  23.      */  
  24.     private static List<Integer> imageValues = getImageValues();  
  25.   
  26.     /** 
  27.      *  获取连连看所有图片的ID(约定所有图片ID以p_开头) 
  28.      */  
  29.     public static List<Integer> getImageValues() {  
  30.         try {  
  31.             // 得到R.drawable所有的属性, 即获取drawable目录下的所有图片  
  32.             Field[] drawableFields = R.drawable.class.getFields();  
  33.             List<Integer> resourceValues = new ArrayList<Integer>();  
  34.             for (Field field : drawableFields) {  
  35.                 // 如果该Field的名称以p_开头  
  36.                 if (field.getName().indexOf("p_") != -1) {  
  37.                     resourceValues.add(field.getInt(R.drawable.class));  
  38.                 }  
  39.             }  
  40.             return resourceValues;  
  41.         } catch (Exception e) {  
  42.             return null;  
  43.         }  
  44.     }  
  45.   
  46.     /** 
  47.      * 随机从sourceValues的集合中获取size个图片ID, 返回结果为图片ID的集合 
  48.      *  
  49.      * @param sourceValues 
  50.      *            从中获取的集合 
  51.      * @param size 
  52.      *            需要获取的个数 
  53.      * @return size个图片ID的集合 
  54.      */  
  55.     public static List<Integer> getRandomValues(List<Integer> sourceValues,  
  56.             int size) {  
  57.         // 创建一个随机数生成器  
  58.         Random random = new Random();  
  59.         // 创建结果集合  
  60.         List<Integer> result = new ArrayList<Integer>();  
  61.         for (int i = 0; i < size; i++) {  
  62.             try {  
  63.                 // 随机获取一个数字,大于、小于sourceValues.size()的数值  
  64.                 int index = random.nextInt(sourceValues.size());  
  65.                 // 从图片ID集合中获取该图片对象  
  66.                 Integer image = sourceValues.get(index);  
  67.                 // 添加到结果集中  
  68.                 result.add(image);  
  69.             } catch (IndexOutOfBoundsException e) {  
  70.                 return result;  
  71.             }  
  72.         }  
  73.         return result;  
  74.     }  
  75.   
  76.     /** 
  77.      * 从drawable目录中中获取size个图片资源ID(以p_为前缀的资源名称), 其中size为游戏数量 
  78.      *  
  79.      * @param size 
  80.      *            需要获取的图片ID的数量 
  81.      * @return size个图片ID的集合 
  82.      */  
  83.     public static List<Integer> getPlayValues(int size) {  
  84.         if (size % 2 != 0) {  
  85.             // 如果该数除2有余数,将size加1  
  86.             size += 1;  
  87.         }  
  88.         // 再从所有的图片值中随机获取size的一半数量,即N/2张图片  
  89.         List<Integer> playImageValues = getRandomValues(imageValues, size / 2);  
  90.         // 将playImageValues集合的元素增加一倍(保证所有图片都有与之配对的图片),即N张图片  
  91.         playImageValues.addAll(playImageValues);  
  92.         // 将所有图片ID随机“洗牌”  
  93.         Collections.shuffle(playImageValues);  
  94.         return playImageValues;  
  95.     }  
  96.   
  97.     /** 
  98.      * 将图片ID集合转换PieceImage对象集合,PieceImage封装了图片ID与图片本身 
  99.      *  
  100.      * @param context 
  101.      * @param resourceValues 
  102.      * @return size个PieceImage对象的集合 
  103.      */  
  104.     public static List<PieceImage> getPlayImages(Context context, int size) {  
  105.         // 获取图片ID组成的集合  
  106.         List<Integer> resourceValues = getPlayValues(size);  
  107.         List<PieceImage> result = new ArrayList<PieceImage>();  
  108.         // 遍历每个图片ID  
  109.         for (Integer value : resourceValues) {  
  110.             // 加载图片  
  111.             Bitmap bm = BitmapFactory.decodeResource(context.getResources(),  
  112.                     value);  
  113.             // 封装图片ID与图片本身  
  114.             PieceImage pieceImage = new PieceImage(bm, value);  
  115.             result.add(pieceImage);  
  116.         }  
  117.         return result;  
  118.     }  
  119.   
  120.     /** 
  121.      *  获取选中标识的图片 
  122.      * @param context 
  123.      * @return 选中标识的图片 
  124.      */  
  125.     public static Bitmap getSelectImage(Context context) {  
  126.         Bitmap bm = BitmapFactory.decodeResource(context.getResources(),  
  127.                 R.drawable.selected);  
  128.         return bm;  
  129.     }  
  130. }  


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

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

该Activity用了两个类,这两个类在下一篇博客中再进行相关描述。
  1. GameConf:负责管理游戏的初始化设置信息。
  2. GameService:负责游戏的逻辑实现。
  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值