游戏介绍:
对对碰游戏在n*n的游戏池中进行,每个格子中有一个图案。鼠标连续选中两个横排或竖排相邻的图案,它们的位置会互换,互换后如果横排或者竖排有3个以上相同的图像,则可以消去该图像,并得分。
游戏的基本规则如下:
①交换
玩家选择两个横排或竖排相邻的图案进行位置互换,如果互换成功则能消去图案,否则取消位置交换。
②消去
玩家选择两个横排或竖排相邻的图案进行位置互换,互换后如果横排或者竖排上有超过3个相同的图像,则消去这几个相同的图像,消去图像后的空格由其上面的图案掉下来补齐,每次消去图像,玩家都可以获得分数。
③连锁
玩家消去图像后,上面的图像掉下来补齐,如果此时游戏池里有连续相同的3个或3个以上的图像,则可以消去这些图像。消去后的空格由上面的图像掉下来补充,继续触发连锁,直到不满足连锁条件为止。
本次制作的对对碰运行效果如下图所示:
使用到的素材文件夹:
素材及完整源码链接:https://pan.baidu.com/s/1J1iXvgvVrNZI_Waq3v3xNA 提取码: iefw
游戏设计的思路:
游戏面板是n*n的方块组成,在完成其游戏功能的前提下,需要尽可能保持其扩展性。在这里我设置图案的种类共有n-2种,假如10*10的面板,则有8种图案,假如是8*8的面板,则有6种图案。游戏池数据使用二维数组map保存,存储的是图案种类ID,使用Image数组pics存储各种图案的图像,绘画面板时通过数组信息和图片ID即可对游戏池状况进行绘画。在定时器progress的控制下,推动游戏进行,这里设置游戏时间是100秒。使用isClick变量去标记玩家是不是第二次点击图案,使用clickX、clickY变量记录第一次点击图案的数组下标。
获取图片及显示图片:
使用Toolkit工具类获取图片,存储到Image数组,再遍历map数组,根据数组下标转换成左上角像素坐标,比如说map[3][4],在这里它的左上角x坐标为4*W+leftX,y坐标为3*W+leftY。最后根据左上角坐标和图案ID,绘制边长为W的图案。
private void getPics() {for(int i=0;i<n-1;i++){pics[i] = Toolkit.getDefaultToolkit().getImage("D://Game//SupperzzleGame//pic"+i+".png");}}public void paint(Graphics g){g.clearRect(0, 0, 700, 600);for(int i=0;i<n;i++){for(int j=0;j<n;j++){if(map[i][j]!=EMPTY){g.drawImage(pics[map[i][j]],leftX+W*j,leftY+W*i,W,W,this);}else{g.clearRect(leftX+W*j,leftY+W*i,W, W);}}}}
玩家鼠标点击事件:
先获取鼠标点击处减去偏移量后的x,y坐标,如果不在游戏池面板内,return;
用tempX存储这次点击的二维数组x下标,tempY存储这次点击的二维数组y下标(形如map[x][y])。
①如果是第一次点击:
修改isClick标记变量为true,用clickX,clickY记录此次点击的数组x下标和y下标。
②如果是第二次点击:
判断两次点击的图案是否横排或竖排相邻,如果相邻则先交换两数组元素的值。
使用isThreeLinked方法判断两个图案交换后是否存在可消去图案的情况,如果存在,则消去可消去的图案并使用downAnimal方法补齐游戏池,接着扫描游戏池中是否存在可消除的图案,如果存在则触发连锁消去事件,接着使用downAnimal方法补齐游戏池....直到当前游戏池没有可消去的图案为止。
如果交换后不存在可消去图案的情况,两次点击的图案位置重新换回。
public void mousePressed(MouseEvent e) {int x = e.getX()-leftX;int y = e.getY()-leftY;if(x<0||y<0||x>=50*n||y>=50*n){return ;}int tempX = y/W;int tempY = x/W;if(isClick){//第二次点击if((tempX==clickX&&(tempY==clickY+1||tempY==clickY-1))||(tempY==clickY&&(tempX==clickX+1||tempX==clickX-1))){//如果两次点击的图案相邻//交换int help = map[tempX][tempY];map[tempX][tempY] = map[clickX][clickY];map[clickX][clickY] = help;repaint();if(isThreeLinked(tempX,tempY)||isThreeLinked(clickX,clickY)){//判断是否存在可消去的方块// System.out.println("可以消去");if(isThreeLinked(tempX,tempY)){removeThreeLinked(tempX,tempY);}if(isThreeLinked(clickX,clickY)){removeThreeLinked(clickX,clickY); }downAnimal();updateAnimal();repaint();while(globalSearach(1)){globalSearach(2);downAnimal();updateAnimal();repaint();}}else{//System.out.println("没有可消去的方块");//交换回来help = map[tempX][tempY];map[tempX][tempY] = map[clickX][clickY];map[clickX][clickY] = help; }isClick = false;}else{//不相邻或者就是点击的还是自身isClick = true;clickX = tempX;clickY = tempY;drawSelectedBlock(tempY*W+leftX, tempX*W+leftY, this.getGraphics());}}else{isClick = true;clickX = tempX;clickY = tempY;drawSelectedBlock(tempY*W+leftX, tempX*W+leftY, this.getGraphics());} }
判断map[x][y]处是否存在可消去图案:
使用count变量记录连续相同的图案数目,count=1,先水平方向判断是否存在三个以上相邻图案,如果count>=3则返回true;
否则再重置count=1,从垂直方向判断是否存在三个以上相邻图案,如果count>=3则返回true。
如果还不存在可消去图案,返回false:
//检测是否存在三个以上相连的方块private boolean isThreeLinked(int x, int y) {int count = 1;if(x+1<n){for(int i=x+1;i<n;i++){if(map[i][y]==map[x][y]){count++;}else{break;}}}if(x-1>=0){for(int i=x-1;i>=0;i--){if(map[i][y]==map[x][y]){count++;}else{break;}}}if(count>=3){return true;}count = 1;if(y+1<n){for(int i=y+1;i<n;i++){if(map[x][i]==map[x][y]){count++;}else{break;}}}if(y-1>=0){for(int i=y-1;i>=0;i--){if(map[x][i]==map[x][y]){count++;}else{break;}}}if(count>=3){return true;}return false;}
消除map[x][y]处三个以上相连的图案:
使用count记录可消去的图案数量,linked作为标记水平方向或竖直方向上相连的图案数量,
先判断竖直方向上相连的图案数量是否>=3,如果是,则消除掉竖直方向相连的图案并且count++;
接着置linked为1,判断水平方向上相连的图案数量是否>=3,如果是,则消除水平方向相连的图案并且count++;
最后置map[x][y]为空,分数+=count*10;
//消除三个以上相连的方块private void removeThreeLinked(int x, int y) {int count = 1;int linked = 1;if(x+1<n){//向下探测for(int i=x+1;i<n;i++){if(map[i][y]==map[x][y]){linked++;}else{break;}}}if(x-1>=0){//向上探测for(int i=x-1;i>=0;i--){if(map[i][y]==map[x][y]){linked++;}else{break;}}}if(linked>=3){//上下相邻超过三个方块for(int i=x-1;i>=0;i--){if(map[i][y]==map[x][y]){count++;map[i][y] = EMPTY;}else{break;}}for(int i=x+1;i<n;i++){if(map[i][y]==map[x][y]){count++;map[i][y] = EMPTY; }else{break;}}}linked = 1;if(y+1<n){//向右探测for(int i=y+1;i<n;i++){if(map[x][i]==map[x][y]){linked++;}else{break;}}}if(y-1>=0){//向左探测for(int i=y-1;i>=0;i--){if(map[x][i]==map[x][y]){linked++;}else{break;}}}if(linked>=3){//左右相邻超过三个方块for(int i=y-1;i>=0;i--){if(map[x][i]==map[x][y]){count++;map[x][i] = EMPTY;}else{break;}}for(int i=y+1;i<n;i++){if(map[x][i]==map[x][y]){count++;map[x][i] = EMPTY;}else{break;}}}map[x][y] = EMPTY;score+=count*10;HelpPanel.score.setText(score+"");}
扫描游戏池:
如果flag==1,只判断游戏池中是否存在可消除的图案,如果存在返回true;
否则消除游戏池中可消除的所有图案。
//1扫描是否存在可消除方块//2扫描并消除可消除方块private boolean globalSearach(int flag) {if(flag == 1){for(int i=0;i<n;i++){for(int j=0;j<n;j++){if(isThreeLinked(i, j)){return true;}}} }else{for(int i=0;i<n;i++){for(int j=0;j<n;j++){if(isThreeLinked(i, j))removeThreeLinked(i, j);}}}return false;}
图案下落填充:
从最后一行向上扫描游戏池,如果数组元素为空,则找到和它同一列,在它上方的第一个非空元素进行交换
//图案下落private void downAnimal() {for(int i=n-1;i>=0;i--){for(int j=0;j<n;j++){if(map[i][j]==EMPTY){int temp = i;while(temp>=0){if(map[temp][j]!=EMPTY){int help = map[i][j];map[i][j] = map[temp][j];map[temp][j] = help;break;}temp--;}}}}}
更新游戏池状况:
图案下落后,此时空块都位于最上层,可以直接随机生成图案ID赋值给空的数组元素:
//更新图案数组private void updateAnimal() {for(int i=0;i<n;i++){for(int j=0;j<n;j++){if(map[i][j]==EMPTY){map[i][j] = (int) (Math.random()*(n-2));}}}}
画选中框:
根据左上角x,y像素坐标,画框:
//画选中框private void drawSelectedBlock(int x, int y, Graphics g) {Graphics2D g2 = (Graphics2D) g;//生成Graphics对象BasicStroke s = new BasicStroke(1);//宽度为1的画笔g2.setStroke(s);g2.setColor(Color.RED);g.drawRect(x+1, y+1, 48, 48);}
历史记录读写:
基础的文件IO操作,如果文件不存在自动新建:
//读取历史记录public int getBestScore(){File file = new File("D://GameRecordAboutSwing");if(!file.exists()){file.mkdirs();}File record = new File("D://GameRecordAboutSwing/SupperzzleGame.txt");try{if(!record.exists()){//如果不存在,新建文本record.createNewFile();fos = new FileOutputStream(record);dos = new DataOutputStream(fos);String s = "0";dos.writeBytes(s);}//读取记录fis = new FileInputStream(record);dis = new DataInputStream(fis);String str = dis.readLine();bestScore = Integer.parseInt(str);}catch(Exception e){e.printStackTrace();}finally{try {if(fis!=null)fis.close();if(dis!=null)dis.close(); if(fos!=null)fos.close();if(dos!=null)dos.close(); } catch (IOException e) {// TODO Auto-generated catch blocke.printStackTrace();}}return bestScore;}//更新历史记录public void updateBestScore(){File record = new File("D://GameRecordAboutSwing/SupperzzleGame.txt");try {//清空原有记录FileWriter fileWriter =new FileWriter(record);fileWriter.write("");fileWriter.flush();fileWriter.close();//重新写入文本fos = new FileOutputStream(record);dos = new DataOutputStream(fos);String s = score.getText();bestScore = Integer.parseInt(score.getText());dos.writeBytes(s);} catch (IOException e) {// TODO Auto-generated catch blocke.printStackTrace();}finally{try {if(fos!=null)fos.close();if(dos!=null)dos.close(); } catch (IOException e) {// TODO Auto-generated catch blocke.printStackTrace();}}HelpPanel.record.setText(bestScore+"");}
开始游戏:
设定进度条progress最大值为100,线程每秒自增1,游戏开始时初始化进度条progress并为游戏面板添加鼠标监听和键盘事件,进度条满之后,移除游戏面板的监听并提示玩家游戏成绩,如果当前分数高于历史记录则进行历史记录的更新。
public MyFrame(){actionPanel.setLayout(new FlowLayout()); actionPanel.add(buttonRestart,BorderLayout.CENTER);this.getContentPane().setLayout(new BorderLayout());this.getContentPane().add(helpPanel,BorderLayout.NORTH);this.getContentPane().add(gamePanel,BorderLayout.CENTER);this.getContentPane().add(actionPanel,BorderLayout.SOUTH); this.setSize(700,700);this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);this.setTitle("对对碰");this.setVisible(true);buttonRestart.addMouseListener(new MouseAdapter(){public void mouseClicked(MouseEvent e){if(flag)return ;flag = true;gamePanel.addKeyListener(gamePanel);gamePanel.addMouseListener(gamePanel);gamePanel.startGame();buttonRestart.setEnabled(false);HelpPanel.score.setText(0+"");new Thread(new Runnable(){@Overridepublic void run() {nowTime = 0;while(true){try {Thread.currentThread().sleep(1000);nowTime++;HelpPanel.setTime(nowTime);if(nowTime==100){gamePanel.removeMouseListener(gamePanel);gamePanel.removeKeyListener(gamePanel);int score = Integer.parseInt(helpPanel.score.getText());int record = Integer.parseInt(helpPanel.record.getText()); if(score>record){JOptionPane.showMessageDialog(null, "游戏结束,你的得分是"+score+",刷新了历史记录"+record);helpPanel.updateBestScore();}else{JOptionPane.showMessageDialog(null, "游戏结束,你的得分是"+HelpPanel.score.getText());}buttonRestart.setEnabled(true);flag = false;break;}} catch (InterruptedException e) {// TODO Auto-generated catch blocke.printStackTrace();}}}}).start();;}});}
主要游戏功能到这里已经介绍完毕,玩家可以使用A键打乱游戏池,游戏保证了开始时不存在图案连锁消除的情况。
由于完整源码篇幅过长,这里不再贴出,素材和工程均已上传到网盘。
素材与完整源码链接:https://pan.baidu.com/s/1J1iXvgvVrNZI_Waq3v3xNA 提取码: iefw