モーキャプデータフロー

UE4におけるUDP受信(モーキャププラグイン作成メモ)

これは UE4 Advent Calendar 2015 12日目の記事です。

はじめに

モーションキャプチャ装置のデータを受信するプラグインを作りました
その時の核となったUDP受信あたりの手順を残しておきたいと思います。

扱ったモーションキャプチャ装置について

Perception Neuron や、その先輩(ライバル)的存在の MVN といったモーションキャプチャ装置があります。

これらのモーションキャプチャのソフト(Axis Neuron / MVN Studio)はもちろん記録もできますが、ネットワーク経由でデータを送る仕組みを持っています。
このデータを受信できれば、基本的にはどんなソフトでもリアルタイムにモーションを参照することができます。

Perception Neuron はどこのご家庭にもあるそうですが、私の身近にあったのは MVN でした。しかし、Unreal Engine 4 用のプラグインはメーカー曰く出す予定は今の所無いそうでしたので、自作することにしました。
そのうち Perception Neuron でも同じようにデータがUDPで送れることが分かったため、ついででそちらにも対応させてみました。

UDPについて

Perception Neuron は TCP と UDP という2種類の方法でデータを送信できますが、MVN は UDP でしたので、それを使うこととしました。
ざっくり言うと下記のような違いかと思います。

  • TCP … 電話みたいな通信のイメージ。相手がちゃんと聞いているか確認しつつデータを送る。
  • UDP … TVみたいな放送、あるいは郵便のイメージ。一方的に送り付ける。途中で消えたり順番が入れ替わったりもする。

それぞれ利点や欠点はありますが、リアルタイムモーションキャプチャの用途だと、完全でなくても即時性が大事なので UDP の方が向いていると個人的には思います。(でも Perception Neuron は TCP の方を主に作ってあるみたい。UDP の実装を改善してほしい点が幾つかあったり…)

さしあたり私が扱ったのは UDP で受信する部分のみです。
図で書くとこんな感じで一方向です。

モーキャプデータフロー
モーションデータ送信と受信
なお、受信側ではデータが正しければ送信元が何かは関係ありません。
そこでKinect から MVN のふりをしてデータを送る側のソフトも作りましたが、それについては別の機会にでも。

Unreal Engine 4 での UDP 受信

UE4は普通に作ったものをベースにプラグイン化できるようなので、「プラグインにする」という点はまず考えず、UDP受信を行おうと、ネットで資料を探しました。

といった記事を拝見したりした結果、「FSocket」「FUdpSocketBuilder」を使えばいいのかなー、というところまでは見当をつけました。

が、それを使おうとするまでで若干ハマりました。

#includeするまで

#include "Networking.h"
#include "Sockets.h"

が必要だと思ったのですが、そのままでは includeの候補に出ていませんし、書いてもビルドできません。

20151212_002_Networking

下記の手順が必要でした。

  1. (プロジェクト名).Build.cs というファイルがあるので、そこの PublicDependencyModuleNames.AddRange() に “Networking”, “Sockets” を追加
  2. Visual Studio を一旦閉じ、「Visual Studio プロジェクトを更新」してから再度開く

20151212_003_Build  20151212_003_Reload

ここできちんと更新できていないと、#includeできずに混乱しました。
正しく更新できていれば #include で候補が出てきます。
場合によってはビルドも必要だったかもしれません。

20151212_004_Networking

FUdpSocketBuilder の利用

私は #include まででしばらく詰まりましたが、それができれば FUdpSocketBuilder を使っている例を調べて、コードは大体すんなり書けた気がします。
Mac でも Android でも同じコードで動いて素晴らしいです。

役にたつかわかりませんが簡単なUDP受信(受信サイズを得るだけ)の例を置いてみます。
GameInstance を継承した下記クラスを作って、プロジェクト設定の GameInstance に指定すると勝手に受信を行ってログに出力します。

UdpReceiveGameInstance.h

// Fill out your copyright notice in the Description page of Project Settings.

#pragma once

#include "Engine/GameInstance.h"
#include "Networking.h"
#include "Sockets.h"
#include "UdpReceiveGameInstance.generated.h"

/**
 * 起動時からUDP受信を行うGameInstance
 */
UCLASS()
class UDPSAMPLE_API UUdpReceiveGameInstance : public UGameInstance
{
    GENERATED_BODY()

    virtual void Init() override;
    virtual void Shutdown() override;

    FSocket *m_Socket;
    FUdpSocketReceiver *m_Receiver;

    /* UDPデータが届いた際に呼ばれるコールバック */
    void UdpReceivedCallback(const FArrayReaderPtr& data, const FIPv4Endpoint& ip);
    
public:
    /* UDP待ち受けポート番号 */
    UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "UDP")
        int32 Port = 7001;

    /* ポートを開いて受信待ちにします */
    UFUNCTION(BlueprintCallable, Category = "UDP")
        bool Connect();

    /* ポートを閉じて受信を終了します */
    UFUNCTION(BlueprintCallable, Category = "UDP")
        void Close();
};

UdpReceiveGameInstance.cpp

// Fill out your copyright notice in the Description page of Project Settings.

#include "UdpSample.h"
#include "UdpReceiveGameInstance.h"

/* 起動時の初期化処理 */
void UUdpReceiveGameInstance::Init()
{
    /* 最初から自動で接続してしまう */
    this->Connect();
}

/*  終了時の処理 */
void UUdpReceiveGameInstance::Shutdown()
{
    /* ポートを閉じる */
    this->Close();
}

/* ポートを開き受信開始 */
bool UUdpReceiveGameInstance::Connect()
{
    if (m_Socket == NULL) {
        m_Socket = FUdpSocketBuilder(TEXT("Mocap UDP socket"))
            .BoundToPort(this->Port)
            .Build();
    }

    if (m_Socket != NULL) {
        /* UDP受信を開始 */
        m_Receiver = new FUdpSocketReceiver(m_Socket, FTimespan(0, 0, 1), TEXT("UDP receiver"));
        m_Receiver->OnDataReceived().BindUObject(this, &UUdpReceiveGameInstance::UdpReceivedCallback);

        // 接続成功
        UE_LOG(LogTemp, Log, TEXT("Connected to UDP port %d"), this->Port);
        return true;
    }

    // 接続失敗
    UE_LOG(LogTemp, Warning, TEXT("Could not open UDP port %d"), this->Port);
    return false;
}

/*  受信を終了してポートを閉じる */
void UUdpReceiveGameInstance::Close()
{
    if (m_Receiver != NULL) {
        m_Receiver->Exit();
        delete m_Receiver;
        m_Receiver = NULL;
    }

    if (m_Socket != NULL) {
        m_Socket->Close();
        delete m_Socket;
        m_Socket = NULL;
    }
}

/*  UDPでデータが届いた際のコールバック */
void UUdpReceiveGameInstance::UdpReceivedCallback(const FArrayReaderPtr& data, const FIPv4Endpoint&)
{
    uint32 receivedSize = data->GetAllocatedSize();
    UE_LOG(LogTemp, Log, TEXT("%d bytes received!"), receivedSize);
}

上記コードでは肝心のデータ解釈がありませんが、それは送られてくるデータの仕様による話になってきます。(もしニーズがあればまた書くかも)

その他 UE4 の C++ コーディングではまったところ

エラーの場所がわからない

Visual Studio でビルド時にエラーが出た場合、「エラー一覧」を見ても原因がわからなかったりしました。
「出力」を見るとわかりやすかったです。
Visual Studio というよりUEのツール がメッセージを出してくれていますので。

コメントに日本語を入れるとトラブル

Visual Studio で開くファイルはそのままだと Shift JIS になりました。
Shift JIS であれば // のコメントも普通に書けました。

ただ、UEエディタでは関数やプロパティに書いたコメントをマウスオーバーで表示してくれますが、Shift JIS では文字化けしていました。
かといってファイルの文字コードをそのまま UTF8 にすると、// のコメント部分でエラーになりました。

最終的に、日本語を含むコメントは常に /* */ を使い、ファイルはUTF8で保存することにしてみました。

終わりに

モーションキャプチャプラグインの話をしようと思っていたものの、UDP受信の話とか、はまった話がメインになりました。
当ブログは基本的に後輩に向けた資料のつもりで書いています。行なったことのメモ程度のものも今後増やしてみようかと思いますが、皆様のお役に立つかはわかりません (^^;


投稿日

カテゴリー:

タグ: