天天看點

JavaSwing多線程小遊戲雷霆戰機

在做完連連看以後,想到要做一個多線程遊戲,本來是做的一個跳傘的小遊戲的。但是做到一半的時候,覺得可玩性太低了。後面想來想去還是打算做一個以前玩過的雷霆戰機小遊戲,也就是飛機大戰。

1.效果展示

2.繪制背景

3.方向類

4.飛機類

5.子彈類

6.爆炸類

7.道具類

8.總結一下界面類裡面的繪制線程

9.播放音樂

10.開始界面

1.效果展示

直接放圖了。

JavaSwing多線程小遊戲雷霆戰機
JavaSwing多線程小遊戲雷霆戰機

部落客自己特别喜歡的一個特效,吃道具後能夠變聲,而且附帶數位寶貝的音效,但是隻能展示動圖了,配合音效會更有感覺一點。

JavaSwing多線程小遊戲雷霆戰機

然後是動圖

JavaSwing多線程小遊戲雷霆戰機

2.繪制背景

我們先不管遊戲的開始界面啥的,先從主要的開始入手。

第一步就是繪制背景界面了。

在實際的效果中,像是飛機在飛一樣,其實隻是背景圖檔在移動,然後看上去就感覺飛機在飛。

JavaSwing多線程小遊戲雷霆戰機

在部落客畫的兩個框裡面,藍色代表背景圖檔。黑色代表軟體界面。因為背景圖檔的長度是大于軟體界面的,是以将背景圖慢慢移動,就會造成一種動态畫面的效果。然後将背景圖檔調用兩次,第一張放完後就放第二張,第二張放完後就再放第一張,循環下去,就會有背景一直在動的感覺。

因為背景圖檔是bmp格式的,部落客試了一下,改為jpg或者png都不能在eclipse裡面顯示。

是以等下如果要用到這張背景圖就不要改格式了。

而且bmp格式的圖檔,好像不能直接調用,不然顯示不出來。

下面的代碼部落客試了一下就是讀取bmp圖檔且調用的代碼。

這段代碼是每次讀取圖檔用到的工具類。

import java.awt.Image;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.net.URL;
import javax.imageio.ImageIO;

public class GameImage {
	private GameImage() {};//工具類一般設定為私有
	public static Image getImage(String path) {
		URL u = GameImage.class.getClassLoader().getResource(path);
		BufferedImage img = null;
		try {
			img = ImageIO.read(u);
		} catch (IOException e) {
			e.printStackTrace();
		}
		return img;
	}
}

           

下面是關于背景圖檔部分的代碼

其中繪畫部分是要放在同一個線程裡面的,不然會導緻畫面中背景或者飛機或者子彈不停的閃爍。

是以這個繪制背景的代碼應該放到繪制線程裡面。

int posY=-650;//窗體的高度減去圖檔高度,810-1460=-650
int posY2 = posY-1412;//posY是第一張圖檔y坐标,posY2是第二張圖檔y坐标


 while(true){
            if(posY>=760){//交替
                posY = posY2 -gameBg.getHeight(null);
            }else{
                if(posY2>=760){//交替
                    posY2 = posY - gameBg.getHeight(null);
                }else{
                    if(begin==false){//開始滾動
                        posY += 2;
                        bg.drawImage(gameBg, 0, posY,  null);
                        posY2 +=2;
                        bg.drawImage(gameBg, 0, posY2,  null);
                    }
                }
            }
           

3.方向類

因為敵方飛機和我方飛機都需要移動。

而在鍵盤監聽器裡面最好不去鍵盤按鍵實作方法,而是鍵盤按鍵代表一個狀态,然後根據狀态調用方法。

是以我們用一個枚舉類來寫方向。

代碼如下:

public enum Direction {//枚舉類型
	L, R, RD, D, LD, STOP,LU, U, RU
}
           

4.飛機類

我們先寫飛機類的構造函數,友善去調用

public void pic(){
//調用飛機圖檔
		myImgs[0] = GameImage.getImage("resources/plane1.png");
		myImgs[1] = GameImage.getImage("resources/plane23.png");
		enemyImgs[0] = GameImage.getImage("resources/enemy3.png");
		bossImgs[0] = GameImage.getImage("resources/boss6.png");
	}

public Plane(int num, int x,int y,int speed,boolean good,gameUI gameui) {
		pic();
		this.num=num;
		this.x=x;
		this.y=y;
		this.speed=speed;
		this.good=good;
		this.gameui=gameui;
		if(good==true) {//good為true時,為我自己飛機,調用我飛機圖檔
			ensureImg = myImgs[num];
			WIDTH = ensureImg.getWidth(null);
			HEIGHT = ensureImg.getHeight(null);
		}else {
			if(num==0) {
			ensureImg = enemyImgs[0];
			dir = Direction.D;//設定普通敵機方向隻有向下
			WIDTH = ensureImg.getWidth(null);
			HEIGHT = ensureImg.getHeight(null);
			}
		}
	}
           

然後再寫飛機的鍵盤監聽器方法。

因為我在方向類裡面說了,在鍵盤監聽器裡用按鍵表示狀态。

是以飛機類裡面都是按鍵表示狀态。

特别注意,在按鍵時比如左移bL的狀态為true;松開時左移的狀态為false;不然會按一次鍵就一隻移動。

public void Press(KeyEvent e) {
		int key = e.getKeyCode();
		switch(key) {
		case KeyEvent.VK_LEFT :
			bL = true;
			break;
			
		case KeyEvent.VK_UP :
			bU = true;
			break;
			
		case KeyEvent.VK_RIGHT :
			bR = true;
			break;
			
		case KeyEvent.VK_DOWN :
			bD = true;
			break;
			
		
		}
		locateDirection();
	}
	
	
	public void Release(KeyEvent e) {
		int key = e.getKeyCode();
		switch(key) {
		case KeyEvent.VK_LEFT :
			bL = false;
			break;
			
		case KeyEvent.VK_UP :
			bU = false;
			break;
			
		case KeyEvent.VK_RIGHT :
			bR = false;
			break;
			
		case KeyEvent.VK_DOWN :
			bD = false;
			break;
			
		case KeyEvent.VK_M:
			fire();
			break;
	}
	locateDirection();	
}
	
	
	public void locateDirection() {
		if(bL && !bU && !bR && !bD) dir = Direction.L;  
        else if(bL && bU && !bR && !bD) dir = Direction.LU;  
        else if(!bL && bU && !bR && !bD) dir = Direction.U;  
        else if(!bL && bU && bR && !bD) dir = Direction.RU;  
        else if(!bL && !bU && bR && !bD) dir = Direction.R;  
        else if(!bL && !bU && bR && bD) dir = Direction.RD;  
        else if(!bL && !bU && !bR && bD) dir = Direction.D;  
        else if(bL && !bU && !bR && bD) dir = Direction.LD;  
        else if(!bL && !bU && !bR && !bD) dir = Direction.STOP; 

	}
           

監聽鍵盤後,我們根據飛機的狀态再來移動。

是以需要寫一個移動的方法。

public void move() {
		switch(dir) {
		case L:
			x -= speed;
			break;
		
		case LU:
			x-=speed;
			y-=speed;
			break;
			
		case U:
			y-=speed;
			break;
			
		case RU:
			x+=speed;
			y-=speed;
			break;
			
		case R:
			x+=speed;
			break;
			
		case RD:  
            x += speed;  
            y += speed;  
            break;  
            
        case D:  
            y += speed;  
            break;  
            
        case LD:  
            x -= speed;  
            y += speed;  
            break; 
            
        case STOP:
        	break;

		}
		if(x<0)   x=0;//左邊界
		if(y<40)  y=40;//上邊界
		if(x+ensureImg.getWidth(null)>600) x=600-ensureImg.getWidth(null);//右邊界
		if(y+ensureImg.getHeight(null)>760) y=760-ensureImg.getHeight(null);//
	}
           

然後是畫飛機的方法。

這個方法一定在繪畫線程裡面和其他所有的繪畫方法一起被調用,不然會導緻畫面閃爍。

最後是建立敵機的方法。

public void createEnemy() {
	if(this.es.size()<4){//使敵機數量保持在4架
		Plane ePlane = new Plane(0,r.nextInt(500),0,5,false,this);//敵機
		this.es.add(ePlane);
	if(r.nextInt(50)>30) {
		ePlane.fire();
	}
   }
}
           

5.子彈類

和飛機類的流程差不大多。

為了友善生成子彈,是以我們第一步任然是寫一個子彈類的構造方法。

pic()仍為調用圖檔方法。

public void pic() {
		myImgs[0] = GameImage.getImage("resources/m5.png");
		enemyImgs[0] = GameImage.getImage("resources/em1.png");
	}

	public Bullet(int x,int y,int speed,int randIndex,boolean good,gameUI gameui) {
		pic();
		this.x=x;
		this.y=y;
		this.randIndex = randIndex;
		this.speed = speed;
		this.good=good;
		this.gameui=gameui;
		pic();
		if(good==true) {
			ensureImg = myImgs[0];
			dir = Direction.U;
			WIDTH = ensureImg.getWidth(null);
			HEIGHT = ensureImg.getHeight(null);
			this.power = 20;//我子彈威力為10
		}else {
			dir = Direction.D;
			ensureImg = enemyImgs[0];
			WIDTH = ensureImg.getWidth(null);
			HEIGHT = ensureImg.getHeight(null);
			this.power = 10;//敵方子彈威力為5
		}
		
	}
           

寫完構造類以後,我們想一想,子彈剩下的就是移動和達到飛機的情況了。

然後情況又分為我方子彈命中敵機,敵機子彈命中我機兩種情況。

是以剩下需要寫的方法分别是對子彈的移動和命中飛機的方法。

private void move() {  
	        switch(dir) {  
	        case U:  
	            y -= speed;  
	            break; 
	        case D:
	            y += speed;  
	            break;  
	        }  
	        if(x < 0 || y < 0 || x > 880 || y > 760) {  
	            isAlive = false;//出界就設定為false  
	        }
	    }



 public boolean hitPlane(Plane p) {  //敵方攻擊我
		 //intersects(Rcetangle),判斷該Rectangle與目前Rectangle是否相交   
		 if(this.isAlive && this.getRect().intersects(p.getRect()) && p.getAlive() && this.good != p.isgood()) 
	        {  
			 this.isAlive = false;
			     p.setLife(p.getLife()-this.power);
			     
		        	if(p.isgood()==false){//敵方飛機FALSE
		        		p.setAlive(false);//飛機死亡  
		        		Explode e = new Explode(x, y, gameui);  
		        		gameui.explodes.add(e);//添加爆炸
		        		return true;
		        	}else if(p.isgood()==true){//我方飛機TRUE
		        		if(p.life<=0){
		        			p.setAlive(false);//飛機死亡  
			        		//myplane.setAlive(false);
		        			Explode e = new Explode(x, y, gameui);  
			        		gameui.explodes.add(e);//添加爆炸
			        		return true;
		        		}
		        	}


	        }  
	        return false;  
	        
	    }
	 
	
	 
	 
	 
		public boolean hitPlanes(List<Plane> planes) {  //我攻擊敵方
		    for(int i=0; i<planes.size(); i++) {  
		        if(hitPlane(planes.get(i))) {  
		        	es.remove(i);
		        	return true;  
		        }  
		    }
		    return false;  
		}
           

這裡需要注意的點是,先寫一個hitPlane敵方攻擊我的方法,在另一個攻擊敵方的方法裡面對這個方法進行調用就可以了,算是一個需要注意的敵方。

另一個是es為敵方飛機的隊列,哪架飛機被擊中,就在隊列裡面被移去。

6.爆炸類

爆炸類比飛機類和子彈類都更加簡單。

因為隻需要在飛機死亡後繪制爆炸。

是以爆炸類的方法就隻有構造方法和繪畫方法。

public void pic() {
		images[0]=GameImage.getImage("Resources/blast_0_5.png");
	}

	public Explode(int x,int y,gameUI gameui) {
		super();
		this.x=x;
		this.y=y;
		this.gameui=gameui;
	}
	
	public void draw(Graphics g) {
		pic();
		WIDTH = images[0].getWidth(null);
		HEIGHT = images[0].getWidth(null);
		if(!live) {
			gameui.explodes.remove(this);//爆炸結束移除
			//System.out.println("live="+live+" ");
			return;
		}
		if(live) {
			g.drawImage(images[0], x-(WIDTH/2), y-(HEIGHT/2), null);
			//System.out.println("畫爆炸圖檔");
			live=false;
		}
	}
           

相對于前面的飛機類和子彈類,爆炸類也沒有什麼需要多講的地方了。

7.道具類

這個道具,部落客因為時間的關系隻寫了一種道具,就是前面展示了的變身的道具類。

方法依然是舊幾個,構造方法,移動方法,繪制方法,道具相撞飛機調用的方法。

隻是飛機吃到道具後,這裡需要特别注意一下!

因為部落客所有的繪畫方法都是放在一個繪畫線程裡面被調用。

而我的變身GIF圖檔有5秒左右,這個時候需要暫停以前的繪畫線程,然後把畫變身單獨做一個新線程用join方法插入,這樣才能有我的GIF那樣的效果。

控制變身的線程

public class ControlThread extends Thread{

	Graphics g;
	
	public void run() {
		while(true) {

			g.drawImage(jinhua.getImage(),0,0,590,800,null);	
		}
	}
}
           

道具類的方法

public void draw(Graphics g) {
		pic();
		if(isAlive==false) {
			return;//道具被吃掉則消失
		}
		 T_WIDTH = propImgs[0].getWidth(null);
		 T_HEIGHT = propImgs[0].getHeight(null);
		 g.drawImage(propImgs[0],x,y,T_WIDTH,T_HEIGHT,null);
		
	}
	
	public void move() {
		if(x<=0 || x>=561) {
			speedx = -speedx;
		}
		if(y<=0 || y>=750) {
			speedy = -speedy;
		}
		x-=speedx;
		y+=speedy;
	}
	
	public boolean hitProp(Plane myplane) {
		if(myplane.isgood()==false) {
			return true;
		}
		else if(myplane.isgood()==true) {
			if(this.isAlive && this.getRect().intersects(myplane.getRect()) && myplane.getAlive()) {
				this.isAlive=false;
				myplane.setAlive(false);
				
				return true;
			}

			//System.out.println("沒有碰到");
		}
		return false;
	}
           

8.總結一下界面類裡面的繪制線程

讓大家容易看懂,直接貼代碼了。

因為這一段代碼稍微有點長,部落客直接把注釋打在代碼注釋裡面了。

public class BgThread extends Thread{
	
	Graphics g;
	JTextField textField;
	JTextField hitField;
	gameUI gameui;
	Image[] myImgs;
	ControlThread t2;
	
	public void run() {
		
	    while(true){
            if(posY>=760){//交替
                posY = posY2 -gameBg.getHeight(null);
            }else{
                if(posY2>=760){//交替
                    posY2 = posY - gameBg.getHeight(null);
                }else{
                    if(begin==false){//開始滾動
                        posY += 2;
                        bg.drawImage(gameBg, 0, posY,  null);
                        posY2 +=2;
                        bg.drawImage(gameBg, 0, posY2,  null);
                    }
                }
            }
           
            myplane.draw(bg);
            
            this.textField.setText("剩餘生命:"+myplane.life);
            
          //繪制子彈
    		for(int i=0; i<bs.size(); i++) {//将集合中的子彈都繪制出來  
    	        Bullet b = bs.get(i);  
    	        b.es=es;//子彈中的飛機清單es
    	        b.draw(bg);
    	      //  System.out.println("重繪部分在畫子彈"); 
    	        b.hitPlanes(es);
    	        b.hitPlane(myplane);
    		} 
    		
   		createEnemy();
    		
    		//繪制敵機
    		for(int i=0; i<es.size(); i++) {  
    	        Plane p = es.get(i);
    	        p.draw(bg);
    	        p.hitBorder(es);
    	    }
    		
    	
    		prop.draw(bg);
    		prop.move();
    		
    	if(prop.hitProp(myplane)) {
        //變身
    		myplane.num=1;
    		myplane.ensureImg = myImgs[1];
    	//	 bg.drawImage(jinhua.getImage(),0,150,590,500,null);
    	
    		t2.start();
    		new PlaySound("進化.mp3", false).start();
    		 try {
    			 
				t2.join(5000);
				t2.stop();
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
    		
    	   // bg.drawImage(jinhua.getImage(),0,150,590,500,null);
    		myplane.setAlive(true);
    	}

    

    		//繪爆炸
    		for(int i=0;i<explodes.size();i++) {
                Explode e = explodes.get(i);
                e.draw(bg);
                count++;
    		}
    		
    		 this.hitField.setText("擊落敵機:"+count/2);

    	g.drawImage(buffer, 0,0, null);//把所有的東西從緩存畫下來
            
            try {
                Thread.sleep(50);//滾動速度的設定
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        	
        }

		
	}
}
           

9.播放音樂。

以前在做連連看的時候,插入的音樂需要轉換成為WAV格式才能使用,然後這一段代碼可以直接适用MP3格式了。很友善,也直接貼出來了。

import java.io.InputStream;
import javazoom.jl.player.advanced.AdvancedPlayer;

/**
 * 
 * 必須使用多線程,播放音效
 *
 */
public class PlaySound extends Thread{
	
	private String mp3Url;
	
	private boolean isLoop;
	
	public PlaySound(String mp3Url, boolean isLoop) {
		super();
		this.mp3Url = mp3Url;
		this.isLoop = isLoop;
	}

	public void run() {
		
		do{
			//讀取音頻檔案流
			InputStream mp3 = PlaySound.class.getClassLoader().getResourceAsStream("resources/"+mp3Url);
			try {
			//建立播放器
				AdvancedPlayer adv = new AdvancedPlayer(mp3);
				//播放
				adv.play();	
			} catch (Exception e) {
				e.printStackTrace();
			}
		}while(isLoop);
	}
}

           

10.開始界面。

開始界面其實和以前講過的qq登入界面差不多。

隻要在按鈕加上監聽器就好。是以也直接貼代碼了。

import java.awt.Dimension;
import java.awt.FlowLayout;
import java.awt.Graphics;

import javax.swing.ImageIcon;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;

public class StartUI extends JFrame{

	public void showUI() {
		StartUI startFrame = new StartUI();
		startFrame.setSize(900,650);
		startFrame.setTitle("Design By TangNan");
		startFrame.setLocationRelativeTo(null);//居中
		startFrame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);//關閉
		
		FlowLayout flowl = new FlowLayout();//布局
		startFrame.setLayout(flowl);
		
		StartListener startL = new StartListener();
		startFrame.addMouseListener(startL);
		startL.j=startFrame;
		
		
		ImageIcon image =new ImageIcon(getClass().getResource("resources/FJDAZ_START1.png"));
	
		JLabel iconLabel = new JLabel(image);
		startFrame.add(iconLabel);
		
		Dimension btnsize = new Dimension(125,50);
		JButton btn1 = new JButton("開始遊戲");
		btn1.setPreferredSize(btnsize);
		startFrame.add(btn1);
		btn1.addActionListener(startL);
		
		JButton btn2 = new JButton("提示說明");
		btn2.setPreferredSize(btnsize);
		startFrame.add(btn2);
		btn2.addActionListener(startL);
		
		startFrame.setVisible(true);
		Graphics g = startFrame.getGraphics();//擷取畫闆放在可視化之後
		
	}
}

           

最後,飛機大戰到這裡就算簡單的完成了。其實部落客還有其他蠻多想加的效果和一些BUG都還沒改,但沒太多時間去繼續弄飛機大戰,等以後有空閑時間的話,會再繼續完善飛機大戰。