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