Skip to content

Commit 9ce772b

Browse files
committed
添加丢失的游戏模式解析
1 parent a203646 commit 9ce772b

File tree

1 file changed

+172
-0
lines changed

1 file changed

+172
-0
lines changed

docs/engine/game_mode.md

Lines changed: 172 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,172 @@
1+
# 切换 GameMmode
2+
3+
在运行游戏时决定Map使用哪一个GameMode。
4+
5+
## 流程
6+
7+
1. 调用`UGameplayStatics::OpenLevel()`函数
8+
2. 传入Options参数"Game=XXX",或者直接在LevelName里面拼接好`LevelName?Game=XXX`,其中XXX是GameMode别名
9+
3. 在 ProjectSettings 中设置 GameModeClassAliases 配置GameMode别名
10+
11+
## 分析
12+
13+
可以在打开关卡时决定选用的GameMode,玄机就在 [UGameplayStatics::OpenLevel](https://github.com/OpenHUTB/engine/blob/421ec66f4d50f5dce22bb2d03fa35858dd7eec2b/Engine/Source/Runtime/Engine/Private/GameplayStatics.cpp#L778) 的参数里。
14+
15+
16+
一般来讲,我们调用这个函数的时候会忽略掉Options参数,而其实OpenLevel里会将LevelName参数和Options参数拼接形成一个Url,格式大致为"LevelName?Options"。
17+
18+
```cpp
19+
void UGameplayStatics::OpenLevel(const UObject* WorldContextObject, FName LevelName, bool bAbsolute, FString Options)
20+
{
21+
UWorld* World = GEngine->GetWorldFromContextObject(WorldContextObject, EGetWorldErrorMode::LogAndReturnNull);
22+
if (World == nullptr)
23+
{
24+
return;
25+
}
26+
27+
const ETravelType TravelType = (bAbsolute ? TRAVEL_Absolute : TRAVEL_Relative);
28+
FWorldContext &WorldContext = GEngine->GetWorldContextFromWorldChecked(World);
29+
FString Cmd = LevelName.ToString();
30+
if (Options.Len() > 0)
31+
{
32+
Cmd += FString(TEXT("?")) + Options;
33+
}
34+
FURL TestURL(&WorldContext.LastURL, *Cmd, TravelType);
35+
if (TestURL.IsLocalInternal())
36+
{
37+
// make sure the file exists if we are opening a local file
38+
if (!GEngine->MakeSureMapNameIsValid(TestURL.Map))
39+
{
40+
UE_LOG(LogLevel, Warning, TEXT("WARNING: The map '%s' does not exist."), *TestURL.Map);
41+
}
42+
}
43+
44+
GEngine->SetClientTravel( World, *Cmd, TravelType );
45+
}
46+
```
47+
48+
49+
打开关卡时,在 [GameInstance](https://github.com/OpenHUTB/engine/blob/421ec66f4d50f5dce22bb2d03fa35858dd7eec2b/Engine/Source/Runtime/Engine/Private/GameInstance.cpp#L1186) 里会根据刚才传输的 Url 设置当前Map的GameMode,此时会解析字符串"Game=",而该参数即作为GameMode的参数。
50+
51+
```cpp
52+
AGameModeBase* UGameInstance::CreateGameModeForURL(FURL InURL, UWorld* InWorld)
53+
{
54+
// Init the game info.
55+
FString Options;
56+
FString GameParam;
57+
for (int32 i = 0; i < InURL.Op.Num(); i++)
58+
{
59+
Options += TEXT("?");
60+
Options += InURL.Op[i];
61+
FParse::Value(*InURL.Op[i], TEXT("GAME="), GameParam);
62+
}
63+
```
64+
65+
而GameMode参数应该传入什么呢?其实解析到的GameMode字符串会作为UGameMapsSettings::GetGameModeForName()的参数被调用,在该函数内会检查它是否为 [GameMode的别名](https://github.com/OpenHUTB/engine/blob/421ec66f4d50f5dce22bb2d03fa35858dd7eec2b/Engine/Source/Runtime/EngineSettings/Private/EngineSettingsModule.cpp#L106) 。简单来说,Game=XXX需要填写的是GameMode的别名。
66+
67+
```cpp
68+
FString UGameMapsSettings::GetGameModeForName(const FString& GameModeName)
69+
{
70+
UGameMapsSettings* GameMapsSettings = Cast<UGameMapsSettings>(UGameMapsSettings::StaticClass()->GetDefaultObject());
71+
72+
// Look to see if this should be remapped from a shortname to full class name
73+
for (const FGameModeName& Alias : GameMapsSettings->GameModeClassAliases)
74+
{
75+
if (GameModeName == Alias.Name)
76+
{
77+
// switch GameClassName to the full name
78+
return Alias.GameMode.ToString();
79+
}
80+
}
81+
```
82+
83+
[GameMode的别名](https://dev.epicgames.com/documentation/zh-cn/unreal-engine/game-mode-and-game-state?application_version=4.27) 的说法来自于GameMapSettings的一个成员。
84+
可以在 `DefaultEngine.ini` 文件的 `/Script/Engine.WorldSettings/` 部分中设置地图前缀(和 URL 法的别名)。这些前缀设置所有拥有特定前缀的地图的默认游戏模式。
85+
86+
```ini
87+
[/Script/EngineSettings.GameMapsSettings]
88+
+GameModeMapPrefixes=(Name="DM",GameMode="/Script/UnrealTournament.UTDMGameMode")
89+
+GameModeClassAliases=(Name="DM",GameMode="/Script/UnrealTournament.UTDMGameMode")
90+
```
91+
92+
93+
## PythonAPI
94+
95+
[PythonAPI/carla/source/libcarla/Client.cpp](https://github.com/OpenHUTB/hutb/blob/c95e2d44a30c1fc066e8c9de082c0ec764b1be8a/PythonAPI/carla/source/libcarla/Client.cpp#L225) 声明 load_world 函数的对应关系:
96+
```cpp
97+
.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))
98+
```
99+
100+
调用 [hutb/LibCarla/source/carla/client/Client.h](https://github.com/OpenHUTB/hutb/blob/c95e2d44a30c1fc066e8c9de082c0ec764b1be8a/LibCarla/source/carla/client/Client.h#L75) 中的LoadWord ,
101+
102+
```cpp
103+
World LoadWorld(
104+
std::string map_name,
105+
bool reset_settings = true,
106+
rpc::MapLayer map_layers = rpc::MapLayer::All) const {
107+
return World{_simulator->LoadEpisode(std::move(map_name), reset_settings, map_layers)};
108+
}
109+
```
110+
111+
然后调用 [LibCarla/source/carla/client/detail/Simulator.cpp](https://github.com/OpenHUTB/hutb/blob/c95e2d44a30c1fc066e8c9de082c0ec764b1be8a/LibCarla/source/carla/client/detail/Simulator.cpp#L87) 中的 LoadEpisode 。
112+
113+
```cpp
114+
EpisodeProxy Simulator::LoadEpisode(std::string map_name, bool reset_settings, rpc::MapLayer map_layers) {
115+
const auto id = GetCurrentEpisode().GetId();
116+
_client.LoadEpisode(std::move(map_name), reset_settings, map_layers);
117+
```
118+
119+
然后调用 [LibCarla/source/carla/client/detail
120+
/Client.cpp](https://github.com/OpenHUTB/hutb/blob/c95e2d44a30c1fc066e8c9de082c0ec764b1be8a/LibCarla/source/carla/client/detail/Client.cpp#L147) 中的 load_new_episode
121+
122+
```cpp
123+
void Client::LoadEpisode(std::string map_name, bool reset_settings, rpc::MapLayer map_layer) {
124+
// Await response, we need to be sure in this one.
125+
_pimpl->CallAndWait<void>("load_new_episode", std::move(map_name), reset_settings, map_layer);
126+
}
127+
```
128+
129+
然后调用 [Unreal/CarlaUE4/Plugins/Carla/Source/Carla/Server
130+
/CarlaServer.cpp](https://github.com/OpenHUTB/hutb/blob/c95e2d44a30c1fc066e8c9de082c0ec764b1be8a/Unreal/CarlaUE4/Plugins/Carla/Source/Carla/Server/CarlaServer.cpp#L308) 中的 SetMapLayer
131+
132+
```cpp
133+
BIND_SYNC(load_new_episode) << [this](const std::string &map_name, const bool reset_settings, cr::MapLayer MapLayers) -> R<void>
134+
{
135+
REQUIRE_CARLA_EPISODE();
136+
137+
UCarlaGameInstance* GameInstance = UCarlaStatics::GetGameInstance(Episode->GetWorld());
138+
if (!GameInstance)
139+
{
140+
RESPOND_ERROR("unable to find CARLA game instance");
141+
}
142+
GameInstance->SetMapLayer(static_cast<int32>(MapLayers));
143+
144+
if(!Episode->LoadNewEpisode(cr::ToFString(map_name), reset_settings))
145+
{
146+
FString Str(TEXT("Map '"));
147+
Str += cr::ToFString(map_name);
148+
Str += TEXT("' not found");
149+
RESPOND_ERROR_FSTRING(Str);
150+
}
151+
152+
return R<void>::Success();
153+
};
154+
```
155+
156+
然后调用 [Unreal/CarlaUE4/Plugins/Carla/Source/Carla/Game
157+
/CarlaGameInstance.h](https://github.com/OpenHUTB/hutb/blob/c95e2d44a30c1fc066e8c9de082c0ec764b1be8a/Unreal/CarlaUE4/Plugins/Carla/Source/Carla/Game/CarlaGameInstance.h#L100) 的 SetMapLayer
158+
159+
```cpp
160+
UFUNCTION(Category = "Carla Game Instance", BlueprintCallable)
161+
void SetMapLayer(int32 MapLayer)
162+
{
163+
CurrentMapLayer = MapLayer;
164+
}
165+
```
166+
167+
168+
169+
170+
## 参考
171+
172+
* [绕开WorldSetting!切换GameMode的新姿势](https://zhuanlan.zhihu.com/p/660795062?share_code=1kQREJgxnStpH&utm_psn=1953537310399370643)

0 commit comments

Comments
 (0)