android开发学习之路——连连看之游戏Activity(四)
前面连连看之游戏界面(一)中已设计出游戏界面的布局文件,该布局文件需要使用一个Activity来负责显示,除此以外,Activity还需要为游戏界面的按钮、GameView组件的事件提供事件监听器。
尤其是对于GameView组件,程序需要监听用户的触碰动作,当用户触碰屏幕时,程序需要获取用户触碰的是哪个方块,并判断是否需要“消除”该方块。为了判断能否消除该方块,程序需要进行如下判断:
·如果程序之前已经选中了某个方块,就判断当前触碰多的方块是否能与之前的方块“相连”,如果可以相连,则消除两个方块;如果两个方块不可以相连,则把当前方块设置为选中方块。
·如果程序之前没有选中方块,直接将当前方块设置为选中方块。
Activity的代码如下:src\org\crazyit\link\Link.java
1 public class Link extends Activity 2 { 3 // 游戏配置对象 4 private GameConf config; 5 // 游戏业务逻辑接口 6 private GameService gameService; 7 // 游戏界面 8 private GameView gameView; 9 // 开始按钮 10 private Button startButton; 11 // 记录剩余时间的TextView 12 private TextView timeTextView; 13 // 失败后弹出的对话框 14 private AlertDialog.Builder lostDialog; 15 // 游戏胜利后的对话框 16 private AlertDialog.Builder successDialog; 17 // 定时器 18 private Timer timer = new Timer(); 19 // 记录游戏的剩余时间 20 private int gameTime; 21 // 记录是否处于游戏状态 22 private boolean isPlaying; 23 // 振动处理类 24 private Vibrator vibrator; 25 // 记录已经选中的方块 26 private Piece selected = null; 27 private Handler handler = new Handler() 28 { 29 public void handleMessage(Message msg) 30 { 31 switch (msg.what) 32 { 33 case 0x123: 34 timeTextView.setText("剩余时间: " + gameTime); 35 gameTime--; 36 // 时间小于0, 游戏失败 37 if (gameTime < 0) 38 { 39 stopTimer(); 40 // 更改游戏的状态 41 isPlaying = false; 42 lostDialog.show(); 43 return; 44 } 45 break; 46 } 47 } 48 }; 49 50 @Override 51 public void onCreate(Bundle savedInstanceState) 52 { 53 super.onCreate(savedInstanceState); 54 setContentView(R.layout.main); 55 // 初始化界面 56 init(); 57 } 58 59 // 初始化游戏的方法 60 private void init() 61 { 62 config = new GameConf(8, 9, 2, 10 , 100000, this); 63 // 得到游戏区域对象 64 gameView = (GameView) findViewById(R.id.gameView); 65 // 获取显示剩余时间的文本框 66 timeTextView = (TextView) findViewById(R.id.timeText); 67 // 获取开始按钮 68 startButton = (Button) this.findViewById(R.id.startButton); 69 // 获取振动器 70 vibrator = (Vibrator) getSystemService(VIBRATOR_SERVICE); 71 gameService = new GameServiceImpl(this.config); 72 gameView.setGameService(gameService); 73 // 为开始按钮的单击事件绑定事件监听器 74 startButton.setOnClickListener(new View.OnClickListener() 75 { 76 @Override 77 public void onClick(View source) 78 { 79 startGame(GameConf.DEFAULT_TIME); 80 } 81 }); 82 // 为游戏区域的触碰事件绑定监听器 83 this.gameView.setOnTouchListener(new View.OnTouchListener() 84 { 85 public boolean onTouch(View view, MotionEvent e) 86 { 87 if (e.getAction() == MotionEvent.ACTION_DOWN) 88 { 89 gameViewTouchDown(e); 90 } 91 if (e.getAction() == MotionEvent.ACTION_UP) 92 { 93 gameViewTouchUp(e); 94 } 95 return true; 96 } 97 }); 98 // 初始化游戏失败的对话框 99 lostDialog = createDialog("Lost", "游戏失败! 重新开始", R.drawable.lost) 100 .setPositiveButton("确定", new DialogInterface.OnClickListener() 101 { 102 public void onClick(DialogInterface dialog, int which) 103 { 104 startGame(GameConf.DEFAULT_TIME); 105 } 106 }); 107 // 初始化游戏胜利的对话框 108 successDialog = createDialog("Success", "游戏胜利! 重新开始", 109 R.drawable.success).setPositiveButton("确定", 110 new DialogInterface.OnClickListener() 111 { 112 public void onClick(DialogInterface dialog, int which) 113 { 114 startGame(GameConf.DEFAULT_TIME); 115 } 116 }); 117 } 118 @Override 119 protected void onPause() 120 { 121 // 暂停游戏 122 stopTimer(); 123 super.onPause(); 124 } 125 @Override 126 protected void onResume() 127 { 128 // 如果处于游戏状态中 129 if (isPlaying) 130 { 131 // 以剩余时间重写开始游戏 132 startGame(gameTime); 133 } 134 super.onResume(); 135 } 136 137 // 触碰游戏区域的处理方法 138 private void gameViewTouchDown(MotionEvent event) 139 { 140 // 获取GameServiceImpl中的Piece[][]数组 141 Piece[][] pieces = gameService.getPieces(); 142 // 获取用户点击的x座标 143 float touchX = event.getX(); 144 // 获取用户点击的y座标 145 float touchY = event.getY(); 146 // 根据用户触碰的座标得到对应的Piece对象 147 Piece currentPiece = gameService.findPiece(touchX, touchY); 148 // 如果没有选中任何Piece对象(即鼠标点击的地方没有图片), 不再往下执行 149 if (currentPiece == null) 150 return; 151 // 将gameView中的选中方块设为当前方块 152 this.gameView.setSelectedPiece(currentPiece); 153 // 表示之前没有选中任何一个Piece 154 if (this.selected == null) 155 { 156 // 将当前方块设为已选中的方块, 重新将GamePanel绘制, 并不再往下执行 157 this.selected = currentPiece; 158 this.gameView.postInvalidate(); 159 return; 160 } 161 // 表示之前已经选择了一个 162 if (this.selected != null) 163 { 164 // 在这里就要对currentPiece和prePiece进行判断并进行连接 165 LinkInfo linkInfo = this.gameService.link(this.selected, 166 currentPiece); 167 // 两个Piece不可连, linkInfo为null 168 if (linkInfo == null) 169 { 170 // 如果连接不成功, 将当前方块设为选中方块 171 this.selected = currentPiece; 172 this.gameView.postInvalidate(); 173 } 174 else 175 { 176 // 处理成功连接 177 handleSuccessLink(linkInfo, this.selected 178 , currentPiece, pieces); 179 } 180 } 181 } 182 // 触碰游戏区域的处理方法 183 private void gameViewTouchUp(MotionEvent e) 184 { 185 this.gameView.postInvalidate(); 186 } 187 188 // 以gameTime作为剩余时间开始或恢复游戏 189 private void startGame(int gameTime) 190 { 191 // 如果之前的timer还未取消,取消timer 192 if (this.timer != null) 193 { 194 stopTimer(); 195 } 196 // 重新设置游戏时间 197 this.gameTime = gameTime; 198 // 如果游戏剩余时间与总游戏时间相等,即为重新开始新游戏 199 if(gameTime == GameConf.DEFAULT_TIME) 200 { 201 // 开始新的游戏游戏 202 gameView.startGame(); 203 } 204 isPlaying = true; 205 this.timer = new Timer(); 206 // 启动计时器 , 每隔1秒发送一次消息 207 this.timer.schedule(new TimerTask() 208 { 209 public void run() 210 { 211 handler.sendEmptyMessage(0x123); 212 } 213 }, 0, 1000); 214 // 将选中方块设为null。 215 this.selected = null; 216 } 217 218 /** 219 * 成功连接后处理 220 * 221 * @param linkInfo 连接信息 222 * @param prePiece 前一个选中方块 223 * @param currentPiece 当前选择方块 224 * @param pieces 系统中还剩的全部方块 225 */ 226 private void handleSuccessLink(LinkInfo linkInfo, Piece prePiece, 227 Piece currentPiece, Piece[][] pieces) 228 { 229 // 它们可以相连, 让GamePanel处理LinkInfo 230 this.gameView.setLinkInfo(linkInfo); 231 // 将gameView中的选中方块设为null 232 this.gameView.setSelectedPiece(null); 233 this.gameView.postInvalidate(); 234 // 将两个Piece对象从数组中删除 235 pieces[prePiece.getIndexX()][prePiece.getIndexY()] = null; 236 pieces[currentPiece.getIndexX()][currentPiece.getIndexY()] = null; 237 // 将选中的方块设置null。 238 this.selected = null; 239 // 手机振动(100毫秒) 240 this.vibrator.vibrate(100); 241 // 判断是否还有剩下的方块, 如果没有, 游戏胜利 242 if (!this.gameService.hasPieces()) 243 { 244 // 游戏胜利 245 this.successDialog.show(); 246 // 停止定时器 247 stopTimer(); 248 // 更改游戏状态 249 isPlaying = false; 250 } 251 } 252 253 // 创建对话框的工具方法 254 private AlertDialog.Builder createDialog(String title, String message, 255 int imageResource) 256 { 257 return new AlertDialog.Builder(this).setTitle(title) 258 .setMessage(message).setIcon(imageResource); 259 } 260 private void stopTimer() 261 { 262 // 停止定时器 263 this.timer.cancel(); 264 this.timer = null; 265 } 266 }
上面代码中的gameViewTouchDown()方法负责处理触碰事件。它会先根据触碰点计算出触碰的方法。接下来该方法会判断是否之前已有选中的方块,如果没有,直接将当前方块设为选中方块,如果有,判断两个方块是否可以相连。
如果两个方块可以相连,程序将会从Piece[][]数组中删除这两个方块,该逻辑由handleSuccessLink()方法完成。
除此之外,该程序为了控制时间流失,定义了一个计时器,该计时器每隔1秒发送一条消息,程序将会根据该消息减少游戏剩余的时间。
该Link Activity用的两个类如下:
·GameConf:负责管理游戏的初始化设置信息。
·GameService:负责游戏的逻辑实现。
上面两个工具类中GameConf只是一个简单的设置类,代码如下:
1 public class GameConf 2 { 3 // 设置连连看的每个方块的图片的宽、高 4 public static final int PIECE_WIDTH = 40; 5 public static final int PIECE_HEIGHT = 40; 6 // 记录游戏的总事件(100秒). 7 public static int DEFAULT_TIME = 100; 8 // Piece[][]数组第一维的长度 9 private int xSize; 10 // Piece[][]数组第二维的长度 11 private int ySize; 12 // Board中第一张图片出现的x座标 13 private int beginImageX; 14 // Board中第一张图片出现的y座标 15 private int beginImageY; 16 // 记录游戏的总时间, 单位是秒 17 private long gameTime; 18 private Context context; 19 20 /** 21 * 提供一个参数构造器 22 * 23 * @param xSize Piece[][]数组第一维长度 24 * @param ySize Piece[][]数组第二维长度 25 * @param beginImageX Board中第一张图片出现的x座标 26 * @param beginImageY Board中第一张图片出现的y座标 27 * @param gameTime 设置每局的时间, 单位是秒 28 * @param context 应用上下文 29 */ 30 public GameConf(int xSize, int ySize, int beginImageX, 31 int beginImageY, long gameTime, Context context) 32 { 33 this.xSize = xSize; 34 this.ySize = ySize; 35 this.beginImageX = beginImageX; 36 this.beginImageY = beginImageY; 37 this.gameTime = gameTime; 38 this.context = context; 39 } 40 41 public long getGameTime() 42 { 43 return gameTime; 44 } 45 46 public int getXSize() 47 { 48 return xSize; 49 } 50 51 public int getYSize() 52 { 53 return ySize; 54 } 55 56 public int getBeginImageX() 57 { 58 return beginImageX; 59 } 60 61 public int getBeginImageY() 62 { 63 return beginImageY; 64 } 65 66 public Context getContext() 67 { 68 return context; 69 } 70 }
具体实现步骤连接: