zer0から始めるプログラミング生活

unreal EngineやUnityのTipsを書いていきます。

Meta Quest2で左右に別々の映像を表示する方法

この記事で伝えたいこと

Meta Quest 2で左右の目に別々の画像を映す方法

環境

Unity 2021.3.4f1
Meta Quest 2

手法

0.XR Interaction Toolkitを使うべきか

XR Interaction Toolkitについて、2022年4月時点では、各ヘッドセット固有の機能(たとえばQuestのハンドトラッキング)が使用できない、モーションコントローラーのグラフィックスを表示できない、パフォーマンス懸念があるといったデメリットがあります。詳細は下記を参照してください。
Unity VR開発メモ(XR Interaction Toolkit + OpenXR Plugin) - フレームシンセシス

1.Unity でプロジェクトを作成する

Unityの新規プロジェクトを「3D」テンプレートで作成する。

2.XR Interaction Toolkitをインストールする

1.Window > Package Managerを開きます。

2.左上の「+」からAdd package by nameで「com.unity.xr.interaction.toolkit」と入力し、インストールします。

3.XR Interaction Toolkit>Sample>Starter Assetsを「import」を押してインポートする。

3.シーンにカメラリグを作成する

次にシーンにカメラリグを作りますが、それに先立って、Preset Managerという機能を使用して、カメラリグのコントローラーの入力設定が自動的に行われるようにします。

1.Edit>Project Settings>Preset Managerを開く。

2.Add Default PresetでXR Controller (Action-based)を選択します。

3.“Left”、“Right"という文字列にXRI Default Left ControllerとXRI Default Right Controllerを割り当ててください。

4.Input Actionを有効

1.シーンに空のゲームオブジェクトを作成。

2.Component>Input>Input Action Managerをアタッチ。

3.XRI Default Input Actionsを設定。

5.OpenXR Pluginのセットアップ

1.Edit > Project Settings > XR Plugin Management を開いて「Install XR Plugin Management」ボタンを押す。

2.Plug-in Provides が表示されたら 「OpenXR」 にチェックを入れる。

6.XR Originの追加

Main CameraをXR Origin(VRカメラ)に変更します。

1.GameObject>XR>XR Originを選択する。

2.XR Originの子にMain Cameraがあることを確認する。

7.Camera設定&Plane追加

2つのカメラを用意して、Culling Maskという特定のオブジェクト(Plane)のみを映す機能を使って別々に映す。

1.Inspector>Layer>Add Layerで「Left Eye」と「Right Eye」を追加。Layerは何番でもよい。

2.Main CameraのCamera>Culling Maskを「Left Eye」のみに設定する。(Main cameraの名前もLeft Cameraと改名。)

3.Camera>Target Eyeを「Left」に設定する。

4.Left Cameraの子にPlane(GameObject>3D Object>Plane)を追加。座標はすべて同じにする。

5.PlaneのLayerを「Left Eye」に設定。

6.Left Cameraを複製して、右目用を作る。

7.右目用のカメラのCulling Maskを「Right Eye」のみ、Target Eyeを「Right」、PlaneのLayerを「Right Eye」にする。


8.Materialと画像の追加

1.Material(project window>右クリック>Create>Material)を2つ作成。(ここではMaterial01,Material02)

2.MaterialのShaderを両方ともDiffuseにする。(Shader>Legacy Shaders>Diffuse)

3.画像をプロジェクトに追加し、Texture Typeを「Sprite(2D and UI)」に変更。(ここではImage01,Image02)

4.それぞれのMaterialの「None(Texture)」部分に画像をドラッグ&ドロップ。(ここではMaterial01←Image01、Material02←Image02)

5.それぞれのMaterialをHierarchyのPlaneにドラッグ&ドロップ。(ここではLeft Plane←Material01、Right Plane←Material02)

【UnrealC++】⑩レベルブループリント【UE4】

C++側でレベルブループリントをいじる方法をメモ。

①クラスの作成

LevelScriptActorを親クラスとしてクラスを作成。
いじりたいレベル(マップ)を作成していない場合は作成してください。

②レベルブループリントを編集

いじりたいレベルのレベルブループリントを開きます。
f:id:bigden:20180227080244p:plain

「Class Settings→Class options→Parent Calss」を①で作成したものに変更してあげることでC++側でレベルブループリントをいじることが出来ます。
f:id:bigden:20180227080511p:plain

【Unreal C++】⑨モジュール 【UE4】

C++でコードを書く際、UE4の公式ドキュメントで調べていると思いますが、UnrealC++ではHeaderファイルをincludeするだけではだめみたいです。


以下によるとUE4C++コードは、「モジュール」と呼ばれる単位で、「UnrealBuildTool」を通してビルドされるそうです。なので必要に応じて追加してあげる必要があります。
[UE4] モジュールについて|株式会社ヒストリア

モジュールに関してUE4.15以降で仕様変更があったので以下を参照してください。
ちなみに自分の環境はver4.18.3です。
miyahuji111.hatenablog.com

追加方法

①[ProjectName].Build.cs

([ProjectFolder]/source/[ProjectName]/)にある[ProjectName].Build.csを開くと、以下のようになっていると思います。

using UnrealBuildTool;

public class hogeProject : ModuleRules
{
	public hogeProject(ReadOnlyTargetRules Target) : base(Target)
	{
		PCHUsage = PCHUsageMode.UseExplicitOrSharedPCHs;
	
		PublicDependencyModuleNames.AddRange(new string[] { "Core", "CoreUObject", "Engine", "InputCore" });

		PrivateDependencyModuleNames.AddRange(new string[] {  });
	}
}

②Moduleの追加

公式ドキュメントのAPIのリファレンスを見るとModuleの項目があり、
f:id:bigden:20180227074532p:plain

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

のnew string[] 以下の部分になければ "" で囲んで追加することでその関数を使用することが出来ます。

【Unreal C++】 ⑧Level Streaming 【UE4】

C++でLevel Streaming 使うときにちょっと躓いたのでメモ。

BPでのLevel Streamingは以下を参考に。
unrealengine.hatenablog.com

詳しくは以下に書いていますがロード後にすぐ表示しているので、今回はロード後すぐに表示しないパターンを書いておきます。
docs.unrealengine.com

ロード

ロード後すぐに表示しないパターンにするため
第3引数・・・読み込み後に表示を行うかどうか
第4引数・・・同期読み込みを行うかどうか
を両者ともfalseにしました。
docs.unrealengine.com
[UE4] レベルストリーミングについて|株式会社ヒストリア

マップ(サブレベル)表示

BPと大体同じで
第1引数・・・表示非表示をいじりたいマップ
第2引数・・・表示非表示のフラグ

UFunctionLibrary::beVisibleMap(MapName, true);

アンロード

関数については以下を参考に。
docs.unrealengine.com

マップ(サブレベル)非表示

UFunctionLibrary::beVisibleMap(MapName, false);

Tips

マップの遷移で分岐があったので2つのマップを同時にロードする必要があり、FLatentActionInfo型の変数を2つ用意するだけでいいのかなと思ったのですが、1つだけしかロードされませんでした。
調べてみると、UUIDで識別しているみたいで2つの変数に別々のUUIDを与えてあげることで2つロードすることができました。
ちなみにUUIDのデフォルトの値は-1みたいです。

【Unreal C++】⑦イベントディスパッチャー【UE4】

今回はイベントディスパッチャーについてです。
BPでの実装は以下を参考に。
unrealengine.hatenablog.com

動的マルチキャストデリケートでBP側のイベントディスパッチャーと同等の処理が出来るみたいです。

呼び出し側

DECLARE_DYNAMIC_MULTICAST_DELEGATEマクロを用いてクラスを作成します。
命名規則からクラス名には「F」の接頭辞が必要です。
また宣言の際にプロパティをBlueprintAssignableにする必要があります。

Call.h

#include "CoreMinimal.h"
#include "Call.generated.h"

DECLARE_DYNAMIC_MULTICAST_DELEGATE(FhogeDispather);

UCLASS()
class HogeProject_API ACall : public AActor
{
	GENERATED_BODY()
	
public:	
	// Sets default values for this actor's properties
       ACall();
protected:
	// Called when the game starts or when spawned
	virtual void BeginPlay() override;
public:	
	// Called every frame
	virtual void Tick(float DeltaTime) override;

	//Event Dispather(Multicast Delegate)
	UPROPERTY(BlueprintAssignable)
        FhogeDispather hogeDispather;
}


先ほど作成したクラスの変数のBroadcastメソッドを呼んであげます。

Call.cpp

#include "Call.h"

ACall::ACall()
{
}

ACall::HogeFunc()
{
hogeDispather.Broadcast();
}

受信側

Funcは呼び出したい処理です。
UFUNCTION()がついてないとクラッシュしました。

Receive.h

#include "CoreMinimal.h"
#include "Call.h"
#include "Engine.h" //GEngine
#include "Receive.generated.h"

UCLASS()
class HogeProject_API AReceive : public AActor
{
	GENERATED_BODY()
	
public:	
	// Sets default values for this actor's properties
       AReceive();
protected:
	// Called when the game starts or when spawned
	virtual void BeginPlay() override;
public:	
	// Called every frame
	virtual void Tick(float DeltaTime) override;

	//Event Dispather(Multicast Delegate)
        UFUNCTION()
        void Func();
        
}

BeginPlayでAddDynamicを用いて作成したクラスの変数と呼びたい処理をバインディングします。

Receive.cpp

#include "Receive.h"


// Sets default values
AReceive::AReceive()
{
}

// Called when the game starts or when spawned
void AReceive::BeginPlay()
{
	Super::BeginPlay();
	
	//Event Dispather
	for (TActorIterator<ACall>ActItr(GEngine->GameViewport->GetWorld()); ActItr; ++ActItr)
	{
		ACall* Call = *ActItr;
		Call->hogeDispather.AddDynamic(this, &AReceive::Func);
	}
}

void AReceive::Func()
{
//呼び出したい処理
}

以上で呼び出し(call)側のHogefunc関数が呼ばれるタイミングで受信(Receive)側のFunc関数が同時に呼ばれます。

【Unreal C++】⑥Interface【UE4】

今回はC++とブループリントでインターフェースを呼ぶ機会があったので備忘録として。
他にもいい方法があると思うのであくまで参考程度でお願いします。
エンジンはVer4.18.3を使用しています。
ちなみにブループリントインターフェースなどインターフェースについては以下のリンクを参照してください。
docs.unrealengine.com
unrealengine.hatenablog.com


①クラスの作成&宣言

おそらくBP側で作成したインターフェースはC++側に呼ぶことが出来ないのでC++側でインターフェースを作成します。C++でインターフェースを継承したクラスを作成します。
関数指定子をBlueprintImplementableEventでもいけるという情報もありましたが、参考したサイトの多くがBlueprintNativeEventを使用していたのでこちらを使用します。
unreallife.hatenablog.com


hogeInterface.h

#include "CoreMinimal.h"
#include "hogeInterface.generated.h"

UINTERFACE(Blueprintable)
class hogeProject_API UhogeInterface : public UInterface
{
	GENERATED_UINTERFACE_BODY()
};


class hogeProject_API IhogeInterface
{
	GENERATED_IINTERFACE_BODY()

public:
	UFUNCTION(BlueprintNativeEvent, BlueprintCallable)
		void hogeFunc();
}

hogeInterface.cpp

#include "hogeInterface.h"


UhogeInterface::UhogeInterface(const FObjectInitializer& ObjectInitializer)
	:Super(ObjectInitializer)
{
}

void IhogeInterface::hogeFunc_Implementation()
{
}

ブループリントでの実装

イベント側

「Event hogeFunc」を呼んであげます。

イベントを呼び出すためには「Class Settings→interface→Implemented Interface」に先ほど作成したhogeInterfaceを追加してコンパイルする必要があります。

呼び出し側

「hogeFunc(Message)」を呼んであげます。
Targetノードにはイテレータ、Get All Actors with Interface、Get All Widgets with Interfaceなどを用いて検索したイベント側のクラスを繋いであげます。
自分はレベルストリーミング使用しているのでGet All Widgets of Classの第2引数のTop Level Onlyはfalseにしました。

f:id:bigden:20180214202143p:plain

C++での実装

イベント側(hogeActor)

hogeInterface を継承したクラスを作成し、関数をOverrideしてあげます。

hogeActor.h

#include "CoreMinimal.h"
#include "GameFramework/Actor.h"
#include "hogeInterface.h”
#include "hogeActor.generated.h"

UCLASS()
class hogeProject_API AApple : public AActor, public IhogeInterface
{
	GENERATED_BODY()
public:
	//Interface 
	virtual void hogeFunc_Implementation() override;
};

hogeActor.cpp

void hogeActor::hogeFunc_Implementation()
{
//処理
}

呼び出し側

検索して配列に格納してRanged-forを用いて配列に対して処理をしてあげます。

TArray<AhogeActor*>hogeActAry;
UWorld* World=GEngine->GameViewport->GetWorld();
UGameplayStatics::GetAllActorsWithInterface(World,UhogeInterface::StaticClass(),hogeActAry);
for(AhogeActor* hogeAct : hogeActAry)
{
IhogeInterface::Execute_hogeFunc(hogeAct);
}


Get All Actors of Class、Get All Widgets of Classなどは遅い処理なのでTickなどで呼ばないでください。
docs.unrealengine.com

Markdown記法

GitBucketに挙げたリポジトリWikiを書く機会があったのでMarkdown記法をメモ。はてなブログでもMarkdownで書けるみたいです。

Markdown

とりあえず、GitBucketのwikiで使えるのだけ。ほかは↓を参考にしてください。

qiita.com

見出し

# 見出し大
## 見出し中
### 見出し小

記号の後ろに半角の空白が必須。

強調

hoge

**hoge**
__hoge__ (_(アンダースコア)2つ)

リスト

* hoge
+ hoge
- hoge

上下に空白行必須。
記号の後ろに半角の空白が必須。

ハイライト(コード挿入時など)

`hoge`

打消し線

hoge

~~hoge~~

リンク

zer0から始めるプログラミング生活

[リンクテキスト](URL)

画像埋め込み

f:id:bigden:20171226213050p:plain

![テキスト](画像のURL)

左揃え 中央 右揃え

hoge

hoge

hoge

|左揃え|中央|右揃え|
|:---|:---:|---:|
|hoge|hoge|hoge|