e.blog

主にUnity/UE周りのことについてまとめていきます

UnrealEngineでVRことはじめ(C++編)

概要

Unreal Engineで、VR向けコンテンツをC++を使って作るための「ことはじめ」を書いていきたいと思います。
大体の書籍を見ていると、ブループリントの説明しかなくてあまりC++に対して言及しているものが少なく感じます。

ただ、やはり自分としてはできるだけコードを書いて作っていきたいので、UE4のC++入門などを通して得た内容をメモとして書いていこうと思います。

新規プロジェクト作成

まずは、なにはなくともプロジェクトを作らないと始まりません。
ということで、プロジェクトを作成します。
今回はイチからVR関連の操作を行うまでをやるので、空のプロジェクトから開始してみます。

f:id:edo_m18:20180207111037p:plain

C++ファイルを追加する

さて、プロジェクトが出来たのでまずはC++のファイルを作成してみます。
「Contents Browser」の「Add New」から、「New C++ Class...」を選択します。

※ ちなみに、エディタの言語を英語にしています。なぜなら、コンポーネントの検索などに日本語でしか反応しないコンポーネントとかもあって作業しづらいからです。

f:id:edo_m18:20180207110057p:plain

続くウィンドウで、Pawnをベースクラスとして作成します。

f:id:edo_m18:20180207110431p:plain

雛形を見てみる

さて、生成されたcppファイルを見てみると以下のように、すでにある程度雛形になった状態になっています。

#pragma once

#include "CoreMinimal.h"
#include "GameFramework/Pawn.h"
#include "VRPawn.generated.h"

UCLASS()
class MYPROJECT_API AVRPawn : public APawn
{
    GENERATED_BODY()

public:
    // Sets default values for this pawn's properties
    AVRPawn();

protected:
    // Called when the game starts or when spawned
    virtual void BeginPlay() override;

public:
    // Called every frame
    virtual void Tick(float DeltaTime) override;

    // Called to bind functionality to input
    virtual void SetupPlayerInputComponent(class UInputComponent* PlayerInputComponent) override;
};

Unityの新規作成したC#コードと見比べても、あまり大きくは違いませんね。
Unityのものと同様、スタート時点で呼ばれるメソッド、アップデートごとに呼ばれるメソッドが最初から定義されています。

protected:
    // Called when the game starts or when spawned
    virtual void BeginPlay() override;

public:   
    // Called every frame
    virtual void Tick(float DeltaTime) override;

この部分ですね。
使い方もUnityのものとほぼ同様です。

ただ、少し異なる点としては、毎フレームのメソッド呼び出し(Tick)が必要かどうかをboolで指定する点でしょうか。

// Sets default values
AVRPawn::AVRPawn()
{
    // Set this actor to call Tick() every frame.  You can turn this off to improve performance if you don't need it.
    PrimaryActorTick.bCanEverTick = true;
}

Updateごとにメソッドを呼ばれないようにするためには、このフラグをfalseにする必要があります。

マクロなどを見てみる

少しだけ脱線して、雛形に最初から挿入されているマクロなどをちょっとだけ覗いてみました。

// GENERATED_BODY
#define GENERATED_BODY(...) BODY_MACRO_COMBINE(CURRENT_FILE_ID,_,__LINE__,_GENERATED_BODY);

#define BODY_MACRO_COMBINE(A,B,C,D) BODY_MACRO_COMBINE_INNER(A,B,C,D)

#define BODY_MACRO_COMBINE_INNER(A,B,C,D) A##B##C##D

// UCLASS
#if UE_BUILD_DOCS || defined(__INTELLISENSE__ )
#define UCLASS(...)
#else
#define UCLASS(...) BODY_MACRO_COMBINE(CURRENT_FILE_ID,_,__LINE__,_PROLOG)
#endif

ふむ。よく分からんw
ただ、様々な機能をブループリントで利用するためだったりで、いたるところでマクロが使われているので、細かいところは後々追っていきたい。
ひとまずは以下の記事が参考になりそうです。

qiita.com

エディタでプロパティを表示させる

さて、話を戻して。

Unityのインスペクタに表示するのと似た機能がUEにもあります。
それがUPROPERTYマクロです。以下のように指定します。

public:
    UPROPERTY(EditAnywhere) int32 Damage;

UPROPERTY(EditAnywhere)を、エディタに公開したいメンバに追加するだけですね。

コンパイルすると以下のように、エディタ上にパラメータが表示されるようになります。

f:id:edo_m18:20180207104916p:plain

参考:

docs.unrealengine.com

docs.unrealengine.com

さて、以上でC++を書き始める準備が整いました。

ここからは実際の開発で使いそうな部分を含めつつ、VRのモーションコントローラを使うまでを書いていきたいと思います。

C++コードを書き始める

さぁ、ここから実際にC++コードを書いて、VRモーションコントローラを操作できるまでを書いていきたいと思います。

ログを出力する

まず始めに、現状を知る上で必要なログ出力から。
UEでログを出力するには以下のようにしります。

UE_LOG(LogTemp, Log, TEXT("hoge"));

また、オブジェクト名などを出力するには以下のようにします。

AActor *actor = GetOwner();
if (actor != nullptr)
{
    FString name = actor->GetName();
    UE_LOG(LogTemp, Log, TEXT("Name: %s"), *name);
}

TEXTに、引数を指定できるようにしつつ、*nameとして変数を指定します。

TEXTには、FStringではなくTCHAR*を指定しないとならないようです。
そのため、*FStringとして変換する必要があります。

変換関連については以下のTipsが詳しいです。

qiita.com

ログは、「アウトプットログ」ウィンドウに表示されます。

画面にログを表示する

コンソールだけでなく、ゲーム画面にもログを出力することができるようになっています。
画面へのログ出力にはGEngineを使うため、Engine.hをincludeする必要があります。

#include "Engine.h"

ログを出力するには以下のようにGEngine->AddOnScreenDebugMessageを利用します。

if (GEngine)
{
    GEngine->AddOnScreenDebugMessage(-1, 5.0f, FColor::Red, FString::Printf(TEXT("hoge")));
}

引数などの細かな項目はこちらの記事が詳しいです。

increment.hatenablog.com

MotionControllerComponentを使う

Oculusなどのモーションコントローラ(Oculus Touchなどのコントローラ)をC++から利用するには、適切なヘッダファイルなどのセットアップが必要となります。

ヘッダをinicludeする

まず、UMotionControllerComponentを利用するためにはMotionControllerComponent.hをincludeする必要があります。

ビルド設定にモジュールを設定する

UEに不慣れなので、地味にハマったところがここ。
どうやらUEでは、特定のC#スクリプトでビルド周りのセットアップなどが実行されているようで、それに対して適切に、利用するモジュールを伝えてやる必要があるようです。

具体的には、PROJECT_NAME.Build.csに適切に依存関係を設定してやる必要があります。
該当のファイルは、C++ファイルが生成されたフォルダに自動的に作られています。

e.g.) MyProjectというプロジェクトを作成した場合はMyProject/Source以下にあります。

該当のCSファイルに、以下のようにモジュール名を指定します。

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

using UnrealBuildTool;

public class MyProject : ModuleRules
{
    public MyProject(ReadOnlyTargetRules Target) : base(Target)
    {
        PCHUsage = PCHUsageMode.UseExplicitOrSharedPCHs;

        PublicDependencyModuleNames.AddRange(new string[] { "Core", "CoreUObject", "Engine", "InputCore" });

        PrivateDependencyModuleNames.AddRange(new string[] { "HeadMountedDisplay" , "SteamVR" });

        // Uncomment if you are using Slate UI
        // PrivateDependencyModuleNames.AddRange(new string[] { "Slate", "SlateCore" });

        // Uncomment if you are using online features
        // PrivateDependencyModuleNames.Add("OnlineSubsystem");

        // To include OnlineSubsystemSteam, add it to the plugins section in your uproject file with the Enabled attribute set to true
    }
}

デフォルトで生成されていた部分からの差分は以下の部分です。

PrivateDependencyModuleNames.AddRange(new string[] { "HeadMountedDisplay" , "SteamVR" });

です。
プロジェクト生成直後はここが空の配列になっているので、ここに依存関係を追加してやる必要があります。(HeadMountedDisplaySteamVRのふたつ)

これで無事、エラーが出ずにコンパイルすることができるようになります。

逆に、この設定をしていないと以下のようなエラーが出力されてコンパイルに失敗します。

error LNK2019: unresolved external symbol "private: static class UClass * __cdecl UMotionControllerComponent::GetPrivateStaticClass(void)" (?GetPrivateStaticClass@UMotionControllerComponent@@CAPEAVUClass@@XZ) referenced in function "public: __cdecl AVRPawn::AVRPawn(void)" (??0AVRPawn@@QEAA@XZ)

以上を踏まえた上で、最後、MotionControllerComponentをセットアップしていきます。

ということで、UMotionControllerComponentC++から設定するサンプルコード。

まずはヘッダ。

// VRPawn.h

#pragma once

#include "CoreMinimal.h"
#include "GameFramework/Pawn.h"
#include "MotionControllerComponent.h"
#include "VRPawn.generated.h"

UCLASS()
class MYPROJECT_API AVRPawn : public APawn
{
    GENERATED_BODY()

private:
    UMotionControllerComponent *_leftMotionController;
    UMotionControllerComponent *_rightMotionController;

public:
    // Sets default values for this pawn's properties
    AVRPawn();

protected:
    // Called when the game starts or when spawned
    virtual void BeginPlay() override;

public:
    // Called every frame
    virtual void Tick(float DeltaTime) override;

    // Called to bind functionality to input
    virtual void SetupPlayerInputComponent(class UInputComponent* PlayerInputComponent) override;
};

実装は以下になります。

// VRPawn.cpp

#include "VRPawn.h"
#include "Engine.h"

AVRPawn::AVRPawn()
{
    PrimaryActorTick.bCanEverTick = true;
    USceneComponent *root = CreateDefaultSubobject<USceneComponent>(TEXT("Root"));
    RootComponent = root;

    _leftMotionController = CreateDefaultSubobject<UMotionControllerComponent>(TEXT("MotionControllerLeft"));
    _rightMotionController = CreateDefaultSubobject<UMotionControllerComponent>(TEXT("MotionControllerRight"));
}

void AVRPawn::BeginPlay()
{
    Super::BeginPlay();
}

void AVRPawn::Tick(float DeltaTime)
{
    Super::Tick(DeltaTime);
}

// Called to bind functionality to input
void AVRPawn::SetupPlayerInputComponent(UInputComponent* PlayerInputComponent)
{
    Super::SetupPlayerInputComponent(PlayerInputComponent);
}

簡単なセットアップコードは以上です。

この状態でコンパイルし、シーンに配置すると以下のような状態になっていれば成功です。

f:id:edo_m18:20180207171131p:plain

これで、あとはOculusなどを起動するとOculus Touchなどの動きを反映してくれるようになります。

ただ、現状の状態はあくまでプログラム上認識させ、位置をアップデートする、というところまでなので、(当然ですが)なにかしらのモデル(メッシュ)を配置してあげないと動いていることが確認できないので、適当に好きなモデルなどを配置してください。

コンポーネントの概念

最後に少しだけコンポーネントの概念をメモっておきます。

最初、Unityのコンポーネントと同義だと思っていたので色々「?」になる状況があったのですが、少しだけUnityとは概念が違うようです。

大きな違いは、コンポーネントも階層構造を持てるということ。
Unityでは普通、GameObjectひとつに対してコンポーネントを追加していくように作成していくかと思います。

しかし、コンポーネント自体はネスト構造を持つことはできません。
もし持たせたい場合は、新しくGameObjectを作り、GameObject自体の入れ子を元にコンポーネントを配置していくことになるかと思います。

UEではコンポーネント自体にネスト構造を持たせることができるため、(自分のイメージでは)Unityのコンポーネントよりもより実体に近い形でインスタンス化されるもの、という認識です。

誤解を恐れずに言うと、UnityのGameObjectに、なにかひとつのコンポーネントを付けた(機能を持った)オブジェクト=UEのコンポーネント、という感じです。

こう把握してから一気に色々と理解が進んだのでメモとして残しておきます。

ハマった点

VRモードで動かすまでにいくつかハマった点があったのでメモ。

VRカメラの位置が指定した位置から開始しない

VRコンテンツ制作がメインなので、当然UEもそれを目的として始めました。
最初のごく簡単なプロジェクトを作成して、いざVRモードで見てみたらなぜかシーンビューのカメラ位置から開始されるという問題が。

結論から言うと、PawnのプロパティであるAuto Process PlayerDisableからPlayer0に変更することで直りました。
UEのドキュメントに載っているやつと載ってないのがあったので、地味にハマりました・・・。

便利拡張

UnityでC#書いているときは、///でコメントを、XMLのフォーマットで自動挿入してくれていたんですが、C++だとそれができないなーと思っていたら、拡張でまったく同じことをしてくれるものがありました。

jyn.jp


余談

ここからはVRコンテンツ開発とは全然関係なくなるので、完全に余談です。
公開されているUnreal Engine自体のソースコードからビルドしたエディタを使おうとして少しだけハマった点をメモ。

UE4を使うメリットのひとつとして、エンジンのソースコードが公開されていることが上げられます。
エンジンのソースコードを読むと、中でどういった処理がされているか、といったことを学ぶのに役立ちます。
またそれを見ておけば、もしエンジンのバグに遭遇したときにもそれを対処する方法が見つかるかもしれません。

ということで、ソースコードからビルドしてUE4を使ってみようと思ってやったところ、いくつか(環境依存の)問題が出てきたので、それをメモとして残しておこうと思います。

Visual Studioのセットアップでエラー

最初、UEをビルドするに当たって、まずプロジェクトファイルを生成する必要があります。
しかし、そのセットアップ用のバッチファイルを実行したところ、以下のようなエラーが表示されました。

ERROR: No 32-bit compiler toolchain found in C:\Program Files (x86)\Microsoft Visual Studio 14.0\VC\bin\cl.exe

調べていくと、Visual C++ Toolsがインストールされていないことが原因のよう。

f:id:edo_m18:20180204165901p:plain

ということで、さっそくインストール・・・と思ったら、セットアップ時にクラッシュするじゃありませんか。

まずはクラッシュした理由をさぐるべく、クラッシュログの場所を探す。
場所はMSDNFAQ:Where are the setup logs stored ?)に掲載されていました。

場所は%Temp%とのこと。
(自分の環境だとC:\Users\[USER_NAME]\AppData\Local\Temp

似たような質問も上がっていました。

Setup detected an issue during the operation. Please click below to check for a solution and help us to improve the setup experience. | Microsoft Developer

フォントが悪さをしていた

色々調べていくと、どうやらフォントが悪さをしているということのよう。
どうも、VSはフォントと相性が悪いらしく、Windows Presentation Foundation Font Cacheというサービスが起動しているとクラッシュするらしい。

ということで、さっそくそれを停止してみたものの、セットアップでのクラッシュは直らず。
どこかで見かけた(記事のURLがわからないんですが・・)、フォントをいったん別の場所にバックアップしておき、消せるやつだけすべて消してからやるとうまくいくよ、というのを思い出してやってみたところ、うまく行きました。

なので、もし似た現象で困っている人がいたら、フォントを消せるだけすべて消してみて(いくつかはワーニングが出て消せない)リトライしてみるとうまく行くかもしれません。

参考にした記事

qiita.com

docs.unrealengine.com

docs.unrealengine.com