diff --git a/Source/nDisplayExtensions/Private/DisplayClusterGameModeCAVE.cpp b/Source/nDisplayExtensions/Private/DisplayClusterGameModeCAVE.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..90f74ef07c5398e35cccdca6eba71554a06aa4f2
--- /dev/null
+++ b/Source/nDisplayExtensions/Private/DisplayClusterGameModeCAVE.cpp
@@ -0,0 +1,8 @@
+#include "DisplayClusterGameModeCAVE.h"
+#include "DisplayClusterPawnCAVE.h"
+
+ADisplayClusterGameModeCAVE::ADisplayClusterGameModeCAVE() : Super()
+{
+	if (!bIsDisplayClusterActive) return;
+	DefaultPawnClass = ADisplayClusterPawnCAVE::StaticClass();
+}
diff --git a/Source/nDisplayExtensions/Private/DisplayClusterPawnBase.cpp b/Source/nDisplayExtensions/Private/DisplayClusterPawnBase.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..270e3f17e86eca27e4a5ce3d22b4901766d7762c
--- /dev/null
+++ b/Source/nDisplayExtensions/Private/DisplayClusterPawnBase.cpp
@@ -0,0 +1,153 @@
+#include "DisplayClusterPawnBase.h"
+
+#include "Engine/World.h"
+#include "Game/IDisplayClusterGameManager.h"
+#include "GameFramework/WorldSettings.h"
+#include "DisplayClusterSceneComponentSyncParent.h"
+#include "DisplayClusterSettings.h"
+#include "DisplayClusterGameMode.h"
+#include "IDisplayCluster.h"
+#include "Kismet/GameplayStatics.h"
+
+ADisplayClusterPawnBase::ADisplayClusterPawnBase(const FObjectInitializer& ObjectInitializer) : Super(ObjectInitializer)
+{
+	MovementComponent                         = CreateDefaultSubobject<UFloatingPawnMovement>     (TEXT("MovementComponent0"));
+	MovementComponent->UpdatedComponent       = RootComponent;
+
+	RotatingComponent                         = CreateDefaultSubobject<URotatingMovementComponent>(TEXT("RotatingComponent0"));
+	RotatingComponent->UpdatedComponent       = RootComponent;
+	RotatingComponent->bRotationInLocalSpace  = true;
+	RotatingComponent->PivotTranslation       = FVector::ZeroVector;
+	RotatingComponent->RotationRate           = FRotator::ZeroRotator;
+
+	RotatingComponent2                        = CreateDefaultSubobject<URotatingMovementComponent>(TEXT("RotatingComponent1"));
+	RotatingComponent2->UpdatedComponent      = RootComponent;
+	RotatingComponent2->bRotationInLocalSpace = false;
+	RotatingComponent2->PivotTranslation      = FVector::ZeroVector;
+	RotatingComponent2->RotationRate          = FRotator::ZeroRotator;
+
+	BaseTurnRate   = 45.f;
+	BaseLookUpRate = 45.f;
+}
+
+void ADisplayClusterPawnBase::MoveForward (float Value)
+{
+  if (Value == 0.f) return;
+	AddMovementInput((TranslationDirection ? TranslationDirection : RootComponent)->GetForwardVector(), Value);
+}
+void ADisplayClusterPawnBase::MoveRight   (float Value)
+{
+  if (Value == 0.f) return;
+  AddMovementInput((TranslationDirection ? TranslationDirection : RootComponent)->GetRightVector  (), Value);
+}
+void ADisplayClusterPawnBase::MoveUp      (float Value)
+{
+  if (Value == 0.f) return;
+  AddMovementInput((TranslationDirection ? TranslationDirection : RootComponent)->GetUpVector     (), Value);
+}
+void ADisplayClusterPawnBase::TurnAtRate  (float Rate )
+{
+	if (IDisplayCluster::Get().GetOperationMode() == EDisplayClusterOperationMode::Cluster)
+	{
+    if (!RotatingComponent->UpdatedComponent) return;
+		auto Manager = IDisplayCluster::Get().GetGameMgr();
+    if (!Manager) return;
+    auto Camera = Manager->GetActiveCamera();
+    if (!Camera ) return;
+
+		const FTransform TransformToRotate  = RotatingComponent->UpdatedComponent->GetComponentTransform();
+		const FVector    RotateAroundPivot  = TransformToRotate.InverseTransformPositionNoScale(Camera->GetComponentLocation());
+		RotatingComponent->PivotTranslation = RotateAroundPivot;
+		RotatingComponent->RotationRate     = FRotator(RotatingComponent->RotationRate.Pitch, Rate * BaseTurnRate, 0.f);
+	}
+  else if (Rate != 0.f)
+		AddControllerYawInput(BaseTurnRate * Rate * GetWorld()->GetDeltaSeconds() * CustomTimeDilation);
+}
+void ADisplayClusterPawnBase::TurnAtRate2 (float Rate )
+{
+	if (IDisplayCluster::Get().GetOperationMode() == EDisplayClusterOperationMode::Cluster)
+	{
+    if (!RotatingComponent2->UpdatedComponent) return;
+    auto Manager = IDisplayCluster::Get().GetGameMgr();
+    if (!Manager) return;
+    auto Camera  = Manager->GetActiveCamera();
+    if (!Camera ) return;
+
+		const FTransform TransformToRotate   = RotatingComponent2->UpdatedComponent->GetComponentTransform();
+		const FVector    RotateAroundPivot   = TransformToRotate.InverseTransformPositionNoScale(Camera->GetComponentLocation());
+		RotatingComponent2->PivotTranslation = RotateAroundPivot;
+		RotatingComponent2->RotationRate     = FRotator(RotatingComponent2->RotationRate.Pitch, Rate * BaseTurnRate, 0.f);
+	}
+	else if (Rate != 0.f)
+		AddControllerYawInput(BaseTurnRate * Rate * GetWorld()->GetDeltaSeconds() * CustomTimeDilation);
+}
+void ADisplayClusterPawnBase::LookUpAtRate(float Rate )
+{
+	if (IDisplayCluster::Get().GetOperationMode() == EDisplayClusterOperationMode::Cluster)
+	{
+		//@note: usually CAVE-like systems don't use roll and pitch rotation since it can cause dizziness.
+#if 0
+		//@todo: rotate around active camera
+    auto Manager = IDisplayCluster::Get().GetGameMgr();
+    if (!Manager) return;
+    auto Camera = Manager->GetActiveCamera();
+    if (!Camera ) return;
+
+		RotatingComponent->bRotationInLocalSpace = true;
+		RotatingComponent->PivotTranslation      = FVector::ZeroVector;
+		
+#endif
+	}
+	else if (Rate != 0.f)
+	{
+		AddControllerPitchInput(BaseTurnRate * Rate * GetWorld()->GetDeltaSeconds() * CustomTimeDilation);
+	}
+}
+
+void                    ADisplayClusterPawnBase::BeginPlay           ()
+{
+	Super::BeginPlay();
+
+	if (!IDisplayCluster::Get().IsModuleInitialized() || !IDisplayCluster::Get().IsAvailable()) return;
+
+	auto IsCluster = (IDisplayCluster::Get().GetOperationMode() == EDisplayClusterOperationMode::Cluster);
+	bUseControllerRotationYaw   = !IsCluster;
+	bUseControllerRotationPitch = !IsCluster;
+	bUseControllerRotationRoll  = !IsCluster;
+
+  TArray<AActor*> SettingsActors;
+  UGameplayStatics::GetAllActorsOfClass(GetWorld(), ADisplayClusterSettings::StaticClass(), SettingsActors);
+  if (SettingsActors.Num() == 0) return;
+
+  ADisplayClusterSettings* Settings = Cast<ADisplayClusterSettings>(SettingsActors[0]);
+  MovementComponent->MaxSpeed     = Settings->MovementMaxSpeed;
+  MovementComponent->Acceleration = Settings->MovementAcceleration;
+  MovementComponent->Deceleration = Settings->MovementDeceleration;
+  MovementComponent->TurningBoost = Settings->MovementTurningBoost;
+  BaseTurnRate                    = Settings->RotationSpeed;
+  BaseLookUpRate                  = Settings->RotationSpeed;
+}
+void                    ADisplayClusterPawnBase::Tick                (float DeltaSeconds)
+{
+	Super::Tick(DeltaSeconds);
+	const float Mult = GetWorld()->GetWorldSettings()->WorldToMeters / 100.f;
+	SetActorScale3D(FVector(Mult, Mult, Mult));
+}
+UPawnMovementComponent* ADisplayClusterPawnBase::GetMovementComponent() const
+{
+  return MovementComponent;
+}
+
+void ADisplayClusterPawnBase::SetupPlayerInputComponent(UInputComponent* PlayerInputComponent)
+{
+	check(PlayerInputComponent);
+	Super::SetupPlayerInputComponent(PlayerInputComponent);
+	if (PlayerInputComponent)
+	{
+		PlayerInputComponent->BindAxis("MoveForward", this, &ADisplayClusterPawnBase::MoveForward );
+		PlayerInputComponent->BindAxis("MoveRight"  , this, &ADisplayClusterPawnBase::MoveRight   );
+		PlayerInputComponent->BindAxis("MoveUp"     , this, &ADisplayClusterPawnBase::MoveUp      );
+		PlayerInputComponent->BindAxis("TurnRate"   , this, &ADisplayClusterPawnBase::TurnAtRate2 );
+		PlayerInputComponent->BindAxis("LookUpRate" , this, &ADisplayClusterPawnBase::LookUpAtRate);
+	}
+}
diff --git a/Source/nDisplayExtensions/Private/DisplayClusterPawnCAVE.cpp b/Source/nDisplayExtensions/Private/DisplayClusterPawnCAVE.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..a50b178c960acc9bd21ef00ba07c70751241b02f
--- /dev/null
+++ b/Source/nDisplayExtensions/Private/DisplayClusterPawnCAVE.cpp
@@ -0,0 +1,17 @@
+#include "DisplayClusterPawnCAVE.h"
+
+#include "Game/IDisplayClusterGameManager.h"
+#include "IDisplayCluster.h"
+
+void ADisplayClusterPawnCAVE::BeginPlay()
+{
+  Flystick = IDisplayCluster::Get().GetGameMgr()->GetNodeById("flystick_tracked");
+}
+void ADisplayClusterPawnCAVE::MoveForward(float value)
+{
+  if (!Flystick)
+    Flystick = IDisplayCluster::Get().GetGameMgr()->GetNodeById("flystick_tracked");
+  if (!Flystick || value == 0.0f)
+    return;
+  AddMovementInput(Flystick->GetForwardVector(), value);
+}
diff --git a/Source/nDisplayExtensions/Public/DisplayClusterGameModeCAVE.h b/Source/nDisplayExtensions/Public/DisplayClusterGameModeCAVE.h
new file mode 100644
index 0000000000000000000000000000000000000000..2c68f15d371f05d18fb4f6fc97196b3e772fe0b6
--- /dev/null
+++ b/Source/nDisplayExtensions/Public/DisplayClusterGameModeCAVE.h
@@ -0,0 +1,14 @@
+#pragma once
+
+#include "CoreMinimal.h"
+#include "DisplayClusterGameMode.h"
+#include "DisplayClusterGameModeCAVE.generated.h"
+
+UCLASS()
+class NDISPLAYEXTENSIONS_API ADisplayClusterGameModeCAVE : public ADisplayClusterGameMode
+{
+	GENERATED_BODY()
+	
+public:
+	ADisplayClusterGameModeCAVE();
+};
\ No newline at end of file
diff --git a/Source/nDisplayExtensions/Public/DisplayClusterPawnBase.h b/Source/nDisplayExtensions/Public/DisplayClusterPawnBase.h
new file mode 100644
index 0000000000000000000000000000000000000000..5eb76f52ff42409c369b56f14f3ff7f3e0403217
--- /dev/null
+++ b/Source/nDisplayExtensions/Public/DisplayClusterPawnBase.h
@@ -0,0 +1,37 @@
+#pragma once
+
+#include "Components/InputComponent.h"
+#include "GameFramework/FloatingPawnMovement.h"
+#include "GameFramework/PawnMovementComponent.h"
+#include "GameFramework/RotatingMovementComponent.h"
+#include "CoreMinimal.h"
+#include "DisplayClusterPawn.h"
+#include "DisplayClusterPawnBase.generated.h"
+
+UCLASS()
+class NDISPLAYEXTENSIONS_API ADisplayClusterPawnBase : public ADisplayClusterPawn
+{
+  GENERATED_UCLASS_BODY()
+
+public:
+  UFUNCTION(BlueprintCallable, Category = "Pawn") virtual void MoveForward (float Value);
+  UFUNCTION(BlueprintCallable, Category = "Pawn") virtual void MoveRight   (float Value);
+  UFUNCTION(BlueprintCallable, Category = "Pawn") virtual void MoveUp      (float Value);
+  UFUNCTION(BlueprintCallable, Category = "Pawn") virtual void TurnAtRate  (float Rate );
+  UFUNCTION(BlueprintCallable, Category = "Pawn") virtual void TurnAtRate2 (float Rate );
+  UFUNCTION(BlueprintCallable, Category = "Pawn") virtual void LookUpAtRate(float Rate );
+
+  virtual void                    BeginPlay           ()                         override;
+  virtual void                    Tick                (float DeltaSeconds)       override;
+  virtual UPawnMovementComponent* GetMovementComponent()                   const override;
+
+  UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Pawn") float BaseTurnRate  ;
+  UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Pawn") float BaseLookUpRate;
+
+protected:
+  virtual void SetupPlayerInputComponent(UInputComponent* PlayerInputComponent) override;
+
+  UPROPERTY(Category = Pawn, VisibleAnywhere, BlueprintReadOnly, meta = (AllowPrivateAccess = "true")) UFloatingPawnMovement*      MovementComponent ;
+  UPROPERTY(Category = Pawn, VisibleAnywhere, BlueprintReadOnly, meta = (AllowPrivateAccess = "true")) URotatingMovementComponent* RotatingComponent ;
+  UPROPERTY(Category = Pawn, VisibleAnywhere, BlueprintReadOnly, meta = (AllowPrivateAccess = "true")) URotatingMovementComponent* RotatingComponent2;
+};
\ No newline at end of file
diff --git a/Source/nDisplayExtensions/Public/DisplayClusterPawnCAVE.h b/Source/nDisplayExtensions/Public/DisplayClusterPawnCAVE.h
new file mode 100644
index 0000000000000000000000000000000000000000..48c64c90681f0aadf6b293247bfc3a9df5eb5b0b
--- /dev/null
+++ b/Source/nDisplayExtensions/Public/DisplayClusterPawnCAVE.h
@@ -0,0 +1,19 @@
+#pragma once
+
+#include "CoreMinimal.h"
+#include "DisplayClusterPawnBase.h"
+#include "DisplayClusterSceneComponent.h"
+#include "DisplayClusterPawnCAVE.generated.h"
+
+UCLASS()
+class NDISPLAYEXTENSIONS_API ADisplayClusterPawnCAVE : public ADisplayClusterPawnBase
+{
+  GENERATED_BODY()
+
+public:
+  virtual void BeginPlay() override;
+  virtual void MoveForward(float value) override;
+
+  UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="Pawn")
+  UDisplayClusterSceneComponent* Flystick = nullptr;
+};