觀前提示: 本文涉及的小成果特別多,即使你不需要寫一個和我完全相同的游戲,也可以按照需要查看某些特定功能的實作程序,說不定能夠給您的程式開發帶來一點小小的啟發!PS:結合源代碼閱讀此博客更加高效,
本博客的內容可看作上一篇多執行緒游戲實體分享的擴展,上一篇博客介紹過的內容(如何實作一個簡單的小游戲)在本博客中不再贅述,如果有疑問可以回訪那篇博客(點擊此處跳轉)或者私信博主,
游戲整體效果演示視頻:
Javad多執行緒實體分享-雷火-穩定版演示視頻
游戲規則簡介
游戲整體框架

這里對String page使用了一個switch判斷,用于判斷現在玩家需要運行哪一個畫面,當我們需要切換畫面時(比如說從游戲界面切入到暫停界面)就可以通過使用陳述句更改page的值來實作界面的切換,每一個界面都有對應的方法體,
動圖實作
在雷火游戲中用到了非常多的圖片元素和動態圖片元素,其中的絕大多數都是通過LoadingImage類加載出來的,當我們想要實作一個功能時,首先要明晰我們想要看到的具體效果,然后再制定方案,就拿這個例子來說,當我想在視窗中繪制一個動態圖片時,只需要一行代碼就能做到,
bufg.drawImage(loadingImg.getMotionGraph(此處輸入圖片地址),0,0,null);
我們需要實作的就是下面這一步:

在上一篇博客中講到,我們游戲中的執行緒是這樣運行的:

所以我們可以這樣去實作想要的功能:程式最開始運行時,先將動圖的每一幀加載到一個陣列中,然后呼叫這個動圖陣列繪制圖片時,根據目前的輪數(len)進行一個判斷,判斷一下現在應該要畫的是動圖的哪一幀圖片,然后回傳這個圖片物件,
將動圖的每一幀加載到一個陣列中
//生成圖片陣列物件generateGraphList
public Image[] ggList(int interval,String[] str){
/*該方法傳入一個圖片地址陣列str,回傳一個圖片物件陣列
其中整數interval指的是每兩幀圖片間隔的圖片數,interval的值越大,動圖運動的速度越慢
*/
//創建一個新的圖片地址陣列str,因為要留出存放間隔圖片的位置
String[] strs=new String[str.length+str.length*interval];
//創建一個和strs相同長度的陣列
Image[] imgs=new Image[str.length+str.length*interval];
String temp = null;
//將str陣列擴充到strs陣列
for(int i=0;i<str.length+str.length*interval;i++){
if(i%(interval+1)==0){
//當索引i為interval+1的整數倍時,插入str陣列中對應的圖片地址
strs[i]=str[i/(interval+1)];
temp=str[i/(interval+1)];
}else{
//插入間隔圖片的地址(相當于復制了前面的圖片)
strs[i]=temp;
}
}
for(int i=0;i<strs.length;i++){
//根據strs陣列中的圖片地址往圖片陣列imgs中加載對應的圖片
ImageIcon imic=new ImageIcon(fileAddress+strs[i]);
imgs[i]=imic.getImage();
}
/*這里的imgss指的是存放imgs陣列的二維陣列
我們往HashMap hm_imgss中存放一對鍵值,這樣我們使用動圖陣列時,
只需要根據動圖陣列第一張圖片的地址就可以獲取這個動圖陣列在imgss中的索引
*/
hm_imgss.put(str[0], imgssCount);
imgssCount++;
return imgs;
}

當我們需要使用動圖時,呼叫以下方法
//傳入計數器len和圖片陣列鍵(這樣程式才能知道你需要使用哪一張動圖)
public Image getMotionGraph(int len,String key){
Image[]img=imgss.get(hm_imgss.get(key));
//根據計數器判斷現在需要回傳哪一幀圖片
int count=len%img.length;
Image image=img[count];
return image;
}
當我們在創建LoadingImage物件的時候就把需要用到的圖片全部加載
public LoadingImage(){
//加載圖片組(多張圖片/動圖)
imgss.add(ggList(10,new String[]{"圖1.png","圖2.png","圖3.png","圖4.png",……}));
}
如果我需要畫出上面這段代碼表示的動圖,使用的代碼是
LoadingImage ldimg=new LoadingImage();
g.drawImage(loadingImg.getMotionGraph("圖1.png"),0,0,null);
判斷碰撞的更新
在上一個版本的飛機大戰中,我們的子彈經常會穿過怪物,導致沒有擊中,這一次博主對判斷碰撞進行了十分細致的優化,大大改善了碰撞判斷的準確性,
我們可以寫出一個方法,當我們需要判斷兩個飛行物是否碰撞時,只需要將兩個飛行物物件傳入該方法,該方法就能回傳一個是否碰撞的Boolean值,該方法對以下四種碰撞情況進行判斷,當其中任何一種情況成立時,回傳true,即兩個飛行物已經碰撞,

private Boolean jgat(FlyObject f1,FlyObject f2){//傳入兩個飛行物物件
int f1_x=0;
int f1_y=0;
f1_x=f1.location.x;
f1_y=f1.location.y;
int f2_x=0;
int f2_y=0;
f2_x=f2.location.x;
f2_y=f2.location.y;
int f1_w=hm.get(f1.imgName).x;
int f1_h=hm.get(f1.imgName).y;
int f2_w=hm.get(f2.imgName).x;
int f2_h=hm.get(f2.imgName).y;
Boolean attack=(f2_x-f1_x<f1_w&f1_x-f2_x<f2_w&f2_y-f1_y<f1_h&f1_y-f2_y<f2_h);
return attack;
}
判斷碰撞更新后的效果:

HashMap的使用
另外,上面的代碼還用到了HashMap(哈希表),HashMap的作用就是創建一個表格,這個表格可以讓我們通過鍵(key)去找到對應的值(value),
比如說我知道一個同學的名字,我想要根據這個名字(鍵key)去查找到該同學的學號(值value),這就是一個非常簡單的類似HashMap的例子,
HashMap有兩個最常用的方法,一個是put(),另一個是get(),put顧名思義就是往HashMap中放入一對鍵值,get就是通過鍵去獲取HashMap中對應的值,
在上面這個例子中我們通過飛行物的名字imgName(key)去找到飛行物圖片的寬和高(valule),
private HashMap<String,Vector>hm=new HashMap<>();//創建HashMap物件,尖括號中存盤的key和value可以是任何類
//當我們需要往HashMap中存入資料時
hm.put("我機.png", new Vector(100,100));//這里的key是飛行物圖片名稱,value是飛行物圖片的寬高
//當我們需要通過飛行物圖片的名字去獲取圖片的寬高時
int f1_w=hm.get(f1.imgName).x;
int f1_h=hm.get(f1.imgName).y;
開始界面
整個開始界面的圖形元素有三類,一類是在畫面中穿插而過的飛機;一類是雷火的圖案,一類是畫面中心的按任意鍵進入游戲,博主在這里將它們的實作方法分別講述一下,
飛機飛過影片效果實作
在上一篇博客中我們講到過如何生成敵機,這里的影片效果使用的原理也是類似的,
首先我們需要創建一個串列來存放開場影片的飛機,并且準備兩張圖片,
//開場飛機
public ArrayList<FlyObject>startfos=new ArrayList<>();


//生成開場飛機影片
if(flag_start%10==0){
for(int i=0;i<6;i++){
//生成開場影片飛機
Vector location=new Vector(i*400,-100);
Vector speed=new Vector(-10,10);
FlyObject fo=new FlyObject(location,speed,10,"開場敵機-左下.png");
Vector location2=new Vector(-800+i*400,900);
Vector speed2=new Vector(10,-10);
FlyObject fo2=new FlyObject(location2,speed2,-100,"開場敵機-右上.png");
//將開場影片飛機放入準備好的startfos串列中
startfos.add(fo);
startfos.add(fo2);
}
}
//繪制開場畫面飛機圖片,飛機移動
for(int i=0;i<startfos.size();i++){
FlyObject fo=startfos.get(i);
bufg.drawImage(loadingImg.getGraph(fo.imgName), fo.location.x, fo.location.y, null);
fo.move();
}
//清理飛出畫面之外的飛機
if(startfos.size()>200){for(int i=0;i<startfos.size()-200;i++){startfos.remove(0);}}
雷火圖案
在開場畫面中,雷火字樣圖案從視窗最上端向下移動,一段時間后停止在視窗中心位置,這是我們想要實作的效果,
在這里博主定義了一個flag_start,每執行完一輪畫圖操作這個flag_start就會加一(和len的作用相似),當這個整數小于26時,雷火字樣圖案的縱坐標每一輪都增加固定的值,從而達到向下以固定速度進行移動的效果,當這個整數大于等于26時,雷火字樣圖案在固定點進行繪制,
if(flag_start<26){
bufg.drawImage(loadingImg.getMotionGraph(flag_start,"開始界面_按任意鍵進入游戲_1.png"),0,500-flag_start*20,null);
bufg.drawImage(loadingImg.getGraph("開始界面_雷火.png"),0,-500+flag_start*20,null);
bufg.drawImage(loadingImg.getMotionGraph(flag_start,"開始界面_雷火_動圖1.png"),0,-500+flag_start*20,null);
flag_start++;
}else{
bufg.drawImage(loadingImg.getMotionGraph(flag_start,"開始界面_按任意鍵進入游戲_1.png"),0,0,null);
bufg.drawImage(loadingImg.getGraph("開始界面_雷火_動圖底圖.png"),0,0,null);
bufg.drawImage(loadingImg.getMotionGraph(flag_start,"開始界面_雷火_動圖1.png"),0,0,null);
flag_start++;
//繪出目前要按的鍵
bufg.drawImage(loadingImg.getGraph(nowPress),nowPressx,nowPressy, null);
}
按鍵效果
當滑鼠置于“按任意鍵進入游戲”字樣時,字體會出現發光效果,包括在后面很多的頁面中,滑鼠置于按鍵上時都會有發光效果,這里我們定義一個String類的物件nowPress,我們可以在滑鼠監聽器中設定當滑鼠置于對應字樣圖案上方時,將nowPress的值更新為這個圖案的名稱;當滑鼠移開時我們就將nowPress定義為"",這樣就可以實作只在滑鼠放置于對應圖案上方時,圖案才會有發光效果,
//滑鼠監聽器中的MouseMove函式
public void mouseMoved(MouseEvent e) {
int x=e.getX();
int y=e.getY();//判斷按鍵
if(x>150&x<1050&y>600&y<700){
myThread.nowPress="開始界面_按任意鍵進入游戲_觸碰.png";//一張發光的圖片
myThread.nowPressx=0;
myThread.nowPressy=0;
}else{//滑鼠移開時
myThread.nowPress="";
}
}
游戲界面
技能實作
相較于上一版飛機大戰,技能的使用是一個新增的功能,

雷火游戲目前可以使用的技能有3個,S鍵是保護罩(使用后抵御一切攻擊,消耗3技能點),D鍵是火球(火球會緩慢向右移動,并炸毀觸及的所有敵機,消耗2技能點),F鍵是向我機周圍發射多重環形高殺傷火力(消耗1技能點),
我們可以構造一個使用技能的方法,鍵盤監聽器監聽到按鍵事件時,呼叫此方法并傳入按鍵代碼完成技能的呼叫,
//使用技能
public void useSkill(int keyCode){//鍵盤監聽器傳入按鍵代碼
if(skillPoint>0){
switch(keyCode){//keycode為70時是F鍵,83是S鍵,68是D鍵
case 70:if(skill_F_wait==SKF){skill_F_wait=0;skillPoint--;skill_F+=5;}break;
case 83:if(skill_S_wait==SKS&skillPoint>2){skill_S_wait=0;skillPoint-=3;skill_S+=300;}break;
case 68:if(skill_D_wait==SKD&skillPoint>1){
/*火球技能,我們可以將火球視作一種特殊的子彈,以下代碼
便是創建火球這個子彈并放入bults串列中*/
skill_D_wait=0;
skillPoint-=2;
Vector location=new Vector(mps.get(mps.size()-5).location.x,mps.get(mps.size()-5).location.y-100);
Vector speed=new Vector(5,0);
bults.add(new FlyObject(location,speed,100,"技能D-火球.png"));
}break;
}
}else if(keyCode==70|keyCode==83|keyCode==68){
//當玩家使用技能按鍵,但技能點不足時表單左下角出現“技能點不足”的提示
Vector location=new Vector(30,730);
explosions.add(new FlyObject(location,30,"技能點不足.png"));
}
}
F技能
F技能向我機周圍發射環狀火力,想要實作這一點,我們只需要建立一個for回圈,在回圈中,每生成一次子彈就讓子彈運動速度方向轉換一個角度,

//F技能
if(len%5==0&mps.size()>5&skill_F>0){
skill_F--;
int direction=60;//子彈發射的方向數量,數量越大發射的子彈越多
int speedx=20;//子彈速度
for(int i=0;i<direction;i++){
Vector location=new Vector((double)mps.get(mps.size()-5).location.x,(double)mps.get(mps.size()-5).location.y);
Vector speed=new Vector(speedx*Math.cos(Math.PI*i*2/direction),speedx*Math.sin(Math.PI*i*2/direction));
bults.add(new FlyObject(location,speed,3,"火球.png"));
}
}
S技能
S技能是保護罩,保護罩的作用是躲避敵人的一切攻擊,要做到這一點,我們可以創建一個整數skill_S(初始值定為0),當我們使用S技能時skill_S增加一個定值,然后在判斷敵機和敵機子彈是否與我機碰撞之前,先判斷一下skill_S的值是否大于0,如果是,則跳過碰撞的判斷;如果為否,則正常進行碰撞判斷,每經過一輪畫圖,skill_S的值就減1,
此外,在S技能即將失效的時候,螢屏右下角會有一個閃爍警告圖片顯示,想要實作這個效果,我們可以每一輪對skill_S的值進行判斷,當它等于某一個值的時候(不是小于等于而是等于,因為我們只需要放置一次圖片),就在右下角放置一張動圖,

技能冷卻(繪制部分圖片)
技能冷卻的三種狀態:

我們需要用到三張圖片,首先一張透明度為40%的圖片打底,繪制在最底層,然后讓一張透明度為100%的圖片部分疊加其上,并隨著時間不斷填充整個圖幅,當技能冷卻完成時,繪制一張邊緣發光的圖片,

這里我們遇到的一個問題是,如何顯示一張圖片的部分影像?博主在網路上尋找了很多裁剪圖片的方法,但大多數要么很復雜,要么需要使用到第三方庫,這里博主有一個非常簡單易行的實作方法:

首先我們創建一個比圖片尺寸要小的BufferedImage物件,然后將這個圖片繪制在這個BufferedImage上,但是縱坐標應該為負值,最終要使圖片的底部和BufferedImage的底部對齊,這樣顯示在表單上的就是BufferedImage和圖片重疊的這一部分了,我們只需要按照一定規律對BufferedImage的尺寸和圖片的縱坐標進行改變,就可以實作技能加載的效果,
//繪制透明圖片
bufg.drawImage(loadingImg.getGraph("技能F2.png"),1100,800,null);
//F技能
if(skill_F_wait<SKF){
//skill_F是等待的“時間”,SKF是技能完成加載需要的“時間”
skill_F_wait++;
//下面的代碼實作的就是繪制部分圖片
int high_f=70*skill_F_wait/SKF+1;
BufferedImage temp_bufimg=new BufferedImage(70,high_f,2);
Graphics temp_g=temp_bufimg.getGraphics();
temp_g.drawImage(loadingImg.getGraph("技能F1.png"),0,high_f-70,null);
bufg.drawImage(temp_bufimg,1100,870-high_f,null);
}else{
//技能加載完成
skill_F_wait=SKF;
//畫一張發光的圖
bufg.drawImage(loadingImg.getGraph("技能F3.png"),1100,800,null);
}
獎勵機
獎勵機是每隔一段時間有概率刷出的,獎勵機被擊中后會隨機掉落道具,被擊毀后會掉落特殊道具,
我們需要實作以下三點:
- 獎勵機被擊中后掉落道具;
- 道具的隨機運動路徑;
- 擊毀后隨機掉落道具,

隨機掉落道具
怎么設定隨機掉落道具?我們可以獲取一個大小在0-79的亂數,當這個亂數等于1時掉落某個道具,那么這個道具出現的概率就為1/80;如果我們想讓某個道具掉落概率增加,就可以減小亂數的大小范圍,或者給該道具多設定幾個數字(比如說當亂數為0、1、2、3時掉落該道具,則其出現的概率就為原來的4倍),
private void dropProp(FlyObject fo){
if(fo.imgName.equals("獎勵機_1.png")){
Random ran=new Random();
//獲取一個0-79的亂數
int pt=ran.nextInt(80);
String propType=null;
switch(pt){
case 1:propType="火力藍_獎勵.png";break;
case 2:propType="技能點_獎勵.png";break;
case 3:propType="技能點_獎勵.png";break;
case 4:propType="生命值_獎勵.png";break;
default:propType="金幣.png";
}
//為掉落的道具設定隨機速度
int spd_x=ran.nextInt(20)-10;
int spd_y=ran.nextInt(20)-10;
Vector speed=new Vector(spd_x,spd_y);
Vector location=new Vector(fo.location.x,fo.location.y);
props.add(new FlyObject(location,speed,320,propType));
}
}
擊中及擊毀后掉落道具
在判斷碰撞的方法中,加入以下代碼,如果確定被子彈擊中的飛行物為獎勵機,則讓獎勵機扣除相應血量并呼叫dropProp函式隨機掉落道具;如果獎勵機血量小于0,說明獎勵機被擊毀,此時呼叫dropProp_bomb函式(和dropProp只有掉落道具的概率不同),
if(enemy.imgName.equals("獎勵機_1.png")){
enemy.HP-=bullet.HP;
if(len%3==1){
dropProp(enemy);
}
if(enemys.get(i).HP<=0){
explosion(enemy);
for(int k=0;k<4;k++){
dropProp_bomb(enemy);
}
moveAway(enemy);
}
Boss模式
Boss技能
Boss一共有三個技能,在Boss使用技能前先獲取一個亂數,這個亂數將決定Boss使用哪一個技能,
Random ran=new Random();
if(len_boss%600==0){
bossSkill=ran.nextInt(3);//獲取亂數
bossSkillTime=450;
}
Boss技能1:
boss技能1的原理與F技能的原理相似(發射一個子彈,轉換一次角度),但boss技能1只向正前方固定角度范圍發射子彈,且有一段固定長度的空缺,


//boss技能1
if(len_boss%20==0&bossSkill==0&bossSkillTime>0){
int direction=100;//代表Boss發射一圈子彈的方向數
int shootAngel=20;//代表direction中有多長一段是發射子彈的
int speedx=10;//子彈速度
int startPoint=ran.nextInt(15);//空缺開始的位置(隨機)
for(int i=direction/2-shootAngel/2;i<direction/2+shootAngel/2;i++){
int flag_skip=i-direction/2+shootAngel/2;
//跳過空缺的一段子彈
if(!(flag_skip<startPoint+5&flag_skip>startPoint)){
Vector location=new Vector((double)enemys.get(0).location.x+200,(double)enemys.get(0).location.y+150);
Vector speed=new Vector((speedx*Math.cos(Math.PI*i*2/direction)),(speedx*Math.sin(Math.PI*i*2/direction)));
enemys.add(new FlyObject(location,speed,3,"boss_子彈.png"));
}
}
}
Boss技能2:
Boss2技能的實作很巧妙,大家仔細對比技能1和技能2的代碼就會發現,它們其實長得差不多!最大不同之處在于,技能1是子彈發射時跳過某一段子彈(造成某一段子彈空缺),而技能2正好就是只發射某一段子彈,相當于同一段代碼實作了兩個技能兩種玩法哈哈哈!

//boss技能2
if(len_boss%2==0&bossSkill==1&bossSkillTime>0){
if(len_boss%10==0){startPoint=ran.nextInt(35);}//每隔一段時間就切換一次發射子彈的角度段
int direction=100;
int shootAngel=40;
int speedx=20;
for(int i=direction/2-shootAngel/2;i<direction/2+shootAngel/2;i++){
int flag_skip=i-direction/2+shootAngel/2;
if(flag_skip<startPoint+5&flag_skip>startPoint){
Vector location=new Vector((double)enemys.get(0).location.x+200,(double)enemys.get(0).location.y+150);
Vector speed=new Vector((speedx*Math.cos(Math.PI*i*2/direction)),(speedx*Math.sin(Math.PI*i*2/direction)));
enemys.add(new FlyObject(location,speed,3,"boss_子彈.png"));
}
}
}
Boss技能3:

Boss技能3實作的是交替使用兩波數量和速度均有差別的子彈發射方式,仔細觀察可以發現這兩波子彈是沒有對齊的,這樣的玩法就是我們的飛機可以在這兩波子彈交錯的空隙中穿插而過,

flag_switch的作用就是控制兩種子彈發射方式交替進行,
//boss技能3
if(len_boss%30==0&bossSkill==2&bossSkillTime>0){
flag_switch++;
if(flag_switch%2==0){
int direction=30;
int shootAngel=30;
int speedx=8;
for(int i=direction/2-shootAngel/2;i<direction/2+shootAngel/2;i++){
Vector location=new Vector((double)enemys.get(0).location.x+200,(double)enemys.get(0).location.y+150);
Vector speed=new Vector((speedx*Math.cos(Math.PI*i*2/direction)),(speedx*Math.sin(Math.PI*i*2/direction)));
enemys.add(new FlyObject(location,speed,3,"boss_子彈.png"));
}
}else if(flag_switch%2==1){
int direction=30;
int shootAngel=30;
int speedx=7;
for(int i=direction/2-shootAngel/2;i<direction/2+shootAngel/2;i++){
Vector location=new Vector((double)enemys.get(0).location.x+200,(double)enemys.get(0).location.y+150);
Vector speed=new Vector((speedx*Math.cos(Math.PI*i*2/direction+Math.PI/direction)),(speedx*Math.sin(Math.PI*i*2/direction+Math.PI/direction)));
enemys.add(new FlyObject(location,speed,3,"boss_子彈.png"));
}
}
}
功能磁區
Boss模式的功能磁區玩法是博主自創的,規則是當我們的飛機停留在某一個區域時會有對應的效果加成,飛機停于回血區時會緩慢回血(并附有加血特效的顯示);停于戰斗區時發射的子彈火力會增大;在道具區停留一段時間,飛機的技能點會增加;停于攻擊區時會對Boss有雷電傷害加成;停于躲避區時飛機會自動獲得保護罩,不受任何攻擊,

首先我們需要撰寫一個方法,來判斷我們的飛機目前處于什么功能區域,功能區域劃分的主要依據是與Boss中心的距離(除躲避區外其余功能區都是以Boss中心為圓心的圓環或圓),當我們的飛機進入對應的區域時,變數areaName和areaName2就更改為對應的區域名稱,方便進行下一步操作,
public void judgeFunctionArea(int x, int y) {
int distance=(int)(Math.sqrt(Math.pow(x-1000,2)+Math.pow(y-450,2)));//距離Boss中心的距離
//進入boss模式后開啟判斷
if(len_boss>0){
if(x>900&x<1200&!(distance<300)){
//因為躲避區是矩形區域,所以判斷方式有所不同
areaName="躲避區-紫.png";
areaName2="躲避區.png";
// System.out.println("目前處于躲避區-紫");
}else{
if(distance<300){
areaName="攻擊區-紅.png";
areaName2="進擊區.png";
// System.out.println("目前處于攻擊區-紅");
}else if(distance<600){
areaName="道具區-黃.png";
areaName2="技能區.png";
// System.out.println("目前處于道具區-黃");
}else if(distance<900){
areaName="戰斗區-藍.png";
areaName2="戰斗區.png";
// System.out.println("目前處于戰斗區-藍");
}else if(distance<1200){
areaName="回血區-綠.png";
areaName2="回血區.png";
// System.out.println("目前處于回血區-綠");
}
}
}
功能磁區效果實作
//繪制功能區
if(!areaName.equals("")){
bufg.drawImage(loadingImg.getGraph(areaName),
1000-hm.get(areaName).x/2,450-hm.get(areaName).y/2,null);
bufg.drawImage(loadingImg.getGraph(areaName2),260,70,null);
switch(areaName){
case"回血區-綠.png":if(len_boss%5==0){newLife++;explosion(mps.get(mps.size()-5),0);}break;
case"躲避區-紫.png":skill_S++;break;//處于躲避區就相當于使用了技能S
case"道具區-黃.png":yellowAreaTime++;break;
case"攻擊區-紅.png":
//Boss模式下enemys的第一個元素為Boss
enemys.get(0).HP-=5;
//加入閃電特效
if(len_boss%20==0){explosion(enemys.get(0),1);}
if(len_boss%10==0){explosion(enemys.get(0),2);explosion(enemys.get(0),2);}
break;
case"戰斗區-藍.png":superPropTime=true;break;
//我機發射子彈時會對superPropTime進行一個判斷,當其為true時發射特殊子彈
}
}
if(yellowAreaTime>100){
//計算在道具區的停留時間,yellowAreaTime每達到100就增加一個技能點
yellowAreaTime-=100;
skillPoint++;
}
回血區

戰斗區

攻擊區

躲避區

背景音樂(帶調節音量效果)
背景音樂和爆炸音效的實作我使用了兩個不同的類,分別是BackgroundMusic和PlayMusic,其中BackgroundMusic類繼承了Runnable介面(也就是說播放背景音樂的時候也需要創建一個執行緒),這個類目前博主已經實作了暫停/播放、重新播放、音量調節、停止播放的功能,并且將這個類做成了一個單獨的demo
背景音樂Demo(只能播放wav音頻檔案)
該Demo大概的實作原理是將wav音頻檔案的波形轉化為一段一段的陣列,然后逐段播放,處理檔案的部分參考了CSDN某個博主的博客,因為當時沒有收藏,所以沒能找到參考的是哪一篇博客,如果這位博主看到了這里,希望能夠聯系我標記出處!
RESTART:重新播放
on_off:暫停/播放
STOP:停止播放
“+”“-”:調節音量

Demo代碼獲取鏈接:提取碼:q4im
播放音樂
播放音樂的類(BackgroundMusic)繼承了Runnable介面,所以想要播放音樂,應該先創建執行緒物件,然后啟動執行緒實作音樂播放,我們可以把這一步放到BackgroundMusic的構造方法中,這樣我們每次創建BackgroundMusic物件時就可以自動創建執行緒并播放音樂了,
public BackgroundMusic(String fileAddress){
this.fileAddress=fileAddress;
Thread micthc=new Thread(this);
micthc.start();
on_off();
}
如果我們想在某一個地方播放音樂,只需要:
//游戲頁面中播放的音樂
level_1_Music=new BackgroundMusic(fileAddress+"雷火BackgroundMusic.wav");
//括號中為音樂檔案路徑名稱
暫停界面調節音量

private void page_pause(Graphics bufg) {
//暫停界面背景
bufg.drawImage(loadingImg.getGraph("背景_1.png"),0,0, null);
//繪制按鍵
bufg.drawImage(loadingImg.getGraph("暫停中.png"),0,100, null);
bufg.drawImage(loadingImg.getGraph("音量關.png"),950,200, null);
bufg.drawImage(loadingImg.getGraph("+.png"),950,270, null);
bufg.drawImage(loadingImg.getGraph("-.png"),950,600, null);
bufg.drawImage(loadingImg.getGraph("空格或點擊此處繼續游戲.png"),0,100, null);
bufg.drawImage(loadingImg.getGraph("重新開始.png"),0,50, null);
//判斷現在在放哪一個背景音樂
BackgroundMusic bgm=new BackgroundMusic();
if(len_boss==0){
bgm=level_1_Music;
}else{
bgm=level_1_boss_Music;
}
//調節音量
if(bgm.value>5){bgm.value=5;}else if(bgm.value<1){bgm.value=0.5;}
//背景音樂暫停/繼續
if(bgm.flag_pause){bufg.drawImage(loadingImg.getGraph("音量開.png"),950,200, null);}
//繪制音量大小
for(int i=0;i<bgm.value;i++){
bufg.drawImage(loadingImg.getGraph("音量.png"),950,540-i*50, null);
}
//繪出目前要按的鍵
bufg.drawImage(loadingImg.getGraph(nowPress),nowPressx,nowPressy, null);
}
經驗總結
經常使用的代碼塊可以撰寫成方法
將經常使用的代碼塊撰寫成方法,可以減少代碼的重復,使代碼更加簡潔,比如在雷火這個小游戲中,判斷碰撞(jgat函式)就經常被使用到,所以可以單獨寫成一個方法,
功能模塊化處理
將功能模塊化處理,可以方便程式的理解以及排查bug和增添功能,在雷火游戲中,每個頁面(開始、暫停、游戲等)都是獨立實作的,生成飛行物、繪制飛行物、判斷碰撞、重繪分數等功能都獨立撰寫了方法,特別是程式的體量比較大的時候,模塊化處理能讓我們的程式更加容易除錯,
Debug模式的使用
debug模式主要作用是在用戶指定的地方讓代碼停下來,然后逐步執行并發現問題,

打斷點
雙擊編輯視窗靠左的區域(行數的左邊),會出現一個藍色的點,當我們以Debug模式啟動程式時,運行到這一步會自動停下,

逐步執行
進入Debug模式后:

消化他人的代碼
有時候我們想實作一個新的效果,比如旋轉圖片、播放音樂,我們可能會直接上百度或者CSDN搜索實作代碼,如果我們只是把代碼簡單的插入到我們的程式中,那我們可能只是一個“調包俠”哈哈哈,如果你打算使用一段別人的代碼,不妨仔細讀一下,理解它的實作原理,理解作者的奇思妙想,尋找更多我們能運用到其他地方的知識,
最后
這個小游戲取名叫雷火,有我的一點情懷在里面,小時候還在按鍵機年代,我爸手機上有一個游戲就叫雷火,但是現在已經很難找到了,現在做了一個這樣的游戲也算是實作了我小時候的夢想,那時候按鍵機游戲很多都是java開發的(開始畫面會有經典的java的咖啡圖示出現)當時還不知道這個圖示的含義,現在,我正在用這種語言嘗試著學會開發程式,這是一種很奇妙的感覺,

游戲中使用到的絕大多數圖片素材都是我用手繪板和PS軟體繪制的,算是我的一個業余小愛好哈哈哈,所以大家不需要擔心著作權問題,可以放心使用,

我的其他一些繪畫作品

最后的最后,感謝你能夠看到這里!這篇博客花費了我非常大的心血,我已經記不清開發這個小游戲的那兩個星期有多少次不自覺地熬到凌晨三四點,夜深人靜,靈感卻不斷涌現,現在回想起來真是相當的充實,所以,我非常也希望能夠幫助讀到這篇博客的你,如果有小伙伴因為看了我的博客頗有識訓,我也非常非常歡迎你來和我交流!
游戲的完整代碼,放在這里!
提取碼:mk1n
解壓后可以直接運行的jar包,放在這里!(可以直接試玩)
提取碼:jagk
轉載請註明出處,本文鏈接:https://www.uj5u.com/qita/266992.html
標籤:其他
