切换 GameMmode

在运行游戏时决定Map使用哪一个GameMode。

流程

  1. 调用UGameplayStatics::OpenLevel()函数
  2. 传入Options参数"Game=XXX",或者直接在LevelName里面拼接好LevelName?Game=XXX,其中XXX是GameMode别名
  3. 在 ProjectSettings 中设置 GameModeClassAliases 配置GameMode别名

分析

可以在打开关卡时决定选用的GameMode,玄机就在 UGameplayStatics::OpenLevel 的参数里。

一般来讲,我们调用这个函数的时候会忽略掉Options参数,而其实OpenLevel里会将LevelName参数和Options参数拼接形成一个Url,格式大致为"LevelName?Options"。

void UGameplayStatics::OpenLevel(const UObject* WorldContextObject, FName LevelName, bool bAbsolute, FString Options)
{
    UWorld* World = GEngine->GetWorldFromContextObject(WorldContextObject, EGetWorldErrorMode::LogAndReturnNull);
    if (World == nullptr)
    {
        return;
    }

    const ETravelType TravelType = (bAbsolute ? TRAVEL_Absolute : TRAVEL_Relative);
    FWorldContext &WorldContext = GEngine->GetWorldContextFromWorldChecked(World);
    FString Cmd = LevelName.ToString();
    if (Options.Len() > 0)
    {
        Cmd += FString(TEXT("?")) + Options;
    }
    FURL TestURL(&WorldContext.LastURL, *Cmd, TravelType);
    if (TestURL.IsLocalInternal())
    {
        // make sure the file exists if we are opening a local file
        if (!GEngine->MakeSureMapNameIsValid(TestURL.Map))
        {
            UE_LOG(LogLevel, Warning, TEXT("WARNING: The map '%s' does not exist."), *TestURL.Map);
        }
    }

    GEngine->SetClientTravel( World, *Cmd, TravelType );
}

打开关卡时,在 GameInstance 里会根据刚才传输的 Url 设置当前Map的GameMode,此时会解析字符串"Game=",而该参数即作为GameMode的参数。

AGameModeBase* UGameInstance::CreateGameModeForURL(FURL InURL, UWorld* InWorld)
{
    // Init the game info.
    FString Options;
    FString GameParam;
    for (int32 i = 0; i < InURL.Op.Num(); i++)
    {
        Options += TEXT("?");
        Options += InURL.Op[i];
        FParse::Value(*InURL.Op[i], TEXT("GAME="), GameParam);
    }

而GameMode参数应该传入什么呢?其实解析到的GameMode字符串会作为UGameMapsSettings::GetGameModeForName()的参数被调用,在该函数内会检查它是否为 GameMode的别名 。简单来说,Game=XXX需要填写的是GameMode的别名。

FString UGameMapsSettings::GetGameModeForName(const FString& GameModeName)
{
    UGameMapsSettings* GameMapsSettings = Cast<UGameMapsSettings>(UGameMapsSettings::StaticClass()->GetDefaultObject());

    // Look to see if this should be remapped from a shortname to full class name
    for (const FGameModeName& Alias : GameMapsSettings->GameModeClassAliases)
    {
        if (GameModeName == Alias.Name)
        {
            // switch GameClassName to the full name
            return Alias.GameMode.ToString();
        }
    }

GameMode的别名 的说法来自于GameMapSettings的一个成员。 可以在 DefaultEngine.ini 文件的 /Script/Engine.WorldSettings/ 部分中设置地图前缀(和 URL 法的别名)。这些前缀设置所有拥有特定前缀的地图的默认游戏模式。

[/Script/EngineSettings.GameMapsSettings]
+GameModeMapPrefixes=(Name="DM",GameMode="/Script/UnrealTournament.UTDMGameMode")
+GameModeClassAliases=(Name="DM",GameMode="/Script/UnrealTournament.UTDMGameMode")

PythonAPI

PythonAPI/carla/source/libcarla/Client.cpp 声明 load_world 函数的对应关系:

.def("load_world", CONST_CALL_WITHOUT_GIL_3(cc::Client, LoadWorld, std::string, bool, rpc::MapLayer), (arg("map_name"), arg("reset_settings")=true, arg("map_layers")=rpc::MapLayer::All))

调用 hutb/LibCarla/source/carla/client/Client.h 中的LoadWord ,

World LoadWorld(
    std::string map_name,
    bool reset_settings = true,
    rpc::MapLayer map_layers = rpc::MapLayer::All) const {
    return World{_simulator->LoadEpisode(std::move(map_name), reset_settings, map_layers)};
}

然后调用 LibCarla/source/carla/client/detail/Simulator.cpp 中的 LoadEpisode 。

EpisodeProxy Simulator::LoadEpisode(std::string map_name, bool reset_settings, rpc::MapLayer map_layers) {
    const auto id = GetCurrentEpisode().GetId();
    _client.LoadEpisode(std::move(map_name), reset_settings, map_layers);

然后调用 LibCarla/source/carla/client/detail /Client.cpp 中的 load_new_episode

void Client::LoadEpisode(std::string map_name, bool reset_settings, rpc::MapLayer map_layer) {
    // Await response, we need to be sure in this one.
    _pimpl->CallAndWait<void>("load_new_episode", std::move(map_name), reset_settings, map_layer);
}

然后调用 Unreal/CarlaUE4/Plugins/Carla/Source/Carla/Server /CarlaServer.cpp 中的 SetMapLayer

  BIND_SYNC(load_new_episode) << [this](const std::string &map_name, const bool reset_settings, cr::MapLayer MapLayers) -> R<void>
  {
    REQUIRE_CARLA_EPISODE();

    UCarlaGameInstance* GameInstance = UCarlaStatics::GetGameInstance(Episode->GetWorld());
    if (!GameInstance)
    {
      RESPOND_ERROR("unable to find CARLA game instance");
    }
    GameInstance->SetMapLayer(static_cast<int32>(MapLayers));

    if(!Episode->LoadNewEpisode(cr::ToFString(map_name), reset_settings))
    {
      FString Str(TEXT("Map '"));
      Str += cr::ToFString(map_name);
      Str += TEXT("' not found");
      RESPOND_ERROR_FSTRING(Str);
    }

    return R<void>::Success();
  };

然后调用 Unreal/CarlaUE4/Plugins/Carla/Source/Carla/Game /CarlaGameInstance.h 的 SetMapLayer

  UFUNCTION(Category = "Carla Game Instance", BlueprintCallable)
  void SetMapLayer(int32 MapLayer)
  {
    CurrentMapLayer = MapLayer;
  }

参考