Java小程序之球球大作战(基于Java线程实现)
一、游戏基本功能:
1、自己的小球可以随着鼠标的移动而改变坐标;
2、敌方小球不断的在界面中移动
3、当检测到敌方小球相互碰撞时,小球会弹开
4、当我方小球和敌方小球碰撞时,会判断敌我双方的半径,如果我方直径大,则吃掉小球,分数累加
若敌方小球比我方大时,游戏界面,切换面板,显示游戏得分
二、游戏运行界面:
三、游戏实现的大概思路
1、创建窗体
2、创建我方小球类,并实现鼠标移动监听
3、创建敌方小球类(继承线程Thread类,每个小球会不停的运动)
4、实现碰撞检测(计算小球圆心与圆心之间的距离)
5、游戏结束,切换面板
难点:
1.多个小球实现(List封装)
实现机制:如果说涉及到比较多的小球移动,则我们需要通过一个容器进行统一管理
容器:ArrayList
1)通过一个循环来创建小球:代码可以定义在当前类的构造方法里面
2)绘制小球的时候,通过遍历容器对象,进行绘制
3)坐标,小球直径都可以定义在小球类中,通过小球的属性进行封装
4)paint方法中只是用于绘制图形,
5)如何计算圆心距
1>先计算每个球的圆心坐标:x+width/2 y+width/2
2>再算出横向的距离和纵向的距离
3>计算斜边长度
2.双缓冲解决闪屏问题
a) 原理:
i. 通过缓冲图片,把所有需要绘制的东西,先绘制在缓冲图片上
ii. 把缓冲图片绘制到窗体/面板上
b) 实现步骤:pant方法改造
1.创建一个缓冲图片
2.获取一只图片上的画笔
3.把所有的图形通过图片画笔进行绘制
4.把缓冲图片绘制到窗体上
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选项
四、面板切换功能代码:每隔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简化了这一过程,也变得更好理解了;音乐的切换需要用到一个架包,调用里面的一个方法就可以实现了,恩,还是期待有一天能够封装自己的音乐架包呢!
行吧,就先分享到这里了!共勉!