UE4でRPCを利用して全クライアントの特定widgetを表示する
はじめに
今回はUE4上でRPCを用いて全クライアントでUMGを表示させてみようと思います。
RPCとは
Remote Procedure Callの略です。
自分のローカル環境にあるプログラムをA、ネットワークを介して別の場所にあるプログラムをBとすると、AがBに対して「そちら側にあるこの関数を実行してください!」とコールしたり、逆にBがAに対して「そっちにあるこの関数を実行してくれるかしら?」とコールしたりすることをRPCといいます。他の環境にイベントを送るような感じです。
UE4ではエンジンにこのRPCを利用する方法が用意されているので、Blueprint/C++両方から簡単に利用することができます。
↓公式ドキュメントはここです
では早速やっていきましょう。
プレイヤーと送るUMGの設定
PlayerControllerと表示用のWidgetを用意しておきます。

レプリケート設定
プレイヤーコントローラーとキャラクターをレプリケート設定しておきましょう。

RPCを利用してメッセージを送る
ではRPCを使っていきます。
先程添付した公式ドキュメントの中にある"表"を見ながら進めていきましょう。

インプット設定
メッセージ送信のためのイベントを飛ばす入力を設定します。

サーバーにお願いをする
サーバーに「全クライアントで指定のUMGを表示して」とおねがいします。
厳密に言うと、クライアントでUMGを表示するという「関数(イベント)」をサーバーから呼び出すようお願いします。
さて、それではサーバーにお願いしてみましょう。
プレイヤーコントローラーにカスタムイベントを作成し
そのカスタムイベントはサーバーで実行されるRPCだよという設定をし
そのカスタムイベントを呼びます

では先程の表を見ながら2のレプリケートのオプションの選択について整理しましょう。
今回はクライアントが
サーバーに対してServerSendMessageを実行してねというお願いをしたいので、↓こうなります
ここでの"呼び出し側クライアント所有のアクタ"はクライアントにレプリケートされている、実際に入力を受け取ったプレイヤーコントローラーのインスタンスです。このインスタンスの所有権はクライアントにあります。(レプリケート元のサーバー側にあるプレイヤーコントローラーのインスタンスの所有者はサーバーです)
クライアントにUMGを表示せよと指令を出す
👆の続きです。
今度はサーバーからクライアントに対してUIを表示せよと司令を出します。
なので例の表でいうと↓こうなります。

なのでClientShowUMGというイベントのレプリケートのオプションはRun On Owning Clientに設定しておきます。
あとはCreateWidgetなどをしてUIを表示させる流れを組んでおけば完了です。

動かしてみましょう。

サーバーはDedicatedサーバーを利用し、3人でプレイします。

3人全員に表示されました!
Dedicatedサーバー・Listenサーバー、その他オンラインマルチプレイに関してUEに限って言えば↓のスライドがわかりやすいです。
C++でもやってみる
C++でも同じことをやってみましょう。
レプリケート設定と入力イベント設定
BeginPlayとSetupInputComponentで設定します。
// OnlineMultiPlayerController.cpp #include "OnlineMultiPlayerController.h" #include "GameFramework/GameState.h" #include "GameFramework/PlayerState.h" #include "Blueprint/UserWidget.h" void AOnlineMultiPlayerController::BeginPlay() { Super::BeginPlay(); SetReplicates(true); } void AOnlineMultiPlayerController::SetupInputComponent() { Super::SetupInputComponent(); InputComponent->BindAction(TEXT("SendMessage"), IE_Pressed, this, &AOnlineMultiPlayerController::ServerSendMessage); }
RPC関数の宣言と定義
C++でRPC関数を宣言する場合にはUFUNCTIONマクロにServer, Client, NetMulticastのいずれかを指定し、さらにReliableかUnreliableも指定します(←これ指定しないとコンパイルエラーになります)。
// OnlineMultiPlayerController.h #pragma once #include "CoreMinimal.h" #include "GameFramework/PlayerController.h" #include "OnlineMultiPlayerController.generated.h" /** * */ UCLASS() class ONLINEMULTI_API AOnlineMultiPlayerController : public APlayerController { GENERATED_BODY() protected: virtual void BeginPlay() override; private: virtual void SetupInputComponent() override; UFUNCTION(Server, Unreliable) void ServerSendMessage(); UFUNCTION(Client, Unreliable) void ClientShowUMG(); };
以下実装ファイルです。
// OnlineMultiPlayerController.cpp つづき void AOnlineMultiPlayerController::ClientShowUMG_Implementation() { TSubclassOf<UUserWidget> MessageWidget = TSoftClassPtr<UUserWidget>(FSoftObjectPath(TEXT("/Game/UI/WBP_Message.WBP_Message_C"))).LoadSynchronous(); if (!MessageWidget) { return; } UUserWidget* MessageWidgetObj = CreateWidget<UUserWidget>(this, MessageWidget); if (!MessageWidgetObj) { return; } MessageWidgetObj->AddToViewport(); } void AOnlineMultiPlayerController::ServerSendMessage_Implementation() { if (HasAuthority() && GetWorld()) { AGameStateBase* GameState = GetWorld()->GetGameState(); if (!GameState) { return; } ClientShowUMG(); TArray<APlayerState*> GamePlayerArray; GamePlayerArray = GameState->PlayerArray; for (APlayerState* OnlineMultiPlayerState : GamePlayerArray) { AOnlineMultiPlayerController* OwnerPlayerController = Cast<AOnlineMultiPlayerController>(OnlineMultiPlayerState->GetOwner()); if (!OwnerPlayerController) { return; } OwnerPlayerController->ClientShowUMG(); } } }
UMGでのキー入力イベントをC++で書く[UUserWidget::NativeOnKeyDown]
はじめに
UMGでは普通のブループリントで実装できるようなインプットキーイベントが受け取れません👇

そこで、ウィジェットブループリント内でOnKeyDown関数(場合によってはOnPreviewKeyDown関数)をオーバーライドして実装することで、キー入力を受け取ることができるようになります。
これについては以下の方の記事を参考にしてください。
ただ、欲しい入力イベントを全部実装していたら結構ノードつなぐのしんどいですよね...
そこでこれらをC++で実装してしまい、それを継承したウィジェットブループリントを作ればかなり楽できそうですね。
それでは実際にやっていきます。
UUserWidget継承のC++クラス作成
UUserWidgetをベースにC++クラスを作成します。

ここにOnKeyDown関数を実装すればよいのですが、なんと、C++ではOnKeyDown関数をoverrideできません。
エンジンソースのUserWidget.hをのぞいてみると...
それもそのはず、BlueprintImplementableなだけでvirtualがついていません。
そしてもちろんC++では、virtualキーワードがない関数をoverrideできません。
どうすればいいか。
UserWidget.hの1250行目あたりを見てみると...
virtualついてるやついっぱいあるやん!!
プレフィックスにNative-がある同様の関数がたくさんありますね!
どうやらこれを使えば良さそうです。
ただこれを使うには[プロジェクト名].Build.csにて"Slate"と"SlateCore"モジュールを有効化してあげる必要があります。
SlateとSlateCoreの有効化
早速[プロジェクト名].Build.csに行って、有効化しましょう。

👆これを、こう👇

ここに関しては公式のドキュメントにも詳しく書かれています。
UMGでもスレートの一部を使ってるからなんでしょうかね、スレートを有効にしないといけないっぽいです。
無事モジュールが有効化できたら先ほど作ったC++クラスに処理を書いていきます。
NativeOnKeyDownのoverride実装
まずはヘッダーでoverrideする関数の宣言と、特定のキーで呼び出す関数を作成します。 今回はWASDとTABだけにしておきます。
呼び出す処理はBlueprintで実装できるようBlueprintImplementableEventにしておくとよいかと思います。

続いてcppファイルです。
Blueprint上でOnKeyDown関数を実装するのと同じようにやっていきます。

ちなみにFReply::HandledとFReply::Unhandled関数はstaticな関数なのでFReplyをインスタンス化しなくても呼び出せます。
ではこれを継承したウィジェットブループリントを作って今実装したものを使ってみましょう。
Widget Blueprintの作成と実装
C++クラスを継承してウィジェットブループリントを作成します。
このとき、C++クラスを右クリックして"Create C++ class derived from..."を選択してしまうと普通のBPエディターが開いてしまうので、WidgetBlueprintを作ってから親を変えます。

リペアレント👇

リペアレント後は以下のように先ほどC++で宣言した関数を実装することができます。

ではこのWidgetを表示してみましょう。
ちゃんと実行されていますね👇

※ちなみに(Native)OnKeyDownを使うときは、対象のWidgetにフォーカスがなければいけないのと、InputModeUIOnly(またはUI and Game)が有効でなければならないので、そこを忘れずに。

Engine Subsystemを使ってEditor Utility Widgetへの入力を反映する
はじめに
今回はゲーム内で使うキャラクターのステータスパラメータ(HPやMPなど)をEditorUtilityWidgetを使って変更できるようにしたいと思います。
色々やり方はあると思いますが、今回はEngine Subsystemを使ってみたかったのでそれを使ってやってみたいと思います。
Editor Utility Widgetとサブシステムに関してはおかずさんやキンアジさんの記事がとても分かりやすいです(いつもお世話になってますm( )m)
EditorUtilityWidget | キンアジのブログ
Editor Utility Widgetを作成その1(Designerモード)
パラメーターを調整するためのEditorUtilityWidget(EUW)を作成します。

中身を編集していきます。

Designerモードで☝のような感じで、HP調整用のSpinBoxとパラメータを反映させるためのボタンを追加します。
後ほどGraphモードで編集していきます。
Engine Subsystemの作成
ここでエンジンサブシステムを作っておきます。
何故エンジンサブシステムを使うかというと、単純に今回の目的を達するためにインスタンスとしての生存期間が理想的なのがEngineだったからです。
- ゲームインスタンスは?→EUWを利用して値を変更するときはまだゲームをプレイしていないのでゲームインスタンスが存在しないので✖
- エディターは?→Standaloneでプレイするときにエディターは呼ばれない(https://docs.unrealengine.com/ja/Gameplay/Framework/GameFlow/index.html)ので✖
☞作業中もプレイ中も参照できるインスタンスとしてはEngineが理想的
と、判断しました。
では早速作っていきます。


EngineSubsystemをもとにC++クラス"EUWParameterKeeper"を作りました。
シンプルにHPというpublicな変数をBlueprintReadWriteで定義します。

TODO
何をしたいか一旦整理すると、
- SpinBox(EUW)で試したいパラメーターを入力(調整)する
- APPLYボタンを押したら、作ったエンジンサブシステムのメンバー変数HPにSpinBoxの値を反映する
- ゲームが始まったらキャラクターのBlueprintからエンジンサブシステムのメンバー変数HPを呼ぶ
といった感じですね。
それでは順にやっていきます。
Editor Utility Widgetを作成その2(Graphモード)

キャラのBPにEngineSubsystemの値を反映
(異なる2つの変数HPができてしまうんでほんまは変数名、分けたほうがいいですね)

Play
ここでゲームをプレイしてみると。

デフォルトの0が表示されていますね。
ではEUWで値を変更してみましょう。

Run Editor Utility Widgetで使えるようにして...
値を変更して👇

APPLYを押すと👇


👆変更された値が表示されました。
では再度プレイしてみましょう。

👆今変更した値が確かに反映されていますね!
最後に
今回はEditorUtilityWidgetでの値の変更ができるようにしてみました。EngineSubsystemを使わない方法もありますが、レベルに空のオブジェクトを置く必要がなかったり(他にやり方はあるかもしれませんが)するのでこちらを使ってみました。
Subsystem便利ですね!