java 线程 、键盘监听器——教你写自己的球球大作战

学习本文需要先准备的知识:窗体编写、窗体元素添加、窗体绘制填充圆

1、 前期准备(知识点讲解)

(1)、java线程
a、为什么要用线程
案例:想要写一个会移动的小球,我们可以采取这样的方法:写一个while(true)循环,在里面不断地给圆的横纵坐标自加,然后把这个球重新画出来,之前画过球的地方用白色的球填充,同时循环里加一个延时的代码就行,如下:

while (true) {
			try {
				Thread.sleep(30);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			double addX = -2+rand.nextInt(4000)*0.001;
			double addY = -2+rand.nextInt(4000)*0.001;
			g.setColor(Color.WHITE);
			g.fillOval(x, y, width, width);
			x+=addX;
			y+=addY;
			g.setColor(c);
			g.fillOval(x, y, width, width);
		}

但这就面临着一个问题,程序在这个循环里的时候,就不能执行别的内容,也就是说只能控制一个球运动。如果想要让这个while循环可以多个同时执行,就需要用到线程。

b、怎样使用线程:
第一步:写一个类继承Thread类或者实现Runnable接口
第二步:在第一步里的类里里重写run()方法,需要用线程执行的代码就放到run方法里面;

import java.awt.Color;
import java.awt.Graphics;
import java.util.Random;

public class ThreaBall extends Thread {
	private Graphics g;
	private int x, y;

	public ThreaBall(Graphics g, int x, int y) {
		this.g = g;
		this.x = x;
		this.y = y;
	}

	/**
	 * 启动线程时执行的方法
	 */
	public void run() {
		Random rand = new Random();  //随机数对象,可以用于产生随机数
		int width = 20 + rand.nextInt(100);
		int red = rand.nextInt(255);  //产生0-255内的随机数
		int green = rand.nextInt(255);
		int blue = rand.nextInt(255);
		Color c = new Color(red, green, blue);  //利用红绿蓝三颜色生成一种颜色
		while (true) {
			try {
				Thread.sleep(30);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			double addX = -2+rand.nextInt(4000)*0.001;
			double addY = -2+rand.nextInt(4000)*0.001;
			g.setColor(Color.WHITE);
			g.fillOval(x, y, width, width);
			x+=addX;
			y+=addY;
			g.setColor(c);
			g.fillOval(x, y, width, width);
		}
	}
}

第三部:在监听器类里面相应地方实例化第一步里的类,在需要用到执行多线程的地方用第一步里的类的对象的start()方法执行run方法里的内容。

public void mouseClicked(MouseEvent e) {
		x = e.getX();
		y = e.getY();
		
		//创建线程对象
		ThreaBall tb = new ThreaBall(g,x,y);
		//启动线程
		tb.start();
	}

c、线程的优化:
用上面多线程写出的可以画出多个运动的小球在相互重叠的时候可以看到明显的闪动,因为不同小球画白色填充圆的时间不同,会有看起来冲突的时候。同时每个线程都相当于一个独立的程序,当有特别多的线程同时执行的时候,会影响程序效率。
其实说白了,计算机真正意义上可以同时执行的线程的数量取决于cpu的核数,一个cpu在某个时间点上只能执行1个线程。那计算机可以同时执行那么多线程的原因是什么?这是因为cpu执行一个线程的时间都特别快,当速度足够快的时候,这些线程看起来就像是同时执行的一样。
所以可以按照这个思路来优化上面的程序:将某一个时间内要画的所有的小球放到一个线程里完成,具体步骤如下:
第一步、写一个记录所有小球信息的类,将小球的左上角坐标和宽度、颜色、横纵坐标的改变值等给保存起来,同时在这个类里写一个将绘制小球的方法:

import java.awt.Color;
import java.awt.Graphics;
import java.util.Random;

public class StoreBall {

	private int x, y,addX,addY;
	private Graphics g;
	private int w;
	private int red = 0, green = 0, blue = 0;
	private Color c;
	private StoreBall[] Ball;
    private Random ran = new Random();

	public StoreBall(int x, int y, Graphics g,StoreBall[] Ball) {
		addX=-5 + ran.nextInt(10);
		addY=-5 + ran.nextInt(10);
		w = 20 + ran.nextInt(50);
		red = ran.nextInt(255);
		green = ran.nextInt(255);
		blue = ran.nextInt(255);
	    c = new Color(red, green, blue);
		this.x = x;
		this.y = y;
		this.g = g;
		this.Ball = Ball;
	}
	
	public void ShowBall() {
		x += addX;
		y += addY;
		g.setColor(c);
		g.fillOval(x-w/2, y-w/2, w, w);
	}
}

第二步、在监听器里创建一个保存小球信息的对象的数组,在监听器的构造方法里实例化监听器对象并执行start()函数(确保只实例化一个监听器):

private StoreBall[] Ball = new StoreBall[200];
public ClickListener(Graphics g) {
		this.g = g;
		ThreadRun tr = new ThreadRun(g, Ball);
		TimeThread tt = new TimeThread(Ball);
		tr.start();
		tt.start();
	}

第三步、鼠标点击的时候,创建一个保存小球信息的类的对象,将其保存进第二部创建的对象数组:

public void mouseClicked(MouseEvent arg0) {
		int x = arg0.getX();
		int y = arg0.getY();
		StoreBall ball = new StoreBall(x, y, g,Ball);
		// ball.ShowBall();
		Ball[count++] = ball;
	}

第四步、在线程类的run方法里写一个for循环,循环开始前用背景颜色在真个窗口上画一个填充矩形,for循环历遍保存小球信息的对象,执行不为空的对象的画小球的方法:

public void ShowFrame() {
		g.setColor(Color.WHITE);
		g.fillRect(0, 0, 910, 910);  //用背景颜色填充整个窗口
		for (int i = 0; i < Ball.length; i++) {
			if (Ball[i] != null && Ball != null) {
				meetBall(Ball[i]);
				Ball[i].ShowBall();     //执行非空对象成员的画小球的方法
			}
		}
		try {
			Thread.sleep(100);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
	}

(2)、鼠标监听器的使用
第一步:写一个对象实现鼠标监听器接口KeyListener:

public class ClickListener implements KeyListener {}

第二步、重写所有KeyListener的成员函数,在keyPressed(KeyEvent e)方法里获取用户按下的按键,并根据按键执行相应的操作:

switch (e.getKeyCode()) {
		case KeyEvent.VK_UP:
			y-=10;
			break;
		case KeyEvent.VK_DOWN:
			y+=10;
			break;
		case KeyEvent.VK_RIGHT:
			x+=10;
			break;
		case KeyEvent.VK_LEFT:
			x-=10;
			break;
		}

第三步、给窗口添加这个类实例化的键盘监听器:

ClickListener l = new ClickListener(g);
jsf.addMouseListener(l);  	
jsf.addKeyListener(l);

2、 开发球球大作战
(1)、游戏规则
一个窗体里有许多不同大小的小球,其中一个玩家控制,玩家可以按住键盘上的方向键控制小球的运动方向。当小球碰到比自己小的小球,可以将其吃掉,小球变得更大,碰到比自己大的小球,玩家死亡。

(2)、代码需要实现的几个点:
a、非玩家小球的产生方法:我的产生方法:鼠标点击就会在20个随机位置产生大小不同、颜色不同的小球。同时还需要注意,如果有两个小球产生的位置重合了,就需要重新产生一个小球:

public void mouseClicked(MouseEvent arg0) {
		int tCount = 0;
		for (int i = 0; i < Ball.length; i++) {
			if (Ball != null && Ball[i] == null) {   //判断是为了防止空指针
				if (tCount++ >= 20)
					break;
				Ball[i] = addBall();
			}
		}
	}

	public StoreBall addBall() { // 随机产生小球
		Random rand = new Random();
		int x = rand.nextInt(900);
		int y = rand.nextInt(900);
		StoreBall ball = new StoreBall(x, y, g, Ball);
		for (int i = 0; i < Ball.length; i++) { // 防止产生重合的小球
			if (Ball[i] != null) {
				int cx = ball.getX() - Ball[i].getX();
				System.out.println("i:" + i);
				int cy = ball.getY() - Ball[i].getY();
				if (Math.sqrt(cx * cx + cy * cy) <= (ball.getW() + Ball[i].getW()) / 2) {
					ball = addBall();
				}
			}
		}
		return ball;
	}

b、非玩家小球的移动方法:可以随机向某个方向运动,持续一段时间后改变运动方向;
这里可以借助另一个监听器来实现,写一个监听器,在一段时间内改变控制小球运动方向的参数的值:

public class TimeThread extends Thread {
	private int addX, addY;
	Random rand = new Random();

	public void run() {
		while (true) {
			
			addX = -20 + rand.nextInt(40);
			addY = -20 + rand.nextInt(40);
			try {

				Thread.sleep(900);
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}
	}

	public int getAddX() {
		return addX;
	}

	public int getAddY() {
		return addY;
	}
}

c、非玩家小球的碰撞方法:两个小球碰撞时,两个小球的运动方向及速度相互变化,即第一个小球的运动方向和速度变成了第二个小球的运动方向和速度。小球与边框碰撞时要能够反弹(碰撞过程中如果只有涉及到一个小球,则只需要在储存小球的类里写碰撞方法,如果涉及多个小球,必须在声明了储存小球对象的数组里写碰撞方法):

public void meetBall(StoreBall ball) {   //两个小球碰撞时,两个小球的运动方向及速度相互变化,即第一个小球的运动方向和速度变成了第二个小球的运动方向和速度。
		for (int i = 0; i < Ball.length; i++) {
			if (Ball[i] != null && ball != null && Ball[i] != ball) {
				int x = Ball[i].getX() - ball.getX();
				int y = Ball[i].getY() - ball.getY();
				if (Math.sqrt(x * x + y * y) <= (Ball[i].getW() + ball.getW()) / 2) {
					int addX1 = Ball[i].getAddX();
					int addY1 = Ball[i].getAddY();
					int addX2 = ball.getAddX();
					int addY2 = ball.getAddY();
					Ball[i].setAddX(addX2);
					Ball[i].setAddY(addY2);
					ball.setAddX(addX1);
					ball.setAddY(addY1);
				}
			}
		}
	}
public void meetWall(){   //小球与边框碰撞时要能够反弹
		if(x+w>910){
			addX=-10 + ran.nextInt(5);
		}
		if(x<40){
			addX=ran.nextInt(10);
		}
		if(y+w>910){
			addY=-10 + ran.nextInt(5);
		}
		if(y<60){
			addY=ran.nextInt(10);
		}
	}

d、玩家的小球与非玩家的小球碰撞,如果玩家小球的大小比非玩家小球的大小大的话,非玩家的小球消失,玩家的小球变大;否则玩家死亡,退出游戏:

public void meetGamer() {
		for (int i = 0; i < Ball.length; i++) {
			if (Ball[i] != null) {
				int x = Ball[i].getX() - gamer.getX();
				int y = Ball[i].getY() - gamer.getY();
				if (Math.sqrt(x * x + y * y) - (Ball[i].getW() + gamer.getW()) / 2 < 0) {
					if (Ball[i].getW() > gamer.getW()) {
						JOptionPane.showMessageDialog(null, "玩家死亡!");
						System.exit(0);
					} else {
						gamer.setW(gamer.getW() + Ball[i].getW() / 4);
						Ball[i] = null;
					}
				}
			}
		}
	}

3、 完整的代码示例:
–ShowFrame.java—

package com.antony.runBall0915;

import java.awt.Color;
import java.awt.Graphics;
import javax.swing.JFrame;



public class ShowFrame {
    public static void main(String[] args) {
    	JFrame jsf = new JFrame();
    	jsf.setSize(910, 910);
    	jsf.setDefaultCloseOperation(3);
    	
    	jsf.setTitle("球球大作战");
        jsf.setLocationRelativeTo(null);
        jsf.getContentPane().setBackground(Color.WHITE);
        jsf.setVisible(true);
    	Graphics g = jsf.getGraphics();
        ClickListener l = new ClickListener(g);
        jsf.addMouseListener(l);  	
        jsf.addKeyListener(l);
    }
}

–ClickListener.java—

package com.antony.runBall0915;

import java.awt.Graphics;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.util.Random;

public class ClickListener extends MouseAdapter implements KeyListener {
	private StoreBall[] Ball = new StoreBall[200];
	private int count = 0;
	private java.awt.Graphics g;
	private int x = 300, y = 500, w = 30;
	Gamer gamer;

	public ClickListener(Graphics g) {
		this.g = g;
		gamer = new Gamer(x, y, w, g);
		ThreadRun tr = new ThreadRun(g, Ball, gamer);
		TimeThread tt = new TimeThread(Ball);
		tr.start();
		tt.start();
	}

	public void mouseClicked(MouseEvent arg0) {
		int tCount = 0;
		for (int i = 0; i < Ball.length; i++) {
			if (Ball != null && Ball[i] == null) {
				if (tCount++ >= 20)
					break;
				Ball[i] = addBall();
			}
		}
	}

	public StoreBall addBall() { // 随机产生小球
		Random rand = new Random();
		int x = rand.nextInt(900);
		int y = rand.nextInt(900);
		StoreBall ball = new StoreBall(x, y, g, Ball);
		for (int i = 0; i < Ball.length; i++) { // 防止产生重合的小球
			if (Ball[i] != null) {
				int cx = ball.getX() - Ball[i].getX();
				System.out.println("i:" + i);
				int cy = ball.getY() - Ball[i].getY();
				if (Math.sqrt(cx * cx + cy * cy) <= (ball.getW() + Ball[i].getW()) / 2) {
					ball = addBall();
				}
			}
		}
		return ball;
	}

	public void keyTyped(KeyEvent e) {
		// TODO Auto-generated method stub

	}

	@Override
	public void keyPressed(KeyEvent e) {
		switch (e.getKeyCode()) {
		case KeyEvent.VK_UP:
			y -= 10;
			break;
		case KeyEvent.VK_DOWN:
			y += 10;
			break;
		case KeyEvent.VK_RIGHT:
			x += 10;
			break;
		case KeyEvent.VK_LEFT:
			x -= 10;
			break;
		}
		System.out.println("x:" + x + "y:" + y);
		if (gamer.getX() + gamer.getW() > 910) {
			x = 910 - gamer.getW();
		}
		if (gamer.getY() + gamer.getW() > 910) {
			y = 910 - gamer.getW();
		}
		if (gamer.getX() < 0) {
			x = gamer.getW();
		}
		if (gamer.getY() < 0) {
			y = gamer.getW();
		}
		gamer.setX(x);
		gamer.setY(y);
	}

	@Override
	public void keyReleased(KeyEvent e) {
		// TODO Auto-generated method stub

	}
}

–StoreBall.java–

package com.antony.runBall0915;

import java.awt.Color;
import java.awt.Graphics;
import java.util.Random;

public class StoreBall {

	private int x, y,addX,addY;
	private Graphics g;
	private int w;
	private int red = 0, green = 0, blue = 0;
	private Color c;
	private StoreBall[] Ball;
    private Random ran = new Random();

	public StoreBall(int x, int y, Graphics g,StoreBall[] Ball) {
		addX=-5 + ran.nextInt(10);
		addY=-5 + ran.nextInt(10);
		w = 20 + ran.nextInt(50);
		red = ran.nextInt(255);
		green = ran.nextInt(255);
		blue = ran.nextInt(255);
	    c = new Color(red, green, blue);
		this.x = x;
		this.y = y;
		this.g = g;
		this.Ball = Ball;
	}
	
	public void setW(int w){
		this.w = w;
	}
	
	public void meetWall(){
		if(x+w>910){
			addX=-10 + ran.nextInt(5);
		}
		if(x<40){
			addX=ran.nextInt(10);
		}
		if(y+w>910){
			addY=-10 + ran.nextInt(5);
		}
		if(y<60){
			addY=ran.nextInt(10);
		}
	}
	
	
	public void setAddX(int x){
		addX = x;
	}
	
	public void setAddY(int y){
		addY = y;
	}
	
	public int getAddX(){
		return addX;
	}
	
	public int getAddY(){
		return addY;
	}
	
    public int getX() {
    	return x-w/2;
    }
    
    public int getY() {
    	return y-w/2;
    }
    
    public int getW() {
    	return w;
    }
 
	public void ShowBall() {
		//g.setColor(Color.WHITE);
		//g.fillOval(x-w/2, y-w/2, w, w); 
		meetWall();
		x += addX;
		y += addY;
		
	//	System.out.println("x:"+x+",y"+y+",Color:"+c);
		
		g.setColor(c);
		g.fillOval(x-w/2, y-w/2, w, w);
	}
}

–Gamer.java—

package com.antony.runBall0915;

import java.awt.Color;
import java.awt.Graphics;
import java.util.Random;

public class Gamer{
	private int x, y, w;
	private Graphics g;
	Random rand = new Random();
	int red = rand.nextInt(255);
	int green = rand.nextInt(255);
	int blue = rand.nextInt(255);
	Color c = new Color(red, green, blue);

	public Gamer(int x, int y, int w, Graphics g) {
		this.x = x;
		this.y = y;
		this.w = w;
		this.g = g;
	}
	
    public int getX() {
    	return x-w/2;
    }
    
    public int getY() {
    	return y-w/2;
    }
    
    public int getW() {
    	return w;
    }

	public void setX(int x) {
		this.x = x;
	}
	
	public void setW(int w) {
		this.w = w;
	}

	public void setY(int y) {
		this.y = y;
	}

	public void showGamer() {
		g.setColor(c);
		g.fillOval(x, y, w, w);
	}
}

–ThreadRun.java—

package com.antony.runBall0915;

import java.awt.Color;
import java.awt.Graphics;
import java.util.Random;

import javax.swing.JOptionPane;

public class ThreadRun extends Thread {
	private Graphics g;
	private StoreBall[] Ball;
	private Random rand = new Random();
	private Gamer gamer;

	public ThreadRun(Graphics g, StoreBall[] Ball, Gamer gamer) {
		this.g = g;
		this.Ball = Ball;
		this.gamer = gamer;
	}

	public void run() {
		while (true) {
			ShowFrame();
		}
	}

	public void meetGamer() {
		for (int i = 0; i < Ball.length; i++) {
			if (Ball[i] != null) {
				int x = Ball[i].getX() - gamer.getX();
				int y = Ball[i].getY() - gamer.getY();
				if (Math.sqrt(x * x + y * y) - (Ball[i].getW() + gamer.getW()) / 2 < 0) {
					if (Ball[i].getW() > gamer.getW()) {
						JOptionPane.showMessageDialog(null, "玩家死亡!");
						System.exit(0);
					} else {
						gamer.setW(gamer.getW() + Ball[i].getW() / 4);
						Ball[i] = null;
					}
				}
			}
		}
	}

	public void ShowFrame() {
		int count = 0;
		g.setColor(Color.WHITE);
		g.fillRect(0, 0, 910, 910);
		gamer.showGamer();
		for (int i = 0; i < Ball.length; i++) {
			// System.out.println("Ball[i]:"+Ball[i]+",i:"+i);
			if (Ball[i] != null && Ball != null) {
				count++;
				meetGamer();
				meetBall(Ball[i]);
				System.out.println("i:" + i);
				if (Ball[i] != null)
					Ball[i].ShowBall();
			}
		}
		try {
			Thread.sleep(100);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
	}

	public void meetBall(StoreBall ball) {
		for (int i = 0; i < Ball.length; i++) {
			if (Ball[i] != null && ball != null && Ball[i] != ball) {
				int x = Ball[i].getX() - ball.getX();
				int y = Ball[i].getY() - ball.getY();
				if (Math.sqrt(x * x + y * y) <= (Ball[i].getW() + ball.getW()) / 2) {
					int addX1 = Ball[i].getAddX();
					int addY1 = Ball[i].getAddY();
					int addX2 = ball.getAddX();
					int addY2 = ball.getAddY();
					Ball[i].setAddX(addX2);
					Ball[i].setAddY(addY2);
					ball.setAddX(addX1);
					ball.setAddY(addY1);
				}
			}
		}
	}
}

–TimeThread.java—

package com.antony.runBall0915;

import java.util.Random;

public class TimeThread extends Thread {
	private StoreBall[] Ball;
	Random rand = new Random();
	public TimeThread(StoreBall[] Ball){
		this.Ball = Ball;
	}
	public void run() {
		while (true) {
			for(int i=0;i<Ball.length;i++){
				if(Ball[i]!=null){
					Ball[i].setAddX( -5 + rand.nextInt(10));
					Ball[i].setAddY(-5 + rand.nextInt(10));
					try {
						Thread.sleep(2000);
					} catch (InterruptedException e) {
						// TODO Auto-generated catch block
						e.printStackTrace();
					}
				}
			}
		}
	}
}