前回の続き。
お約束 | |
---|---|
この記事作成にあたって使用した主なUnreal Engine バージョン | UE 5.1.1 |
本日のゴール
Botスポーンのルールを変更する
C++ファイルの変更について
C++ファイルを変更して、コンパイルして使うには、ソースファイルのUE5が必要になります。
ソースファイルはEpic Game Lancherからダウンロードするわけでなく、Githubなどからダウンロードするので結構面倒です。やり方については公式に書いてあるので御覧ください。
LyraでのBot生成
Lyraでは生成されたBotでもプレイヤーでも、5秒ごとに生き返るようになっています。
これはGameplay Ability(GA)で実装されているようで、当然GAをオフにすれば、生き返りの機能はなくなるという設定が可能だったりします。これはまた後ほど・・・。
さて、LyraではBot生成が非常に分かりにくい手順で行われています。実は私も良くわかっていません(オイ)
Botの生成の設定は、
「B_ShooterGame_Elimination」で指定されている
「B_ShooterBotSpawner」というものがGameStateの一つであり、
これがBotの生成数だったり、Botの名前だったりを決めています。このGameState「B_ShooterBotSpawner」が同時にAI Controllerも指定しています。
じゃあBotに関してはこの「B_ShooterBotSpawner」だけ見ればいいのかと思われるのですが、実はそうじゃないのが我々を混乱させます。
Botやプレイヤーはチームによって、色が変わりますが、この色は「B_TeamSetup_TwoTeams」で設定しています。
また、プレイヤーもBotも、プレイのたびにMannyとQuinがランダムで選ばれるようになっていますが、この設定は「B_PickRandomCharacter」で指定されています。
つまり、プレイヤーもBotも同じファイルによってメッシュが決まっているということです。Lyraは優れた設計なのですが、ここだけは後でアレンジが難しくなるので辞めてほしかったです。(私情)
何が問題なの?
さて、上記だと何が問題なのでしょうか?
例えばチーム分けもその例です。Lyraでは、生成するBotの数は自由に決めることができますが、誰をどのチームに分けるかの設定ができません。このBotはチーム1, このBotはチーム2というような設定ができないんですね。全部自動で振られてしまいます。
Botは必ず人数が一番少ないチーム側に生成されるようになっています。プレイヤーが3人参加してチーム1に全員いたら、チーム2にBotが生成される、みたいな感じです。このため、Bot10人 vs プレイヤー5人 みたいなこともできません。
また、Botのブループリントを指定することもできません。Botは「B_ShooterGame_Elimination」で指定されている「HeroData_ShooterGame」に沿って生成されます。
「HeroData_ShooterGame」では下記の通り、「B_Hero_ShooterMannequin」が指定されています。このブループリントを使って、プレイヤーもBotも生成されるという形です。
じゃあこれらの設計を回避して実装しようとなるわけですが、設計に沿わない実装をするということは、Lyraで作り上げられたシステムを使わないということになり、そうなると実装のコストがかかってしまいます。
というわけで、Lyraを改造することになります。ただ、Lyraはその基幹システムをC++で実装しているため、必然的にC++を弄ることになります…。
改造① Botは必ずチーム2になるようにする
チーム分けを担当しているC++ファイルは「LyraTeamCreationComponent.cpp」になります。
参照:https://forums.unrealengine.com/t/manually-setting-team-id-in-lyra/619856
これのServerChooseTeamForPlayerに関する記述を下記のように変更します。コメントアウトしている16行目はもともとあった命令です。
- void ULyraTeamCreationComponent::ServerChooseTeamForPlayer(ALyraPlayerState* PS)
- {
- if (PS->IsOnlyASpectator())
- {
- PS->SetGenericTeamId(FGenericTeamId::NoTeam);
- }
- else
- {
- int32 SelectedTeamID;
- if (PS->IsABot()) {
- SelectedTeamID = 2;
- }
- else {
- SelectedTeamID = 1;
- }
- //const FGenericTeamId TeamID = IntegerToGenericTeamId(GetLeastPopulatedTeamID());
- const FGenericTeamId TeamID = IntegerToGenericTeamId(SelectedTeamID);
- PS->SetGenericTeamId(TeamID);
- }
- }
やっていることは、10行目でBotの場合はSelectedTeamIDを2にして、それ以外ではSelectedTeamIDを1にしています。
マジックナンバーなのは…まあ気にしない気にしない。
このSelectedTeamIDはint32型なので、FGenericTeamID型に変更しないとTeamIDにそのまま使うことができないので、IntegerToGenericTeamId()で型を変更しています。(17行目)
18行目でそのTeamIDをSetしている感じです。
改造② チームを手動で変更できるようにする
これはなぜ標準で搭載されてないのか分からない機能なんですが、LyraTeamSubsystemには予め「ChangeTeamForActor」というアクターのTeamを変更する関数が用意されています。ただなぜかブループリントに公開されていません。このため、これを公開することで利用することができます。
また、Subsystemなので、どこからでも関数にアクセスが可能です。
LyraTeamSubsystem.hを下記のように変更します。
- bool ChangeTeamForActor(AActor* ActorToChange, int32 NewTeamId);
↑変更前
- UFUNCTION(BlueprintCallable, BlueprintPure = false, Category = Teams, meta = (Keywords = "Set"))
- bool ChangeTeamForActor(AActor* ActorToChange, int32 NewTeamId);
↑変更後
01行目を追加するようなイメージですね。
あとはLyra Team Subsystemから引っ張って、Change Team for Actorを引っ張ってきます。改造③ 指定したPlayerStartからのみスポーンするようにする
一方で、Spawn自体は Botは「LyraBotCreationComponent」の「ServerCreateBots」関数が実行しています。ただしここでSpawnするのはAIコントローラーだけで、アクターは「LyraControllerComponent_CharacterParts」が後付でスポーンさせています。
これが非常にややこしいです。
まあ何を言ってるのかというと、Lyraは処理があっちこっちに分散しており分かりにくいという話です。なんでこんなに分かりにくいのかというと、機能を付け加えたり変更したりを自由自在にするために、モジュール方式を採用しているからですね。LyraGameModeにあれこれ記述してしまうと、ルールを変更するときに非常に面倒になってしまいます。詳しくは前回の記事を参照してください。
※C++が嫌な場合は、LyraGameModeを継承した新しいBPを作成して、ChoosePlayerStartを継承したファンクションを作成してやれば一応大丈夫なはずです。ただ競合が発生するのでどうなるかはやってみないとわからない…。
まずやりたい事を一つ一つ分けていきましょう。
①Authorityアカウント、つまりサーバーのPlayerはPlayerStartタグが「Server」である PlayerStartからしかSpawnできないようにする
②ClientアカウントのPlayerはPlayerStartタグが「Client」からしかSpawnできないようにする③BotはPlayerStartタグが「Bot」からしかSpawnできないようにする
①、②、③は前述の「LyraPlayerSpawningManagerComponent」の「ChoosePlayerStart」関数 を弄ることでなんとかなりそうですね。
- LyraPlayerSpawningManagerComponent.hについて下記のように変更
- class AController;
- class APlayerController;
- class APlayerState;
- class APlayerStart;
- class ALyraPlayerStart;
- class AActor;
- class AAIController; //追加
まず、AIControllerを使うので上記のようにLyraPlayerSpawningManagerComponent.hで呼び出しておきます。
- LyraPlayerSpawningManagerComponent.cppについて下記のようにする
- AActor* ULyraPlayerSpawningManagerComponent::ChoosePlayerStart(AController* Player)
- {
- if (Player)
- {
- //#if WITH_EDITOR
- // if (APlayerStart* PlayerStart = FindPlayFromHereStart(Player))
- // {
- // return PlayerStart;
- // }
- //#endif
- TArray<ALyraPlayerStart*> StarterPoints;
- TArray<ALyraPlayerStart*> ServerSpawnPoints;
- TArray<ALyraPlayerStart*> ClientSpawnPoints;
- TArray<ALyraPlayerStart*> BotSpawnPoints;
- for (auto StartIt = CachedPlayerStarts.CreateIterator(); StartIt; ++StartIt)
- {
- if (ALyraPlayerStart* Start = (*StartIt).Get())
- {
- StarterPoints.Add(Start);
- if ((*StartIt)->PlayerStartTag == FName(TEXT("Server"))) {
- ServerSpawnPoints.Add(Start);
- }
- else if ((*StartIt)->PlayerStartTag == FName(TEXT("Client"))) {
- ClientSpawnPoints.Add(Start);
- }
- else if ((*StartIt)->PlayerStartTag == FName(TEXT("Bot"))) {
- BotSpawnPoints.Add(Start);
- }
- }
- else
- {
- StartIt.RemoveCurrent();
- }
- }
- if (APlayerState* PlayerState = Player->GetPlayerState<APlayerState>())
- {
- // start dedicated spectators at any random starting location, but they do not claim it
- if (PlayerState->IsOnlyASpectator())
- {
- if (!StarterPoints.IsEmpty())
- {
- return StarterPoints[FMath::RandRange(0, StarterPoints.Num() - 1)];
- }
- return nullptr;
- }
- }
- AActor* PlayerStart = OnChoosePlayerStart(Player, StarterPoints);
- if (Player->IsLocalPlayerController() && !ServerSpawnPoints.IsEmpty()) {
- PlayerStart = ServerSpawnPoints[0];
- }
- else if (AAIController* AICon = Cast<AAIController>(Player)) {
- if (!BotSpawnPoints.IsEmpty()) {
- PlayerStart = GetFirstRandomUnoccupiedPlayerStart(Player, BotSpawnPoints);
- }
- }
- else if (!ClientSpawnPoints.IsEmpty()) {
- PlayerStart = GetFirstRandomUnoccupiedPlayerStart(Player, ClientSpawnPoints);
- }
- if (ALyraPlayerStart* LyraStart = Cast<ALyraPlayerStart>(PlayerStart))
- {
- LyraStart->TryClaim(Player);
- }
- return PlayerStart;
- }
- return nullptr;
- }
次にcppの方のファイルで、ChoosePlayerStartを上記のように変更します。
やっていることは、
7~12行 : Editorでの実行時は、完全ランダムにスポーンする設定になっているので、これをコメントアウトすることで無視させます。15~17行 : LyraPlayerStartのArray変数ServerSpawnPoints, BotSpawnPoints, ClientSpawnPointsを用意しておきます。
20~46行:Worldに設置されたすべてのPlayerStartのPlayerStartTagを調べて、条件に合致していたらServer, Client, BotのそれぞれのArray変数に保存しておきます。
62~78行:サーバーの場合、つまりIsLocalControllerの場合はServerSpawnPointsの一番最初[0]のところからスポーンするように設定します。
Botの場合、つまりControllerがAIコントローラーにキャストできた場合は、BotSpawnPointsの中からランダムにスポーンするように設定します。
Clientの場合、つまり上記のいずれでもない場合は、ClientSpawnPointsの中からランダムにスポーンするように設定します。
0 件のコメント:
コメントを投稿