ObjectDelivererでUE4のプロセス間通信を簡単に


UE4でのプロセス間通信

私はUnreal Engineで開発したアプリケーションとC#等で作ったアプリを連携させて使うことが多いのですが、そこでよく使うのはTCP/IPやUDPなどのソケット通信が一番多いです。

今までは必要になるたびにその時の仕様に合わせた機能を作っていました。

ただそれだとその分の実装を毎回することで面倒だなという思いもありますし、通信部分はデリケートなのでちゃんと作らないと不具合も入りやすいです。

そこでここらでプラグイン化することで、使い回しやすさを狙いました。

まだ実装予定で未実装の項目はあるのですが、 現状の実装は以下のリポジトリから確認できます。

簡単な使い方はREADMEを見ていただければ分かると思いますが、苦手な英語で書いているので読みづらいと思います。。。

なので今回この記事でもう一回書きます。

コンセプト

今回作ったプラグイン(ObjectDeliverer)では以下の3点をコンセプトにしてます。

コンパクトさ

UE4のSocket通信、特にTCP/IPの実装は結構ローレベルな実装になっておりC#とかに比べると使うのに実装しなければいけない量が多いです。

そこである程度の縛りは生まれてしまいますが、とにかく使うときの実装量をコンパクトにすることをコンセプトに設計しました。

Blueprintで使えること

UE4の良さであるBlueprintで使えることをもう1つのコンセプトにしました。Blueprint対応を捨ててC++のみで使えるプラグインにしたほうが出来る事は多くなるのですがここはこだわってみました。

Blueprintの線が切れないこと

ObjectDelivererでは複数の通信プロトコルをサポートしてます(今後増やしていく予定)が、それらを別々の型として実装してしまうと、実装済みのBlueprintにおいてプロトコルの型を切り替えたときにBlueprintの断線が起こってしまいます。

そこでそういった実装後の仕様変更時になるべく断線がおこらないような設計を心がけました。

機能説明

ObjectDelivererの機能を簡単に説明します。

通信プロトコル

現在以下の4つのプロトコルに対応してます。

TCP/IPはサーバー、クライアントどちらも送受信両方に対応してます。

サーバーにて複数のクライアントが接続している状況では、サーバーからの送信はブロードキャストする設定がデフォルトです。

UDPは迷ったのですが、UDPを使うユースケースでは送受信を同時に行う事は少ないだろうと考え別々にしています。 もちろん2つ同時に使用すれば送受信にも対応できます。

データ分割ルール

UDPでは1回の受信データが1データとして使われることが多いですが、TCP/IPでは送受信データが途中までしか届かないことや、複数データが結合して届くこともあるため分割ルールが必要です。

そこで(私が)よく使う分割ルールを現状3つ実装してあります。

固定サイズは予め決めたサイズ分で毎回区切る方式です。 送受信データサイズが固定サイズ未満の場合でも、固定サイズ分送る必要があります。

逆に1回に送りたいデータサイズが固定サイズ以上になる場合は対応していません。

次のヘッダー + 本体形式では、データの先頭に本体サイズを格納します。

そのため受信側は一旦サイズ領域を読み取って次に続く本体部分のサイズを認識し、本体を読み込むといった挙動になります。

サイズ部分のバイト数とエンディアンは外側から変更可能です。

個人的にはこれが一番使い勝手が良いと思っています。

最後の終端記号式は、よくある「改行コードで区切る」といったパターンに対応するために作りました。

送受信データ形式

ObjectDelivererではデフォルトではバイト配列形式(TArray)でのデータフォーマットで送受信を行います。

ただDeliveryBoxというオプションを使うことで、文字列やオブジェクトをそのまま送受信することが可能です。

現状は以下の2パターンに対応しています。

使い方

GitHubのリポジトリをクローンしていただき、Pluginsフォルダをプロジェクトフォルダにコピーしていただくと使用可能です。 Editor上でObjectDelivererプラグインを有効にしてください。

Blueprintでの利用方法

C++での利用方法

利用手順はBlueprintと同様です。

// ObjectDelivererManagerのインスタンスを作成
auto deliverer = NewObject<UObjectDelivererManager>();

// 受信イベントを監視
deliverer->ReceiveData.AddDynamic(this, &UMyClass::OnReceive);

// ProtocolとPacketRuleをセットしてStart
deliverer->Start(UProtocolFactory::CreateProtocolTcpIpServer(9099),
                     UPacketRuleFactory::CreatePacketRuleSizeBody());

データの送受信

データの送受信は送るデータがバイト配列かそれ以外かで実装方法が異なります。

バイト配列の場合

ObjectDelivererManagerのインスタンスを介して送受信を行います。

// ObjectDelivererManagerのインスタンスを作成
auto deliverer = NewObject<UObjectDelivererManager>();

// 受信イベントを監視
deliverer->ReceiveData.AddDynamic(this, &UMyClass::OnReceive);

// ProtocolとPacketRuleをセットしてStart
deliverer->Start(UProtocolFactory::CreateProtocolTcpIpServer(9099),
                     UPacketRuleFactory::CreatePacketRuleSizeBody());

TArray<uint8> buffer;

// バイト配列を送信
deliverer->Send(buffer);

void UMyClass::OnReceive(UObjectDelivererProtocol* ClientSocket, const TArray<uint8>& Buffer)
{
    // 受信したバイト配列を使う
}

バイト配列以外の場合

ObjectDelivererManagerにセットしたDeliveryBox経由で行います。

// Json形式のシリアライズを経由したObject送受信機能を持ったDeliveryboxの作成
auto deliverybox = NewObject<UObjectDeliveryBoxUsingJson>();

// Deliveryboxに対してオブジェクトの受信イベントを監視
deliverybox->Received.AddDynamic(this, &UMyClass::OnReceiveObject);

deliverer->Start(UProtocolFactory::CreateProtocolTcpIpServer(9099),
                 UPacketRuleFactory::CreatePacketRuleSizeBody(),
                 deliverybox);

auto obj = NewObject<SampleObject>();

// Deliveryboxを使ってオブジェクトを送信
deliverybox->Send(obj);

void UMyClass::OnReceiveObject(UObject* ReceivedObject)
{
    // 受信したオブジェクトを取り出す
    USampleObject* obj = Cast<USampleObject>(ReceivedObject);
}

今後

ObjectDelivererは現在開発中のプラグインのため、今後も変更がまだしばらくは続く状態です。

そのためなるべくないようにしたいですが、下位互換性がない変更もあるためもし試される方はご了承ください。

追加予定項目

以下の機能を実装予定です。


See also