VR内でOutputLogを見よう

[Unreal Engine 4 アドベントカレンダー2016](http://qiita.com/advent-calendar/2016/ue4)の14日目の記事です。

#VR内でOutputLogを見る

VRアプリ開発で不便に思うことがあるので作ってみました。
OutputLogに出ている情報を、Blueprintから利用できるようにします。
ログをゲーム内の壁に表示してみたのがこちら↓（スクショはVRモードではないですが・・・）

![console2.png](https://qiita-image-store.s3.amazonaws.com/0/36233/57b62ecf-5aba-733a-4ce4-2831594076f8.png)

これでHMDをかぶって、モーションコントローラで操作をしながらOutputLogを確認できます。

##中身の解説

C++でログにアクセスする機能を追加します。
ちょっと煩雑になるので、先にこの機能を使って組んだBPを示します。
GameSingletonにログのバッファを持たせて、通知を受け取れるようにしています。

![totalBP.png](https://qiita-image-store.s3.amazonaws.com/0/36233/a77a439a-edca-ebd6-bdd6-905c3e1effe0.png)


##ログを取得、通知する

ログを拾うには FOutputDevice を継承したクラスを作成し、AddOutputDeviceで登録すれば完了です。
お手本としては OutputLogModule にある FOutputLogHistory を見るのが良いと思います。
今回は とりあえずGameSingletonにログバッファと通知機能を追加します。

うっかりすると、通知先でもOutputLog出力処理がある場合に無限ループになってエディタが停止してしまいます。
適当にフラグを追加して回避しています。（全体的にも適当ですが・・・）

```CPP:MySingleton.h
#pragma once
#include "UObject/NoExportTypes.h"
#include "MySingleton.generated.h"

DECLARE_DYNAMIC_MULTICAST_DELEGATE(FMySingletonOutputDeviceUpdate);

UCLASS(Blueprintable, BlueprintType)
class MYCONSOLETEST_API UMySingleton : public UObject
{
  GENERATED_UCLASS_BODY()

  // ログ保持のバッファ
  UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = MyConsole)
  TArray<FString> Buffer;

  // 登録解除用
  virtual void BeginDestroy() override;

  // バッファへログを追加する
  void addConsoleText(const TCHAR* Data);

  // ログ追加時の通知
  UPROPERTY(BlueprintAssignable)
  FMySingletonOutputDeviceUpdate OnOutputUpdate;

};
```

```cpp:MySingleton.cpp
// Fill out your copyright notice in the Description page of Project Settings.

#include "MyConsoleTest.h"
#include "MySingleton.h"

class MYCONSOLETEST_API FMyOutputDevice : public FOutputDevice
{
public:
  UMySingleton *mySingleton;
  FMyOutputDevice::FMyOutputDevice()
  {
    mySingleton = nullptr;
  }

  virtual void Serialize(const TCHAR* Data, ELogVerbosity::Type Verbosity, const class FName& Category, const double Time) override
  {
    if (!mySingleton) return;
    if (!mySingleton->IsValidLowLevel()) return;
    mySingleton->addConsoleText(Data);
  }

  virtual void Serialize(const TCHAR* Data, ELogVerbosity::Type Verbosity, const class FName& Category) override {
    if (!mySingleton) return;
    if (!mySingleton->IsValidLowLevel()) return;
      mySingleton->addConsoleText(Data);
  }

  virtual bool CanBeUsedOnAnyThread() const override { return false; }
};

// OutputDeviceは1つ
static FMyOutputDevice *d = nullptr;

UMySingleton::UMySingleton(const FObjectInitializer& ObjectInitializer)
  : Super(ObjectInitializer)
{
  // 登録
  if (d == nullptr) {
    d = new FMyOutputDevice;
    check(GLog);
    GLog->AddOutputDevice(d);
  }
  if (d) {
    d->mySingleton = this;
  }
}

void UMySingleton::BeginDestroy() {
  // 登録解除
  if (d) {
    d->mySingleton = nullptr;
  }
  Super::BeginDestroy();
}

void UMySingleton::addConsoleText(const TCHAR* Data) {
  Buffer.Insert(Data, 0);
  Buffer.SetNum(30);

  // 再起呼び出しを防ぐ
  static bool bFirst = false;
  if (bFirst == false) {
    bFirst = true;
    OnOutputUpdate.Broadcast();
    bFirst = false;
  }
}
```

作成したMySingletonを登録します。
エディタを起動して、↓のように設定します。
![singleton.png](https://qiita-image-store.s3.amazonaws.com/0/36233/eaaf5a64-258a-810d-a035-ba83f7c70a20.png)

##BPからアクセスできるようにする

このGameSingletonをBlueprintからアクセスできるようにします。
↓このノードです。
![GetSingleton.png](https://qiita-image-store.s3.amazonaws.com/0/36233/d3c3aed8-730e-d352-067a-85e6148e421f.png)

```cpp:MyFunctionLibrary.h
#pragma once
#include "MyFunctionLibrary.generated.h"


UCLASS()
class MYCONSOLETEST_API UMyFunctionLibrary : public UBlueprintFunctionLibrary
{
  GENERATED_BODY()
public:
  UFUNCTION(BlueprintPure, Category = "MyConsole")
  static UObject* GetSingleton(bool& IsValid);
};
```

```cpp:MyFunctionLibrary.cpp
#include "MyConsoleTest.h"
#include "Core.h"
#include "Engine.h"
#include "MyFunctionLibrary.h"

UObject* UMyFunctionLibrary::GetSingleton(bool& IsValid)
{
  IsValid = false;
  UObject *o = (GEngine->GameSingleton);

  if (!o) return NULL;
  if (!o->IsValidLowLevel()) return NULL;

  IsValid = true;
  return o;
}
```

#最後に
地味なVRネタになってしまいました。
VRアプリ開発者の方は普段どんな環境でデバッグしてるんでしょうかね？
良い機能や情報があったら知りたいです。

明日は、[monsho1977](http://qiita.com/monsho1977)さんの「エンジン拡張的な何かをやりたい」です。