☰
基于虚幻引擎与AirSim的无人机作战仿真环境搭建与算法验证实战
2026/10/9 15:33:39 网站建设 项目流程

简介:本资源面向计算机、人工智能、自动化等专业的在校学生、教师及企业研发人员,提供一套基于虚幻引擎与AirSim的无人机作战仿真系统完整工程,可用于毕业设计、课程设计、项目立项演示或进阶学习。压缩包共162个文件,约93.22MB,以79个Python脚本和52个pyc编译文件为核心,辅以XML、YML配置、MATLAB脚本、PNG/GIF演示图及Markdown说明文档,覆盖仿真环境搭建、无人机突防任务配置与训练日志等模块。目前已有180人学习关注。资源包含可运行的定制化无人机作战环境源码与详细文档,读者可据此复现仿真场景、理解AirSim与虚幻引擎的联合调试流程,并在此基础上修改任务逻辑、扩展功能,适合作为毕设或课设的完整参考方案。

1. 仿真环境搭建:从零到一构建无人机作战验证平台

飞控算法在真实场景中调试一次的成本,往往够在仿真里跑上千次。基于虚幻引擎和 AirSim 搭建定制化无人机作战环境,核心价值就在于把“试错”这件事从物理世界搬到数字世界。你不需要真机、不需要空域审批、不需要担心炸机,只要一台带独显的机器,就能在几小时内拉起一个包含地形、障碍、动态目标的虚拟战场。这套方案适合做无人机集群算法验证的开发者、做感知与避障研究的实验室团队,以及需要快速迭代战术逻辑的工程人员。它解决的不是“能不能飞”的问题,而是“飞得对不对、快不快、稳不稳”的问题。接下来我会把从环境配置到场景定制的完整路径拆开,每一步都给出可复现的命令和参数。

2. 虚幻引擎与 AirSim 的联调:把仿真跑起来的最小闭环

2.1 版本匹配与依赖安装

虚幻引擎和 AirSim 的版本兼容性是第一道坎。常见做法是锁定 UE 4.27 搭配 AirSim 1.8.1,这个组合在社区里验证最充分,插件编译成功率最高。如果你用 UE 5.x,需要从源码编译 AirSim 并手动处理 API 变更,新手不建议一上来就啃这块硬骨头。

先装 Visual Studio 2022,勾选“使用 C++ 的桌面开发”和“使用 C++ 的游戏开发”两个工作负载,Windows SDK 选 10.0.19041 或更高。然后装 UE 4.27,安装时勾选“引擎源码”选项,后面编译 AirSim 插件会用到。

# 克隆 AirSim 仓库(指定 1.8.1 标签) git clone --recursive -b v1.8.1 https://github.com/microsoft/AirSim.git cd AirSim # 生成 Visual Studio 工程文件 build.cmd

build.cmd会拉取 Eigen、rpclib 等依赖并编译。如果卡在 rpclib 下载,检查网络代理设置,或者手动把 rpclib 放到AirSim/external/rpclib下。编译完成后,在Unreal/Plugins目录下会生成AirSim插件文件夹,这就是后面要拖进 UE 项目的东西。

2.2 创建 UE 项目并挂载 AirSim 插件

打开 UE 4.27,选“游戏”分类下的“空白”模板,项目设置里选“C++”而非“蓝图”,目标平台勾选“桌面”,质量预设选“可缩放”。项目命名用英文,路径不要有空格和中文,否则插件加载会报莫名其妙的错。

创建完成后,把AirSim/Unreal/Plugins/AirSim整个文件夹复制到你的 UE 项目根目录下的Plugins文件夹里。没有Plugins就新建一个。然后右键.uproject文件,选“Generate Visual Studio project files”,再用 VS 打开.sln,编译Development Editor配置。

// 在项目的 Build.cs 里添加 AirSim 模块依赖 PublicDependencyModuleNames.AddRange(new string[] { "Core", "CoreUObject", "Engine", "InputCore", "AirSim" // 这一行是关键,漏了会链接失败 });

编译通过后,打开 UE 编辑器,在“编辑”菜单里找到“插件”,确认 AirSim 已启用。此时在场景里拖入一个BP_AirSimPawn或直接用 AirSim 提供的FlyingPawn,按播放键,你应该能看到一个四旋翼模型悬停在空中。如果没动,检查settings.json是否放在了Documents/AirSim目录下。

2.3 settings.json 的关键参数

AirSim 的行为几乎全由settings.json控制。这个文件默认在C:\Users\你的用户名\Documents\AirSim\settings.json,如果不存在就手动创建。下面是一个能跑通多机仿真的最小配置:

{ "SettingsVersion": 1.2, "SimMode": "Multirotor", "ClockSpeed": 1.0, "Vehicles": { "Drone1": { "VehicleType": "SimpleFlight", "AutoCreate": true, "X": 0, "Y": 0, "Z": 0, "Yaw": 0 }, "Drone2": { "VehicleType": "SimpleFlight", "AutoCreate": true, "X": 5, "Y": 0, "Z": 0, "Yaw": 0 } } }

SimMode选Multirotor表示多旋翼模式,ClockSpeed控制仿真时间流速,设为 1.0 是实时,设 2.0 就是两倍速,做强化学习时常用 5.0 以上加速采样。VehicleType用SimpleFlight是 AirSim 自带的简化飞控,适合算法验证;如果要接 PX4 或 ArduPilot,改成PX4Multirotor或ArduCopter,但需要额外配硬件在环或软件在环。X/Y/Z是初始位置,单位是米,Z 轴向上为负,所以Z: 0表示在地面。

注意:多机仿真时每台车的X/Y间距至少留 3 米,否则启动瞬间会互相碰撞导致物理引擎发散。

3. 定制化作战场景:地形、障碍与动态目标的搭建

3.1 地形生成与高度图导入

UE 自带的地形工具足够做基础战场。在“地形”模式下,用“雕刻”笔刷拉出山丘和沟壑,或者用“高度图导入”直接加载一张 PNG 灰度图。灰度值 0 对应最低点,255 对应最高点,导入时设置“Z 轴缩放”为 512 能得到比较自然的起伏。

# 用 Python 生成一张 1024x1024 的噪声高度图 import numpy as np from PIL import Image size = 1024 scale = 200.0 noise = np.random.rand(size, size) * scale # 简单高斯模糊让地形更自然 from scipy.ndimage import gaussian_filter noise = gaussian_filter(noise, sigma=8) # 归一化到 0-255 noise = (noise - noise.min()) / (noise.max() - noise.min()) * 255 Image.fromarray(noise.astype(np.uint8)).save("terrain_heightmap.png")

这段脚本生成一张随机起伏的高度图,sigma控制平滑程度,值越大地形越缓。导入 UE 后,地形组件会自动根据高度图生成网格。如果要做城市废墟场景,可以在高度图基础上叠加建筑模型,用“植被”工具批量刷一些倒塌的墙体碎片。

3.2 静态障碍物与碰撞体设置

作战环境里的障碍物分两类:一类是建筑、墙体这种静态碰撞体,另一类是树木、电线杆这种需要精确碰撞的细长物体。UE 的“静态网格体”默认带简单碰撞,但复杂模型需要手动生成“凸包碰撞”或“盒体碰撞”。

在 AirSim 里,碰撞检测依赖 UE 的物理系统。如果你发现无人机穿墙而过,八成是碰撞体没设对。选中模型,在“细节”面板里找到“碰撞”,把“碰撞复杂度”设为“将复杂碰撞用作简单碰撞”,然后点“创建凸包碰撞”。对于薄墙,用“盒体简化碰撞”更稳。

// 在 UE 的 Actor 蓝图里动态生成障碍物 UStaticMeshComponent* ObstacleMesh = CreateDefaultSubobject<UStaticMeshComponent>(TEXT("Obstacle")); ObstacleMesh->SetStaticMesh(LoadObject<UStaticMesh>(nullptr, TEXT("/Game/Models/Wall.Wall"))); ObstacleMesh->SetCollisionEnabled(ECollisionEnabled::QueryAndPhysics); ObstacleMesh->SetCollisionProfileName(TEXT("BlockAll")); ObstacleMesh->SetWorldLocation(FVector(100.0f, 200.0f, 50.0f));

SetCollisionProfileName("BlockAll")确保无人机撞上去会被弹开或炸机,而不是直接穿透。SetWorldLocation的坐标单位是厘米,所以Z: 50表示离地 0.5 米。

3.3 动态目标与 AI 行为树

作战环境里不能只有静态障碍,得有移动的靶标或敌方单位。UE 的“行为树”加“黑板”是做 AI 移动的标准方案。创建一个BP_TargetPawn,在事件图表里用“AI MoveTo”节点让它沿预设路径巡逻。

// 在 TargetPawn 的 Tick 里更新移动目标 void ATargetPawn::Tick(float DeltaTime) { Super::Tick(DeltaTime); if (Waypoints.Num() > 0) { FVector TargetLoc = Waypoints[CurrentWaypointIndex]; AddMovementInput((TargetLoc - GetActorLocation()).GetSafeNormal(), 1.0f); if (FVector::Dist(GetActorLocation(), TargetLoc) < 100.0f) { CurrentWaypointIndex = (CurrentWaypointIndex + 1) % Waypoints.Num(); } } }

Waypoints是一个TArray<FVector>,在编辑器里手动摆几个点就行。AddMovementInput的第二个参数是缩放值,1.0 表示全速。到达当前点后索引循环,目标就会一直绕圈跑。AirSim 的 API 可以实时获取这些目标的位置,用来做跟踪算法的输入。

4. AirSim API 编程:用 Python 控制无人机完成战术动作

4.1 连接与基础控制

AirSim 提供 Python 包airsim,装好后用MultirotorClient连接仿真器。下面是一个起飞、移动、降落的完整示例:

import airsim import time # 连接仿真器,超时 5 秒 client = airsim.MultirotorClient(ip="127.0.0.1", timeout_value=5) client.confirmConnection() # 解锁电机并起飞到 5 米高度 client.enableApiControl(True, "Drone1") client.armDisarm(True, "Drone1") client.takeoffAsync(vehicle_name="Drone1").join() client.moveToZAsync(-5, 2, vehicle_name="Drone1").join() # 飞到指定坐标点,速度 3 m/s client.moveToPositionAsync(20, 10, -5, 3, vehicle_name="Drone1").join() # 悬停 2 秒后降落 time.sleep(2) client.landAsync(vehicle_name="Drone1").join() client.armDisarm(False, "Drone1") client.enableApiControl(False, "Drone1")

enableApiControl是接管控制权,不调用的话 API 指令会被忽略。takeoffAsync返回一个Future,.join()表示阻塞等待完成。moveToZAsync的第二个参数是速度,单位米每秒。moveToPositionAsync的 Z 轴用负数表示向上,-5就是离地 5 米。

4.2 获取传感器数据做感知验证

AirSim 支持深度图、RGB 图、激光雷达、IMU 等多种传感器。在settings.json里给每台车配好传感器,然后用 API 拉数据:

# 获取深度图和 RGB 图 responses = client.simGetImages([ airsim.ImageRequest("0", airsim.ImageType.DepthPlanar, True), airsim.ImageRequest("0", airsim.ImageType.Scene, False, False) ], vehicle_name="Drone1") # 处理深度图 depth_img = responses[0] depth_data = np.array(depth_img.image_data_float, dtype=np.float32) depth_data = depth_data.reshape(depth_img.height, depth_img.width) # 处理 RGB 图 rgb_img = responses[1] rgb_data = np.frombuffer(rgb_img.image_data_uint8, dtype=np.uint8) rgb_data = rgb_data.reshape(rgb_img.height, rgb_img.width, 3)

ImageRequest的第一个参数是相机 ID,"0"表示前视相机。DepthPlanar返回的是平面深度图,每个像素值是距离,单位米。Scene返回 RGB 图,False, False表示不压缩、不转灰度。拿到数据后可以直接喂给 OpenCV 或 PyTorch 做目标检测。

4.3 多机协同与编队控制

多机场景下,每台车独立控制,但需要同步状态。下面是一个简单的编队保持逻辑:

# 三机三角形编队 formation_offsets = { "Drone1": (0, 0, -5), "Drone2": (5, 0, -5), "Drone3": (2.5, 4.33, -5) } # 获取领航者位置 leader_pos = client.getMultirotorState(vehicle_name="Drone1").kinematics_estimated.position for name, offset in formation_offsets.items(): target_x = leader_pos.x_val + offset[0] target_y = leader_pos.y_val + offset[1] target_z = offset[2] client.moveToPositionAsync(target_x, target_y, target_z, 3, vehicle_name=name)

getMultirotorState返回的kinematics_estimated.position是 NED 坐标系,X 朝北、Y 朝东、Z 朝下。offset里的 Z 用负数表示高度。这个逻辑每帧跑一次就能让三台车保持三角形队形跟随领航者。实际做编队时还要加避碰和速度限制,否则转弯时容易撞。

5. 避坑与排查:仿真环境搭建中的五个血泪教训

5.1 现象:UE 编辑器一播放就崩溃,报 PhysX 错误

原因:AirSim 插件和 UE 的物理引擎版本不匹配,或者显卡驱动太旧。常见于 UE 4.26 以下版本。

解决:升级到 UE 4.27,更新 NVIDIA 驱动到 470 以上。如果还崩,在Engine/Config/BaseEngine.ini里把bUsePhysX设为False,改用 Chaos 物理引擎,但 AirSim 对 Chaos 的支持不完整,可能会丢碰撞。

5.2 现象:Python API 连接超时,confirmConnection 卡住

原因:仿真器没启动,或者settings.json里的ApiServerPort被防火墙拦了。

解决:先确认 UE 编辑器在播放状态,然后检查settings.json里有没有"ApiServerPort": 41451。Windows 防火墙会拦这个端口,手动加一条入站规则放行 UDP 41451。

5.3 现象:无人机起飞后原地打转或翻跟头

原因:SimpleFlight的 PID 参数在复杂地形下不稳定,或者初始位置 Z 值设成了正数导致出生在地面以下。

解决:把settings.json里每台车的Z设为0或负数,确保出生点在地面以上。如果还翻,把SimpleFlight换成PX4Multirotor,PX4 的控制器鲁棒性更强,但需要额外配PX4的仿真环境。

5.4 现象:深度图全是 0 或全是无穷大

原因:相机没启用,或者ImageRequest的相机 ID 写错了。AirSim 默认只启用前视相机,其他相机要在settings.json里手动加。

解决:在settings.json的Vehicles下给每台车加Cameras字段,比如"Cameras": {"0": {"CaptureSettings": [{"ImageType": 0, "Width": 640, "Height": 480}]}}。ImageType: 0是 RGB,1是深度。

5.5 现象:多机仿真时帧率暴跌到个位数

原因:每台车都在跑独立的物理和渲染,GPU 扛不住。常见于 4 台以上无人机同时仿真。

解决:把ClockSpeed降到 0.5,牺牲实时性换帧率。或者在settings.json里关掉不必要的传感器,只保留算法需要的。如果还卡,用ViewMode切到NoDisplay,只跑物理不渲染,帧率能翻三倍。

6. 进阶技巧:用域随机化提升算法泛化能力

仿真环境最大的价值不是复现真实,而是批量制造“不同”。域随机化就是让每次仿真开始时,光照、纹理、障碍物位置、风力都随机变化,逼着算法学会忽略无关特征。在 AirSim 里做这件事,核心是动态改settings.json和 UE 场景参数。

import random import airsim def randomize_scene(client): # 随机光照强度 light_intensity = random.uniform(0.5, 3.0) client.simSetLightIntensity("DirectionalLight", light_intensity) # 随机风力 wind = airsim.Vector3r( random.uniform(-5, 5), random.uniform(-5, 5), random.uniform(-2, 2) ) client.simSetWind(wind) # 随机移动障碍物 for i in range(10): pos = airsim.Vector3r( random.uniform(-50, 50), random.uniform(-50, 50), random.uniform(-10, 0) ) client.simSetObjectPose(f"Obstacle_{i}", airsim.Pose(pos))

simSetLightIntensity改的是 UE 场景里方向光的光强,simSetWind给所有无人机加一个全局风场。simSetObjectPose需要障碍物在 UE 里被标记为可移动,否则改位置会报错。每次训练前调一次randomize_scene,跑几千轮下来,算法对光照和风扰的鲁棒性会明显提升。

验证域随机化有没有生效,最简单的办法是固定随机种子跑两次,看传感器数据是否一致。如果两次深度图完全一样,说明随机化没起作用,检查simSetWind的返回值是不是False。另一个技巧是把随机化参数记到日志里,训练崩了能回溯是哪组参数太极端。

我自己的习惯是:每次改完场景先跑 100 轮空载飞行,确认没有物理发散再上算法。仿真里的玄学问题,九成是碰撞体或坐标系没对齐,剩下一成是显卡驱动。希望帮到你。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询