アンリアル日記

ゲームプログラマー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();
        }
    }
}