JavaSwing多线程小游戏雷霆战机

在做完连连看以后,想到要做一个多线程游戏,本来是做的一个跳伞的小游戏的。但是做到一半的时候,觉得可玩性太低了。后面想来想去还是打算做一个以前玩过的雷霆战机小游戏,也就是飞机大战。

1.效果展示
2.绘制背景
3.方向类
4.飞机类
5.子弹类
6.爆炸类
7.道具类
8.总结一下界面类里面的绘制线程
9.播放音乐
10.开始界面

1.效果展示

直接放图了。
在这里插入图片描述
在这里插入图片描述

博主自己特别喜欢的一个特效,吃道具后能够变声,而且附带数码宝贝的音效,但是只能展示动图了,配合音效会更有感觉一点。
在这里插入图片描述
然后是动图
在这里插入图片描述

2.绘制背景

我们先不管游戏的开始界面啥的,先从主要的开始入手。
第一步就是绘制背景界面了。
在实际的效果中,像是飞机在飞一样,其实只是背景图片在移动,然后看上去就感觉飞机在飞。
在这里插入图片描述
在博主画的两个框里面,蓝色代表背景图片。黑色代表软件界面。因为背景图片的长度是大于软件界面的,所以将背景图慢慢移动,就会造成一种动态画面的效果。然后将背景图片调用两次,第一张放完后就放第二张,第二张放完后就再放第一张,循环下去,就会有背景一直在动的感觉。

因为背景图片是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都还没改,但没太多时间去继续弄飞机大战,等以后有空闲时间的话,会再继续完善飞机大战。

  • 3
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 7
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 7
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值