李少芳
(莆田學(xué)院 機(jī)電與信息工程學(xué)院, 福建 莆田 351100)
游戲產(chǎn)業(yè)作為現(xiàn)代電腦電子技術(shù)的產(chǎn)物,正以其獨(dú)特的魅力在娛樂領(lǐng)域占據(jù)主流位置.隨著Java的廣泛應(yīng)用,以及Java開發(fā)系統(tǒng)的可移植性的提高,Java開發(fā)的游戲大受歡迎.雖然Java開發(fā)的游戲在畫面表現(xiàn)、場景效果等受到一定的限制,但是游戲響應(yīng)比較快,具備一定的優(yōu)勢[1-5].連連看游戲是源自中國臺(tái)灣的桌面小游戲,經(jīng)過發(fā)展,眾多程序員開發(fā)出桌面游戲、在線游戲和社交游戲等多種版本的連連看[6].飽受工作壓力的人們通常沒有太多的時(shí)間進(jìn)行復(fù)雜的游戲,而對于這種動(dòng)動(dòng)鼠標(biāo)就能過關(guān)的游戲情有獨(dú)鐘.寵物連連看、數(shù)字連連看[6]、果蔬連連看、阿達(dá)連連看等各種琳瑯滿目的連連看版本,網(wǎng)上甚至推出在線自己制作的全免費(fèi)連連看游戲,提供下載,享受輕松、自由、快樂的游戲休閑時(shí)刻.一些連連看游戲已成為幼兒園大班科學(xué)活動(dòng)備選項(xiàng)目.許多人在PC機(jī)或手機(jī)上都玩過圖片連連看游戲,看著很簡單,但自己真的動(dòng)手設(shè)計(jì),完成過程其實(shí)很艱辛.如果是制作手機(jī)連連看游戲,編譯完成的連連看游戲需要將生成的安裝包上傳至手機(jī),在生成的源文件里打開bin文件,將里面的apk文件通過數(shù)據(jù)線下載至手機(jī),apk文件即游戲安裝包.在手機(jī)上找到下載完成的 apk文件,點(diǎn)擊安裝.安裝成功后,即可進(jìn)入連連看游戲登錄界面[7].
連連看,又稱對對卡,是指圖案配對的一種經(jīng)典的電子益智游戲,游戲規(guī)則是將相同圖案的兩張圖片用三根以內(nèi)的直線連在一起消除[8].具體操作是:在游戲中,玩家可以將2個(gè)相同圖案的圖片成對連接起來,連接線不多于3根直線,就可以成功將對子消除.第一次使用鼠標(biāo)點(diǎn)擊地圖上的一張圖片,這張被選中的圖片將加框顯示;再次用鼠標(biāo)點(diǎn)擊第二張圖片,若該圖片與被選中圖片的圖案相同,且把第一張圖片到第二張圖片連起來,中間的直線不超過3根,則消掉這兩張圖片,否則第一張圖片恢復(fù)成未被選中狀態(tài),而第一張圖片變成被選中狀態(tài).當(dāng)將棋盤上的對子全部消除掉時(shí),判為勝利.可以限制玩家選擇圖片的時(shí)間不超過30秒,超過時(shí)間而未消除一對則判為輸?shù)簦B連看游戲通過選擇不同的地圖、游戲速度、背景音樂,規(guī)則簡單容易上手.
游戲界面的主窗體的棋盤網(wǎng)格可以設(shè)計(jì),格子中的圖片隨著格子的大小自適應(yīng)顯示.關(guān)卡1按10×8設(shè)置,效果圖如圖1所示.關(guān)卡2按12×10設(shè)置.游戲進(jìn)度可以根據(jù)消除的圖片數(shù)量/網(wǎng)格數(shù)量進(jìn)行計(jì)算顯示,也可以設(shè)定一定的游戲得分規(guī)則,按消除的圖片數(shù)量計(jì)算積分排列名次,增加游戲的競技樂趣.主窗體上還添加菜單設(shè)置.菜單的具體實(shí)現(xiàn)如下:先建立JMenuBar菜單欄,新建四個(gè)JMenu菜單[9-10],分別命名為游戲開局、難度設(shè)置、音效音樂和計(jì)時(shí)得分,如圖2所示.“游戲開局”菜單下建立JMenuItem菜單項(xiàng),分別命名為開始、結(jié)束、撤銷、提示、亂序和暫停;“難度設(shè)置”菜單下建立JMenuItem菜單項(xiàng),分別命名為關(guān)卡1、關(guān)卡2;“音效音樂”菜單下建立JMenuItem菜單項(xiàng),分別命名為音效、音樂,“計(jì)時(shí)得分”菜單下建立JMenuItem菜單項(xiàng),分別命名為時(shí)間、進(jìn)度、生命、得分、排名等.
圖1 按10×8設(shè)置的關(guān)卡1游戲界面主窗體
圖2 游戲界面的菜單設(shè)置
各菜單項(xiàng)功能說明如下:
開始:開始新的游戲.
結(jié)束:結(jié)束正在進(jìn)行中的游戲.
撤銷:撤銷最近一次消除動(dòng)作.
提示:加框提示要消除的兩張圖片,扣除一個(gè)生命值.
亂序:打亂當(dāng)前剩余的圖案,扣除兩個(gè)生命值.
暫停:暫停游戲,不計(jì)游戲時(shí)間.
關(guān)卡1:棋盤大小設(shè)置為10×8,游戲時(shí)間240秒,生命值4.
關(guān)卡2:棋盤大小設(shè)置為12×10,游戲時(shí)間360秒,生命值6.
音效:開啟/關(guān)閉游戲音效.
音樂:開啟/關(guān)閉背景音樂.
時(shí)間:顯示剩余的游戲時(shí)間,按秒顯示.
進(jìn)度:按百分比顯示游戲進(jìn)度.
生命:顯示當(dāng)前剩余的生命值,生命值為0,則結(jié)束游戲.
得分:顯示玩家所得分?jǐn)?shù).
排名:點(diǎn)擊后可查看排行榜.
建立菜單的代碼如下:
JMenuBar menubar=new JMenuBar();//菜單欄
JMenu gameMenu1=new JMenu("游戲開局");
JMenu gameMenu2=new JMenu("難度設(shè)置");
JMenu gameMenu3=new JMenu("音效音樂");
JMenu gameMenu4=new JMenu("計(jì)時(shí)得分");
JMenuItem m11=new JMenuItem("開始", KeyEvent.VK_A);//設(shè)置快捷方式Ctrl+A
JMenuItem m12=new JMenuItem("結(jié)束");
……
JMenuItem m45=new JMenuItem("排名");
menubar.add(gameMenu1);// 把菜單項(xiàng)添加到菜單中
menubar.add(gameMenu2);
menubar.add(gameMenu3);
menubar.add(gameMenu4);
gameMenu1.add(m11);
gameMenu1.add(m12);
……
游戲界面圖片顯示的實(shí)現(xiàn)過程如下:首先收集一張游戲主界面背景圖片bg01.jpg和20張圣誕卡通圖片,分別命名為a1.png,a2.png,……,a20.png,存放于工程文件夾的png文件夾下.根據(jù)主界面棋盤網(wǎng)格的多少,20張圣誕卡通圖片要重復(fù)多次讀取,例如8行10列的棋盤網(wǎng)格,總共要顯示80張圖片,則對于20張圣誕卡通圖片每張需要顯示4次.圖片顯示可以根據(jù)主界面棋盤網(wǎng)格的大小設(shè)置為自適應(yīng)大?。尘皥D片變量bg和存放20張圣誕卡通圖片的數(shù)組img定義代碼如下:
public static Image bg= new ImageIcon("png\bg01.jpg").getImage();
public static Imageimg=new Image[20];
static {
for (int i=0; i < img.length; i++) {
try{
img[i]=ImageIO.read(new File("png\a" + (i + 1) + ".png"));
}catch(IOException e){ e.printStackTrace(); }
}}
如果背景圖片未設(shè)置自適應(yīng)大小,則其顯示效果不能按窗口大小拉伸顯示,相應(yīng)代碼為g.drawImage(bg, 0, 0, this);設(shè)置背景圖片自適應(yīng)大小顯示,可以通過this獲取窗體大小來拉伸顯示圖片,相應(yīng)代碼如下:
ImageIcon m=new ImageIcon(bg);
m.setImage(bg.getScaledInstance(this.getWidth(),this.getHeight(),Image.SCALE_AREA_AVERAGING));
g.drawImage(m.getImage(), 0, 0, this);
連連看的棋盤網(wǎng)格可以簡單地看成一個(gè)二維數(shù)組,數(shù)組的大小取決于所要繪制的圖片個(gè)數(shù),但是必須要保證圖片是成對出現(xiàn)的.首先定義一個(gè)棋盤Chess類,其成員變量status表示棋盤網(wǎng)格圖片的狀態(tài),對應(yīng)給定的20張圣誕卡通圖片,設(shè)定棋盤網(wǎng)格圖片的狀態(tài)值分別為1~20.當(dāng)圖片狀態(tài)值為0時(shí),表明圖片被消除.以10行12列的棋盤網(wǎng)格為例,總共要顯示120張圖片,則對于20張圣誕卡通圖片每張需要循環(huán)顯示6次,用count表示圖片循環(huán)顯示的次數(shù).棋盤網(wǎng)格數(shù)組arr的橫、縱坐標(biāo)值是棋盤網(wǎng)格的行和列,行取值范圍為1~10,列取值范圍為1~12,采用隨機(jī)方式填充設(shè)置棋盤網(wǎng)格圖片[11].偽隨機(jī)數(shù)在軟件開發(fā)和程序設(shè)計(jì)中應(yīng)用很廣,Java提供了多種生成偽隨機(jī)數(shù)的方法,如Random類和Math類,不同算法產(chǎn)生的偽隨機(jī)數(shù)的隨機(jī)性有所差別,可以根據(jù)實(shí)際需要選擇以滿足不同的設(shè)計(jì)要求.這里選用Random類來產(chǎn)生隨機(jī)數(shù),棋盤網(wǎng)格數(shù)組arr中最上面一行、最下面一行、最左邊一列和最右邊一列的值初始化為0.
相應(yīng)的代碼如下:
class Chess {
private int status;
public Chess(int status) { this.status=status;}
public int getStatus() { return status;}
public void setStatus(int status) { this.status=status;}
}
Chess arr=new Chess[row + 2][col+ 2];
public void init() {
for (int i=0; i < arr[0].length; i++) {
arr[0][i]=new Chess(0);
arr[arr.length - 1][i]=new Chess(0);
}
for (int i=0; i < arr.length; i++) {
arr[i][0]=new Chess(0);
arr[i][arr[0].length - 1]=new Chess(0);
}
Random random=new Random();
for (int i=1; i <= 20; i++) {
int count=0;
while (count < row*col/20) {
int x=random.nextInt(row) + 1;
int y=random.nextInt(col) + 1;
if (arr[x][y]==null) {arr[x][y]=new Chess(i);count++;}
}}}
//棋盤網(wǎng)格圖片的顯示
for (int i=1; i < arr.length; i++) {
for (int j=1; j < arr[i].length; j++) {
if (arr[i][j].getStatus() != 0) {
int x=(j-1) *gridWidth + left;
int y=(i-1) * gridHeight + up;
g.drawImage(img[arr[i][j].getStatus() - 1], x,y,this);
}}}
連連看的游戲規(guī)則是點(diǎn)擊兩張相同圖案的圖片,當(dāng)這兩張圖片能夠用不超過三根直線相連時(shí),則可以消除這兩張圖片.判斷兩個(gè)圖片的圖案相同比較簡單,設(shè)定20張圖片的狀態(tài)值分別為1~20,則只需對比圖片的狀態(tài)值是否相同.消除圖片算法中比較難的是判斷兩個(gè)圖片的連接是否超過兩次拐角.消除圖片的情況有三種:一是直線連通,即兩張圖片直接通過一根直線相連,如圖3所示;二是一拐角連通,即兩張圖片通過一個(gè)拐角連接的兩條直線連通,如圖4所示;三是二拐角連通,即兩張圖片通過兩個(gè)拐角連接的三條直線連通,如圖5、圖6所示.當(dāng)判斷兩張圖片連通可以被消除時(shí),則將這兩張圖片所對應(yīng)的狀態(tài)值置零,棋盤網(wǎng)格圖片消除.
圖3 橫向相鄰直線連通效果圖
圖4 一拐角L型連通的示意圖
情況1 直線連通.兩張圖片直接連通的方式有4種,分別是縱向相鄰、橫向相鄰、橫向中間為空和縱向中間為空.如果是兩張圖片相鄰的話,只需要判斷兩個(gè)圖片的行或列值是否相差1,如圖3所示.對于中間為空的兩個(gè)圖片,則需要判斷兩個(gè)圖片之間是否存在其他圖片.
設(shè)點(diǎn)a的坐標(biāo)值為(ax,ay),點(diǎn)b的坐標(biāo)值為(bx,by),則點(diǎn)a與點(diǎn)b直線連通線的繪制代碼如下:
Point a=list.get(0);
Point b=list.get(1);
int ax=a.y * gridWidth + left;
int ay=a.x * gridHeight + up;
int bx=b.y * gridWidth + left;
int by=b.x * gridHeight + up;
g2d.drawLine(ax, ay, bx, by);
情況2 一拐角連通.兩張圖片通過一個(gè)拐角連接的兩條直線連通,即L型連通,如圖4所示.設(shè)綠色的A、B格子表示要判斷是否連通的兩張相同圖案的圖片,細(xì)點(diǎn)填充的格子表示其他圖案的圖片,白色背景的格子表示沒有圖片.要判斷圖片A和圖片B是否一拐角連通,其實(shí)相當(dāng)于以圖片A和圖片B為一對對角頂點(diǎn)劃出一個(gè)矩形,然后找出C、D兩點(diǎn),然后判斷路徑ADB和ACB上是否存在狀態(tài)不為0的圖片.
設(shè)點(diǎn)a的坐標(biāo)值為(ax,ay),點(diǎn)b的坐標(biāo)值為(bx,by),拐點(diǎn)c的坐標(biāo)值為(cx,cy),則點(diǎn)a與點(diǎn)b通過一個(gè)拐點(diǎn)進(jìn)行L型連通時(shí)L型線的繪制代碼如下:
Point a=list.get(0);
Point c=list.get(1);
Point b=list.get(2);
int ax=a.y* gridWidth + left;
int ay=a.x* gridHeight + up;
int cx=c.y* gridWidth + left;
int cy=c.x* gridHeight + up;
int bx=b.y* gridWidth + left;
int by=b.x* gridHeight + up;
g2d.drawLine(ax, ay, cx, cy);
g2d.drawLine(cx, cy, bx, by);
情況3 二拐角連通.兩張圖片通過兩個(gè)拐角連接的三條直線連通,分U型連通和非U型連通,如圖5、圖6所示.要判斷圖片A與圖片B是否二拐角連通,其實(shí)可以轉(zhuǎn)化為判斷能否找到一個(gè)點(diǎn)C,滿足C與圖片A直線連通,且C與圖片B一拐角p連通,或者找到一個(gè)點(diǎn)D,滿足D與圖片B直線連通,且D與圖片A一拐角連通.
圖5 二拐角U型連通的示意圖
圖6 二拐角非U型連通的示意圖
設(shè)點(diǎn)a的坐標(biāo)值為(ax,ay),點(diǎn)b的坐標(biāo)值為(bx,by),拐點(diǎn)c的坐標(biāo)值為(cx,cy),另一個(gè)拐點(diǎn)d的坐標(biāo)值為(dx,dy),則點(diǎn)a與點(diǎn)b通過兩個(gè)拐點(diǎn)進(jìn)行連通時(shí)軌跡線的繪制代碼如下:
Point a=list.get(0);
Point c=list.get(1);
Point d=list.get(2);
Point b=list.get(3);
int ax=a.y*gridWidth +left;
int ay=a.x*gridHeight +up;
int cx=c.y*gridWidth +left;
int cy=c.x*gridHeight + up;
int dx=d.y*gridWidth +left;
int dy=d.x*gridHeight +up;
int bx=b.y*gridWidth +left;
int by=b.x*gridHeight +up;
g2d.drawLine(ax, ay, cx, cy);
g2d.drawLine(cx, cy, dx, dy);
g2d.drawLine(dx, dy, bx, by);
游戲音效上可以在圖片消除時(shí)設(shè)置一個(gè)簡短的提示音效,也可以添加背景音樂.目前由于Java語言中AudioClip包的廢棄,導(dǎo)致背景音樂通常要導(dǎo)入第三方的jar包來解決.利用Java語言的AudioInputStream流文件讀入方式定義Music類.設(shè)音樂文件路徑為工程文件夾下的wav//r.wav,需要播放背景音樂時(shí),只需通過定義Music類對象m,即Music m=new Music();并調(diào)用m.player().
Music類實(shí)現(xiàn)背景音樂播放的相關(guān)代碼如下:
import java.io.File;
import java.io.IOException;
import javax.sound.sampled.*;
public class Music {
private int t=0; // 音樂播放時(shí)間
AudioInputStream bgMusic;
private boolean playing=false;
public boolean isPlaying() {return playing;}
public void setPlaying(boolean playing) {this.playing=playing;}
public int getT() {return t;}
public void setT(int t) {this.t=t;}
public Music() {
try {
bgMusic= AudioSystem.getAudioInputStream(new File("wav//r.wav"));
} catch (UnsupportedAudioFileException e) {e.printStackTrace();}
catch (IOException e) {e.printStackTrace();} //獲得音頻輸入流
}
public void player() {
AudioInputStream ais;
AudioFormat baseFormat;
DataLine.Info info;
ais=bgMusic;
baseFormat=ais.getFormat();//指定聲音流中特定數(shù)據(jù)格式
info=new DataLine.Info(SourceDataLine.class, baseFormat);
SourceDataLine line=null;//定義SourceDataLine類變量line
try {
line=(SourceDataLine) AudioSystem.getLine(info);
line.open(baseFormat);
//打開指定格式的行,獲得所需的系統(tǒng)資源
line.start();// 允許line執(zhí)行數(shù)據(jù) I/O
int BUFFER_SIZE=4000 * 4;
int intBytes=0;
byte audioData=new byte[BUFFER_SIZE]; // 音頻數(shù)據(jù)數(shù)組
while (intBytes != -1 && (playing == false)) {
intBytes=ais.read(audioData, 0, BUFFER_SIZE);
// 從音頻流讀取BUFFER_SIZE字節(jié),并放入字節(jié)數(shù)組intBytes
if (intBytes >= 0) {
line.write(audioData, 0, intBytes);//將音頻數(shù)據(jù)寫入
t += 1;
}}} catch (LineUnavailableException | IOException e1) {e1.printStackTrace();}
}}
設(shè)計(jì)主窗體游戲界面比較簡單,難點(diǎn)是如何判別連通消除圖片的算法.本文用Java語言設(shè)計(jì)圖片連連看游戲,用到的關(guān)鍵技術(shù)包括游戲界面圖片的自適應(yīng)顯示、初始化棋盤網(wǎng)格生成地圖、消除圖片的算法分析以及游戲音效和背景音樂設(shè)置等.由于圖片設(shè)置自適應(yīng)顯示,實(shí)測游戲的響應(yīng)速度會(huì)變慢,應(yīng)優(yōu)先選用合適大小的連連看圖片,盡量減少圖片轉(zhuǎn)換時(shí)間.為了用戶更好的體驗(yàn),在一些細(xì)節(jié)設(shè)計(jì)上還有待進(jìn)一步優(yōu)化和完善,比如通過多線程添加背景音樂[12]、按消除圖片的比例添加顯示進(jìn)度條、計(jì)時(shí)增加游戲難度、歷史排行榜的記錄等,將另文闡述.