Unreal Engine 4でのFieldモジュールの利用方法

Fieldモジュールは二次元平面上に仮想世界を構築し、距離的に近いクライアントのみ同期を取る性質があります。 これはMMORPGのような広大なフィールドを扱うゲームに最適です。 このエントリではDiarkisのUnreal Engine 4(UE4)用プラグインを使ってフィールドに参加し、実際にキャラクターの位置を同期してみます。

サンプルプログラムの実行方法

Field Demosの画面

こちらよりサンプルプログラムをダウンロードし実行してください。

最初にDiarkis Cloudとの接続画面が表示されるので、必要な情報を入力し接続してください(詳しくはこちらのエントリを参照してください)。

接続が成功するとSelect Featureと書かれた画面が表示されるので、Field Demosを選択してください。

Field Demos(上記画像参考)ではFieldモジュールに関するデモを試すことができます(今回のデモは一つしかありません)。

1. Sync Character Demoを選択し、次に表示された画面でJoinのボタンを押すとフィールドに参加します。

Field参加中の画面

フィールドへの参加が成功するとJoining fieldと書かれた画面(上記画像参考)が表示されます。

デモでは半径5メートル(UE4での)以内のキャラクターの位置が同期されます。

またフロアが四色(赤、青、緑、黄)に色分けされていますが、異なる色のフロアにいるキャラクターは同期がとれない(姿が見えない)ことがわかります。これはDiarkis Fieldの機能の一つで、フィールドの座標空間(x, y)において、正の座標を持つクライアントと負の座標を持つクライアントは互いに同期をとらないようになっているからです(よって四色の交わるところが原点(0, 0)となります)。

Fieldモジュールの利用方法

ここからはプラグインの使い方を実際のソースコードを交えて解説します。

プラグインの機能にアクセスするには、DiarkisFieldBaseのクラスを継承する必要があります(詳しくは以前のエントリを参照してください)。

今回のエントリでは便宜上、以下の名前を派生クラスとして使います。

// 実際は自由なクラス名をつけてください
DiarkisInterfaceBaseを継承したクラス:CustomDiarkisInterface
DiarkisFieldBaseを継承したクラス:CustomDiarkisField

CustomDiarkisInterfaceの実装

DiarkisInterfaceBaseを継承してCustomDiarkisInterfaceを作成します。

コンストラクタの中で、CustomDiarkisFieldを持たせるようにします。

#include "DiarkisInterfaceBase.h"

class CustomDiarkisInterface : public DiarkisInterfaceBase
{
public:
CustomDiarkisInterface(std::string host, std::string clientKey, uint64_t uid, LogOutType out);

virtual ~CustomDiarkisInterface() {};
};
#include "CustomDiarkisInterface.h"
#include "CustomDiarkisField.h" // CustomDiarkisFieldのインクルード(後に解説)。

CustomDiarkisInterface::CustomDiarkisInterface(std::string host, std::string clientKey, uint64_t uid, LogOutType out)
: DiarkisInterfaceBase(host, clientKey, uid, out)
{
this->fieldBase = make_shared<CustomDiarkisField>(); // Fieldモジュールを上書き
}

最後にDiarkisInterfaceBaseインスタンスの代わりにCustomDiarkisInterfaceインスタンスを作成するようにします(この部分は以前のエントリと同じです)。

+ #include "CustomDiarkisInterface.h"

bool AMyClass::Connect(const FString& Host, const FString& ClientKey, int64 UID, bool bUseUdp)
{
...
...
- this->diarkis = MakeShared<DiarkisInterfaceBase>(this, host, clientKey, (uint64_t) UID, LogOutType::FILE_OUT); // 削除
+ this->diarkis = MakeShared<CustomDiarkisInterface>(this, host, clientKey, (uint64_t) UID, LogOutType::FILE_OUT);
this->diarkis->WaitUntilReady();
...
...
}

CustomDiarkisFieldの実装

DiarkisFieldBaseを継承してCustomDiarkisFieldを作成します。この時各種コールバック関数(OnSyncInt ~ OnDisappear)をオーバーライドします。

class CustomDiarkisField : public DiarkisFieldBase
{
public:
CustomDiarkisField() {};

virtual ~CustomDiarkisField() {};

protected:
// 各種コールバック関数をオーバーライド
void OnSyncInt(const std::vector<std::vector<uint8_t>>& list) override;

void OnSync(const std::vector<uint8_t>& msg) override;

void OnDisappear(const std::string& remoteUid) override;
};

フィールドモジュールを初期化する

フィールドに参加するには、まずDiarkisInterfaceBase::SetupFieldを呼び出してFieldモジュールを初期化します。

void AMyClass::SetupField()
{
this->diarkis->SetupField();
}

自分の位置情報を送信する

DiarkisInterfaceBase::SendFieldSyncを呼び出し、自分の位置情報をサーバに送信します。

void AMyClass::SendFieldSync(int X, int Y, int Z, const TArray<uint8>& Payload, bool bReliable)
{
std::vector<uint8_t> payload(Payload.Num());
for (int i = 0; i < Payload.Num(); ++i)
{
payload[i] = Payload[i];
}
this->diarkis->SendFieldSync(X, Y, Z, payload, bReliable, 0, PT_NONE, 0);
}

SendFieldSyncには以下の引数を渡します。

x:ワールドX座標(単位メートル。Fieldは半径50メートル以内のクライアントと同期する)。
y:ワールドY座標(単位メートル。Fieldは半径50メートル以内のクライアントと同期する)。
z:空間(高さではない)。
payload:送信するデータ。
reliable:RUDPで送るかどうか(トランスポート層にUDP指定時のみ)。

SendFieldSyncで送られたデータは、距離的に近い他のクライアントのOnSyncIntもしくはOnSync関数が呼ばれることで伝わります。OnSyncによって受け取ったデータには先頭にメタ情報が加えられているので、RemoveHeaderを呼び出してpayloadだけを取り出します。

void CustomDiarkisField::OnSyncInt(const std::vector<std::vector<uint8_t>>& list)
{
DiarkisFieldBase::OnSyncInt(list);
}

void CustomDiarkisField::OnSync(const std::vector<uint8_t>& payload)
{
DiarkisFieldBase::OnSync(payload);

uint16_t dataType;
ProfileType profileType;
uint32_t createDelta;
int64_t sendTime;
int32_t sendDelta;
uint32_t serverDelta;

// remove header
vector<uint8_t>& removeHeaderPayload = (vector<uint8_t>&) payload;
this->RemoveHeader(removeHeaderPayload, dataType, profileType, createDelta, sendTime, sendDelta, serverDelta);
}

他のクライアントが視界から消えたのを検出する

他のクライアントが視界から消えた場合、OnDisappearが呼ばれます。

void CustomDiarkisField::OnDisappear(const std::string& remoteUid)
{
DiarkisFieldBase::OnDisappear(remoteUid);

std::string disappearClientUID = remoteUID;
}