主頁 >  其他 > Java多執行緒游戲實體分享2-雷火(手把手教你做個超炫酷的星際爭霸)

Java多執行緒游戲實體分享2-雷火(手把手教你做個超炫酷的星際爭霸)

2021-03-07 10:08:44 其他

觀前提示: 本文涉及的小成果特別多,即使你不需要寫一個和我完全相同的游戲,也可以按照需要查看某些特定功能的實作程序,說不定能夠給您的程式開發帶來一點小小的啟發!PS:結合源代碼閱讀此博客更加高效,

本博客的內容可看作上一篇多執行緒游戲實體分享的擴展,上一篇博客介紹過的內容(如何實作一個簡單的小游戲)在本博客中不再贅述,如果有疑問可以回訪那篇博客(點擊此處跳轉)或者私信博主,

游戲整體效果演示視頻:

<iframe id="r5ed16c6-1614527205956" src="https://player.bilibili.com/player.html?aid=801897169" allowfullscreen="true" data-mediaembed="bilibili"></iframe>

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);
}

獎勵機

獎勵機是每隔一段時間有概率刷出的,獎勵機被擊中后會隨機掉落道具,被擊毀后會掉落特殊道具,

我們需要實作以下三點:

  1. 獎勵機被擊中后掉落道具;
  2. 道具的隨機運動路徑;
  3. 擊毀后隨機掉落道具,

在這里插入圖片描述

隨機掉落道具

怎么設定隨機掉落道具?我們可以獲取一個大小在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

標籤:其他

上一篇:2021-03-06 阿里春招實習筆試演算法題記錄

下一篇:【LeetCode】283. 移動零(C++)

標籤雲
其他(157675) Python(38076) JavaScript(25376) Java(17977) C(15215) 區塊鏈(8255) C#(7972) AI(7469) 爪哇(7425) MySQL(7132) html(6777) 基礎類(6313) sql(6102) 熊猫(6058) PHP(5869) 数组(5741) R(5409) Linux(5327) 反应(5209) 腳本語言(PerlPython)(5129) 非技術區(4971) Android(4554) 数据框(4311) css(4259) 节点.js(4032) C語言(3288) json(3245) 列表(3129) 扑(3119) C++語言(3117) 安卓(2998) 打字稿(2995) VBA(2789) Java相關(2746) 疑難問題(2699) 细绳(2522) 單片機工控(2479) iOS(2429) ASP.NET(2402) MongoDB(2323) 麻木的(2285) 正则表达式(2254) 字典(2211) 循环(2198) 迅速(2185) 擅长(2169) 镖(2155) 功能(1967) .NET技术(1958) Web開發(1951) python-3.x(1918) HtmlCss(1915) 弹簧靴(1913) C++(1909) xml(1889) PostgreSQL(1872) .NETCore(1853) 谷歌表格(1846) Unity3D(1843) for循环(1842)

熱門瀏覽
  • 網閘典型架構簡述

    網閘架構一般分為兩種:三主機的三系統架構網閘和雙主機的2+1架構網閘。 三主機架構分別為內端機、外端機和仲裁機。三機無論從軟體和硬體上均各自獨立。首先從硬體上來看,三機都用各自獨立的主板、記憶體及存盤設備。從軟體上來看,三機有各自獨立的作業系統。這樣能達到完全的三機獨立。對于“2+1”系統,“2”分為 ......

    uj5u.com 2020-09-10 02:00:44 more
  • 如何從xshell上傳檔案到centos linux虛擬機里

    如何從xshell上傳檔案到centos linux虛擬機里及:虛擬機CentOs下執行 yum -y install lrzsz命令,出現錯誤:鏡像無法找到軟體包 前言 一、安裝lrzsz步驟 二、上傳檔案 三、遇到的問題及解決方案 總結 前言 提示:其實很簡單,往虛擬機上安裝一個上傳檔案的工具 ......

    uj5u.com 2020-09-10 02:00:47 more
  • 一、SQLMAP入門

    一、SQLMAP入門 1、判斷是否存在注入 sqlmap.py -u 網址/id=1 id=1不可缺少。當注入點后面的引數大于兩個時。需要加雙引號, sqlmap.py -u "網址/id=1&uid=1" 2、判斷文本中的請求是否存在注入 從文本中加載http請求,SQLMAP可以從一個文本檔案中 ......

    uj5u.com 2020-09-10 02:00:50 more
  • Metasploit 簡單使用教程

    metasploit 簡單使用教程 浩先生, 2020-08-28 16:18:25 分類專欄: kail 網路安全 linux 文章標簽: linux資訊安全 編輯 著作權 metasploit 使用教程 前言 一、Metasploit是什么? 二、準備作業 三、具體步驟 前言 Msfconsole ......

    uj5u.com 2020-09-10 02:00:53 more
  • 游戲逆向之驅動層與用戶層通訊

    驅動層代碼: #pragma once #include <ntifs.h> #define add_code CTL_CODE(FILE_DEVICE_UNKNOWN,0x800,METHOD_BUFFERED,FILE_ANY_ACCESS) /* 更多游戲逆向視頻www.yxfzedu.com ......

    uj5u.com 2020-09-10 02:00:56 more
  • 北斗電力時鐘(北斗授時服務器)讓網路資料更精準

    北斗電力時鐘(北斗授時服務器)讓網路資料更精準 北斗電力時鐘(北斗授時服務器)讓網路資料更精準 京準電子科技官微——ahjzsz 近幾年,資訊技術的得了快速發展,互聯網在逐漸普及,其在人們生活和生產中都得到了廣泛應用,并且取得了不錯的應用效果。計算機網路資訊在電力系統中的應用,一方面使電力系統的運行 ......

    uj5u.com 2020-09-10 02:01:03 more
  • 【CTF】CTFHub 技能樹 彩蛋 writeup

    ?碎碎念 CTFHub:https://www.ctfhub.com/ 筆者入門CTF時時剛開始刷的是bugku的舊平臺,后來才有了CTFHub。 感覺不論是網頁UI設計,還是題目質量,賽事跟蹤,工具軟體都做得很不錯。 而且因為獨到的金幣制度的確讓人有一種想去刷題賺金幣的感覺。 個人還是非常喜歡這個 ......

    uj5u.com 2020-09-10 02:04:05 more
  • 02windows基礎操作

    我學到了一下幾點 Windows系統目錄結構與滲透的作用 常見Windows的服務詳解 Windows埠詳解 常用的Windows注冊表詳解 hacker DOS命令詳解(net user / type /md /rd/ dir /cd /net use copy、批處理 等) 利用dos命令制作 ......

    uj5u.com 2020-09-10 02:04:18 more
  • 03.Linux基礎操作

    我學到了以下幾點 01Linux系統介紹02系統安裝,密碼啊破解03Linux常用命令04LAMP 01LINUX windows: win03 8 12 16 19 配置不繁瑣 Linux:redhat,centos(紅帽社區版),Ubuntu server,suse unix:金融機構,證券,銀 ......

    uj5u.com 2020-09-10 02:04:30 more
  • 05HTML

    01HTML介紹 02頭部標簽講解03基礎標簽講解04表單標簽講解 HTML前段語言 js1.了解代碼2.根據代碼 懂得挖掘漏洞 (POST注入/XSS漏洞上傳)3.黑帽seo 白帽seo 客戶網站被黑帽植入劫持代碼如何處理4.熟悉html表單 <html><head><title>TDK標題,描述 ......

    uj5u.com 2020-09-10 02:04:36 more
最新发布
  • 2023年最新微信小程式抓包教程

    01 開門見山 隔一個月發一篇文章,不過分。 首先回顧一下《微信系結手機號資料庫被脫庫事件》,我也是第一時間得知了這個訊息,然后跟蹤了整件事情的經過。下面是這起事件的相關截圖以及近日流出的一萬條資料樣本: 個人認為這件事也沒什么,還不如關注一下之前45億快遞資料查詢渠道疑似在近日復活的訊息。 訊息是 ......

    uj5u.com 2023-04-20 08:48:24 more
  • web3 產品介紹:metamask 錢包 使用最多的瀏覽器插件錢包

    Metamask錢包是一種基于區塊鏈技術的數字貨幣錢包,它允許用戶在安全、便捷的環境下管理自己的加密資產。Metamask錢包是以太坊生態系統中最流行的錢包之一,它具有易于使用、安全性高和功能強大等優點。 本文將詳細介紹Metamask錢包的功能和使用方法。 一、 Metamask錢包的功能 數字資 ......

    uj5u.com 2023-04-20 08:47:46 more
  • vulnhub_Earth

    前言 靶機地址->>>vulnhub_Earth 攻擊機ip:192.168.20.121 靶機ip:192.168.20.122 參考文章 https://www.cnblogs.com/Jing-X/archive/2022/04/03/16097695.html https://www.cnb ......

    uj5u.com 2023-04-20 07:46:20 more
  • 從4k到42k,軟體測驗工程師的漲薪史,給我看哭了

    清明節一過,盲猜大家已經無心上班,在數著日子準備過五一,但一想到銀行卡里的余額……瞬間心情就不美麗了。最近,2023年高校畢業生就業調查顯示,本科畢業月平均起薪為5825元。調查一出,便有很多同學表示自己又被平均了。看著這一資料,不免讓人想到前不久中國青年報的一項調查:近六成大學生認為畢業10年內會 ......

    uj5u.com 2023-04-20 07:44:00 more
  • 最新版本 Stable Diffusion 開源 AI 繪畫工具之中文自動提詞篇

    🎈 標簽生成器 由于輸入正向提示詞 prompt 和反向提示詞 negative prompt 都是使用英文,所以對學習母語的我們非常不友好 使用網址:https://tinygeeker.github.io/p/ai-prompt-generator 這個網址是為了讓大家在使用 AI 繪畫的時候 ......

    uj5u.com 2023-04-20 07:43:36 more
  • 漫談前端自動化測驗演進之路及測驗工具分析

    隨著前端技術的不斷發展和應用程式的日益復雜,前端自動化測驗也在不斷演進。隨著 Web 應用程式變得越來越復雜,自動化測驗的需求也越來越高。如今,自動化測驗已經成為 Web 應用程式開發程序中不可或缺的一部分,它們可以幫助開發人員更快地發現和修復錯誤,提高應用程式的性能和可靠性。 ......

    uj5u.com 2023-04-20 07:43:16 more
  • CANN開發實踐:4個DVPP記憶體問題的典型案例解讀

    摘要:由于DVPP媒體資料處理功能對存放輸入、輸出資料的記憶體有更高的要求(例如,記憶體首地址128位元組對齊),因此需呼叫專用的記憶體申請介面,那么本期就分享幾個關于DVPP記憶體問題的典型案例,并給出原因分析及解決方法。 本文分享自華為云社區《FAQ_DVPP記憶體問題案例》,作者:昇騰CANN。 DVPP ......

    uj5u.com 2023-04-20 07:43:03 more
  • msf學習

    msf學習 以kali自帶的msf為例 一、msf核心模塊與功能 msf模塊都放在/usr/share/metasploit-framework/modules目錄下 1、auxiliary 輔助模塊,輔助滲透(埠掃描、登錄密碼爆破、漏洞驗證等) 2、encoders 編碼器模塊,主要包含各種編碼 ......

    uj5u.com 2023-04-20 07:42:59 more
  • Halcon軟體安裝與界面簡介

    1. 下載Halcon17版本到到本地 2. 雙擊安裝包后 3. 步驟如下 1.2 Halcon軟體安裝 界面分為四大塊 1. Halcon的五個助手 1) 影像采集助手:與相機連接,設定相機引數,采集影像 2) 標定助手:九點標定或是其它的標定,生成標定檔案及內參外參,可以將像素單位轉換為長度單位 ......

    uj5u.com 2023-04-20 07:42:17 more
  • 在MacOS下使用Unity3D開發游戲

    第一次發博客,先發一下我的游戲開發環境吧。 去年2月份買了一臺MacBookPro2021 M1pro(以下簡稱mbp),這一年來一直在用mbp開發游戲。我大致分享一下我的開發工具以及使用體驗。 1、Unity 官網鏈接: https://unity.cn/releases 我一般使用的Apple ......

    uj5u.com 2023-04-20 07:40:19 more