画笔小球 多线程控制小球运动——线程运行时的线程

来源:优游网责编:网络时间:2024-02-07 04:03:26

自己是最了解自己的,每晚都想着我明早早点起来把代码敲了把文章写了,事实上还是斗不过自己的睡意呀,所以今天就选择修个仙,把该做的事情做完了,再去睡觉。我是不会告诉你前面一段时间我是在准备放在文末的小惊喜(也有可能是惊吓),所以要是好奇就接着往下看啦。

今天要写的是多线程控制小球运动。

其实一开始说要上这个课,我是懵逼的。之前写过关于线程的一篇文章,简而言之就是一个应用程序中的各个部分,这样说好像不太对,举个例子也不知道恰不恰当,我要去一个学校统计今天的到校人数,有一种方法是我一栋楼一栋楼的去数,另一种方法是我找5个人来帮我,分别到5栋教学楼中去数,后者的效率明显高于前者吧~所以我理解的大概意思就是,我要去统计人数这件事情是一个进程,而我找的5个帮手就是这个进程中的线程,他们同时工作互不影响。(我实在是佩服自己的想象力竟然想出的例子是这样的,希望脑洞开的小伙伴一起多多交流呀~)

嗯,所以我们要实现的就是在一个窗体上有好多好多个小球呢都会自己运动,然后我们可以通过鼠标创建小球,通过键盘按键来控制小球的暂停与开始以及停止和重新开始。

那么跟上面说的线程有关,我们就要给这些小球加上线程!但是要知道线程的运行时会消耗cpu和内存的,是在寄存器中运行的,如果我们产生了很多很多个小球,那么就会有很多很多个线程同时在工作,势必给cpu带来很大的工作压力,所以联想之前学习到的数组队列,将小球存进数组队列中,作为监听(事件处理类)的属性,将这个事件处理类的实例作为的构造方法的参数,就可以直接启动线程了,而且不需要很多个线程同时工作。(我咋感觉自己没讲明白!明儿我去问清楚了再回来修改!)

让小球在窗体内运动,首先要有一个窗体。

1.创建一个窗体类,继承:

好的编程习惯是现在主函数中实例化这个类的对象,然后再让这个对象去调用一个初始化界面的方法。

在初始化界面的方法中,设置好窗体的基本属性。

import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Image;
import java.awt.RenderingHints;
import java.util.ArrayList;
import java.util.Random;
import javax.swing.JFrame;
/**
 * 实现小球的界面
 * 
 * @author Elaine
 * 
 */
public class BallMain extends JFrame {
	private ArrayList ball = new ArrayList(); //保存小球的数组队列
	private Image img;	//次画布为了使用双缓冲使得窗体看起来不那么闪烁
	private Graphics2D g2d;	//画笔属性
	/**入口主函数
	 * @param args
	 */
	public static void main(String[] args) {
		BallMain bm = new BallMain();
		bm.InitUI();
	}

	public void InitUI() {
		this.setTitle("Ball");	//给窗体设置名字
		this.setSize(500, 600);	//给窗体设置大小
		this.setDefaultCloseOperation(3);	//设置用户在此窗体上发起 "close" 时默认执行的操作,此处设置为直接关闭
		this.setPreferredSize(null);	    //将组件的首选大小设置为常量值此处null表示默认
		this.setResizable(false);	        //设置此窗体是否可由用户调整大小此处设置为不能调整大小
		this.setVisible(true);	            //设置此对象的可见状态此处为设置窗体可见
		BallListener bl = new BallListener(this, ball);		//实例化事件处理类的对象通过该对象将窗体与小球的数组队列与事件处理类联系起来
		this.addMouseListener(bl);			//给窗体加上鼠标监听
		this.addKeyListener(bl);			//给窗体加上键盘监听
		Thread td = new Thread(bl);     //事件处理类BallListener实现了Runnable接口将这个类的实例作为Thread的构造方法的参数创建一个Thread类的实例
		td.start();						//启动线程
	}

而后,之前我们说过画图板的重绘,在画图板上画图之后如果改变该窗体的大小,那么这些所画的图案都会消失,但是按钮却不会消失。我们为了解决这个问题,了解到这个类中有一个叫做paint的方法,每次改变大小时,按钮这些组件会自动的去调用这个方法,将所有的按钮重新再窗体上画一遍,为了实现重绘,我们重写了paint方法,将所有画的图案也保存到一个数组队列中,在paint方法中进行绘制。在这里也是一样,我们是通过画填充圆当做小球,我们也将画小球的这个过程放到paint方法中,使之可以进行重绘,(可能会有问题,上面好像有说这个窗体设置了不能改变大小,为什么还要重绘?首先,可能会进行最小化操作,其次,当我们初具雏形的时候就会发现小球闪烁,而且之后我们要实现键盘控制小球暂停开始以及停止重新开始等功能,所以我们会使用到次画布,那么要用到次画布就得用上重绘了~)

public void paint(Graphics g) {
//		 super.paint(g);   	//没有使用次画布之前需要这句代码调用父类的paint方法
//		 if(img==null){		    //如果加上这条语句就只有一个次画布每次重绘将会重新画这一个画布不能达到重绘开始绘制小球的目的
		img = this.createImage(this.getWidth(), this.getHeight());	    //将这块画布的大小设置与窗体的高和宽一致
//		 }
		g2d = (Graphics2D) img.getGraphics();    		//取到次画布上面的画笔,并且转化为Graphics2D,具有更多功能。
		g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
				RenderingHints.VALUE_ANTIALIAS_ON);     //开启画笔抗锯齿,通俗来说使得画出来的小球更加圆润光滑
		for (int i = 0; i < ball.size(); i++) {
			Ball myball = (Ball) ball.get(i);    	//取到每一个小球将其用myball这个对象来存储需要用到强转为Ball类
			myball.clearBall(g2d, this);		    //清除小球
			myball.moveBall();    					//移动小球
			myball.collision(g2d, this, ball);	    //小球碰撞
			myball.drawBall(g2d);				    //画小球
		}
		g.drawImage(img, 0, 0, this);	//将次画布画到这个窗体上
	}

到这里,我们这个类就写好了;

我们希望小球有颜色大小等等属性,在窗体中运动的小球就是一个个对象,而且为了使用数组队列,我们就将这些对象抽象成一个Ball类,用来书写小球的属性和方法。

2.独立Ball小球类的书写。

首先要给小球的属性是横纵坐标、大小、水平速度、竖直速度以及颜色。

接下来去设置所有属性的get和set方法,然后再生成这个Ball小球的构造方法,用于之后实例化对象。

import java.awt.Color;
import java.awt.Graphics;
import java.util.ArrayList;
import javax.swing.JFrame;
/**
 * 包含小球的属性和方法的Ball类
 * @author Elaine
 *
 */
public class Ball{
	private int x, y, size, movex, movey;
	private Color color;
	/**
	 * 
	 * @param x
	 *            小球的横坐标
	 * @param y
	 *            小球的纵坐标
	 * @param size
	 *            小球的大小直径
	 * @param movex
	 *            水平速度
	 * @param movey
	 *            竖直速度
	 * @param color
	 *            小球的颜色
	 */
	public Ball(int x, int y, int size, int movex, int movey, Color color) {
		super();
		this.x = x;
		this.y = y;
		this.size = size;
		this.movex = movex;
		this.movey = movey;
		this.color = color;
	}

	public int getX() {
		return x;
	}

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

	public int getY() {
		return y;
	}

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

	public int getSize() {
		return size;
	}

	public void setSize(int size) {
		this.size = size;
	}

	public int getMovex() {
		return movex;
	}

	public void setMovex(int movex) {
		this.movex = movex;
	}

	public int getMovey() {
		return movey;
	}

	public void setMovey(int movey) {
		this.movey = movey;
	}

	public Color getColor() {
		return color;
	}

	public void setColor(Color color) {
		this.color = color;
	}

设置好之后,我们就要来写小球具体如何产生、运动的方法了,这些方法都写在这个类中,便于之后数组队列中的对象直接进行调用。

①画小球:将画笔作为参数,设置颜色,画填充圆;

②移动小球:之前还在想小球的速度要怎么体现,加上线程以后我们就可以配合小球的坐标移动来体现速度,所以只需要改变小球的横纵坐标即可,横纵坐标分别加上两个方向上的速度值。

③清除小球:也是利用画的方法,将画笔和窗体两个参数传入,将颜色设置为背景色,在之前小球的位置画一个圆,从视觉上就可以体现为小球在运动。(具体画圆的参数还要考量一下)

④碰撞函数:这也是最复杂的一个问题,以交换速度来考虑。

如果左右碰壁,根据小球的横坐标设置好条件,将其水平方向的速度反向;

如果上下碰壁,根据小球的纵坐标设置好条件,将其竖直方向的速度反向;

如果两个小球碰撞,则考虑遍历整个数组队列,小球与不是自己的小球进行比较,通过距离来判断。一开始会考虑到画圆的时候的坐标是左上角的地方,但是根据数学知识这个数据是可以直接用来计算圆心距的,所以求出两个小球的圆心距,与他们的半径之和相比(事实上小于size即可,因为每个小球的大小时一样的,两个半径加起来就是直径),如果小于,也就是两个小球碰撞了,这里的方法就因人而异了,我之前设置的是两个小球互相交换速度,后来我又改成了两个小球相撞后朝着与自己原来运动方向相反的方向运动,(会出现很揪心的一段哈哈),其实也可以设置两个小球就消失好了哈哈。

/**
	 * 画小球的方法
	 * 
	 * @param g是画笔
	 */
	public void drawBall(Graphics g) {
		g.setColor(color);		//设置画笔颜色
		g.fillOval(x, y, size, size);	//画填充圆
	}

	/**
	 * 移动小球的方法
	 */
	public void moveBall() {
		x += movex;		//横坐标加上水平速度
		y += movey;		//纵坐标加上竖直速度
	}

	/**
	 * 清除小球的方法
	 * 
	 * @param g
	 *            画笔
	 * @param bm
	 *            窗体
	 */
	public void clearBall(Graphics g, BallMain bm) {
		g.setColor(bm.getBackground());		//设置颜色为背景色
		g.fillOval(x - movex, y - movey, size,size );		//在合适的位置画圆使得看起来小球是在运动
	}

	/**
	 * 碰撞函数
	 * 
	 * @param g
	 *            画笔
	 * @param bm
	 *            窗体
	 * @param ball
	 *            小球的数组队列
	 */
	public void collision(Graphics g, BallMain bm, ArrayList ball) {
		// 左右碰壁 小球的最右边的坐标大于窗体的最右边的坐标或者最左边小于窗体的最左边的坐标
		if (x + size >= bm.getWidth() || x <= 0) {
			movex = -movex;// x速度反向
		}
		// 上下碰壁 因为窗体有标题栏所以上方在30处就会碰壁,或者小球的最下方大于窗体的最下边的坐标
		if (y <= 30 || y + size >= bm.getHeight()) {
			movey = -movey;// y速度反向
		}
		// 小球和小球碰撞处理,比较圆心之间距离,遍历数组队列,找出当前球与其他球的圆心距离
		for (int i = 0; i < ball.size(); i++) {
			Ball myball = (Ball) ball.get(i);	//取得小球
			if (myball != this) {				//不是与自己相撞
			    double xx = Math.abs(this.x - myball.x);	//求横坐标之差
				double yy = Math.abs(this.y - myball.y);	//求纵坐标之差
				double xy = Math.sqrt(xx * xx + yy * yy);	//两球圆心的距离
//				int tempx = 0;
//				int tempy = 0;
				if (xy <= (this.size / 2 + myball.size / 2)) {// 距离与相切的圆心距相比较判断两个小球是否相撞
//					tempx = this.movex;
//					tempy = this.movey;
//					this.movex = myball.movex;    // 相撞小球交换速度
//					this.movey = myball.movey;
//					myball.movey = tempx;
//					myball.movex = tempy;
					this.movex=-this.movex;
					this.movey=-this.movey;       //相撞小球往之前运动的相反方向运动
					myball.movey = -myball.movey;
					myball.movex = -myball.movex;
				                             }
                         			}
		                   }
            	}
      }

3.最后我们来写事件处理类,也就是处理监听问题。

我们需要通过鼠标和键盘来控制,毫无疑问要继承两个类,但是!只能继承一个类是我们都知道的,而且这个小项目呢我们也是分两次完成的,第一次完成的时候只写了鼠标的操作,所以已经写上了继承这个类,不能再继承其他类了,但是我们还可以实现接口来达到继承的目的呀,所以这个类还实现了(监听键盘)以及(线程)两个接口。

①写上该类的构造函数,接收事件源对象;

②继承了类,要求监听到鼠标按下这个动作,于是选择重写其中的按下方法;

③实现了接口,实现其中的抽象方法,这个接口只有一个run抽象方法,于是就编写run方法;

④实现了接口,实现其中的抽象方法,其中我们只需要用到键盘按下这个方法,但是这个接口中还有两个抽象方法,我们将他们复制过来,将关键字去掉,将分号去掉加上中括号即可,需要用到的方法我们就在中括号中书写具体内容。

import java.awt.Color;
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.ArrayList;
import java.util.Random;
import javax.swing.JFrame;
/**
 * 实现事件处理类继承鼠标适配器实现线程键盘监听的接口
 * @author Elaine
 *
 */
public class BallListener extends MouseAdapter implements Runnable, KeyListener {
	private BallMain bm;	//窗体属性
	private Graphics g;		//画笔属性
	private volatile boolean pauseFlag = true, stopFlag = true; //暂停和停止的标记属性volatile是作为指令关键字确保指令不会因编译器的优化而被忽略每次直接读值
	private Random rand = new Random();		//随机数属性为了产生随机颜色
	private ArrayList ball = new ArrayList(); 	//储存小球的数组队列属性
	/**
	 * 构造函数接收事件源对象
	 * @param bm 窗体
	 * @param ball 小球的数组队列
	 */
	public BallListener(BallMain bm, ArrayList ball) {
		this.bm = bm;
		this.ball = ball;
	}
	/**
	 * 重写鼠标按下的方法
	 */
	public void mousePressed(MouseEvent e) {
		int x = e.getX();	//获取按下时的横坐标
		int y = e.getY();	//获取按下时的纵坐标
		//再按下的同时实例化一个Ball小球类的对象设置好坐标大小速度和颜色这些设置与小球类的构造函数相对应
		Ball myball = new Ball(x, y, 30, -5, 5, new Color(rand.nextInt(255),rand.nextInt(255),rand.nextInt(255)));
		ball.add(myball);	//将这个对象添加到小球的数组队列中
	}
	/**
	 * 重写线程的run方法
	 */
	public void run() {
		while (stopFlag) {	//停止标记为真表示的是未按下停止键时进入循环
			if (pauseFlag) {	//如果暂停标记为真
				bm.repaint();	//重绘联系前面也就是启用一块新画布
				try {
					Thread.sleep(50);	//暂停线程需要处理异常
				} catch (InterruptedException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
			}
		}
	}
	/**
	 * 重写键盘按下的方法
	 */
	public void keyPressed(KeyEvent e) {
		int code = e.getKeyCode();
//		System.out.println("<<<<<"+code);  //这是为了查看具体键盘上每一个按键对应的值是多少
		//运用switch语句对不同的code执行不同的操作
		switch(code){
		case 32://表示按下的是空格键
			pauseFlag = !pauseFlag; //空格键控制暂停和开始所以每次直接对标记量取非即可这样第一次按下是暂停第二次按下就是恢复
			break;
		case 27://表示按下的是Esc键
			stopFlag = false;	//Esc键控制停止按下后将其置为false这里可能会理解上有一点点绕
			break;
		case 82://表示按下的是R键代表reset
			ball.clear();   //首先是清除之前画的小球清除之前的画布
			bm.repaint();	//重绘窗体启用新画布
			stopFlag=true;	//既然重新开始标记也应该恢复默认将停止标记置为真
			pauseFlag=true;	//将暂停标记置为真
			new Thread(this).start();	//重新建一个线程并且启动
			break;
		}
	}

	public void keyTyped(KeyEvent e) {
	}

	public void keyReleased(KeyEvent e) {
	}
}

好啦,到这里整个代码的编写就完成了,现在的我肩膀好疼呀,不过把整个项目的经过全部再过了一遍表示自己也学到了很多新的东西呢,不理解的东西好像也有了新的理解的。了解自己的学习方式,一定是要完全弄懂以后才可以讲得清楚。

把效果图上传一下,本来win10有自带的xbox录屏工具的,但是我的微软账号没办法打开画笔小球,只能先用个网上的录屏器先录一段看看效果~

我今晚跳舞的时候就在想,这么好的天气不然晚上修个仙把这两天的内容搞清楚,因为也知道自己可以自律到早上起床,但是不能保证自己头脑清醒,所以就趁深夜把这个问题搞清楚,不过以后还是要合理安排时间,少修仙,毕竟身体是革命的本钱嘛,今天还在和同伴说最近眼睛看东西都模糊了呢。

下面是要提醒自己的一段话:

之前有人和我说,觉得我是努力又善良的人,没有谁会不喜欢努力又善良的人,我就自以为是沾沾自喜了一段时间,后来发现努力与善良之后还得要有成绩,要有成果来证明你的努力。所以,千万不要觉得自己做了某一件事,就觉得自己好像很努力,前段时间流行的“社会社会”这个梗也不是空穴来风,社会就是这样,看成绩,看结果。

这些年我一直提醒自己一件事情,千万不要自己感动自己。大部分人看似的努力,不过是愚蠢导致的,什么熬夜看书到天亮,连续几天只睡几个小时,多久没放假了,如果这些东西也指的夸耀,那么富士康流水线上的任何一个人都比你努力多了。人难免天生有自怜的情绪,唯有时刻保持清醒,才能看清真正的价值在哪里。

--知乎

这段话很久前就看到过,今天又看到,记下来。

最后说要给一个惊喜,搞不好是惊吓哈哈哈哈。

是因为今天啊,热了20天的长沙终于下了一场雨,跳舞回来的路上竟然感受到了凉风,今天的舞蹈考核好像效果还不错,就想着放一小段(真的是一小段)出来臭美一下。

咦大家随便看看就好,我也还有很大进步空间。

现在是真的结束啦,好像也就2个多小时吧,希望自己以后学习呀,学是一方面,总结其实更重要噢!

晚安,下了雨的长沙。

猜你喜欢
最新游戏更多
热门专题更多
最新资讯更多