之前做了弹球游戏,用了线程,以为自己懂了,但是做飞机大战的时候觉得有点乱,所以回过头来整理一下弹球游戏的做法:
文章目录
一、做出界面并在界面上画出球
1.写一个主类显示界面,这个很简单可以直接跳过
2.给窗体添加鼠标监听,定义一个窗体鼠标监听器类,点击界面出现小球
public class BallListener extends MouseAdapter{
private BallMain ballMain;
private Graphics g;
private int size=30;
public BallListener(BallMain ballMain) {
this.ballMain = ballMain;
}
public void mouseClicked(MouseEvent e) {
if(g == null) {
g = ballMain.getGraphics();//如果没有画笔,给窗体添加画笔
}
int x = e.getX();
int y = e.getY();
g.fillOval(x, y, size, size);
System.out.println("点击");
}
}
观看效果:
二、如何让小球运动起来
想要让小球运动起来其实很简单,只需要改变小球的坐标:
for(int i=x,j=y;i<ballMain.getWidth() && j<ballMain.getHeight();i++,j++) {
g.fillOval(i, j, size, size);
}
但是这样小球会一下子全都画完,我们看到的效果是一条粗线,不是我们想要的效果
上面小球运动得太快了,还没看到,就已经出了边界,我们可以增加休眠sleep(),这样我们发现能够看到小球的运动轨迹,但是画出来还是一条线,而且不能再点击画出另一个球。针对画出的不是单一的球,我们可以进行轨迹的擦除,轨迹擦除可以利用背景颜色在原有的位置在画一个球将轨迹覆盖。
这里我总是晕,但是想清楚之后发现很简单,我们想着先画一个黑球,然后球移动的时候我们要把之前的球的印记擦除使用画背景颜色球的方法,我们在画黑球和画背景球之间休眠,这样有黑球显示的效果,然后改变xy的值(也可以这样想,不管有球没球我都先擦一下,然后在进行移动和画球)
while(x<ballMain.getWidth() && y<ballMain.getHeight()) {
g.setColor(Color.BLACK);//先显示球
g.fillOval(x, y, size, size);
try {//休眠,让显示的效果肉眼可以不被马上擦除
Thread.sleep(100);
} catch (InterruptedException e1) {
// TODO Auto-generated catch block
e1.printStackTrace();
}
g.setColor(ballMain.getContentPane().getBackground());
g.fillOval(x, y, size, size);//擦除轨迹
x+=4;//改变xy,为小球移动做铺垫
y+=4;
}
再来看一下效果:
因为不知道如何显示gif,所以添加的图片看不出效果,但是亲身验证效果是出来了,但是还是不令人满意,因为在小球运动的时候不能再画其他球了,必须等该小球运行结束。这种情况我们就要使用线程了!多线程执行这样可以让多个球并发运动。
三、多个小球同时运动
我们先创建一个线程类,这里就直接让监听器类继承Runnable,我们把要实现的效果(这里是让小球运动)的代码块放进run()方法,然后创建线程,并启动。
如果单纯的这样实现,会发现点击一次,之前的小球就会停止运行,这是因为每次点击创建一个新的线程的时候,画笔画球的起始位置就发生了变化
那么需要怎么修改代码呢?从开始学习Java到现在,我渐渐爱上敲代码,不仅仅是因为做出游戏的效果是我有一种成就感,更重要的是我感受到了编程的快乐,之前一直觉得解决代码bug很难,但其实挺简单的,计算机的思维是直的,按步骤执行,哪里错就改哪里咯,既然每次创建新进程的时候,改变了小球正在运行的坐标,那么就定义两个变量存储前一个进程的坐标咯!
在run函数里面,定义局部变量t1=x和t2=y,每次改变的是t1,t2的值而不直接是x,y,这样效果就正确了!
但是问题又来了,我们知道计算机并不是线程越多越好,每次我们点击的时候都会创建一个新的线程,过多的线程反而会降低效率,那么有没有什么办法可以只创建一个线程但是能画多个小球呢?答案是肯定的,我们可以创建一个数组存储每个小球,然后在启动进程的时候将所有的小球都画出来。
因为要创建数组存储小球的状态,所有我们抽象小球的属性和方法创建一个小球类Ball
/**
* 小球类
* @author Administrator
*
*/
public class Ball{
//属性
private int x,y,size;
private int speedx,speedy;
private Graphics g;
//方法
public Ball() {//构造函数,传递对象参数
}
public void clearBall() {//擦除轨迹
}
public void drawBall() {//画小球
}
public void move() {//小球移动
}
public void crashBall() {//小球碰撞反向运动
}
}
下面来看一下小球类的完整代码:
public class Ball{
//属性
private int x,y,size;
private int speedx,speedy;
private Graphics g;
private BallMain ballMain;
private Color color;
//方法
public Ball(int size,BallMain ballMain,Graphics g,int x,int y,int speedx,int speedy) {//构造函数,传递对象参数
this.size = size;
this.ballMain = ballMain;
this.g = g;
this.x = x;
this.y = y;
this.speedx = speedx;
this.speedy = speedy;
}
public void clearBall() {//擦除轨迹
g.setColor(ballMain.getContentPane().getBackground());
g.fillOval(x-speedx, y-speedy, size, size);
}
public void drawBall() {//画小球
g.setColor(Color.BLACK);
g.fillOval(x, y, size, size);
}
public void move() {//小球移动
//越界
if(x<=0 || x+size>=ballMain.getWidth()) {
speedx = -speedx;
}
if(y<=0 || y+size>=ballMain.getHeight()) {
speedy = -speedy;
}
x += speedx;
y += speedy;
}
public void swap(Ball a,Ball b) {
int t1 = a.speedx;
int t2 = a.speedy;
a.speedx = b.speedx;
a.speedy = b.speedy;
b.speedx = t1;
b.speedy = t2;
}
public void crashBall(ArrayList<Ball> list) {//小球碰撞反向运动,如果直接将小球传进去,那么在run函数里面还是要循环数组队列判断
//对列中每个小球都要判断
for(int i=0;i<list.size();i++) {
Ball ball = list.get(i);
int d = (this.x - ball.x)*(this.x - ball.x)+(this.y - ball.y)*(this.y - ball.y);
if(Math.sqrt(d) <= size) {
//如果碰撞,则调换顺序;
swap(this,ball);
}
}
}
}
监听器类BallListener的完整代码:
public class BallListener extends MouseAdapter implements Runnable{
private BallMain ballMain;
private Graphics g;
private ArrayList<Ball> list;
private int speedx,speedy;
public BallListener(BallMain ballMain) {
this.ballMain = ballMain;
list = new ArrayList<>();
g = ballMain.getGraphics();
Random rand = new Random();
speedx = rand.nextInt(10);
speedy = rand.nextInt(10);
}
public void mouseClicked(MouseEvent e) {
int x = e.getX();
int y = e.getY();
//点击的时候就实例化一个小球对象
Ball ball = new Ball(30,ballMain,g,x,y,speedx,speedx);
System.out.println("小球加入队列");
list.add(ball);//加入数组队列
}
/**
* 重写Runnable接口的run方法
*/
public void run() {
System.out.println("线程启动了!!!");
while(true) {
for(int i=0;i<list.size();i++) {//一旦创建一个线程那么就将所有小球都输出
System.out.println("小球启动!!!");
Ball ball = list.get(i);
ball.clearBall();//不管有没有球先擦了再说
ball.crashBall(list);
ball.drawBall();
ball.move();
}
try {
Thread.sleep(100);
} catch (InterruptedException e1) {
e1.printStackTrace();
}
}
}
}
主界面类:
public class BallMain extends JFrame{
public static void main(String[] args) {
BallMain bm = new BallMain();
bm.initUI();
}
private void initUI() {
this.setTitle("弹球");
this.setSize(500, 700);
this.setDefaultCloseOperation(3);
this.setLocationRelativeTo(null);
this.setResizable(false);
this.setVisible(true);
BallListener bl = new BallListener(this);
this.addMouseListener(bl);//添加监听器
Thread t = new Thread(bl);//创建线程
t.start();//启动线程
}
}
四、最后总结一下:
我理顺了思路后重新敲了一遍代码,但发现编译通过,但是画不出小球,结果发现是未设置小球的大小。
事实上,我之前经常搞错变量的本源,比如在这个案例中,变量的本源应该是在BallListener类中初始化设置的,包括小球的坐标,速度和大小,我们是要将BallListener类中的变量传入到小球Ball类中,而非反过来,还有一点就是,我是在点击的时候要画出一个小球,所以在点击事件的函数里面实例化小球对象并添加进入队列数组。
很开心,困扰了我许久的思绪理清了!大家也努力吧!