アンリアル日記

ゲームプログラマー2年目です。主に家庭用。

UE4でRPCを利用して全クライアントの特定widgetを表示する

はじめに

今回はUE4上でRPCを用いて全クライアントでUMGを表示させてみようと思います。

RPCとは

Remote Procedure Callの略です。

自分のローカル環境にあるプログラムをA、ネットワークを介して別の場所にあるプログラムをBとすると、AがBに対して「そちら側にあるこの関数を実行してください!」とコールしたり、逆にBがAに対して「そっちにあるこの関数を実行してくれるかしら?」とコールしたりすることをRPCといいます。他の環境にイベントを送るような感じです。

e-words.jp

UE4ではエンジンにこのRPCを利用する方法が用意されているので、Blueprint/C++両方から簡単に利用することができます。

↓公式ドキュメントはここです

docs.unrealengine.com

では早速やっていきましょう。

プレイヤーと送るUMGの設定

PlayerControllerと表示用のWidgetを用意しておきます。

f:id:taronoguchi:20200822141322p:plain

レプリケート設定

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

f:id:taronoguchi:20200822145442p:plain

RPCを利用してメッセージを送る

ではRPCを使っていきます。

先程添付した公式ドキュメントの中にある"表"を見ながら進めていきましょう。

f:id:taronoguchi:20200822145004p:plain

インプット設定

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

f:id:taronoguchi:20200822142202p:plain

サーバーにお願いをする

サーバーに「全クライアントで指定のUMGを表示して」とおねがいします。

厳密に言うと、クライアントでUMGを表示するという「関数(イベント)」をサーバーから呼び出すようお願いします。

さて、それではサーバーにお願いしてみましょう。

  1. プレイヤーコントローラーにカスタムイベントを作成し

  2. そのカスタムイベントはサーバーで実行されるRPCだよという設定をし

  3. そのカスタムイベントを呼びます

f:id:taronoguchi:20200829180107p:plain

では先程の表を見ながら2のレプリケートのオプションの選択について整理しましょう。

今回はクライアントサーバーに対してServerSendMessageを実行してねというお願いをしたいので、↓こうなります f:id:taronoguchi:20200822162119p:plain ここでの"呼び出し側クライアント所有のアクタ"はクライアントにレプリケートされている、実際に入力を受け取ったプレイヤーコントローラーのインスタンスです。このインスタンスの所有権はクライアントにあります。(レプリケート元のサーバー側にあるプレイヤーコントローラーのインスタンスの所有者はサーバーです)

クライアントにUMGを表示せよと指令を出す

👆の続きです。

今度はサーバーからクライアントに対してUIを表示せよと司令を出します。

なので例の表でいうと↓こうなります。

f:id:taronoguchi:20200822163016p:plain

なのでClientShowUMGというイベントのレプリケートのオプションはRun On Owning Clientに設定しておきます。

あとはCreateWidgetなどをしてUIを表示させる流れを組んでおけば完了です。

f:id:taronoguchi:20200829160040p:plain

動かしてみましょう。

f:id:taronoguchi:20200829162728p:plain

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

f:id:taronoguchi:20200829175557p:plain

3人全員に表示されました!

Dedicatedサーバー・Listenサーバー、その他オンラインマルチプレイに関してUEに限って言えば↓のスライドがわかりやすいです。

UE4でマルチプレイヤーゲームを作ろう

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では普通のブループリントで実装できるようなインプットキーイベントが受け取れません👇 f:id:taronoguchi:20200617195126p:plain

そこで、ウィジェットブループリント内でOnKeyDown関数(場合によってはOnPreviewKeyDown関数)をオーバーライドして実装することで、キー入力を受け取ることができるようになります。

これについては以下の方の記事を参考にしてください。

katze.hatenablog.jp

ただ、欲しい入力イベントを全部実装していたら結構ノードつなぐのしんどいですよね...

そこでこれらをC++で実装してしまい、それを継承したウィジェットブループリントを作ればかなり楽できそうですね。

それでは実際にやっていきます。

UUserWidget継承のC++クラス作成

UUserWidgetをベースにC++クラスを作成します。

f:id:taronoguchi:20200617200646p:plain

ここにOnKeyDown関数を実装すればよいのですが、なんと、C++ではOnKeyDown関数をoverrideできません。

エンジンソースのUserWidget.hをのぞいてみると... f:id:taronoguchi:20200617201211p:plain それもそのはず、BlueprintImplementableなだけでvirtualがついていません。 そしてもちろんC++では、virtualキーワードがない関数をoverrideできません

どうすればいいか。

UserWidget.hの1250行目あたりを見てみると... f:id:taronoguchi:20200617201633p:plain virtualついてるやついっぱいあるやん!!

プレフィックスNative-がある同様の関数がたくさんありますね! どうやらこれを使えば良さそうです。

ただこれを使うには[プロジェクト名].Build.csにて"Slate""SlateCore"モジュールを有効化してあげる必要があります。

SlateとSlateCoreの有効化

早速[プロジェクト名].Build.csに行って、有効化しましょう。

f:id:taronoguchi:20200617202340p:plain

👆これを、こう👇

f:id:taronoguchi:20200617202428p:plain

ここに関しては公式のドキュメントにも詳しく書かれています。

docs.unrealengine.com

UMGでもスレートの一部を使ってるからなんでしょうかね、スレートを有効にしないといけないっぽいです。

無事モジュールが有効化できたら先ほど作ったC++クラスに処理を書いていきます。

NativeOnKeyDownのoverride実装

まずはヘッダーでoverrideする関数の宣言と、特定のキーで呼び出す関数を作成します。 今回はWASDとTABだけにしておきます。

呼び出す処理はBlueprintで実装できるようBlueprintImplementableEventにしておくとよいかと思います。

f:id:taronoguchi:20200617210016p:plain

続いてcppファイルです。

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

f:id:taronoguchi:20200617203737p:plain

ちなみにFReply::HandledFReply::Unhandled関数はstaticな関数なのでFReplyインスタンス化しなくても呼び出せます。

ではこれを継承したウィジェットブループリントを作って今実装したものを使ってみましょう。

Widget Blueprintの作成と実装

C++クラスを継承してウィジェットブループリントを作成します。

このとき、C++クラスを右クリックして"Create C++ class derived from..."を選択してしまうと普通のBPエディターが開いてしまうので、WidgetBlueprintを作ってから親を変えます。

f:id:taronoguchi:20200617204348p:plain

リペアレント👇

f:id:taronoguchi:20200617204433p:plain

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

f:id:taronoguchi:20200617204709p:plain

ではこのWidgetを表示してみましょう。

ちゃんと実行されていますね👇

f:id:taronoguchi:20200617204946p:plain

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

Engine Subsystemを使ってEditor Utility Widgetへの入力を反映する

はじめに

今回はゲーム内で使うキャラクターのステータスパラメータ(HPやMPなど)をEditorUtilityWidgetを使って変更できるようにしたいと思います。

色々やり方はあると思いますが、今回はEngine Subsystemを使ってみたかったのでそれを使ってやってみたいと思います。

Editor Utility Widgetとサブシステムに関してはおかずさんやキンアジさんの記事がとても分かりやすいです(いつもお世話になってますm( )m)

EditorUtilityWidget | キンアジのブログ

猫でも分かるUE4.22から入ったSubsystem

Editor Utility Widgetを作成その1(Designerモード)

パラメーターを調整するためのEditorUtilityWidget(EUW)を作成します。

f:id:taronoguchi:20200526152110p:plain

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

f:id:taronoguchi:20200526152127p:plain

Designerモードで☝のような感じで、HP調整用のSpinBoxとパラメータを反映させるためのボタンを追加します。

後ほどGraphモードで編集していきます。

Engine Subsystemの作成

ここでエンジンサブシステムを作っておきます。

何故エンジンサブシステムを使うかというと、単純に今回の目的を達するためにインスタンスとしての生存期間が理想的なのがEngineだったからです。

☞作業中もプレイ中も参照できるインスタンスとしてはEngineが理想的

と、判断しました。

では早速作っていきます。

f:id:taronoguchi:20200526152155p:plain

f:id:taronoguchi:20200526152210p:plain

EngineSubsystemをもとにC++クラス"EUWParameterKeeper"を作りました。

シンプルにHPというpublicな変数をBlueprintReadWriteで定義します。

f:id:taronoguchi:20200526152225p:plain

C++側はこれで終わりです。コンパイルしておきましょう。

TODO

何をしたいか一旦整理すると、

  1. SpinBox(EUW)で試したいパラメーターを入力(調整)する
  2. APPLYボタンを押したら、作ったエンジンサブシステムのメンバー変数HPにSpinBoxの値を反映する
  3. ゲームが始まったらキャラクターのBlueprintからエンジンサブシステムのメンバー変数HPを呼ぶ

といった感じですね。

それでは順にやっていきます。

Editor Utility Widgetを作成その2(Graphモード)

f:id:taronoguchi:20200526152246p:plain

キャラのBPにEngineSubsystemの値を反映

(異なる2つの変数HPができてしまうんでほんまは変数名、分けたほうがいいですね)

f:id:taronoguchi:20200526152302p:plain

Play

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

f:id:taronoguchi:20200526152317p:plain

デフォルトの0が表示されていますね。

ではEUWで値を変更してみましょう。

f:id:taronoguchi:20200526152332p:plain

Run Editor Utility Widgetで使えるようにして...

値を変更して👇

f:id:taronoguchi:20200526152347p:plain

APPLYを押すと👇

f:id:taronoguchi:20200526152404p:plain

f:id:taronoguchi:20200526152420p:plain

👆変更された値が表示されました。

では再度プレイしてみましょう。

f:id:taronoguchi:20200526152434p:plain

👆今変更した値が確かに反映されていますね!

最後に

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

Subsystem便利ですね!