Java游戏开发——对对碰

游戏介绍:

对对碰游戏在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 block
					e.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 block
			e.printStackTrace();
		}finally{
		     try {
				if(fos!=null)
		    	 fos.close();
				if(dos!=null)
				 dos.close();				
		     } catch (IOException e) {
				// TODO Auto-generated catch block
				e.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(){
					@Override
					public 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 block
							e.printStackTrace();
						}
						}
					}
				}).start();;
			}
		});

	}
	

主要游戏功能到这里已经介绍完毕,玩家可以使用A键打乱游戏池,游戏保证了开始时不存在图案连锁消除的情况。

由于完整源码篇幅过长,这里不再贴出,素材和工程均已上传到网盘。

素材与完整源码链接:https://pan.baidu.com/s/1J1iXvgvVrNZI_Waq3v3xNA 提取码: iefw

 

没有更多推荐了,返回首页

私密
私密原因:
请选择设置私密原因
  • 广告
  • 抄袭
  • 版权
  • 政治
  • 色情
  • 无意义
  • 其他
其他原因:
120
出错啦
系统繁忙,请稍后再试

关闭