1. 程式人生 > >#java 執行緒 、鍵盤監聽器——教你寫自己的球球大作戰

#java 執行緒 、鍵盤監聽器——教你寫自己的球球大作戰

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();
					}
				}
			}
		}
	}
}