AirSim代码解读日记(2)无人机初始化过程

发布于:2025-03-14 ⋅ 阅读:(19) ⋅ 点赞:(0)

1.在UE edit中把AAirSimGameMode设置为当前关卡游戏模式,运行游戏后UE会自动创建AAirSimGameMode对象,在

AAirSimGameMode的构造函数中创建ASimHUD对象 。
HUDClass = ASimHUD::StaticClass()
AAirSimGameMode::AAirSimGameMode(const FObjectInitializer& ObjectInitializer)
    : Super(ObjectInitializer)
{
    DefaultPawnClass = nullptr;
    HUDClass = ASimHUD::StaticClass();

    common_utils::Utils::getSetLogger(&GlobalASimLog);

    //module loading is not allowed outside of the main thread, so we load the ImageWrapper module ahead of time.
    static IImageWrapperModule& ImageWrapperModule = FModuleManager::LoadModuleChecked<IImageWrapperModule>(TEXT("ImageWrapper"));
}
  1. ASimHUD的构造函数中先进行前期的初始化工作,包括AirSim setting文件的读取、UE配置文件的设置、创建前端界面、创建simmode、开启PRC服务。
void ASimHUD::BeginPlay()
{
    Super::BeginPlay();

    try {
        UAirBlueprintLib::OnBeginPlay();
        initializeSettings();
        loadLevel();

        // Prevent a MavLink connection being established if changing levels
        if (map_changed_) return;

        setUnrealEngineSettings();
        createSimMode();
        createMainWidget();
        setupInputBindings();
        if (simmode_)
            simmode_->startApiServer();
    }
    catch (std::exception& ex) {
        UAirBlueprintLib::LogMessageString("Error at startup: ", ex.what(), LogDebugLevel::Failure);
        //FGenericPlatformMisc::PlatformInit();
        //FGenericPlatformMisc::MessageBoxExt(EAppMsgType::Ok, TEXT("Error at Startup"), ANSI_TO_TCHAR(ex.what()));
        UAirBlueprintLib::ShowMessage(EAppMsgType::Ok, std::string("Error at startup: ") + ex.what(), "Error");
    }
}

3.在createSimMode()中根据setting文件设置的模式创建对应的ASimMode对象。

void ASimHUD::createSimMode()
{
    std::string simmode_name = AirSimSettings::singleton().simmode_name;

    FActorSpawnParameters simmode_spawn_params;
    simmode_spawn_params.SpawnCollisionHandlingOverride = ESpawnActorCollisionHandlingMethod::AdjustIfPossibleButAlwaysSpawn;

    //spawn at origin. We will use this to do global NED transforms, for ex, non-vehicle objects in environment
    if (simmode_name == AirSimSettings::kSimModeTypeMultirotor)
        simmode_ = this->GetWorld()->SpawnActor<ASimModeWorldMultiRotor>(FVector::ZeroVector,
                                                                         FRotator::ZeroRotator,
                                                                         simmode_spawn_params);
    else if (simmode_name == AirSimSettings::kSimModeTypeCar)
        simmode_ = this->GetWorld()->SpawnActor<ASimModeCar>(FVector::ZeroVector,
                                                             FRotator::ZeroRotator,
                                                             simmode_spawn_params);
    else if (simmode_name == AirSimSettings::kSimModeTypeComputerVision)
        simmode_ = this->GetWorld()->SpawnActor<ASimModeComputerVision>(FVector::ZeroVector,
                                                                        FRotator::ZeroRotator,
                                                                        simmode_spawn_params);
    else {
        UAirBlueprintLib::ShowMessage(EAppMsgType::Ok, std::string("SimMode is not valid: ") + simmode_name, "Error");
        UAirBlueprintLib::LogMessageString("SimMode is not valid: ", simmode_name, LogDebugLevel::Failure);
    }
}

4.以创建为例ASimModeWorldMultiRotor,它的构造函数里并没有进一步初始化的内容,而是放在了BeginPlay()中,因为它的基类是AActor,所以这里重写了BeginPlay()方法,项目运行时UE自动调用。

void ASimModeWorldMultiRotor::BeginPlay()
{
    Super::BeginPlay();

    //let base class setup physics world
    initializeForPlay();
}

函数里执行的这两句都是它的父类ASimModeWorldBase的方法。ASimModeWorldMultiRotor本身只是重写了几个父类虚函数。initializeForPlay()用于初始化AirSim的物理引擎和物理世界,放到下节展开深究。
现在先来看看ASimModeWorldBase的BeginPlay(),它又调用了它的父类ASimModeBase的BeginPlay()。


```cpp
void ASimModeWorldBase::BeginPlay()
{
    Super::BeginPlay();
}

5.我们接着向下追代码,ASimModeBase::BeginPlay()的内容就丰富了,因为无论无人机还是无人车这些内容都得设置,所以放在基类比较合适。
主要内容包括:生成玩家控制器APlayerController;
改变世界坐标系的原点到玩家控制器位置;
创建WorldSimApi(整个仿真对外的交互接口);设置
创建ApiProvider;
设置更新时间类型;
初始化无人机和相机;

void ASimModeBase::BeginPlay()
{
    Super::BeginPlay();

    debug_reporter_.initialize(false);
    debug_reporter_.reset();

    //get player start
    //this must be done from within actor otherwise we don't get player start
    TArray<AActor*> pawns;
    getExistingVehiclePawns(pawns);
    bool have_existing_pawns = pawns.Num() > 0;
    AActor* fpv_pawn = nullptr;
    // Grab player location
    FTransform player_start_transform;
    FVector player_loc;
    if (have_existing_pawns) {
        fpv_pawn = pawns[0];
    }
    else {
        APlayerController* player_controller = this->GetWorld()->GetFirstPlayerController();
        fpv_pawn = player_controller->GetViewTarget();
    }
    player_start_transform = fpv_pawn->GetActorTransform();
    player_loc = player_start_transform.GetLocation();
    // Move the world origin to the player's location (this moves the coordinate system and adds
    // a corresponding offset to all positions to compensate for the shift)
    this->GetWorld()->SetNewWorldOrigin(FIntVector(player_loc) + this->GetWorld()->OriginLocation);
    // Regrab the player's position after the offset has been added (which should be 0,0,0 now)
    player_start_transform = fpv_pawn->GetActorTransform();
    global_ned_transform_.reset(new NedTransform(player_start_transform,
                                                 UAirBlueprintLib::GetWorldToMetersScale(this)));

    UAirBlueprintLib::GenerateAssetRegistryMap(this, asset_map);

    world_sim_api_.reset(new WorldSimApi(this));
    api_provider_.reset(new msr::airlib::ApiProvider(world_sim_api_.get()));

    UAirBlueprintLib::setLogMessagesVisibility(getSettings().log_messages_visible);

    setupPhysicsLoopPeriod();

    setupClockSpeed();

    setStencilIDs();

    record_tick_count = 0;
    setupInputBindings();

    initializeTimeOfDay();
    AirSimSettings::TimeOfDaySetting tod_setting = getSettings().tod_setting;
    setTimeOfDay(tod_setting.enabled, tod_setting.start_datetime, tod_setting.is_start_datetime_dst, tod_setting.celestial_clock_speed, tod_setting.update_interval_secs, tod_setting.move_sun);

    UAirBlueprintLib::LogMessage(TEXT("Press F1 to see help"), TEXT(""), LogDebugLevel::Informational);

    setupVehiclesAndCamera();
    FRecordingThread::init();

    if (getSettings().recording_setting.enabled)
        startRecording();

    UWorld* World = GetWorld();
    if (World) {
        UWeatherLib::initWeather(World, spawned_actors_);
        //UWeatherLib::showWeatherMenu(World);
    }
    UAirBlueprintLib::GenerateActorMap(this, scene_object_map);

    loading_screen_widget_->AddToViewport();
    loading_screen_widget_->SetVisibility(ESlateVisibility::Hidden);
}  

在void ASimModeBase::setupVehiclesAndCamera()中主要初始化相机和无人机,相机部分后面再看。先看无人机,主要是两条语句:
auto spawned_pawn = createVehiclePawn(vehicle_setting);
auto vehicle_sim_api = createVehicleApi(vehicle_pawn);
先创建FlyPawn,然后把FlyPawn作为参数传递给createVehicleApi生成无人机。
在createVehiclePawn中使用了UE的LoadClass方法加载资源中的蓝图类,类名就是FlyPawn,之后使用SpawnActor生成。

APawn* ASimModeBase::createVehiclePawn(const AirSimSettings::VehicleSetting& vehicle_setting)
{
    //get UU origin of global NED frame
    const FTransform uu_origin = getGlobalNedTransform().getGlobalTransform();

    // compute initial pose
    FVector spawn_position = uu_origin.GetLocation();
    Vector3r settings_position = vehicle_setting.position;
    if (!VectorMath::hasNan(settings_position))
        spawn_position = getGlobalNedTransform().fromGlobalNed(settings_position);

    FRotator spawn_rotation = toFRotator(vehicle_setting.rotation, uu_origin.Rotator());

    std::string vehicle_name = vehicle_setting.vehicle_name;

    //spawn vehicle pawn
    FActorSpawnParameters pawn_spawn_params;
    pawn_spawn_params.Name = FName(vehicle_name.c_str());
    pawn_spawn_params.SpawnCollisionHandlingOverride =
        ESpawnActorCollisionHandlingMethod::AdjustIfPossibleButAlwaysSpawn;

    auto vehicle_bp_class = UAirBlueprintLib::LoadClass(
        getSettings().pawn_paths.at(getVehiclePawnPathName(vehicle_setting)).pawn_bp);
    APawn* spawned_pawn = static_cast<APawn*>(this->GetWorld()->SpawnActor(
        vehicle_bp_class, &spawn_position, &spawn_rotation, pawn_spawn_params));

    spawned_actors_.Add(spawned_pawn);

    return spawned_pawn;
}

createVehicleApi内容就比较精彩了,注释在代码上

std::unique_ptr<PawnSimApi> ASimModeBase::createVehicleApi(APawn* vehicle_pawn)
{
    initializeVehiclePawn(vehicle_pawn);//子类重写了这方法,实际执行pawn的initializeForBeginPlay

    //create vehicle sim api,
    //这部分是经纬度坐标到ue坐标转换,pawn的位置如果没在setting中设置,那么home_geopoint的位置就是OriginGeopoint
    const auto& ned_transform = getGlobalNedTransform();
    const auto& pawn_ned_pos = ned_transform.toLocalNed(vehicle_pawn->GetActorLocation());
    const auto& home_geopoint = msr::airlib::EarthUtils::nedToGeodetic(pawn_ned_pos, getSettings().origin_geopoint);
    const std::string vehicle_name(TCHAR_TO_UTF8(*(vehicle_pawn->GetName())));
    //创建生成无人机需要的数据的参数结构体
    PawnSimApi::Params pawn_sim_api_params(vehicle_pawn, &getGlobalNedTransform(), getVehiclePawnEvents(vehicle_pawn), getVehiclePawnCameras(vehicle_pawn), pip_camera_class, collision_display_template, home_geopoint, vehicle_name);
    //这里子类重写了createVehicleSimApi方法
    std::unique_ptr<PawnSimApi> vehicle_sim_api = createVehicleSimApi(pawn_sim_api_params);
    auto vehicle_sim_api_p = vehicle_sim_api.get();
    auto vehicle_api = getVehicleApi(pawn_sim_api_params, vehicle_sim_api_p);//获取VehicleApiBase指针
    getApiProvider()->insert_or_assign(vehicle_name, vehicle_api, vehicle_sim_api_p);//给ApiProvider赋值,这个东西是个桥梁

    return vehicle_sim_api;
}
  1. createVehicleSimApi实际执行的是ASimModeWorldMultiRotor::createVehicleSimApi,创建MultirotorPawnSimApi智能指针,这个类的作用可看上节PawnSimApi的介绍,是个重要的交互类。之后执行vehicle_sim_api->initialize();
void MultirotorPawnSimApi::initialize()
{
    PawnSimApi::initialize();

    //create vehicle API
    std::shared_ptr<UnrealSensorFactory> sensor_factory = std::make_shared<UnrealSensorFactory>(getPawn(), &getNedTransform());
    vehicle_params_ = MultiRotorParamsFactory::createConfig(getVehicleSetting(), sensor_factory);
    vehicle_api_ = vehicle_params_->createMultirotorApi();
    //setup physics vehicle
    multirotor_physics_body_ = std::unique_ptr<MultiRotor>(new MultiRotorPhysicsBody(vehicle_params_.get(), vehicle_api_.get(), getKinematics(), getEnvironment()));
    rotor_count_ = multirotor_physics_body_->wrenchVertexCount();
    rotor_actuator_info_.assign(rotor_count_, RotorActuatorInfo());

    vehicle_api_->setSimulatedGroundTruth(getGroundTruthKinematics(), getGroundTruthEnvironment());

    //initialize private vars
    last_phys_pose_ = Pose::nanPose();
    pending_pose_status_ = PendingPoseStatus::NonePending;
    reset_pending_ = false;
    did_reset_ = false;
    rotor_states_.rotors.assign(rotor_count_, RotorParameters());

    //reset roll & pitch of vehicle as multirotors required to be on plain surface at start
    Pose pose = getPose();
    float pitch, roll, yaw;
    VectorMath::toEulerianAngle(pose.orientation, pitch, roll, yaw);
    pose.orientation = VectorMath::toQuaternion(0, 0, yaw);
    setPose(pose, false);
}

首先执行父类的voidPawnSimApi::initialize(),初始化kinematics_、environment_等变量,这两个变量是单例,存放姿态、位置数据,环境数据,其他地方使用的都是从这获取的指针。
之后构建SimpleFlightQuadXParams(描述机架的参数),再执行vehicle_params_->createMultirotorApi()构建SimpleFlightApi

virtual std::unique_ptr<MultirotorApiBase> createMultirotorApi() override
        {
            return std::unique_ptr<MultirotorApiBase>(new SimpleFlightApi(this, vehicle_setting_));
        }

这里就到飞控了,飞控里有包括固件和板子,apm和px4还包含mavlink通信部分,还有很多内容,这里不展开讲了。
MultirotorPawnSimApi的initialize()还会构建MultiRotorPhysicsBody,这是无人机的刚体,之后在初始化引擎的时候会添加到物理世界中,进行动力学运动学计算。

无人机初始化过程图