1. 程式人生 > >Java遊戲開發——對對碰

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