Java課程設計-泡泡堂(個人)


1. 團隊課程設計博客鏈接

https://www.cnblogs.com/choco1ate/p/12172223.html

2.需求分析

(1)人物屬性:

生命值,攜帶炸彈數,移動速度,炸彈威力

(2)通過讀取人物能夠丟炸彈,並且在人物向不同方向移動的時候,人物方向也會隨之改動

(3)道具:

   加速道具,增加炸彈攜帶數量,增加炸彈威力
   無敵南瓜-吃到后獲得5秒的無敵效果
   生命泡泡-吃到該道具后生命值加1

(4)游戲背景音樂和游戲地圖在每次游戲啟動的時候能夠隨機改動

3. 本組課題及本人任務

本組課題:泡泡堂游戲
本人任務:主要編寫Player類以及PlayerAttribute類,添加部分道具以及游戲背景音樂以及部分地圖

本人在項目中的Git提交記錄截圖


4.代碼分析

private static final long serialVersionUID = 1L;
	/**玩家鍵盤控制參數,初始均為false*/
	private boolean bL = false, bU = false, bD = false, bR = false;
	/**玩家當前生命值,初始4*/
	public int live=4;
	/**玩家是否存活*/
	public boolean isalive=true;
	/**玩家當前速度,初始10*/
	public int speed=10;
	/**玩家速度限制最大值*/
	public int maxspeed=20;
	/**玩家同時可投炸彈數,初始1*/
	public int bombnum=1;
	/**玩家可投炸彈數限制最大值*/
	public int maxbombnum=6;
	/**現存的炸彈數*/
	int bombexist=0;
	/**玩家當前炸彈威力,初始1*/
	public int power=1;
	/**玩家炸彈威力限制最大值*/
	public int maxpower=5;

	/**人物方向轉動圖片*/
	String pathU;
	String pathD;
	String pathL;
	String pathR;

此部分代碼為玩家的各種初始屬性

Player類的構造函數

public Player(String pathU,String pathD,String pathL,String pathR,int x,int y,int index)//初始化玩家信息
	{
		this.pathU=pathU;
		this.pathD=pathD;
		this.pathL=pathL;
		this.pathR=pathR;//四個存儲人物各個方向的圖片
		imgPath=pathD;
		this.index=index;//標記是1P還是2P玩家
		x*=80;
		y*=80;
		this.x=x;
		this.y=y;//人物的坐標
		this.lastX=x;
		this.lastY=y;//人物的上一坐標
		this.setBounds(this.x,this.y,80,80);//設置人物大小
		setIcon(pathD,this);//設置人物圖片
	}

枚舉類 代表上、下、左、右和停止

public enum Direction {
		//分別代表上下左右和停止
		L, D, U, R, STOP
	}

人物移動的實現:

通過一個有參方法進行。該函數通過對人物的坐標更新賦值,從而進行人物的移動,部分代碼如下

public void moveStep(int n)
	{
			 int l;
			 int loop=80;
        for(l=0;l<=loop;l+=speed)
			{
        	 switch(n)
        	 {
        	 case 1:
        		 this.setLocation(x-l,y);
        		 break;
        	 case 2:
        		 this.setLocation(x,y-l);
        		 break;
        	 case 3:
        		 this.setLocation(x+l,y);
        		 break;
        	 case 4:
        		 this.setLocation(x,y+l);
        		 break;
        	 default:
         		break;
        		 
        	 }
				
				//如果遇到碰撞物,則不移動
				if(meetbox())
					
				{ 
					break;
				}
				if(l<80)
				{
					try {
						Thread.sleep(40);
					} catch (InterruptedException e) {
						e.printStackTrace();
					}
				}
			}
			//如果循環后最后移動值不為80,則修復。強制用戶每次只能移動一格的距離
			if(l!=loop)

			{
				l=80;
				switch(n)
	        	 {
	        	 case 1:
	        		 this.setLocation(x-l,y);
	        		 break;
	        	 case 2:
	        		 this.setLocation(x,y-l);
	        		 break;
	        	 case 3:
	        		 this.setLocation(x+l,y);
	        		 break;
	        	 case 4:
	        		 this.setLocation(x,y+l);
	        		 break;
	        	 default:
	            		break;
	        		 
	        	 }
				
			}
	}

通過讀取方向變量dir,調用moveStep函數移動人物

public void move() {
		//定義上一位置
		this.lastX = x;
		//定義上一位置
		this.lastY = y;
		//在不遇到(碰撞物、邊界、炸彈)的情況下,每40μs移動speed個像素,一共移動80像素(地圖上一個格子的寬度)
		switch (dir) {
			//向左移動
			case L:
				
				 if(!invincible)
				 {
					 setIcon(pathL);
				 }
				moveStep(1);
				break;
			//向下移動	
			case U:
				 if(!invincible)
				 {
					 setIcon(pathU);
				 }
				moveStep(2);
				break;
			//向右移動
			case R:	
				 if(!invincible)
				 {
					 setIcon(pathR);
				 }
				moveStep(3);
				break;
			//向下移動
			case D:
				 if(!invincible)
				 {
					 setIcon(pathD);
				 }
				moveStep(4);
				break;
			case STOP:
				//停止
				break;
			default:
				break;
		}
		//更新x,y坐標值
		this.x=this.getX();
		this.y=this.getY();
	}

meetbox方法來檢測是否遇到了各種障礙物,如果檢測到了障礙物則返回false結果

boolean meetbox()
	
	{
		int x = 1120;
		int y = 880;
		if(this.getX()<0||this.getX()>x||this.getY()<0||this.getY()>y)
		{
			return true;
		}
		int w = 15;
		int h = 12;
		for(int i=0;i<w;i++)
		{	
			for(int j=0;j<h;j++)
			{
				Box temp=GameFrame.thismap.getBoxbyLocation(i,j);
				if(temp.getRect().intersects(this.getRect())&&temp.isExist)
				{
					if(!temp.isdestroyshowT)
						//遇到箱子
					{
						return true;
					}
				}
				if(temp.isExistBomb&&!temp.isExistPlayer&&temp.getRect().intersects(this.getRect()))
					//遇到炸彈
				{
					return true;
				}
			}
		}
		return false;
	}

無敵道具效果的實現

class InvincibleThread2 extends Thread
	{

		@Override
		public void run(){
			//無敵時間開始
			invincible=true;

			int loop = 50;
			//獲得5秒的閃爍無敵時間
			for(int i=0;i<loop;i++)
			{
				//每0.1秒閃爍一個循環,一共閃爍50次
				setIcon("images/default.png");

				try {
					Thread.sleep(50);
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
				setIcon("images/invinciblePlayer.png");
				try {
					Thread.sleep(50);
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
			}
			setIcon(imgPath);
			invincible=false;
		}
	}

當玩家受到炸彈傷害時,調用這個方法

/**玩家被炸到(受傷)*/
	public void beInjured()
	{
		//生命值-1
		this.live--;
		//設置玩家屬性面板的生命值
		this.pla.setLabel("生命值:"+this.live,this.pla.live);

		//如果死了(生命=0)
		if(live==0)

		{
			//玩家死亡
			this.isalive=false;
			//玩家屬性面板頭像更換
			setIcon("images/player"+this.index+"DIED.png",this.pla.photo);
			//玩家圖片更換
			setIcon("images/player"+this.index+"DIED.png",this);
			//提示消息
			JOptionPane.showMessageDialog(this,"玩家"+this.index+"陣亡!","GameOver",2);
			//總人數減1
			GameFrame.NumofAlive--;
			//如果總存活數為1,則游戲結束
			if(GameFrame.NumofAlive==1)

			{
				JOptionPane.showMessageDialog(this,"游戲結束!單機確認退出~","GameOver",2);
				//顯示主界面
				System.exit(0);

			}
		}
		else
		{
			//定義玩家無敵的線程
			Thread th=new InvincibleThread1();
			th.start();
		}
	}

以下代碼為加速道具,加威力道具,加生命道具以及加攜帶炸彈數量道具,通過對相應的屬性增加進行功能的實現

public void plusspeed()
	{
		//如果當前速度小於最高速度
		if(myP.speed<myP.maxspeed)
		{
			myP.speed+=2;
			//更新速度數據
			setLabel("速度:"+myP.speed,speed);

		}
	}
	
	public void plusbombnum()
	{
		//如果當前炸彈數小於最高炸彈數
		if(myP.bombnum<myP.maxbombnum)

		{
			//炸彈數加一
			myP.bombnum++;
			//更新炸彈數據
			setLabel("泡泡數:"+myP.bombnum,bombnum);
		}
	}
	
	public void pluspower()

	{
		if(myP.power<myP.maxpower)
			//如果當前威力小於最高威力
		{
			//威力加一
			myP.power++;
			//更新威力數據
			setLabel("威力:"+myP.power,power);

		}
	}
	public void pluslive()

	{
		//生命加一
		myP.live++;
		//刷新生命值數據
		setLabel("生命值:"+myP.live,live);

		
	}

背景音樂的線程

public class Music extends Thread

{
	
	 public volatile boolean flag = true; 
    
    
	
	
	private String fileName;
	private final int EXTERNAL_BUFFER_SIZE = 524288;

	public Music(int  n) {
		
		switch (n)
		{
		case 1:
			this.fileName="gameBgm.wav";
			break;
		case 2:
			this.fileName="Music.wav";
			break;
		case 3:
			this.fileName="MUsic2.wav";
			break;
			default :
			break;
		}
		}
	@Override
	public void run() {
		//1 獲取你要播放的音樂文件
		File soundFile = new File(fileName);
		if (!soundFile.exists()) {
			System.err.println("Wave file not found:" + fileName);
			return;
		}
		while (flag){
			//2、定義一個AudioInputStream用於接收輸入的音頻數據
			AudioInputStream audioInputStream = null;
			try {
				//3、使用AudioSystem來獲取音頻的音頻輸入流(處理(拋出)異常)
				audioInputStream = AudioSystem.getAudioInputStream(soundFile);
			} catch (UnsupportedAudioFileException e1) {
				e1.printStackTrace();
				return;
			} catch (IOException e1) {
				e1.printStackTrace();
				return;
			}
			//4、使用AudioFormat來獲取AudioInputStream的格式
			AudioFormat format = audioInputStream.getFormat();
			//5、一個源數據行
			SourceDataLine auline = null;
			//6、獲取受數據行支持的音頻格式DataLine.info
			DataLine.Info info = new DataLine.Info(SourceDataLine.class, format);
			try {
				//7、獲取與上面類型相匹配的行 寫到源數據行里 
				auline = (SourceDataLine) AudioSystem.getLine(info);
				auline.open(format);
			} catch (LineUnavailableException e) {
				e.printStackTrace();
				return;
			} catch (Exception e) {
				e.printStackTrace();
				return;
			}
			//9 允許某個數據行執行數據i/o
			auline.start();
			//10、寫數據
			int nBytesRead = 0;
			//設置字節數組大小
			byte[] abData = new byte[EXTERNAL_BUFFER_SIZE];
			try {
				//11、從音頻流讀取指定的最大數量的數據字節,並將其放入給定的字節數組中
				while (nBytesRead != -1) {
					nBytesRead = audioInputStream.read(abData, 0, abData.length);
					if (nBytesRead >= 0) {
						//12、讀取了之后將數據寫入混頻器,開始播放
						auline.write(abData, 0, nBytesRead);
					}
				}
			} catch (IOException e) {
				e.printStackTrace();
				return;
			} finally {
				//關閉
				auline.drain();
				auline.close();
			}
		}

總結音樂播放的步驟:

總結步驟:
1 獲取你要播放的音樂文件
2、定義一個AudioInputStream用於接收輸入的音頻數據
3、使用AudioSystem來獲取音頻的音頻輸入流(處理(拋出)異常)
4、使用AudioFormat來獲取AudioInputStream的格式
5、創建一個源數據行
6、獲取受數據行支持的音頻格式 DataLine.info 如果采用.getSourceDataLine()方法可以省略)
7、獲取與上面類型相匹配的行 寫到源數據行里 二選一
8、打開具有指定格式的行,這樣可以使行獲得資源並進行操作
9、允許某個數據行執行數據i/o
10、寫數據
11、從音頻流讀取指定的最大數量的數據字節,並將其放入給定的字節數組中。
12、讀取哪個數組
13、讀取了之后將數據寫入混頻器,開始播放

5. 測試、改進與感想。

測試與改進

(1) 對於音樂,程序剛開始使用的是java.applet.AudioClip來進行音樂文件的播放,實際測試后,容易和后面的爆炸線程等沖突,導致音樂停止播放,所以后來改成了使用javax.sound.sampled.*進行音樂的播放,通過讀取音樂文件的Io流進行播放音樂

(2) 人物的移動方案最開始使用的是直接對坐標進行有間隔的賦值,但是這樣會造成人物移動的“瞬移”,不僅不美觀,而且給人感覺游戲不是很流暢

改進:改進之后使用一個for循環對人物進行移動,但是改進后的移動方法是對人物進行“像素”級別的移動,這樣會讓人物移動有一個簡易的動畫,提高游玩體驗。

感想:

對於這次java的課程設計,我明白了處理好多線程的關系對於程序的重要性,當一個線程運行時,如果想使另外一個線程運行正常,需要注意線程之間的共享資源問題,可適當地使用synchronized關鍵字對多個線程進行優化。對於圖片和音樂的調用,要注意格式以及分辨率的問題。例如圖片,如果在程序的某一個塊需要放一個圖片,要注意圖片分辨率的大小最好和程序塊吻合,否則,無論原圖分辨率過高或者過低,在實際程序運行的時候,都會造成圖片的模糊,影響感官體驗。另外,通過這次的java實驗,我還學習到了部分圖片處理的知識,例如去除圖片的背景色,進行透明化處理,以及將多張圖片合成為一張gif動圖,實現簡易的動畫效果。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM