先上效果图,子弹左边的滑稽就是我的鼠标控制的
其实只是用了“滑稽”的图片而已,把图片拿掉本质上就是用鼠标控制一个可以发射很多小球的小球,以及一堆随机产生的带有随机速度的小球而已,前面是讲解,代码放在了最后
随机产生的小球思路在java画图板之三中有过详细描述
https://blog.csdn.net/qq_37465638/article/details/81195210,
我们创建了四个类
窗体类Framball——建立画图板,开启线程的主方法
球类Ball——描述了球的属性,包括横纵坐标xy,半径R,运动方法run()
画球线程drawBall——将产生的球放在一个队列中,存到一定数量
动球线程moveBall——将队列中的球依次释放,每个球都执行Ball中的run,crash(),注意,此时要放在循环中,延迟时间0.1秒,这样球每run一次坐标就改变一次,花费0.1秒,看起来就像运动一样
其实稍微改一改就是球球大作战,飞机大战,大鱼吃小鱼这类游戏了。现在我们升级到“滑稽”版本,将画图板三升级到四我们需要以下四步操作
一
在Ball类中增加crash()方法,使用勾股定理判定距离,当接近时双方速度交换(有一个问题没有处理好,就是有时候新出生的两个球靠得很近,导致黏在一起抖动,因为速度一直在交换,以其中一个消失告终,用图片代替后又多了一个问题是图片的背景色和窗体背景色一致,看不出来是图片,但是有时候撞在一起时其中一张的图片背景色会掩盖另一张的表情)
添加blood属性,传入crash()中进行值的变化,同时添加一个方法画出血条,
public void crash(ArrayList<Ball> list) {
}
public void blood(Graphics g) {
}
二
增加子弹类Bullet,和球类相似,但是血量设为1,纵坐标速度设为0,大小恒定,同时设置自己的队列ListB,每次点击就新建一个Bullet
public class Bullet {
}
最重要的是Bullet是在点击后才产生,所以我们还需要
三
增加鼠标***ListenerBall,新建一个Ball的实例(作为我们控制的球),在mouseMoved事件中将鼠标的坐标传给这个球,并且加到包含球的队列List中,也就是说,他是List的第一个球。
public class ListernerBall implements MouseListener,MouseMotionListener{
}
同时,在mousePressed事件中新建Bullet的实例,注意,Ball的实例新建是放在属性中,因为我们只需要控制一个Ball,而Bullet的实例是放在mousePressed中,每点击一次,就产生一个bullet,并且加到队列ListB里面去
Bullet bullet=new Bullet(fb,g,listB);
bullet.x=e.getX();
bullet.y=e.getY();
listB.add(bullet);
这个队列同样放在moveBall中执行,当然是新建一个for循环,因为这个队列和Ball队列的长度不一样
四
在moveBall中我们做了另外一个关键性的改动——引入次画布ig,还记得我们让小球动起来的本质是什么吗?在Ball类中的run()方法每执行一次就改变一次圆心坐标,但是这样其实原来的小球还在,就会使得图形变成一条射线,在画图板之三中我用的方法是在run()中一开始还没改坐标时画一个原坐标的背景色的小球来擦除,这样的方法在增加crash()方法后容易判断不准原坐标,导致擦除偏移留下像残月一样的痕迹
g.setColor(fb.getContentPane().getBackground());//切换为背景色
g.fillOval(x - R - speedX, y - R - speedY, R, R);//减掉R表示在坐标替换为圆心,减掉速度表示将上一个小球掩盖掉
而次画布ig比较奇特,所有的球都花在次画布上,画完之后,次画布消失,也就是所一切都消失不见了,无论上面有多少个球,包括子弹,然后再在极短的时间之内重画出来,然后各种球和子弹再画出他们变换坐标后的位置,这样可以永久性,无残留的擦除上一次所留下的一切痕迹,毕竟皮之不存毛将焉附?
所以他应该放在哪呢?当然是moveBall中了,因为他画的频率一定和画Ball和Bullet的频率一样,才能保证画面的正常,所以要一起放在run()中,而且要放在前面,先画出来,在run()的最后,别忘了次画布ig是我们新造的,所以要移花接木,将ig画在g上
Image img = fb.createImage(fb.getWidth(), fb.getHeight()); // 图片要多次创建!!!!!!
Graphics2D ig = (Graphics2D) img.getGraphics();
……
g.drawImage(img, 0, 0, fb);//等小球画完之后一次性清除一遍,如果在for循环中会闪烁
以下是代码(要想运行在你的编译器你还需要先建一个BallGame的包,以及有一张图片,在Ball类中引用)
窗体类,相对于画图板之三,需要增加一些代码:创建控制的小球,传入***
package BallGame;
import java.awt.Color;
import java.awt.FlowLayout;
import java.awt.Graphics;
import java.util.ArrayList;
import javax.swing.Icon;
import javax.swing.ImageIcon;
import javax.swing.JComponent;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
public class Framball extends JFrame{
private static ArrayList <Ball> list = new ArrayList<Ball>();//新建的list一定要初始化
static ArrayList<Bullet>listB=new ArrayList<Bullet>();
public Graphics g;
public void showUI() {
this.setTitle("滑稽大作战");
this.setSize(1200, 700);
this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
this.setLocationRelativeTo(null);
// 设置布局,流式布局
this.setLayout(new FlowLayout());
this.setVisible(true);
g=this.getGraphics();
//添加***
// g.setColor(Color.black);
Ball ball=new Ball(this,g);
ball.set(600, 500, 40);
list.add(ball);
ListernerBall l=new ListernerBall(this,g,ball);
l.setB(list,listB);
this.addMouseListener(l);
this.addMouseMotionListener(l);
}
public static void main(String[] args) {
Framball fb=new Framball();
fb.showUI();//可见才能得到画布
drawBall db=new drawBall();
db.setG(fb,fb.g,list);//传三个参数
db.start();//启动线程,线程启动后,他会自动运行,并且只运行run方法,想要运行其他方法必须另外调用
moveBall mb=new moveBall();
mb.setL(fb, fb.g,list,listB);
mb.start();//启动线程
}
}
Ball类
package BallGame;
import java.awt.Color;
import java.awt.Font;
import java.awt.Graphics;
import java.awt.Image;
import java.util.ArrayList;
import java.util.Random;
import javax.swing.Icon;
import javax.swing.ImageIcon;
public class Ball {
Random rand = new Random();
int R = rand.nextInt(70) + 20;// 半径
int x = rand.nextInt(1200);
int y = rand.nextInt(600);
int blood = rand.nextInt(5) + 3;
int fblood = blood;//满血值
int choose=rand.nextInt(4);
//球的几种颜色的不停变换
int []arr= {255,192,128,64,0};
int i=rand.nextInt(arr.length);
int j=rand.nextInt(arr.length);
int k=rand.nextInt(arr.length);
int speedX = 70-R-20, speedY = 70-R-20;// 小球运动速度
private int r = 1200, d = 700;// 右限,下限
public Graphics g;
public Framball fb;
public Ball(Framball fb,Graphics g) {//这个是必须要传的,放在构造方法中
this.g = g;
this.fb = fb;
}
public void set(int x,int y,int R) {//这个是可选传的
this.x=x;
this.y=y;
this.R=R;
}
public void draw(Graphics g) {
Color C=new Color(arr[k],arr[j],arr[i]);
g.setColor(C);
ImageIcon image = null;
//这里可以使用不同的图片,从而出现不同的滑稽表情,为了方便我全部用了一种
if(choose==0) image = new ImageIcon("D:\\Pictures\\项目图片\\滑稽2.jpg");
else if(choose==1)image = new ImageIcon("D:\\Pictures\\项目图片\\滑稽2.jpg");
else if(choose==2)image = new ImageIcon("D:\\Pictures\\项目图片\\滑稽2.jpg");
else if(choose==3)image = new ImageIcon("D:\\Pictures\\项目图片\\滑稽2.jpg");
Image img=image.getImage();
g.drawImage(img, x-R, y-R,2*R,2*R, null);
// g.fillOval(x - R, y - R, 2*R, 2*R);//这里是宽和高,所以要写直径,否则就会在奇怪的地方相撞
//设置字体,将血量值显示在图片内部
Font mf=new Font(Font.DIALOG,Font.BOLD,15);
g.setFont(mf);
g.setColor(Color.black);
String Blood=String.valueOf(blood);//同步球的血量和血值
g.drawString(Blood, x-6, y+5);
}
public void run() {// 表示小球的运动,可反弹,必须放在while循环中才能跑起来
if (y >= d)// 当y接触到下限时
speedY *= -1;// 速度反向
else if (y <= 0)
speedY *= -1;
if (x >= r)
speedX *= -1;
else if (x <= 0)
speedX *= -1;
x += speedX;// 每一次run,就移动一次速度值
y += speedY;
// 只执行一次run方法,小球是不会动的
}
public void crash(ArrayList<Ball> list) {
for (int i = 0; i < list.size(); i++) {
Ball ball = list.get(i);
if (this != ball) {
int d = (x - ball.x) * (x - ball.x) + (y - ball.y) * (y - ball.y);// 横纵坐标平方和
int dis = (R + ball.R) * (R + ball.R);// 半径之和的平方
if (d <= dis) {
int sx=speedX;
int sy=speedY;
speedX=ball.speedX;
speedY=ball.speedY;
ball.speedX=sx;
ball.speedY=sy;
}
}
}
}
public void blood(Graphics g) {
g.setColor(Color.red);
g.fillRect(x - R, y - 5 - R, blood * 10, 5);
g.setColor(Color.gray);//失血时空去的血条
g.fillRect(x - R + blood * 10, y - R - 5, (fblood - blood) * 10, 5);
}
}
drawBall线程
package BallGame;
//import java.awt.Color;
import java.awt.Graphics;
import java.util.ArrayList;
public class drawBall extends Thread{
ArrayList <Ball> list;//注意后面的写法,写错会报空指针异常
Graphics g;
Framball fb;
int count=0;
public void setG(Framball fb,Graphics g,ArrayList <Ball> list) {
this.g=g;
this.fb=fb;
this.list=list;
}
public void run() {
while(true) {
if(list.size()==10) {
break;//设置小球存储的个数
}
Ball b=new Ball(fb,g);//循环中不断新建一个球
list.add(b);//把球放进队列
}
}
}
moveBall线程
package BallGame;
import java.awt.Color;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Image;
import java.awt.event.MouseEvent;
import java.awt.event.MouseMotionListener;
import java.util.ArrayList;
import javax.swing.ImageIcon;
public class moveBall extends Thread {
int i = 0;
private ArrayList<Ball> list;// =new ArrayList<Ball>();
private ArrayList<Bullet> listB;
private Framball fb;
private Graphics g;
private int x,y,r=20;
Color c = new Color(238, 238, 238);//背景色
int count=1;//控制球的数量
public void setL( Framball fb, Graphics g,ArrayList<Ball> list,ArrayList<Bullet> listB) {
this.list = list;
this.listB=listB;
this.fb = fb;
this.g = g;
}
public void run() {
while (true) {//创建次画布放在循环中,每次循环完一次队列中的小球之后就重新添加画布
Image img = fb.createImage(fb.getWidth(), fb.getHeight()); // 图片要多次创建!!!!!!
Graphics2D ig = (Graphics2D) img.getGraphics();
// ImageIcon imgi=new ImageIcon("D:\\Pictures\\项目图片\\滑稽2.jpg");
// ig.drawImage(imgi.getImage(), 0, 0,fb.getWidth(),fb.getHeight(),fb);//设置背景图片
ig.setColor(Color.white);
ig.fillRect(0, 0, fb.getWidth(), fb.getHeight());
Ball mball=list.get(0);
mball.draw(ig);
mball.blood(ig);
mball.crash(list);
if(list.size()<7) {
drawBall db=new drawBall();
db.setG(fb,fb.g,list);//传参数
db.start();//启动线程,线程启动后,他会自动运行,并且只运行run方法,想要运行其他方法必须另外调用
}
//执行队列中的小球
for (i = 1; i < list.size(); i++) {
// list.get(i).clear();
Ball ball=list.get(i);
ball.draw(ig);
ball.blood(ig);
ball.crash(list);
ball.run();
}
//执行子弹
for(i=0;i<listB.size();i++) {
Bullet bullet=listB.get(i);
bullet.draw(ig);
bullet.crash(list);
bullet.run();
}
g.drawImage(img, 0, 0, fb);//等小球画完之后一次性清除一遍,如果在for循环中会闪烁
try {
sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
Bullet类
package BallGame;
import java.awt.Color;
import java.awt.Font;
import java.awt.Graphics;
import java.awt.Image;
import java.util.ArrayList;
import java.util.Random;
import javax.swing.Icon;
import javax.swing.ImageIcon;
public class Bullet {
ArrayList<Bullet>listB;
int x,y;
int R = 5;// 半径
private int blood = 1;
private int speedX = 10, speedY = 0;// 子弹运动速度
Graphics g;
Framball fb;
public Bullet(Framball fb,Graphics g,ArrayList<Bullet>listB) {
this.g = g;
this.fb = fb;
this.listB=listB;
}
public void set(int x,int y,int R) {
this.x=x;
this.y=y;
this.R=R;
}
public void draw(Graphics g) {
ImageIcon image =new ImageIcon("D:\\Pictures\\项目图片\\滑稽图标.png");
Image img=image.getImage();
g.drawImage(img, x-R, y-R,2*R,2*R, null);
}
public void run() {// 子弹运动
x += speedX;// 每一次run,就移动一次速度值
y += speedY;
// 只执行一次run方法,子弹是不会动的
}
public void crash(ArrayList<Ball> list) {
for (int i = 1; i < list.size(); i++) {
Ball ball = list.get(i);
int d = (x - ball.x) * (x - ball.x) + (y - ball.y) * (y - ball.y);// 横纵坐标平方和
int dis = (R + ball.R) * (R + ball.R);// 半径之和的平方
if (d <= dis) {
blood--;
ball.blood--;
if (ball.blood <= 0 && list != null)
list.remove(i);// 不能和下行调换,否则会指针溢出,因为下面的可能为空
if (blood <= 0)
listB.remove(this);
}
}
}
}
***ListenerBall
package BallGame;
import java.awt.Color;
import java.awt.Graphics;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.awt.event.MouseMotionListener;
import java.util.ArrayList;
public class ListernerBall implements MouseListener,MouseMotionListener{
private int x,y,r=20;
public Graphics g;
Framball fb;
Ball ball=new Ball(fb,g);
ArrayList<Bullet>listB;
ArrayList<Ball> list;
public ListernerBall(Framball fb,Graphics g,Ball ball) {
// TODO Auto-generated constructor stub
this.g=g;
this.fb=fb;
this.ball=ball;
}
public void setB(ArrayList<Ball> list,ArrayList<Bullet>listB) {
this.list=list;
this.listB=listB;
}
public void mouseClicked(MouseEvent e) {
}
public void mouseDragged(MouseEvent e) {
// x=ball.x;
// y=ball.y;
}
@Override
public void mouseMoved(MouseEvent e) {
// TODO Auto-generated method stub
ball.x=e.getX();
ball.y=e.getY();
}
@Override
public void mousePressed(MouseEvent e) {
// TODO Auto-generated method stub
Bullet bullet=new Bullet(fb,g,listB);
bullet.x=e.getX();
bullet.y=e.getY();
listB.add(bullet);
}
@Override
public void mouseReleased(MouseEvent e) {
}
@Override
public void mouseEntered(MouseEvent e) {
}
@Override
public void mouseExited(MouseEvent e) {
}
}
看代码多累啊,再“滑稽”一下,玩了一个小时哈哈