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"));
}
- 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;
}
- 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,这是无人机的刚体,之后在初始化引擎的时候会添加到物理世界中,进行动力学运动学计算。