智能仿真无人机平台(多线程V2.0)技术笔记
本文记录一个包含无人机生产、入侵者生成、运动控制、雷达扫描功能的多线程GUI项目,涉及5个核心类的设计与实现,重点说明关键技术点、注意事项及代码注释。
一、核心类:Drone(无人机类)
负责定义无人机的属性、绘制逻辑和运动规则,是业务核心实体。
java
package duoxiancheng.xq0122.dronev2;
import java.awt.*;
/**
* @author xuqiang
* @date 2026/1/22 16:07
* @description 无人机实体类:封装属性、绘制逻辑、运动规则
*/
public class Drone {
int x,y; // 无人机坐标(雷达扫描区域左上角起点)
int speedx,speedy; // 水平/垂直移动速度
int size; // 无人机机身大小
int state; // 无人机状态(预留扩展:如巡逻/战斗/返航)
int stateSize; // 状态指示灯大小
int scanSize; // 雷达扫描范围大小
// 构造方法:初始化无人机核心属性
public Drone(int x,int y,int state,int speedx,int speedy){
this.x=x;
this.y=y;
this.state=state;
this.speedx=speedx;
this.speedy=speedy;
this.size=30; // 机身默认30px
this.stateSize=15; // 状态灯默认15px
this.scanSize=100; // 雷达扫描范围默认100px(半透明蓝色)
}
// 绘制无人机:分层绘制(雷达区域→机身→状态灯)
public void drawDrone(Graphics bg) {
// 1. 绘制雷达扫描区域(半透明蓝色,alpha=60实现透明效果)
Color radarColor = new Color(0, 0, 255, 60);
bg.setColor(radarColor);
bg.fillOval(x, y, scanSize, scanSize); // 雷达区域为椭圆形
// 2. 绘制无人机机身(绿色实体圆)
Color bodyColor = new Color(64, 195, 66);
bg.setColor(bodyColor);
bg.fillOval(x + 35, y + 35, size, size); // 机身位置相对于雷达区域偏移
// 3. 绘制状态指示灯(红色实体圆,默认异常状态,可扩展状态切换)
Color stateColor = new Color(255, 0, 0);
bg.setColor(stateColor);
bg.fillOval(x + 42, y + 42, stateSize, stateSize);
}
// 无人机运动逻辑:边界反弹+位置更新
public void move(){
// 水平边界检测:左边界200px,右边界1000px(雷达区域不超出红色矩形战斗区)
if(x < 200 || (x+scanSize) > 1000){
speedx = -speedx; // 碰到边界反向运动
}
// 垂直边界检测:上边界175px,下边界775px(与战斗区边界对齐)
if (y < 175 || (y+scanSize) > 775){
speedy = -speedy; // 碰到边界反向运动
}
x+=speedx; // 更新水平位置
y+=speedy; // 更新垂直位置
}
}
关键注意点
- 坐标关系:无人机的
x,y是雷达区域的左上角,机身和状态灯基于此坐标偏移,需保持绘制层级顺序(雷达→机身→状态灯); - 边界控制:运动边界与UI中的红色战斗区(200,175,800,600)严格对齐,避免无人机超出战斗范围;
- 透明效果:通过
Color构造方法的第四个参数(alpha值)实现雷达半透明,需注意alpha范围为0-255; - 预留扩展:
state属性未实际使用,可扩展为状态枚举(如巡逻/战斗/返航),配合stateColor动态切换指示灯颜色。
二、核心类:Intruder(入侵者实体类)
定义入侵者的属性、绘制逻辑和运动规则,与无人机形成对抗关系。
java
package duoxiancheng.xq0122.dronev2;
import java.awt.*;
/**
* @author xuqiang
* @date 2026/1/22 16:31
* @description 入侵者实体类:封装属性、绘制逻辑、运动规则、血量机制
*/
public class Intruder {
int x,y; // 入侵者坐标(左上角)
int speedx,speedy; // 水平/垂直移动速度
int size; // 入侵者大小
int blood; // 入侵者血量(用于后续攻击逻辑扩展)
// 构造方法:初始化入侵者核心属性
public Intruder(int x,int y,int speedx,int speedy,int size){
this.x=x;
this.y=y;
this.speedx=speedx;
this.speedy=speedy;
this.size=size;
this.blood=100; // 默认血量100
}
// 绘制入侵者:血量>0时才绘制(死亡后隐藏)
public void drawIntruder(Graphics g){
if(blood <= 0){
return; // 血量为0,不绘制(视为被消灭)
}
g.setColor(Color.BLACK);
g.fillOval(x,y,size,size); // 黑色实体圆作为入侵者主体
g.setColor(Color.RED);
g.drawOval(x-1,y-1,size+2,size+2); // 红色边框增强视觉区分
}
// 入侵者运动逻辑:边界反弹+位置更新(与无人机边界不同)
public void move(){
if(blood <= 0){
return; // 血量为0,停止运动
}
// 水平边界:整个窗口(0-1200px),与UI窗口宽度一致
if(x < 0 || (x+size) > 1200){
speedx = -speedx; // 边界反弹
}
// 垂直边界:整个窗口(0-950px),与绘制缓冲区高度一致
if(y < 0 || (y+size) > 950){
speedy = -speedy; // 边界反弹
}
x += speedx; // 更新水平位置
y += speedy; // 更新垂直位置
}
}
关键注意点
- 边界差异:入侵者运动边界是整个窗口(1200x950),而无人机仅在战斗区(200-1000x175-775)内运动,体现"外部入侵"的业务逻辑;
- 血量机制:
blood属性预留攻击逻辑扩展,当前仅用于控制绘制和运动状态,后续可添加无人机攻击减血功能; - 视觉设计:黑色主体+红色边框,与无人机的绿色机身形成鲜明对比,便于视觉区分。
三、事件监听器:DroneListener(按钮事件处理)
处理"生产无人机"和"生产入侵者"按钮的点击事件,负责实体对象的创建和集合添加。
java
package duoxiancheng.xq0122.dronev2;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.ArrayList;
import java.util.Random;
/**
* @author xuqiang
* @date 2026/1/22 16:32
* @description 按钮事件监听器:处理无人机和入侵者的创建逻辑
*/
public class DroneListener implements ActionListener {
ArrayList<Drone> droneList; // 存储所有无人机的集合(由UI类注入初始化)
ArrayList<Intruder> intruderList; // 存储所有入侵者的集合(由UI类注入初始化)
Random random = new Random(); // 随机数生成器(用于位置和速度随机)
@Override
public void actionPerformed(ActionEvent e) {
String ac = e.getActionCommand(); // 获取按钮点击命令(与按钮文本一致)
// 处理"生产无人机"按钮点击
if(ac.equals("生产无人机")){
// 随机位置:x在200-900px(战斗区内),y在175-675px(战斗区内)
int x = 200 + random.nextInt(700); // 700=1000-200,确保x+scanSize≤1000
int y = 175 + random.nextInt(500); // 500=775-175,确保y+scanSize≤775
// 随机速度:-2~2(包含0,可后续优化避免静止)
int speedx = random.nextInt(5) - 2;
int speedy = random.nextInt(5) - 2;
// 创建无人机对象(state=0预留状态)
Drone drone = new Drone(x,y,0,speedx,speedy);
// 添加到集合(集合由UI初始化,避免空指针)
droneList.add(drone);
}
// 处理"生产入侵者"按钮点击
else if(ac.equals("生产入侵者")){
int intruderSize = 45; // 入侵者固定大小45px
// 初始随机位置:x在200-1355px,y在175-1080px(覆盖窗口+外部区域)
int x = 200 + random.nextInt(1155);
int y = 175 + random.nextInt(905);
// 循环确保入侵者初始位置在战斗区外部(业务规则:从外部入侵)
while (true){
// 条件:入侵者完全在战斗区(200-1000x175-775)外部
if(x + intruderSize <= 200 || x >= 1000 || y + intruderSize <= 175 || y + intruderSize >= 775){
break; // 位置合法,退出循环
}
// 位置非法,重新随机
x = random.nextInt(1150);
y = random.nextInt(900);
}
// 随机速度:-2~2(包含0,可后续优化避免静止)
int speedx = random.nextInt(5) - 2;
int speedy = random.nextInt(5) - 2;
// 创建入侵者对象
Intruder itd = new Intruder(x,y,speedx,speedy,intruderSize);
// 添加到集合(集合由UI初始化)
intruderList.add(itd);
}
}
}
关键注意点
- 集合注入:
droneList和intruderList由UI类初始化并注入,避免直接创建导致的多实例问题,同时防止空指针; - 位置合法性:入侵者通过
while循环强制初始位置在战斗区外部,符合"入侵"的业务逻辑,需注意边界条件的准确性(包含入侵者大小); - 随机范围:无人机位置严格限制在战斗区内,确保初始位置合法,避免一创建就触发边界反弹;
- 速度优化:当前速度可能为0(导致物体静止),可后续修改为
random.nextInt(4) - 2(排除0)或添加非零校验。
四、多线程类:DroneThread(绘制与运动控制线程)
负责后台循环执行绘制、运动更新、雷达扫描逻辑,避免UI线程阻塞。
java
package duoxiancheng.xq0122.dronev2;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.util.ArrayList;
/**
* @author xuqiang
* @date 2026/1/22 18:53
* @description 后台线程:负责绘制刷新、实体运动更新、雷达扫描检测
*/
public class DroneThread extends Thread{
ArrayList<Drone> droneList; // 无人机集合(与UI、监听器共享)
ArrayList<Intruder> intruderList; // 入侵者集合(与UI、监听器共享)
Graphics g; // 窗口绘图上下文(从UI获取)
// 构造方法:接收UI的绘图上下文
public DroneThread(Graphics g){
this.g=g;
}
// 线程核心逻辑:循环执行(绘制→运动→扫描)
@Override
public void run(){
while (true) { // 无限循环,保持后台运行
// 1. 创建双缓冲图像(解决绘制闪烁问题)
BufferedImage img = new BufferedImage(1200,950,BufferedImage.TYPE_INT_RGB);
Graphics bg = img.getGraphics(); // 获取缓冲图像的绘图上下文
// 2. 绘制背景和战斗区
bg.setColor(Color.WHITE);
bg.fillRect(0,0,1200,950); // 白色背景(覆盖整个窗口)
bg.setColor(Color.RED);
bg.drawRect(200,175,800,600); // 红色战斗区边框(核心交互区域)
// 3. 无人机更新:运动+绘制(集合非空校验,避免空指针)
if (droneList != null && !droneList.isEmpty()) {
for (Drone drone : droneList) {
drone.move(); // 先更新位置,再绘制(避免绘制延迟)
drone.drawDrone(bg);
}
}
// 4. 入侵者更新:绘制+运动(顺序不影响,与无人机逻辑一致即可)
if (intruderList != null && !intruderList.isEmpty()) {
for (Intruder intruder : intruderList) {
intruder.drawIntruder(bg);
intruder.move();
}
}
// 5. 雷达扫描检测:仅取第一个无人机的扫描区域(可扩展为所有无人机)
if(droneList != null && droneList.size()>0){
Drone drone = droneList.get(0);
// 遍历雷达扫描区域的每个像素(x到x+scanSize,y到y+scanSize)
for (int i = drone.x; i < drone.x + drone.scanSize ; i++) {
for(int j = drone.y;j < drone.y + drone.scanSize;j++) {
// 边界校验:避免像素坐标超出缓冲图像范围(空指针)
if(i <0 || i>=1200 || j<0 || j>=950){
continue;
}
// 获取像素RGB值,转换为Color对象
int colorNum = img.getRGB(i,j);
Color c = new Color(colorNum);
// 灰度值判断:(R+G+B)/3 <10 视为黑色(入侵者主体颜色)
if((c.getRed() + c.getBlue() + c.getGreen())/3 < 10){
System.out.println("雷达扫描到了~");
}
}
}
}
// 6. 将缓冲图像绘制到窗口(双缓冲核心:一次性绘制,解决闪烁)
g.drawImage(img,0,0,null);
// 7. 线程休眠1ms:控制刷新频率(约1000fps,可调整为10ms优化性能)
try {
Thread.sleep(1);
}catch (InterruptedException e){
throw new RuntimeException(e); // 中断异常抛出运行时异常
}
}
}
}
关键注意点
- 双缓冲机制:通过
BufferedImage创建缓冲图像,先在缓冲中完成所有绘制,再一次性绘制到窗口,彻底解决GUI绘制闪烁问题; - 线程安全:
droneList和intruderList被多线程共享(UI线程添加元素,DroneThread读取元素),当前未加锁,高并发下可能出现ConcurrentModificationException,需后续添加Collections.synchronizedList或ReentrantLock; - 像素扫描:雷达扫描通过遍历像素颜色实现,需添加像素坐标边界校验,避免超出图像范围导致空指针;
- 性能优化:当前休眠1ms(1000fps)性能消耗较大,可调整为10ms(100fps),视觉效果无差异;扫描逻辑仅遍历第一个无人机,可扩展为遍历所有无人机;
- 扫描逻辑:通过灰度值判断黑色入侵者,需确保入侵者颜色与背景颜色差异明显,避免误判。
五、UI窗口类:DroneUI(程序入口)
负责创建窗口、初始化组件、关联集合与线程,是程序的入口点。
java
package duoxiancheng.xq0122.dronev2;
import javax.swing.*;
import java.awt.*;
import java.util.ArrayList;
/**
* @author xuqiang
* @date 2026/1/22 18:53
* @description UI窗口类:程序入口,负责窗口初始化、组件创建、对象关联
*/
public class DroneUI extends JFrame {
// 共享集合:存储所有无人机和入侵者(UI、监听器、线程共享同一集合)
ArrayList<Drone> droneList = new ArrayList<>();
ArrayList<Intruder> intruderList = new ArrayList<>();
// 构造方法:初始化窗口和组件
public DroneUI(){
setTitle("智能无人机平台"); // 窗口标题
setSize(1200,1000); // 窗口大小(与绘制缓冲区匹配)
setDefaultCloseOperation(EXIT_ON_CLOSE); // 关闭窗口退出程序
setLocationRelativeTo(null); // 窗口居中显示
// 1. 创建底部按钮面板
JPanel btnPanel = new JPanel();
btnPanel.setBackground(Color.LIGHT_GRAY); // 灰色背景
// 2. 创建"生产无人机"按钮
JButton createDroneBtn = new JButton("生产无人机");
btnPanel.add(createDroneBtn);
// 3. 创建"生产入侵者"按钮(注意:按钮文本与监听器命令一致)
JButton createIntryderBtn = new JButton("生产入侵者");
btnPanel.add(createIntryderBtn);
// 4. 添加按钮面板到窗口底部(BorderLayout.SOUTH)
add(btnPanel,BorderLayout.SOUTH);
setVisible(true); // 显示窗口(必须在组件添加后调用)
// 5. 获取窗口绘图上下文(用于线程绘制)
Graphics g = this.getGraphics();
// 6. 初始化线程和监听器
DroneThread dt = new DroneThread(g); // 传入绘图上下文
DroneListener droneL = new DroneListener(); // 事件监听器
// 7. 为按钮绑定监听器
createDroneBtn.addActionListener(droneL);
createIntryderBtn.addActionListener(droneL);
// 8. 注入共享集合(关键:确保所有组件操作同一集合)
droneL.droneList = droneList;
dt.droneList = droneList;
droneL.intruderList = intruderList;
dt.intruderList = intruderList;
// 9. 启动后台线程(开始绘制和运动更新)
dt.start();
}
// 重写paint方法:保留父类绘制逻辑(避免窗口刷新异常)
@Override
public void paint(Graphics g){
super.paint(g); // 必须调用父类方法,否则组件(如按钮)无法显示
}
// 程序入口:main方法
public static void main(String[] args){
new DroneUI(); // 创建UI对象,启动程序
}
}
关键注意点
- 集合共享:
droneList和intruderList在UI类中初始化,然后注入到监听器和线程中,确保所有组件操作同一集合,数据一致; - 窗口显示:
setVisible(true)必须在组件添加后调用,否则组件无法显示; - 绘图上下文:
this.getGraphics()获取窗口的绘图上下文,用于线程绘制,需注意窗口大小变化时可能需要重新获取; - paint方法:重写时必须调用
super.paint(g),否则父类的组件绘制逻辑会被覆盖,导致按钮等组件无法显示; - 按钮文本:按钮文本必须与监听器中的
actionCommand一致(如"生产入侵者"),否则监听器无法识别事件; - 线程启动:
dt.start()启动线程,开始后台循环,需确保在所有依赖对象(集合、绘图上下文)注入后调用。
六、整体架构与扩展建议
1. 架构总结
- 实体层:
Drone和Intruder封装核心属性和行为; - 控制层:
DroneThread负责后台逻辑(绘制、运动、扫描); - 交互层:
DroneListener处理用户输入,DroneUI提供可视化界面; - 数据共享:通过
ArrayList实现跨组件数据共享,核心是"同一集合注入所有组件"。
2. 优化扩展方向
- 线程安全:为共享集合添加同步机制(
Collections.synchronizedList或Lock); - 速度优化:避免速度为0,修改随机逻辑为
speedx = random.nextInt(4) - 2; if(speedx ==0) speedx=1;; - 攻击逻辑:扩展无人机攻击功能,碰撞检测后减少入侵者血量;
- 状态扩展:利用
Drone的state属性,实现巡逻、战斗、返航等状态切换; - 性能优化:减少雷达扫描的像素遍历范围,或使用碰撞检测算法(如矩形交集)替代像素扫描;
- 界面优化:添加血量显示、无人机数量统计、暂停/继续按钮等。