Java小程序之球球大作战(基于Java线程实现)


一、游戏基本功能:

       1、自己的小球可以随着鼠标的移动而改变坐标;

       2、敌方小球不断的在界面中移动

       3、当检测到敌方小球相互碰撞时,小球会弹开

       4、当我方小球和敌方小球碰撞时,会判断敌我双方的半径,如果我方直径大,则吃掉小球,分数累加

            若敌方小球比我方大时,游戏界面,切换面板,显示游戏得分


二、游戏运行界面:

python球球大作战源码 球球大作战java代码_游戏


python球球大作战源码 球球大作战java代码_python球球大作战源码_02


python球球大作战源码 球球大作战java代码_线程_03


三、游戏实现的大概思路


1、创建窗体

2、创建我方小球类,并实现鼠标移动监听

        3、创建敌方小球类(继承线程Thread类,每个小球会不停的运动)

        4、实现碰撞检测(计算小球圆心与圆心之间的距离)

        5、游戏结束,切换面板


     难点:

        

1.多个小球实现(List封装)

实现机制:如果说涉及到比较多的小球移动,则我们需要通过一个容器进行统一管理

容器:ArrayList

 

1)通过一个循环来创建小球:代码可以定义在当前类的构造方法里面

         

python球球大作战源码 球球大作战java代码_Java小项目_04

2)绘制小球的时候,通过遍历容器对象,进行绘制


python球球大作战源码 球球大作战java代码_游戏_05

3)坐标,小球直径都可以定义在小球类中,通过小球的属性进行封装


python球球大作战源码 球球大作战java代码_线程_06

4)paint方法中只是用于绘制图形,

5)如何计算圆心距

 1>先计算每个球的圆心坐标:x+width/2   y+width/2

 2>再算出横向的距离和纵向的距离

 3>计算斜边长度


python球球大作战源码 球球大作战java代码_游戏_07

 

 

 

2.双缓冲解决闪屏问题

a) 原理:

i. 通过缓冲图片,把所有需要绘制的东西,先绘制在缓冲图片上

ii. 把缓冲图片绘制到窗体/面板上

b)  实现步骤:pant方法改造

1.创建一个缓冲图片

2.获取一只图片上的画笔

3.把所有的图形通过图片画笔进行绘制

4.把缓冲图片绘制到窗体上


python球球大作战源码 球球大作战java代码_Java小项目_08

3.键盘监听器控制自己小球移动:KeyListener

a) 需要自己定义一个类,用于实现监听器接口

b) w:87   s:83  a:65  d:68

上:38  下:40  左:37  右:39

4.鼠标监听器控制自己小球移动:MouseMotionListener鼠标移动监听

 

5.音乐添加

a) 把music.jar包复制到工程目录下

b) 右击music.jar,选择build path,选择add to build path选项


python球球大作战源码 球球大作战java代码_python球球大作战源码_09


四、面板切换功能代码:每隔2秒切换页面


package com.bluesky.movingball;

import java.awt.Color;
import java.awt.Graphics;

import javax.swing.ImageIcon;
import javax.swing.JFrame;
import javax.swing.JPanel;

public class GameFrame extends JFrame{
	
	public boolean flag=true;
	public JPanel panel,panel2;
	public Graphics g;
	
	public ImageIcon bgimg= new ImageIcon("img/bg1.jpg");
	
	public void initFrame(){
		this.setTitle("移动的小球");
		this.setSize(800, 600);
		this.setDefaultCloseOperation(3);
		this.setLocationRelativeTo(null);
		panel = new JPanel();
		panel.setBackground(Color.red);
		panel2 = new JPanel();
		panel2.setBackground(Color.green);
		changePanel(this);
		this.add(panel);
		this.setVisible(true);
		g=panel.getGraphics();
		System.out.println("this:"+g);
	}
	

	
	public void changePanel(GameFrame f){
		new Thread(){
			public void run(){
				while(true){
					if(!flag){
						f.remove(panel);
						f.add(panel2);
						f.repaint();
						f.setVisible(true);
					}
					if(flag){
						f.remove(panel2);
						f.add(panel);
						f.repaint();
						f.setVisible(true);
					}
					
					try {
						sleep(2000);
						flag=!flag;
					} catch (InterruptedException e) {
						e.printStackTrace();
					}

				}
			}
			
		}.start();
	}

}




         

package com.bluesky.movingball;

public class Test {
	public static void main(String[] args) {
		GameFrame gf = new GameFrame();
		gf.initFrame();

	}
}



       

五、球球大作战源代码:

主界面类:

package com.huaxin.zhou;

import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Image;
import java.awt.Stroke;
import java.util.ArrayList;
import java.util.Random;

import javax.swing.ImageIcon;
import javax.swing.JFrame;
import javax.swing.JPanel;

public class MoveBallGame extends JFrame{
	
	//敌方小球容器类
	public static ArrayList<MoveThread>list =new ArrayList<MoveThread>();
	//随机器
	public Random r = new Random();
	
	public MyBall myball;
	
	public BallMouseListener bl;
	
	public JPanel panel,overPanel;
	
	public MoveBallGame jf;
	
	public int score=0;
	
	//检测游戏是否结束的标志
	public boolean overFlag=false;
	
	//检测添加小球的线程是否结束
	public boolean over=false;
	
	public MoveBallGame(){
		
		myball = new MyBall(0, 500, 1, 60);
		bl = new BallMouseListener(myball);
		//添加背景音乐
		com.huaxin.music.Util.startMusic("music/bg1.wav");
	}
	
	
	//窗体初始化
	public void initFrame(){
		jf=this;
		this.setSize(800,600);
		this.setDefaultCloseOperation(3);
		this.setResizable(false);
		this.setTitle("球球大作战");
		this.setLocationRelativeTo(null);
		panel= new JPanel();
		overPanel= new JPanel();
		this.add(panel);
		
		this.setVisible(true);
		
		//拿到窗体的画笔
		Graphics g =panel.getGraphics();
		//给窗体添加鼠标移动监听
		BallKeyListener kl = new BallKeyListener(myball);
		panel.addKeyListener(kl);
		panel.addMouseMotionListener(bl);
		
		checkBallCount();
		
	}
	//paint方法画小球(敌方小球和我们小球),兵利用双缓冲解决闪屏问题
	public void paint(Graphics g) {
		
		//创建一张图片
		Image buffImage = this.createImage(800,600);
		//拿到这张图片的画笔
		Graphics buffg=buffImage.getGraphics();
		
		ImageIcon bgimg = new ImageIcon("img/bg1.jpg");
		//给图片画背景
		buffg.drawImage(bgimg.getImage(), 0,0, null);
		//循环将容器里面的小球画出来
		for (int i = 0; i < list.size(); i++) {
			MoveThread mt = list.get(i);
			buffg.setColor(mt.color);
			buffg.fillOval(mt.x, mt.y, mt.width, mt.width);
		}
		
		//设置画笔颜色画我方的小球
		buffg.setColor(Color.ORANGE);
		
		buffg.fillOval(myball.x, myball.y, myball.width, myball.width);
		
		//利用窗体的画笔将整张图片画进窗体
		g.drawImage(buffImage, 0, 0, null);
	}
	
	
	public void checkBallCount(){
		//检测球是否吃完了的线程,吃完了则重新产生六个小球
		new Thread(){
			public void run(){
				while(!over){
					while(!overFlag){
						// 如果屏幕上没有敌方小球了,那么重新画6个敌方小球
						if (MoveBallGame.list.size() == 0) {
							for (int j = 0; j < 6; j++) {
								MoveThread mt = new MoveThread(jf);
								mt.x += 70 * j;
								mt.y += 50 * j;
								mt.xSpeed = 5;
								mt.ySpeed = 5;
								mt.width = jf.r.nextInt(20) + 50;
								mt.color = new Color(jf.r.nextInt(10000));
								MoveBallGame.list.add(mt);
								mt.start();
							}
						}
						try {
							Thread.sleep(100);
						} catch (InterruptedException e) {
							e.printStackTrace();
						}
					}
					
					if(overFlag){
						jf.remove(panel);
						jf.add(overPanel);
						jf.repaint();
						jf.setVisible(true);
//						System.out.println("面板切换");
						Graphics g=overPanel.getGraphics();
						Graphics2D g1=(Graphics2D) g;
						ImageIcon overimg= new ImageIcon("img/bg.jpg");
						g1.drawImage(overimg.getImage(), 0, 0, null);
						g1.setColor(Color.red);
						g1.setStroke(new BasicStroke(5));
						g1.drawString("游戏结束", 300,200);
						g.drawString("得分:"+score, 350, 200);
						over=true;
					}
					
					try {
						Thread.sleep(100);
					} catch (InterruptedException e) {
						e.printStackTrace();
					}
				}
				try {
					Thread.sleep(100);
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
			}
			
		}.start();
	}
	
	
}



敌方小球类:敌方的每个小球都是一个独立的线程


package com.huaxin.zhou;

import java.awt.Color;

import javax.swing.JOptionPane;

public class MoveThread extends Thread {

	public MoveBallGame mb;
	public int x = 100, y = 100;
	public int xSpeed = 5;
	public int ySpeed = 5;
	public int width;
	public Color color;
	
	public boolean isbegin = true;
//	public boolean over =false;
	
	public MoveThread(MoveBallGame mb) {
		this.mb = mb;
	}

	// 控制每个小球跑的线程
	public void run() {
		while(!mb.overFlag){
			while (isbegin) {
				// 控制敌方小球移动
				x += xSpeed;
				y += ySpeed;
				// 判断敌方小球是否出界
				if (this.x < 10 || this.x > 800 - this.width - 10) {
					xSpeed = -xSpeed;
				}
				if (this.y < 30 || this.y > 600 - this.width - 10) {
					ySpeed = -ySpeed;
				}
				// 判断我方小球是否出界
				if (mb.myball.x < 4) {
					mb.myball.x = 4;
				}
				if (mb.myball.y <28) {
					mb.myball.y =28;
				}

				if (mb.myball.x > 800 - mb.myball.width - 4) {
					mb.myball.x = 800 - mb.myball.width - 4;
				}

				if (mb.myball.y > 600 - mb.myball.width - 4) {
					mb.myball.y = 600 - mb.myball.width - 4;
				}

				// 碰撞检测
				isHit();

				mb.repaint();
				// 线程休眠
				try {
					Thread.sleep(50);
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
			}
			// 线程休眠
			try {
				Thread.sleep(50);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
		
//		if(over){
//			JOptionPane.showMessageDialog(null, "游戏结束");
//			mb.overFlag=true;
//		}
		
		
	}

	// 检车是否碰撞的函数
	public void isHit() {

		// 判断敌方小球和我方小球是否发生碰撞
		int x = ((this.x + this.width) - (mb.myball.x + mb.myball.width))
				* ((this.x + this.width) - (mb.myball.x + mb.myball.width));
		int y = ((this.y + this.width) - (mb.myball.y + mb.myball.width))
				* ((this.y + this.width) - (mb.myball.y + mb.myball.width));
		int r = (int) Math.sqrt(x + y);

		// 判断敌我双方谁的半径大
		if (r <= (this.width / 2 + mb.myball.width / 2)) {
			// 敌方直径比我大,则我方被吃掉,游戏结束!
			if (this.width > mb.myball.width) {
				
//				mb.overFlag=true;
//				this.setDaemon(true);
				isbegin = false;
//				MoveBallGame.list.remove(this);
				
				for (int i = 0; i <MoveBallGame.list.size(); i++) {
//					MoveBallGame.list.get(i).setDaemon(true);
					MoveBallGame.list.get(i).isbegin=false;
				}
				JOptionPane.showMessageDialog(null, "你被大球吃掉了!");
				mb.overFlag=true;
				return;
			}
			// 如果我方直径大,则让我方直径加2个像素,同时从容器里面移除吃掉的小球
			if (this.width <= mb.myball.width) {
				mb.myball.width += 2;
				mb.score=+this.width;

//				this.setDaemon(true);
				this.isbegin = false;
				MoveBallGame.list.remove(this);
				
				// 我方直径达到500,我方获胜
				if (mb.myball.width >= 300) {
					
//					mb.overFlag=true;
					isbegin = false;
					MoveBallGame.list.remove(this);
					for (int i = 0; i <MoveBallGame.list.size(); i++) {
						MoveBallGame.list.get(i).isbegin=false;
//						MoveBallGame.list.get(i).setDaemon(true);
					}
					JOptionPane.showMessageDialog(null, "恭喜你赢了!");
//					over=true;
					mb.overFlag=true;
				}
			}

		}

		// 从容器里面取出小球来
		for (int i = 0; i < MoveBallGame.list.size(); i++) {
			MoveThread mtb = MoveBallGame.list.get(i);
			// 如果是正在执行run的小球,则另外取一个小球
			if (mtb == this) {
				continue;
			} else {

				// 判断敌方小球是否碰撞
				int x1 = ((mtb.x + mtb.width) - (this.x + this.width)) * ((mtb.x + mtb.width) - (this.x + this.width));
				int y1 = ((mtb.y + mtb.width) - (this.y + this.width)) * ((mtb.y + mtb.width) - (this.y + this.width));
				int r1 = (int) Math.sqrt(x1 + y1);

				if (r1 <= (mtb.width / 2 + this.width / 2)) {
					mtb.xSpeed = -mtb.xSpeed;
					mtb.ySpeed = -mtb.ySpeed;
					this.xSpeed = -this.xSpeed;
					this.ySpeed = -this.ySpeed;
				}
			}
		}

	}
}




我方小球类:

package com.huaxin.zhou;

import java.awt.Color;

public class MyBall {
	
	//我方自己的球类

	//我方球类的属性
	public int x,y;
	public int Speed;
	public int width;
	
	//我方求球类的构造方法
	public MyBall(int x, int y, int speed, int width) {
		this.x = x;
		this.y = y;
		this.Speed = speed;
		this.width = width;
	}
	
	
}





我方小球鼠标移动监听类:

package com.huaxin.zhou;

import java.awt.event.MouseEvent;
import java.awt.event.MouseMotionListener;

import javax.swing.JOptionPane;

public class BallMouseListener implements MouseMotionListener{
	
	//我方小球鼠标移动监听器
	
	public MyBall ball;
	
	//构造函数
	public BallMouseListener(MyBall ball) {
		this.ball=ball;
	}
	
	//实现鼠标移动时,小球坐标改变
	public void mouseMoved(MouseEvent e) {
		int x=e.getX();
		int y=e.getY();
		
		ball.x=x-ball.width/2;
		ball.y=y;/*-ball.width/2;*/
		
	}
	public void mouseDragged(MouseEvent e) {
		
	}
}




我方小球键盘移动监听类:


package com.huaxin.zhou;

import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;

import javafx.scene.input.KeyCode;

public class BallKeyListener implements KeyListener{
	
	public MyBall ball;
	
	public BallKeyListener(MyBall ball){
		this.ball=ball;
	}

	public void keyPressed(KeyEvent e) {
		
		int key=e.getKeyCode();
		
		
		if(key==37)
		ball.x-=3;
		if(key==38)
			ball.y-=3;
		if(key==39)
			ball.x+=5;
		if(key==40)
		  ball.y+=5;
		
	}

	public void keyReleased(KeyEvent e) {
		
	}

	public void keyTyped(KeyEvent e) {
		
	}

}




主函数:程序入口

package com.huaxin.zhou;

import java.awt.Color;

public class MainFrame {
	
	public static void main(String[] args) {
		//程序入口
		MoveBallGame mbg = new MoveBallGame();
		mbg.initFrame();
	}
}




       

六、总结

通过这个小项目,学到了非常多的知识、比如线程的应用、碰撞检测、双缓冲原理以及应用、如何切换面板,音乐添加等;在这个项目中遇到了很多的问题,比如内存泄漏,因为后面判断游戏结束,使用了双重循环,因为try 的位置不正确,导致内存泄漏,即陷入死循环中;

完成这个小项目花了好几天的时间,还是在有老师指导的情况下,这里面设计到了很多知识的细节,原理其实多不是很难理解,主要是如何转化成代码,还有对线程的理解等,如何为去结束一个线程;碰撞检测主要用到圆和圆之间的圆心距的计算,这里看到了数学在计算机的应用,果然,数学很重要,当然也不是那么重要,你想要往高的地方走,必须的汲取足够多的知识;个人觉得面板切换还是比较炫酷的,我也不知道为什么,可能因为终于可以不是局限在同一个面板中了吧!双缓冲原理竟然变得和以前不太一样了,以前还有用到update方法,新版JDK简化了这一过程,也变得更好理解了;音乐的切换需要用到一个架包,调用里面的一个方法就可以实现了,恩,还是期待有一天能够封装自己的音乐架包呢!

行吧,就先分享到这里了!共勉!