2.7 线程相关类
上一节已为读者详细介绍了绘制相关类,在读者进一步了解本案例的基础上,在这一节将对线程相关类的开发进行详细的介绍。前面已经完成了对水族馆背景及水族馆中的鱼、鱼群、鱼食和气泡的绘制开发,只绘制出模型是不够的,还需要让它们动起来,从而产生更加真实的效果。
该壁纸开发中开启了多个线程,使得本案例中的场景更加活灵活现,更加逼真。线程相关类主要包括气泡移动线程类、群鱼游动线程类、鱼群游动线程类、鱼食移动线程类和吸引力线程类,下面将对线程相关类的开发进行详细的介绍。
2.7.1 气泡移动线程类——BubbleThread
前面已经完成了对3D水族馆中气泡的开发,但只开发出一个模型是远远不够的,这就需要让气泡移动起来,并可以让多处位置连续不断地冒出气泡来,这样场景才会更加逼真。这就是本类的作用,本类开启了一个线程定时移动气泡,并在不同位置冒出。其具体代码如下。
代码位置:见随书光盘源代码/第2章/MyWallPaper/src/wyf/lxg/bubble/目录下的BubbleThread.java。
1 package wyf.lxg.bubble; //声明包名
2 ......//此处省略部分类和包的引入代码,读者可自行查阅随书光盘中的源代码
3 public class BubbleThread extends Thread {
4 float x; //气泡左右移动标志位
5 float y; //气泡位置标志位
6 boolean flag = true; //标志位
7 BubbleControl Bcl; //气泡的控制类
8 public BubbleThread(BubbleControl Bcl){ //构造方法
9 this.Bcl=Bcl; //获取气泡控制类对象
10 }
11 public void run(){
12 while (flag) { // 循环定时移动气泡
13 try {
14 for(int i=0;i<Bcl.BubbleSingle.size();i++){ //遍历气泡列表
15 if((i+3)%3==0){ //将气泡的总数量,切分为3份
16 if(((i+3)/3)%2==0){ //进行奇偶判断,为气泡的x、z轴方向偏移做准备
17 y=1; //偶数位气泡标志位
18 }else{
19 y=-1; //奇数位气泡标志位
20 }
21 x=1; //第一处气泡位置队列
22 }
23 ......//该处省略了与第一个if语句中相似的两个if语句,读者可自行查阅随书光盘中的源代码
24 Bcl.BubbleSingle.get(i).bubbleMove(x,y); //执行气泡移动方法
25 }} catch (Exception e) { //进行异常处理
26 e.printStackTrace(); //打印异常
27 }
28 try {
29 Thread.sleep(10); //线程休眠10ms
30 } catch (Exception e) { //异常处理
31 e.printStackTrace(); //打印异常
32 }}}}
第1~10行为声明相关成员变量,通过构造器,获取BubbleControl的引用,为后面线程中调用BubbleControl类中的bubbleMove方法做准备。其中省略了部分类和包的引入代码,请读者自行查阅随书光盘中的源代码
第11~32行为该类中气泡移动线程方法,在该方法中遍历气泡列表BubbleSingle。为了能够在场景中出现3处气泡,所以将气泡队列切分成3队,并根据每个队列中气泡的奇偶性来给出y的值,以作为气泡x、z轴移动的扰动变量,并调用气泡移动方法bubbleMove,然后休眠10ms。
2.7.2 群鱼游动线程类——FishGoThread
上小节介绍了气泡移动的线程BubbleThread,本小节将为读者介绍群鱼游动的线程。在线程中有关于群鱼之间的受力算法,以防止两条鱼互穿;还有关于群鱼碰到鱼群的受力变化,以及群鱼和墙壁碰撞时群鱼的受力如何变化。其具体代码如下所示。
代码位置:见随书光盘源代码/第2章/MyWallPaper/src/wyf/lxg/fish/目录下的FishGoThread.java。
1 package wyf.lxg.fish; //声明包名
2 ......//此处省略部分类和包的引入代码,读者可自行查阅随书光盘中的源代码
3 public class FishGoThread extends Thread {
4 ......//该处省略了部分变量与构造方法代码,读者可自行查阅随书光盘中的源代码
5 public void run() { //定时运动所有群鱼的线程
6 while (flag) { //循环定时移动鱼类
7 try { //动态地修改鱼受到的力的大小
8 for (int i = 0; i < fishControl.fishAl.size(); i++) {
//计算鱼群对该鱼产生力的大小
9 Vector Vwall = null;
10 inside: for (int j=0; j < fishControl.fishAl.size();j++){
11 Vector V3 = null;
12 if (i == j) { continue inside;}//自己不能对自己产生力
13 V3 = fishControl.fishAl.get(i).position.cut(
//向量减法得到力改变方向
14 fishControl.fishAl.get(j).position,Constant.
MinDistances);
15 V3.getforce(fishControl.fishAl.get(i).weight);
//力与质量的比
16 fishControl.fishAl.get(i).force.plus(V3);
//两条鱼之间的力
17 }
18 if (fishControl.My.fishSchool != null&& fishControl.My.
fishSchool.fishSchool
19 .size() != 0) {
20 Vector V4 = fishControl.fishAl.get(i).position.
cut(//力的方向
21 fishControl.My.fishSchool.fishSchool.get(0).
position, Constant.MinDistances);
22 V4.getforce(fishControl.fishAl.get(i).weight);
23 fishControl.fishAl.get(i).force.plus(V4);
//两条鱼之间的力
24 }
25 Vwall = new Vector(0, 0, 0);
26 if (fishControl.fishAl.get(i).position.x <= ) {
//判断鱼和左墙壁的碰撞
27 Vwall.x = ; //撞上之后产生力的作用
28 }
29 if (fishControl.fishAl.get(i).position.x > ) {
//判断鱼和右墙壁的碰撞
30 Vwall.x = ; //撞上之后产生力的作用
31 }
32 if (fishControl.fishAl.get(i).position.y >= ) {
//判断鱼和上墙壁的碰撞
33 Vwall.y = ; //撞上之后产生力的作用
34 }
35 if (fishControl.fishAl.get(i).position.y <= -3) {
//判断鱼和下墙壁的碰撞
36 Vwall.y = ; //撞上之后产生力的作用
37 if(fishControl.fishAl.get(i).position.y <= -4){
//鱼和下墙壁太近
38 Vwall.y =; //鱼所受到反向力加倍
39 }}
40 if (fishControl.fishAl.get(i).position.z < ) {
//判断鱼和后墙壁的碰撞
41 Vwall.z = ; //撞上之后产生力的作用
42 }
43 if (fishControl.fishAl.get(i).position.z > 2) {
//判断鱼和前墙壁的碰撞
44 Vwall.z = ; //撞上之后产生力的作用
45 }
46 Vwall.y -= 0.000009;
47 fishControl.fishAl.get(i).force.plus(Vwall);//二力相加
48 }
49 for (int i = 0; i < fishControl.fishAl.size(); i++) {
//定时修改鱼的速度和位移
50 fishControl.fishAl.get(i).fishMove();//调用鱼游动方法的作用
51 }}
52 ......//该处省略了异常处理与线程休眠代码,读者可自行查阅随书光盘中的源代码
53 }}}
第3~24行首先遍历鱼群、群鱼列表,并计算单条鱼所受到的其他鱼的力和鱼群对该鱼的力。当鱼与其他单条鱼或群鱼之间的距离小于阙值后会产生力的作用,这样计算群鱼中的鱼一直游动,并不会与鱼群发生碰撞。
第25~47行是对碰壁检测处理的代码,这里计算了鱼与上、下、左、右、前、后墙壁的检测,然后判断鱼某个方向的位置是否超过了墙壁的范围,如果超过,则墙壁给一个相反方向的力,然后将鱼所受到的力与墙壁给鱼的力相加求出鱼所受到的合力。
第49~50行遍历所有鱼的速度与位移。遍历所有群鱼中的鱼,调用fishMove方法使之动态地修改鱼的速度与位移,并让线程睡眠后刷新群鱼,这样,群鱼在场景中就会一直的由来游去,非常酷炫。
2.7.3 鱼群游动线程类——FishSchoolThread
上一小节为读者介绍了群鱼游动的线程类,本小节将着重介绍鱼群游动的线程类——FishSchoolThread,其中计算了鱼群与群鱼之间的受力,使之不会碰撞,还计算了鱼群中鱼受到从该位置指向相对位置的力,以及鱼群与墙壁碰撞时的受力情况。
(1)首先给出了FishSchoolThread类的整体框架。鱼群中单条鱼受到的向心力以及群鱼碰壁检测等其他算法代码过多,将在后面详细介绍。接下来详细介绍鱼群之间的受力算法,主要是群鱼对鱼群的作用力情况等。具体代码如下所示。
代码位置:见随书光盘源代码/第2章/MyWallPaper/src/wyf/lxg/fishschool/目录下的FishSchoolThread.java。
1 package wyf.lxg.fishschool; //声明包名
2 ......//此处省略部分类和包的引入代码,读者可自行查阅随书光盘中的源代码
3 public class FishschoolThread extends Thread {
4 boolean flag = true; //线程标志位
5 FishSchoolControl fishschools; //鱼群控制类对象
6 float Length; //两条鱼之间的距离
7 public FishschoolThread(FishSchoolControl fishschools) {
8 this.fishschools = fishschools;
9 }
10 public void run() {
11 while (flag) { //循环定时移动鱼类
12 try {
13 //群鱼对鱼群里面的鱼的作用力
14 outside: for (int i = 1; i < fishschools.fishSchool.size(); i++) {
15 for (int j = 0;j < fishschools.Tr.fishControl.fishAl.size(); j++) {
16 if (Length > Constant.SMinDistaces-0.5) {continue outside; }
//判定范围
17 Vector V3 = null;
18 V3=.cut(fishschools.Tr.fishControl.fishAl
19 .get(j).position,Constant.SMinDistaces);//获取力的方向
20 V3.getforce(Constant.WeightScals); //力的缩放比
21 fishschools.fishSchool.get(i).force.plus(V3);//两条鱼之间的力
22 }}
23 Vector Vwall = null;
24 float Cx = fishschools.fishSchool.get(0).position.x;
//第零条鱼的位置
25 float Cy = fishschools.fishSchool.get(0).position.y;
26 float Cz = fishschools.fishSchool.get(0).position.z;
27 int j=1; //鱼群里面3条能动的鱼
28 for(int i=-90;i<=90.;i=i+90){
29 fishschools.fishSchool.get(j).ConstantPosition.x=(float)
30 (Cx+Constant.Radius*Math.cos(i));
31 fishschools.fishSchool.get(j).ConstantPosition.y = Cy;
32 fishschools.fishSchool.get(j).ConstantPosition.z=(float)
33 (Cz+Constant.Radius*Math.sin(i));
34 j++;
35 }
36 ......//该处省略了群鱼需要指向初始位置的力的算法代码,将在下面介绍
37 ......//该处省略群鱼碰壁检测算法代码,将在下面介绍
38 for (int i = 0; i < fishschools.fishSchool.size(); i++) {
39 fishschools.fishSchool.get(i).fishschoolMove();
40 }} catch (Exception e) { //异常处理
41 e.printStackTrace(); //打印异常
42 }try {
43 Thread.sleep(50); //线程休眠
44 } catch (Exception e) { //异常处理
45 e.printStackTrace(); //打印异常
46 }}}}
第4~9行是本类中一些变量的声明还有构造器的初始化。将线程是否开始的标志位设置为true,并获得鱼群控制类对象FishSchoolControl,为下面的算法调用控制类对象中的方法,以及声明两条鱼之间的距离。
第14~35行当鱼群中的某条鱼与鱼群中的距离小于阙值后便对该鱼产生作用力。第一条鱼只受墙壁的力。鱼群中其他3条鱼互相没有力的作用,也没有第一条鱼的力。它们受到从该位置指向相对位置(以鱼群中第一条鱼的位置为中心,以定半径确定的球面上的一个点)的力,并受群鱼的力。
第36~45行为修改鱼群里面所有鱼的速度和位移。调用每条鱼的fishschoolMove方法定时修改鱼群里面鱼的速度和位移。进行异常处理,如果出现异常,则打印出异常。并让线程睡眠50ms后重新刷新鱼群。
(2)上面详细介绍了鱼群碰见群鱼之间的受力算法与鱼群中鱼的受力情况算法。下面将为读者详细介绍(1)中省略的鱼离开鱼群的受到恒力的算法,以及鱼群和墙壁碰撞时的鱼群受力变化的算法。这样鱼群一直是鱼群,不会被冲散。其具体代码如下所示。
代码位置:见随书光盘源代码/第2章/MyWallPaper/src/wyf/lxg/fishschool/目录下的FishSchoolThread.java。
1 // 每条鱼受到从当前位置指向应该所在位置(Home)的力(恒力作用)
2 for (int i = 1; i < fishschools.fishSchool.size(); i++){ //遍历鱼类列表
3 Vector VL = null; //计算恒力的中间变量
4 VL = fishschools.fishSchool.get(i).ConstantPosition
5 .cutGetforce(fishschools.fishSchool.get(i).position);
6 //得到从Position到ConstantPosition的的向量长度
7 Length = VL.Vectormodule(); //计算中间距离
8 if ((Length) >= Constant.SMinDistaces){
9 VL.getforce(Constant.ConstantForceScals / ); //距离远,恒力增加
10 }else if (Length<= 0.3){ //距离<阙值不产生力
11 VL.x = VL.y = VL.z = 0;
12 } else{
13 VL.getforce(Constant.ConstantForceScals);
14 }
15 float MediaLength = fishschools.fishSchool.get(i).force.Vectormodule();
16 if (Math.abs(MediaLength) == 0) {
17 // 把计算得到的恒力赋给恒力ConstantForce.
18 fishschools.fishSchool.get(i).ConstantForce.x = VL.x; //x方向
19 fishschools.fishSchool.get(i).ConstantForce.y = VL.y; //y方向
20 fishschools.fishSchool.get(i).ConstantForce.z = VL.z; //z方向
21 } else {
22 // 把计算得到的恒力赋给恒力ConstantForce.
23 fishschools.fishSchool.get(i).ConstantForce.x = 0;
24 fishschools.fishSchool.get(i).ConstantForce.y = 0;
25 fishschools.fishSchool.get(i).ConstantForce.z = 0;
26 }}
27 //判断鱼和墙壁的碰撞
28 Vwall = new Vector(0, 0, 0);
29 if (fishschools.fishSchool.get(0).position.x <= ){Vwall.x = ;}
//鱼与左墙壁的碰撞
30 if (fishschools.fishSchool.get(0).position.x >){ Vwall.x = ;}
//鱼与右墙壁的碰撞
31 if (fishschools.fishSchool.get(0).position.y >= 7){ Vwall.y = ;}
//鱼与上墙壁的碰撞
32 if (fishschools.fishSchool.get(0).position.y <= ) { Vwall.y = ;}
//鱼与下墙壁的碰撞
33 if (fishschools.fishSchool.get(0).position.z < -15) { Vwall.z = ;}
//鱼与后墙壁的碰撞
34 if (fishschools.fishSchool.get(0).position.z > 3) { Vwall.z = ; }
//鱼与前墙壁的碰撞
35 fishschools.fishSchool.get(0).force.plus(Vwall);
第1~26行给离开鱼群的鱼赋予一个恒力。一旦这条鱼相对脱离了鱼群之后就会受到一个恒力使这条鱼游回鱼群,该条鱼游的距离鱼群越远这个恒力就会越大,从而使鱼群里面的鱼能够快速地回到鱼群,使鱼群一直是鱼群,不会被冲散。
第27~35行是鱼群里面第一条鱼与水族馆中的上墙壁、下墙壁、左墙壁、右墙壁、前墙壁及后墙壁的碰撞检测,碰撞时墙壁会对鱼群里面的第一条鱼产生力的作用,这样鱼群就会一直在鱼缸中游来游去。
2.7.4 鱼食移动线程类——FoodThread
上一小节详细介绍了对鱼群的移动线程控制类,读者已经了解了鱼群的移动方法。本小节将为读者详细介绍鱼食的移动线程类FoodThread,本小节中将着重为读者介绍鱼食的移动方法,以及对鱼食标志位的设置等,具体代码如下。
代码位置:见随书光盘源代码/第2章/MyWallPaper/src/wyf/lxg/fishfood/目录下的FoodThread.java。
1 package wyf.lxg.fishfood; //声明包名
2 ......//此处省略部分类和包的引入代码,读者可自行查阅随书光盘中的源代码
3 public class FoodThread extends Thread { //定时运动食物的线程
4 public boolean flag1 = true; //线程的标志位
5 public boolean Fresit=true; //食物y是否重置的标志位
6 boolean FxMove=true; //移动x方向的标志位
7 public boolean Go=false; //线程里面的算法是否走标志位
8 public SingleFood SingleF; //SingleFood对象的引用
9 public FoodThread(SingleFood singleF){
10 this.SingleF=singleF; //实例化SingleFood对象
11 }
12 public void run(){
13 while (flag1) {
14 try {
15 if(Go){ //如果标志位为true
16 if(FxMove){ //食物晃动的标志位
17 SingleF.mv.Xposition+=Constant.FoodMove_X;
18 FxMove=!FxMove; //标志位置反
19 }else{
20 SingleF.mv.Xposition-=Constant.FoodMove_X;
21 FxMove=!FxMove; //标志位置反
22 }
23 SingleF.Ypositon-=Constant.FoodSpeed; //定时的修改Y坐标
24 }}
25 catch (Exception e) { //异常处理
26 e.printStackTrace(); //打印异常
27 }try {
28 Thread.sleep(100); //线程休眠
29 } catch (Exception e) {
30 e.printStackTrace(); //打印异常
31 }}}}
第4~11行是本类中一些标志位的初始化与本类中的构造。其中一些标志位的初始化有助于读者更加容易地理解本类中的逻辑关系,其构造器是拿到SingleFood类的引用,为后面调用其中鱼食的坐标做准备。
第12~31行是本类中鱼食移动的线程方法,本案例中的鱼食从上到下匀速运动,并且每次计算失误y轴所在的位置之前,会通过增加或减少食物的x、z坐标来使食物产生轻微的晃动效果,从而增加食物的真实感,让线程休眠100ms后刷新鱼食。
2.7.5 吸引力线程类——AttractThread
上一小节中介绍了食物的移动线程。本小节着重介绍鱼食对群鱼的吸引力线程类AttractThread,本案例中群鱼是可以看到鱼食的,但是鱼群看不到鱼食,所以鱼群不会受到食物吸引力的影响。本节将详细介绍这是如何操作的。
(1)下面将为读者重点介绍本类中鱼食对群鱼的吸引力的算法,并定时对每条鱼的吸引力进行刷新。每当鱼食落下的时候,如果某条鱼看到了鱼食,这条鱼就会朝鱼食游去,将鱼食吃掉,这样使百纳水族馆壁纸更加地真实逼真。具体代码如下。
代码位置:见随书光盘源代码/第2章/MyWallPaper/src/wyf/lxg/fishfood/目录下的AttractThread.java。
1 package wyf.lxg.fishfood; //声明包名
2 ......//此处省略部分类和包的引入代码,读者可自行查阅随书光盘中的源代码
3 public class AttractThread extends Thread {
4 ......//该处省略了部分变量与构造方法代码,读者可自行查阅随书光盘中的源代码
5 public void run() {
6 while (Feeding) { //Feeding为永真
7 try {
8 if (Go) { //添加能看到食物的鱼类列表
9 if (Fforcefish) { //每次在点击喂食时要把列表清空
10 fl.clear(); //清空列表
11 Fforcefish = false; //只清空一次
12 }
13 if (fl != null ) {
14 for (int i = 0; i < Sf.mv.fishAl.size(); i++) {//寻找满足条件的鱼
15 if (Sf.mv.fishAl.get(i).position.x > Sf.mv.Xposition
16 && Sf.mv.fishAl.get(i).speed.x < 0) {
17 if(!fl.contains(Sf.mv.fishAl.get(i))){//判断是否满足条件
18 fl.add(Sf.mv.fishAl.get(i)); //添加进列表
19 }}
20 else if (Sf.mv.fishAl.get(i).position.x < Sf.mv.Xposition
21 && Sf.mv.fishAl.get(i).speed.x > 0) {
22 if (!fl.contains(Sf.mv.fishAl.get(i))) {
23 fl.add(Sf.mv.fishAl.get(i)); //添加进列表
24 }}}}
25 if (fl.size() != 0) { //给能看到食物的鱼加力的作用
26 for (int i = 0; i < fl.size(); i++) {
27 Vector VL = null; //计算诱惑力的中间变量
28 Vector Vl2 = null; //食物的位置信息
29 Vl2 = new Vector(Sf.mv.Xposition,
30 Sf.mv.singlefood.Ypositon, Sf.mv.Zposition);
31 VL = Vl2.cutPc(fl.get(i).position); //获取需要的向量
32 Length = VL.Vectormodule(); //吸引力的模长
33 if (Length != 0){VL.ChangeStep(Length);} //将力的大小规格化
34 if (Length <= Constant.FoodFeedDistance || Sf.Ypositon
35 < Constant.FoodPositionMin_Y) { //吃掉或者超过阈值
36 StopAllThread();
37 }
38 VL.getforce(Constant.AttractForceScals); //诱惑力的比例
39 fl.get(i).attractforce.x = VL.x; //给诱惑恒力
40 fl.get(i).attractforce.y = VL.y;
41 fl.get(i).attractforce.z = VL.z;
42 }}}
43 if (Sf.Ypositon < Constant.FoodPositionMin_Y) {
44 StopAllThread(); //调用方法
45 }}
46 ......//该处省略了异常处理与线程休眠代码,读者可自行查阅随书光盘中的源代码
47 }}
48 ......//该处省略了StopAllThread()的 方法,将在下面介绍
49 }
第4行为省略掉的部分变量与构造方法代码。此代码中部分变量主要是设置线程标志位、设置清空标志位(每次喂食之前会清空列表f1)、是否计算食物吸引力的标志位。并创建受到食物吸引力的鱼列表。
第5~24行首先判断是否需要喂食。开启喂食后寻找能看到鱼食的鱼,每喂食一次清空受到吸引力的鱼列表f1,然后在喂食的时候重新寻找满足条件的鱼,如果满足条件把该条鱼添加到受到吸引力的鱼列表f1中。
第25~45行是开始喂食后,计算f1里面鱼受到食物吸引力的算法。能看到鱼食的鱼受到一个由该条鱼当前位置指向食物的吸引力的作用。这样当开始喂食时,一条鱼看到鱼食会自动地向鱼食游去,并将鱼食吃掉。
(2)上面介绍了当开始喂食时,如何寻找能够看到鱼食的鱼的算法,并介绍了如何操作当鱼看到鱼食后的游动问题。下面将为读者介绍如果鱼食被吃掉或者鱼食位置超过地面后,如何对鱼食进行操作的方法StopAllThread()。
代码位置:见随书光盘源代码/第2章/MyWallPaper/src/wyf/lxg/fishfood/目录下的AttractThread.java。
1 public void StopAllThread() {
2 Sf.Ypositon = Constant.FoodPositionMax_Y; //重置SingleY
3 this.Fforcefish = true; //清空受到吸引力的鱼列表
4 this.Go = false; //吸引力算法的标志位
5 Sf.Ft.Go = false; //食物移动的标志位
6 Constant.isFeed = true; //喂食的标志位
7 Sf.mv.Fooddraw = false; //绘制的标志位
8 }
第1~8行为StopAllThread方法,若鱼食位置超过地面,或者鱼食被鱼吃掉后,就会调用此方法,把鱼食移动线程里面的计算标志位和计算群鱼是否受到的食物吸引力标志位变为false,同时点击喂食的标志位设置为true,从而能点击屏幕在此喂食。