智能无人机平台V4
V3版本回顾
在V3版本中,实现了无人机的自动索敌追踪、并在扫描到入侵者后将信息“广播”给其他无人机,其他巡逻状态的无人机可以协同该无人机进行“围剿”行为。
V4版本提升
- 为入侵者加入了可视化的血条,便于玩家更直观的看到入侵者血量减少的过程
- 加入了“任务系统”,现在玩家可以通过点击守卫区中的位置来指挥最近的巡逻状态无人机前往该位置
- 将无人机的move方法内部的庞大代码分成几个小的方法,并通过不同的限定条件来调用不同的方法。同时为了便于后期功能的添加,现在将无人机移动方案的限定条件改为由状态码的不同来决定无人机的移动方式。
V4版本的具体实现
可视化血条的添加
- 思路:只需要在入侵者的正上方画出一个矩形边框和一个实心矩形即可,通过改变实心矩形的宽度来达到显示血量的效果
具体实现
Color color3 =newColor(0,0,0); g.setColor(color3); g.drawRect(x, y-5, size,6);Color color4 =newColor(124,244,21); g.setColor(color4); g.fillRect(x+1,y-4,(int)(blood/200.0*(size-1)),5);任务系统的添加
- 思路及代码:
在上个版本中,无人机的move方法过于庞大且判定条件没有统一化,导致后续再添加其他功能时非常麻烦,在V4版本中我改进了move方法,让move方法通过状态码来调用对应的移动方法来完成操作。代码实现如下
publicvoidmove(){if(state ==2){moveToTask();}elseif(state ==1){//先判断目标是否在守卫区外,若在守卫区外则不在追逐if(target!=null){if(target.x+target.size/2<200||target.x+target.size/2>900||target.y+target.size/2<100||target.y+target.size/2>600){ state=0; target=null;}else{moveToHunt();}}else{ state=0;}}elseif(state ==0){moveToFind();}}publicvoidmoveToTask(){int dx = targetTask.x+targetTask.size/2-(x+scanSize/2);int dy = targetTask.y+targetTask.size/2-(y+scanSize/2);double distance =Math.sqrt(dx*dx+dy*dy);if(distance>20){ huntingSpeedx=(int)(dx/distance*huntingSpeed); huntingSpeedy=(int)(dy/distance*huntingSpeed);}else{ huntingSpeedx=0; huntingSpeedy=0;System.out.println("完成任务"); state=0;//完成任务 targetTask.state=2; targetTask=null;//任务完成后清除任务目标}//硬限位,防止出界limit();}publicvoidmoveToHunt(){int dx = target.x+target.size/2-(x+scanSize/2);int dy = target.y+target.size/2-(y+scanSize/2);double distance =Math.sqrt(dx*dx+dy*dy);if(distance>20){ huntingSpeedx=(int)(dx/distance*huntingSpeed); huntingSpeedy=(int)(dy/distance*huntingSpeed);}else{ huntingSpeedx=0; huntingSpeedy=0;}//硬限位,防止出界limit();}publicvoidmoveToFind(){ x += speedx; y += speedy;if(x <200|| x >900){ speedx =-speedx;}if(y <100|| y >600){ speedy =-speedy;}}publicvoidlimit(){int newx = x+huntingSpeedx;int newy = y+huntingSpeedy; x =Math.max(200,Math.min(1000- scanSize, newx)); y =Math.max(100,Math.min(700- scanSize, newy));}在Drone类中构建moveToTask方法,让最近的无人机在接到任务后计算速度并前往,逻辑与追踪入侵者类似,并在无人机中心与任务点距离小于20时,将状态设置为任务完成,并清空任务目标。代码实现如下
publicvoidmoveToTask(){int dx = targetTask.x+targetTask.size/2-(x+scanSize/2);int dy = targetTask.y+targetTask.size/2-(y+scanSize/2);double distance =Math.sqrt(dx*dx+dy*dy);if(distance>20){ huntingSpeedx=(int)(dx/distance*huntingSpeed); huntingSpeedy=(int)(dy/distance*huntingSpeed);}else{ huntingSpeedx=0; huntingSpeedy=0;System.out.println("完成任务"); state=0;//完成任务 targetTask.state=2; targetTask=null;//任务完成后清除任务目标}//硬限位,防止出界int newx = x+huntingSpeedx;int newy = y+huntingSpeedy; x =Math.max(200,Math.min(1000- scanSize, newx)); y =Math.max(100,Math.min(700- scanSize, newy));}无人机move方法的优化
在DroneThread线程中画出任务点,若任务完成,则移除任务对象且不进行绘制。具体代码实现如下
if(tasks !=null){for(int i =0; i < tasks.size(); i++){Task task = tasks.get(i);if(task.state==2){ tasks.remove(i);}else{ task.draw(bg);}}}创建任务线程类TaskThread,在类中重写run方法。在run方法中遍历无人机与任务数组,将未被分配的任务分配给距离最近且处于巡逻状态的无人机,任务被分配后将任务的状态设置为已被领取,若此时没有空闲的无人机,则输出“没有可指挥的无人机”来提示玩家。代码实现如下
packagecom.nwu.by0129.droneV4;importjava.util.ArrayList;publicclassTaskThreadextendsThread{ArrayList<Task> tasks;ArrayList<Drone> drones;@Overridepublicvoidrun(){for(;;){//System.out.println("进入TaskThread");if(tasks !=null){if(drones !=null){for(int i = tasks.size()-1; i >=0; i--){Task task = tasks.get(i);if(task.state !=1){int nearestIndex =-1;double minDis =1000.0;for(int j = drones.size()-1; j >=0; j--){Drone drone = drones.get(j);//找到没有领取任务的无人机(只有巡逻状态的无人机可以接任务)if(drone.state ==0){//计算距离并找出最近的double currentDis =caculateDis(task, drone);if(currentDis <= minDis){ minDis = currentDis; nearestIndex = j;//把最近的无人机编号取出来}}}if(nearestIndex !=-1){ drones.get(nearestIndex).state =2;//无人机状态设为正在执行任务 drones.get(nearestIndex).targetTask = task;System.out.println("任务被领取"); task.state =1;//任务状态设为已被领取}else{System.out.println("没有可指挥的无人机"); task.state =0;}}}}}try{Thread.sleep(16);}catch(InterruptedException e){thrownewRuntimeException(e);}}}doublecaculateDis(Task task,Drone drone){double dis =Math.sqrt((task.x - drone.x)*(task.x - drone.x)+(task.y - drone.y)*(task.y - drone.y)*1.0);return dis;}}创建鼠标监听器,并在DroneListener类中加入鼠标监听器的相关内容。重写mousePressed方法,将鼠标点击的坐标作为参数构建task对象,使用共享内存的思路,并存入tasks动态数组,便于其他无人机领取任务。代码实现如下
publicvoidmousePressed(MouseEvent e){int x=e.getX();int y=e.getY();Task task =newTask(0,x,y);//对任务点进行判断,不得超出守卫区的范围if(!(task.x<200+ task.size/2+50||task.x>1000- task.size/2-50||task.y<100+ task.size+50||task.y>700-task.size-50)){ tasks.add(task);}}创建Task类,将玩家发布的任务封装起来,类中包括该任务点的坐标、状态属性以及可视化的绘制方法。代码实现如下
packagecom.nwu.by0129.droneV4;importjava.awt.*;publicclassTask{int x;int y;int size=10;int state;//0未被领取的任务;1被领取的任务;2已完成的任务publicTask(int state,int x,int y){this.state = state;this.x = x;this.y = y;}publicvoiddraw(Graphics g){ g.setColor(Color.yellow); g.fillRect(x-size/2,y-size/2,size,size);}}V4版本完整代码如下
packagecom.nwu.by0129.droneV4;importjava.awt.*;importjava.awt.image.BufferedImage;publicclassDrone{int x;int y;int stateSize =16;int scanSize =100;int size =30;int state =0;// 0巡逻中;1追踪中;2执行任务中int speedx;int speedy;int huntingSpeedx;int huntingSpeedy;int rSquare=scanSize*scanSize/4;int huntingSpeed =4;//设置追逐速度Intruder target =null;Task targetTask =null;publicDrone(int x,int y,int speedx,int speedy){this.x = x;this.y = y;this.speedx = speedx;this.speedy = speedy;}publicvoiddrawDrone(Graphics g){Color color1 =newColor(52,146,241,75); g.setColor(color1); g.fillOval(x, y, scanSize, scanSize);Color color2 =newColor(111,200,34); g.setColor(color2); g.fillOval(x +35, y +35, size, size);Color color3 =newColor(243,82,82);if(state ==0){ g.setColor(color3); g.fillOval(x +42, y +42, stateSize, stateSize);}elseif(state ==1||state ==2){Color color4 =newColor(5,25,246); g.setColor(color4); g.fillOval(x +42, y +42, stateSize, stateSize);}}publicvoidmove(){if(state ==2){moveToTask();}elseif(state ==1){//先判断目标是否在守卫区外,若在守卫区外则不在追逐if(target!=null){if(target.x+target.size/2<200||target.x+target.size/2>900||target.y+target.size/2<100||target.y+target.size/2>600){ state=0; target=null;}else{moveToHunt();}}else{ state=0;}}elseif(state ==0){moveToFind();}}publicScanResultscan(int x,int y,int size,BufferedImage img){int distanceSquare;for(int i = x; i < x + size; i++){for(int j = y; j < y + size; j++){int roundx=x + scanSize /2;int roundy=y + scanSize /2; distanceSquare =(roundx - i)*(roundx - i)+(roundy - j)*(roundy - j);if(distanceSquare < rSquare){try{int colorNum = img.getRGB(i,j);Color color =newColor(colorNum);Color targetColor1 =newColor(213,15,15);Color targetColor2 =newColor(0,0,0);if(isIntruderColor(color,targetColor1,25)||isIntruderColor(color,targetColor2,30)){//System.out.println("扫描到入侵者");ScanResult sr=newScanResult(i,j);return sr ;}}catch(Exception e){// 处理超出图像范围的情况continue;}}}}returnnull;}//颜色判断方法publicboolean isIntruderColor (Color c1,Color c2,int tolerance){returnMath.abs(c1.getRed()-c2.getRed())<= tolerance &&Math.abs(c1.getGreen()-c2.getGreen())<= tolerance &&Math.abs(c1.getBlue()-c2.getBlue())<= tolerance;}publicvoidmoveToTask(){int dx = targetTask.x+targetTask.size/2-(x+scanSize/2);int dy = targetTask.y+targetTask.size/2-(y+scanSize/2);double distance =Math.sqrt(dx*dx+dy*dy);if(distance>20){ huntingSpeedx=(int)(dx/distance*huntingSpeed); huntingSpeedy=(int)(dy/distance*huntingSpeed);}else{ huntingSpeedx=0; huntingSpeedy=0;System.out.println("完成任务"); state=0;//完成任务 targetTask.state=2; targetTask=null;//任务完成后清除任务目标}//硬限位,防止出界limit();}publicvoidmoveToHunt(){int dx = target.x+target.size/2-(x+scanSize/2);int dy = target.y+target.size/2-(y+scanSize/2);double distance =Math.sqrt(dx*dx+dy*dy);if(distance>20){ huntingSpeedx=(int)(dx/distance*huntingSpeed); huntingSpeedy=(int)(dy/distance*huntingSpeed);}else{ huntingSpeedx=0; huntingSpeedy=0;}//硬限位,防止出界limit();}publicvoidmoveToFind(){ x += speedx; y += speedy;if(x <200|| x >900){ speedx =-speedx;}if(y <100|| y >600){ speedy =-speedy;}}publicvoidlimit(){int newx = x+huntingSpeedx;int newy = y+huntingSpeedy; x =Math.max(200,Math.min(1000- scanSize, newx)); y =Math.max(100,Math.min(700- scanSize, newy));}}packagecom.nwu.by0129.droneV4;importjava.awt.event.ActionEvent;importjava.awt.event.ActionListener;importjava.awt.event.MouseEvent;importjava.awt.event.MouseListener;importjava.util.ArrayList;importjava.util.Random;publicclassDroneListenerimplementsActionListener,MouseListener{Random rand =newRandom();ArrayList<Drone> drones;ArrayList<Intruder> intruders;ArrayList<Task> tasks ;@OverridepublicvoidactionPerformed(ActionEvent e){if(e.getActionCommand().equals("生产无人机")){int x = rand.nextInt(700)+200;int y = rand.nextInt(500)+100;int speedx = rand.nextInt(5)-2;while(speedx==0){ speedx = rand.nextInt(5)-2;}int speedy = rand.nextInt(5)-2;while(speedy==0){ speedy = rand.nextInt(5)-2;}Drone drone =newDrone(x,y,speedx,speedy); drones.add(drone);}elseif(e.getActionCommand().equals("生产入侵者")){int x = rand.nextInt(1160);int y = rand.nextInt(760);while(true){if(y<60||y>700||x<160||x>1000){break;} x = rand.nextInt(1160); y = rand.nextInt(760);}int speedx = rand.nextInt(5)-2;while(speedx==0){ speedx = rand.nextInt(5)-2;}int speedy = rand.nextInt(5)-2;while(speedy==0){ speedy = rand.nextInt(5)-2;}Intruder intruder =newIntruder(x,y,speedx,speedy); intruders.add(intruder);}}@OverridepublicvoidmouseClicked(MouseEvent e){}@OverridepublicvoidmousePressed(MouseEvent e){int x=e.getX();int y=e.getY();Task task =newTask(0,x,y);//对任务点进行判断if(!(task.x<200+ task.size/2+50||task.x>1000- task.size/2-50||task.y<100+ task.size+50||task.y>700-task.size-50)){ tasks.add(task);}/*for (int i = 0; i < tasks.size(); i++) { System.out.println(tasks.get(i).x); }*///System.out.println(tasks.size());//System.out.println("task添加了");}@OverridepublicvoidmouseReleased(MouseEvent e){}@OverridepublicvoidmouseEntered(MouseEvent e){}@OverridepublicvoidmouseExited(MouseEvent e){}}packagecom.nwu.by0129.droneV4;importjava.awt.*;importjava.awt.image.BufferedImage;importjava.util.ArrayList;publicclassDroneThreadextendsThread{Graphics g;ArrayList<Drone> drones;ArrayList<Intruder> intruders;ArrayList<Intruder> foundedIntruders =newArrayList<>();//将所有被扫描到的入侵者存入这里ArrayList<Task> tasks;publicDroneThread(Graphics g){this.g = g;}@Overridepublicvoidrun(){for(;;){BufferedImage image =newBufferedImage(1200,800,2);Graphics bg = image.getGraphics(); bg.setColor(Color.white); bg.fillRect(0,0,1200,800); bg.setColor(Color.red); bg.drawRect(200,100,800,600);if(drones !=null){for(int i =0; i < drones.size(); i++){Drone drone = drones.get(i); drone.drawDrone(bg); drone.move();}}if(intruders !=null){for(int i =0; i < intruders.size(); i++){Intruder intruder = intruders.get(i); intruder.drawIntruder(bg); intruder.move();}}//在这里用bg画笔画出任务点if(tasks !=null){//System.out.println("进入if");for(int i =0; i < tasks.size(); i++){Task task = tasks.get(i);if(task.state==2){ tasks.remove(i);}else{ task.draw(bg);}}}if(drones.size()>0){for(int k =0; k < drones.size(); k++){Drone drone = drones.get(k);ScanResult sr = drone.scan(drone.x, drone.y, drone.scanSize, image);if(sr !=null){//如果扫到目标if(drone.state==2){continue;//正在执行任务中就不处理入侵者}Intruder intruder =getTargetIntruder(sr.targetX, sr.targetY);//通过坐标把这个目标找出来if(intruder !=null){//找到目标后 drone.target = intruder;//把自己的目标设置为这个 drone.state =1; foundedIntruders.add(intruder);//把这个目标传入动态数组}}else{//如果没扫描到目标if(drone.state==0&& drone.target==null){if(foundedIntruders !=null){//看看动态数组里面有没有别人发现的目标for(int i = foundedIntruders.size()-1; i >=0; i--){Intruder intruder = foundedIntruders.get(i);if(intruder !=null){if(intruder.blood <=0||!isInRange(intruder)){//先判断这个目标还是否存在或在范围内 foundedIntruders.remove(i);//删掉死亡的或者逃跑的}else{ drone.target = intruder;//把自己的目标设置成这个目标 drone.state =1;}}}}}}}}if(intruders.size()>0){//如果该入侵者被某个无人机列为目标,就进行扣血for(int k =0; k < intruders.size(); k++){Intruder intruder = intruders.get(k);for(int i =0; i < drones.size(); i++){if(intruder == drones.get(i).target){ intruder.underAttack();System.out.println("第"+ k +"个入侵者被扫到,血量剩余:"+ intruder.blood);if(intruder.blood <=0||!isInRange(intruder)){//消灭入侵者 intruders.remove(k);for(Drone drone : drones){if(drone.target == intruder){ drone.target =null;if(drone.state==1){ drone.state =0;}}}}}}}} g.drawImage(image,0,0,null);try{Thread.sleep(16);}catch(InterruptedException e){thrownewRuntimeException(e);}}}publicIntrudergetTargetIntruder(int scannedI,int scannedJ){for(int k =0; k < intruders.size(); k++){Intruder intruder = intruders.get(k);int distanceSquare;for(int i = intruder.x; i < intruder.x + intruder.size; i++){for(int j = intruder.y; j < intruder.y + intruder.size; j++){int roundx = intruder.x + intruder.size /2;int roundy = intruder.y + intruder.size /2; distanceSquare =(roundx - i)*(roundx - i)+(roundy - j)*(roundy - j);if(distanceSquare < intruder.rSquare){if(i == scannedI && j == scannedJ){return intruder;}}}}}returnnull;}//判断入侵者是否在守卫区内publicbooleanisInRange(Intruder intruder){if(intruder.x <200+ intruder.size || intruder.x >1000- intruder.size || intruder.y <100+ intruder.size || intruder.y >700- intruder.size){returnfalse;}else{returntrue;}}}packagecom.nwu.by0129.droneV4;importjavax.swing.*;importjava.awt.*;importjava.util.ArrayList;publicclassDroneUIextendsJFrame{ArrayList<Drone> drones=newArrayList<>();ArrayList<Intruder> intruders=newArrayList<>();ArrayList<Task> tasks=newArrayList<>();publicDroneUI(){setTitle("智能无人机平台4.0");setSize(1200,900);setDefaultCloseOperation(EXIT_ON_CLOSE);setLocationRelativeTo(null);JButton button1 =newJButton("生产无人机");JButton button2 =newJButton("生产入侵者");JPanel panel =newJPanel(); panel.setBackground(Color.lightGray); panel.add(button1); panel.add(button2);add(panel,BorderLayout.SOUTH);setVisible(true);Graphics g=this.getGraphics();//创建按钮监听器DroneListener droneListener =newDroneListener();//在创建监听器后立刻为集合赋值,让集合真正可用 droneListener.drones=drones; droneListener.intruders=intruders; droneListener.tasks=tasks; button1.addActionListener(droneListener); button2.addActionListener(droneListener);//创建鼠标监听器addMouseListener(droneListener);//创建无人机线程DroneThread droneThread =newDroneThread(g);//创建任务线程TaskThread taskThread =newTaskThread();//将共享内存的数组传入到两个线程中 droneThread.drones=drones; droneThread.intruders=intruders; droneThread.tasks=tasks;//要先将DroneUI类中的tasks传入给droneThread,这样才能在执行DroneThread的时候保证此时的tasks不是null taskThread.drones=drones; taskThread.tasks=tasks; droneThread.start(); taskThread.start();}@Overridepublicvoidpaint(Graphics g){super.paint(g);}publicstaticvoidmain(String[] args){newDroneUI();}}packagecom.nwu.by0129.droneV4;importjava.awt.*;publicclassIntruder{int x;int y;int speedx;int speedy;int size=30;int blood=200;int rSquare=size*size/4;publicIntruder(int x,int y,int speedx,int speedy){this.x = x;this.y = y;this.speedx = speedx;this.speedy = speedy;}publicvoiddrawIntruder(Graphics g){if(blood>0){Color color1 =newColor(0,0,0); g.setColor(color1); g.fillOval(x, y, size, size);Color color2 =newColor(213,15,15); g.setColor(color2); g.fillOval(x+5, y+5, size-10, size-10);Color color3 =newColor(0,0,0); g.setColor(color3); g.drawRect(x, y-5, size,6);Color color4 =newColor(124,244,21); g.setColor(color4); g.fillRect(x+1,y-4,(int)(blood/200.0*(size-1)),5);}}publicvoidmove(){ x+=speedx; y+=speedy;if(x<=0||x>=1160){ speedx=-speedx;}if(y<=0||y>=760){ speedy=-speedy;}}publicvoidunderAttack(){this.blood--;}}packagecom.nwu.by0129.droneV4;publicclassScanResult{int targetX;int targetY;publicScanResult(int targetX,int targetY){this.targetY = targetY;this.targetX = targetX;}}packagecom.nwu.by0129.droneV4;importjava.awt.*;publicclassTask{int x;int y;int size=10;int state;//0未被领取的任务;1被领取的任务;2已完成的任务publicTask(int state,int x,int y){this.state = state;this.x = x;this.y = y;}publicvoiddraw(Graphics g){ g.setColor(Color.yellow); g.fillRect(x-size/2,y-size/2,size,size);}}packagecom.nwu.by0129.droneV4;importjava.util.ArrayList;publicclassTaskThreadextendsThread{ArrayList<Task> tasks;ArrayList<Drone> drones;@Overridepublicvoidrun(){for(;;){//System.out.println("进入TaskThread");if(tasks !=null){if(drones !=null){for(int i = tasks.size()-1; i >=0; i--){Task task = tasks.get(i);if(task.state !=1){int nearestIndex =-1;double minDis =1000.0;for(int j = drones.size()-1; j >=0; j--){Drone drone = drones.get(j);//找到没有领取任务的无人机(只有巡逻状态的无人机可以接任务)if(drone.state ==0){//计算距离并找出最近的double currentDis =caculateDis(task, drone);if(currentDis <= minDis){ minDis = currentDis; nearestIndex = j;//把最近的无人机编号取出来}}}if(nearestIndex !=-1){ drones.get(nearestIndex).state =2;//无人机状态设为正在执行任务 drones.get(nearestIndex).targetTask = task;System.out.println("任务被领取"); task.state =1;//任务状态设为已被领取}else{System.out.println("没有可指挥的无人机"); task.state =0;}}}}}try{Thread.sleep(16);}catch(InterruptedException e){thrownewRuntimeException(e);}}}doublecaculateDis(Task task,Drone drone){double dis =Math.sqrt((task.x - drone.x)*(task.x - drone.x)+(task.y - drone.y)*(task.y - drone.y)*1.0);return dis;}}运行结果截图及视频演示
运行截图


- 视频演示
智能无人机V4视频演示
总结
在本次V4版本的开发中,我加深了对于面向对象的理解,对于大部分重复的代码,我们可以通过一个方法来将其封装起来,重复调用即可;同时我也认识到了代码前期设计的重要性,前期的设计要规范化,要为之后的功能添加留出充足的拓展空间。