diff --git a/Content/M_PointOfInterest.uasset b/Content/M_PointOfInterest.uasset new file mode 100644 index 0000000000000000000000000000000000000000..a8139e4ff912b49947eea83e25b273d05b8715fc Binary files /dev/null and b/Content/M_PointOfInterest.uasset differ diff --git a/Content/location_marker.uasset b/Content/location_marker.uasset new file mode 100644 index 0000000000000000000000000000000000000000..9596735fa4f3699f4a0effa1340d9ad94508e6a9 Binary files /dev/null and b/Content/location_marker.uasset differ diff --git a/Source/RWTHVRQuickStart/Private/PointOfInterest.cpp b/Source/RWTHVRQuickStart/Private/PointOfInterest.cpp new file mode 100644 index 0000000000000000000000000000000000000000..d467d4b630188f52371d84cb5670cbc8937c0f37 --- /dev/null +++ b/Source/RWTHVRQuickStart/Private/PointOfInterest.cpp @@ -0,0 +1,30 @@ +#include "PointOfInterest.h" + +#include "Logging/StructuredLog.h" +#include "Components/BillboardComponent.h" + +DEFINE_LOG_CATEGORY(POILog) + +APointOfInterest::APointOfInterest() +{ + UE_LOGFMT(POILog, Verbose, "POI instantiated"); + + SetRootComponent(CreateDefaultSubobject<USceneComponent>("SceneComponent")); + + auto Billboard = CreateDefaultSubobject<UBillboardComponent>("BillboardComponent"); + + static ConstructorHelpers::FObjectFinder<UTexture2D> LocationMarker(TEXT("/Script/Engine.Texture2D'/RWTHVRQuickStart/location_marker.location_marker'")); + if(LocationMarker.Succeeded()) + { + Billboard->SetSprite(LocationMarker.Object); + } + else + { + UE_LOGFMT(POILog, Verbose, "POILog: Could not find location marker Texture. The Asset might have moved."); + } + Billboard->SetupAttachment(RootComponent); + + Billboard->SetHiddenInGame(true); + Billboard->SetWorldScale3D(FVector(0.1f, 0.1f, 0.1f)); +} + diff --git a/Source/RWTHVRQuickStart/Private/PointOfInterestManager.cpp b/Source/RWTHVRQuickStart/Private/PointOfInterestManager.cpp new file mode 100644 index 0000000000000000000000000000000000000000..427c8189c140cedf758c64cedd108cb707c8e4fe --- /dev/null +++ b/Source/RWTHVRQuickStart/Private/PointOfInterestManager.cpp @@ -0,0 +1,236 @@ +#include "PointOfInterestManager.h" + +#include "PointOfInterest.h" +#include "GameFramework/PlayerStart.h" +#include "Kismet/GameplayStatics.h" +#include "Logging/StructuredLog.h" + +DEFINE_LOG_CATEGORY(POIManagerLog); + +APointOfInterestManager::APointOfInterestManager() +{ + PrimaryActorTick.bCanEverTick = true; + PrimaryActorTick.bStartWithTickEnabled = true; + PrimaryActorTick.SetTickFunctionEnable(true); + + POIs = TArray<APointOfInterest*>(); + + SplineComponent = CreateDefaultSubobject<USplineComponent>("SplineComponent"); + SplineComponent->SetupAttachment(RootComponent.Get()); +} + +void APointOfInterestManager::TickActor(float DeltaTime, enum ELevelTick TickType, FActorTickFunction& ThisTickFunction) +{ + SplineComponent->Duration = SplineComponent->GetSplineLength() / CameraSpeed; + if (IsRidingCamera) + { + ProgressCameraRide(); + } +} + +void APointOfInterestManager::ResetToPlayerStart() +{ + APawn* Pawn = UGameplayStatics::GetPlayerPawn(GetWorld(), 0); + if (!Pawn) + { + UE_LOGFMT(POIManagerLog, Warning, "Attempted to move player pawn to PlayerStart but no pawn found to move."); + return; + } + TArray<AActor*> AllPlayerStarts; + UGameplayStatics::GetAllActorsOfClass(GetWorld(), APlayerStart::StaticClass(), AllPlayerStarts); + if (AllPlayerStarts.IsEmpty()) + { + UE_LOGFMT(POIManagerLog, Warning, "Attempted to move player pawn to PlayerStart but no PlayerStart found to move."); + return; + } + AActor* PlayerStart = AllPlayerStarts[0]; + Pawn->Controller->SetControlRotation(PlayerStart->GetActorRotation()); + Pawn->SetActorLocationAndRotation(PlayerStart->GetActorLocation(), PlayerStart->GetActorRotation()); +} + +void APointOfInterestManager::AddPointOfInterest() +{ + APointOfInterest* POI = static_cast<APointOfInterest*>(GetWorld()->SpawnActor(APointOfInterest::StaticClass())); + + POI->GetRootComponent()->TransformUpdated.AddLambda([this](USceneComponent*, EUpdateTransformFlags, ETeleportType) + { + UpdateSpline(); + }); + + POIs.Add(POI); + + UpdateSpline(); +} + +void APointOfInterestManager::VisitNextPointOfInterest() +{ + if (CurrentPOIIndex >= GetPointOfInterestCount() - 1) + { + CurrentPOIIndex = 0; + } + else + { + ++CurrentPOIIndex; + } + VisitPointOfInterest(POIs[CurrentPOIIndex]); +} + +void APointOfInterestManager::VisitPreviousPointOfInterest() +{ + if (CurrentPOIIndex <= 0) + { + CurrentPOIIndex = GetPointOfInterestCount() - 1; + } + else + { + --CurrentPOIIndex; + } + VisitPointOfInterest(POIs[CurrentPOIIndex]); +} + +void APointOfInterestManager::VisitPointOfInterestByIndex(int index) +{ + VisitPointOfInterest(POIs[index]); +} + + +void APointOfInterestManager::VisitPointOfInterest(APointOfInterest* POI) const +{ + APawn* Pawn = UGameplayStatics::GetPlayerPawn(GetWorld(), 0); + if (!Pawn) + { + UE_LOGFMT(POIManagerLog, Warning, "Attempted to move player pawn but no pawn found to move."); + return; + } + FVector Location = POI->GetActorLocation(); + FRotator Rotation = POI->GetActorRotation(); + Rotation.Pitch = 0; + Rotation.Roll = 0; + + Pawn->Controller->SetControlRotation(Rotation); + Pawn->SetActorLocationAndRotation(Location, Rotation); +} + +int APointOfInterestManager::GetPointOfInterestCount() +{ + return POIs.Num(); +} + +void APointOfInterestManager::StartCameraRide() +{ + CameraRideStart = UGameplayStatics::GetTimeSeconds(this); + CameraRideEnd = CameraRideStart + SplineComponent->Duration; + CurrentUpdate = 0; + DurationInUpdates = static_cast<int>(SplineComponent->Duration * CameraRideSecondsToUpdates); + + IsRidingCamera = true; +} + +void APointOfInterestManager::StopCameraRide() +{ + CameraRideEnded(); +} + + +void APointOfInterestManager::UpdateSpline() +{ + for (int i = 0; i < GetPointOfInterestCount(); ++i) + { + if (POIs[i] == nullptr) + { + POIs.RemoveAt(i); + SplineComponent->RemoveSplinePoint(i); + --i; + } + else if (!POIs[i]->GetRootComponent()->TransformUpdated.IsBound()) + { + POIs[i]->GetRootComponent()->TransformUpdated.AddLambda( + [this](USceneComponent*, EUpdateTransformFlags, ETeleportType) + { + UpdateSpline(); + }); + } + } + + int POIsNum = GetPointOfInterestCount(); + int SplinePointsNum = SplineComponent->GetNumberOfSplinePoints(); + + if (POIsNum > SplinePointsNum) + { + for (int i = SplinePointsNum; i < POIsNum; ++i) + { + SplineComponent->AddSplinePointAtIndex(FVector::Zero(), i, ESplineCoordinateSpace::World); + } + } + else if (POIsNum < SplinePointsNum) + { + for (int i = SplinePointsNum - 1; i >= POIsNum; --i) + { + SplineComponent->RemoveSplinePoint(i); + } + } + + for (int i = 0; i < GetPointOfInterestCount(); ++i) + { + APointOfInterest* POI = POIs[i]; + FVector POIPosition = POI->GetActorLocation(); + FRotator POIRotation = POI->GetActorRotation(); + SplineComponent->SetLocationAtSplinePoint(i, POIPosition, ESplineCoordinateSpace::World); + SplineComponent->SetRotationAtSplinePoint(i, POIRotation, ESplineCoordinateSpace::World); + } +} + +void APointOfInterestManager::ProgressCameraRide() +{ + double Current = UGameplayStatics::GetTimeSeconds(this); + float SecondsSinceStart = Current - CameraRideStart; + CurrentUpdate++; + + APawn* Pawn = UGameplayStatics::GetPlayerPawn(GetWorld(), 0); + if (!Pawn) + { + UE_LOGFMT(POIManagerLog, Warning, "Attempted to move player pawn but no pawn found to move."); + return; + } + if (MakeCameraRideProgressionConstantBetweenTicks) + { + if (CurrentUpdate < DurationInUpdates) + { + float CurrentTime = 1.0f * CurrentUpdate / CameraRideSecondsToUpdates; + FVector Location = SplineComponent->GetLocationAtTime(CurrentTime, ESplineCoordinateSpace::World); + FRotator Rotation = SplineComponent->GetRotationAtTime(CurrentTime, ESplineCoordinateSpace::World); + Rotation.Pitch = 0; + Rotation.Roll = 0; + + Pawn->Controller->SetControlRotation(Rotation); + Pawn->SetActorLocationAndRotation(Location, Rotation); + } + else + { + CameraRideEnded(); + } + } + else + { + if (Current < CameraRideEnd) + { + FVector Location = SplineComponent->GetLocationAtTime(SecondsSinceStart, ESplineCoordinateSpace::World); + FRotator Rotation = SplineComponent->GetRotationAtTime(SecondsSinceStart, ESplineCoordinateSpace::World); + Rotation.Pitch = 0; + Rotation.Roll = 0; + + Pawn->Controller->SetControlRotation(Rotation); + Pawn->SetActorLocationAndRotation(Location, Rotation); + } + else + { + CameraRideEnded(); + } + } +} + +void APointOfInterestManager::CameraRideEnded() +{ + IsRidingCamera = false; + VisitPointOfInterestByIndex(GetPointOfInterestCount() - 1); +} diff --git a/Source/RWTHVRQuickStart/Public/PointOfInterest.h b/Source/RWTHVRQuickStart/Public/PointOfInterest.h new file mode 100644 index 0000000000000000000000000000000000000000..71f2139f90008f671978a255d81bece406ad6513 --- /dev/null +++ b/Source/RWTHVRQuickStart/Public/PointOfInterest.h @@ -0,0 +1,16 @@ +#pragma once + +#include "CoreMinimal.h" + +#include "PointOfInterest.generated.h" + +DECLARE_LOG_CATEGORY_EXTERN(POILog, All, All) + +UCLASS() +class APointOfInterest : public AActor +{ + GENERATED_BODY() + +public: + APointOfInterest(); +}; diff --git a/Source/RWTHVRQuickStart/Public/PointOfInterestManager.h b/Source/RWTHVRQuickStart/Public/PointOfInterestManager.h new file mode 100644 index 0000000000000000000000000000000000000000..a5342173f3f4a4a421e7f0b38d7fe89e4dbbe11b --- /dev/null +++ b/Source/RWTHVRQuickStart/Public/PointOfInterestManager.h @@ -0,0 +1,83 @@ +#pragma once + +#include "CoreMinimal.h" +#include "Components/SplineComponent.h" + +#include "PointOfInterestManager.generated.h" + +DECLARE_LOG_CATEGORY_EXTERN(POIManagerLog, All, All); + +class APointOfInterest; +class FCameraRide; + +UCLASS(Blueprintable) +class APointOfInterestManager : public AActor +{ + GENERATED_BODY() + +public: + APointOfInterestManager(); + + virtual void TickActor(float DeltaTime, enum ELevelTick TickType, FActorTickFunction& ThisTickFunction) override; + + UFUNCTION(CallInEditor, BlueprintCallable) + void ResetToPlayerStart(); + + UFUNCTION(CallInEditor, BlueprintCallable, Category="Point Of Interest Manager") + void AddPointOfInterest(); + + UFUNCTION(CallInEditor, BlueprintCallable, Category="Point Of Interest Manager") + void VisitNextPointOfInterest(); + + UFUNCTION(CallInEditor, BlueprintCallable, Category="Point Of Interest Manager") + void VisitPreviousPointOfInterest(); + + UFUNCTION(BlueprintCallable, Category="Point Of Interest Manager") + void VisitPointOfInterestByIndex(int index); + UFUNCTION(BlueprintCallable, Category="Point Of Interest Manager") + void VisitPointOfInterest(APointOfInterest* POI) const; + + UFUNCTION(CallInEditor, BlueprintCallable, Category="Point Of Interest Manager") + void StartCameraRide(); + UFUNCTION(CallInEditor, BlueprintCallable, Category="Point Of Interest Manager") + void StopCameraRide(); + + UFUNCTION(BlueprintCallable, Category="Point Of Interest Manager") + int GetPointOfInterestCount(); + + /** + * Whether the camera should progress a constant distance between ticks (true) or a variable distance based on frametimes (false). + * This should be set to true if you expect long/inconsistent frame times or for e.g. PSO gathering. + */ + UPROPERTY(EditAnywhere, Category="Point Of Interest Manager") + bool MakeCameraRideProgressionConstantBetweenTicks = true; + + /** + * Only relevant when MakeCameraRideProgressionConstantBetweenTicks = true. + * The conversion rate of how many ticks the camera should update to equal the distance traveled during 1 second. + */ + UPROPERTY(EditAnywhere, Category="Point Of Interest Manager", + meta = (EditCondition = "MakeCameraRideProgressionConstantBetweenTicks")) + int CameraRideSecondsToUpdates = 60; + +protected: + UPROPERTY(EditAnywhere) + TArray<APointOfInterest*> POIs; + + int CurrentPOIIndex = 0; + +private: + UFUNCTION(CallInEditor, BlueprintCallable, Category="Point Of Interest Manager") + void UpdateSpline(); + + void ProgressCameraRide(); + void CameraRideEnded(); + + USplineComponent* SplineComponent; + + UPROPERTY(EditAnywhere, meta=(UIMin = 0.0, UIMax = 500)) + float CameraSpeed = 100; + bool IsRidingCamera; + float CameraRideStart, CameraRideEnd; + int DurationInUpdates, CurrentUpdate; +}; diff --git a/Source/RWTHVRQuickStart/RWTHVRQuickStart.Build.cs b/Source/RWTHVRQuickStart/RWTHVRQuickStart.Build.cs index 894b0c6ab811c64470fa1dad70e6fda8b7a0553f..21f957f00a2fc6d0887f4f42b50215e6cae15878 100644 --- a/Source/RWTHVRQuickStart/RWTHVRQuickStart.Build.cs +++ b/Source/RWTHVRQuickStart/RWTHVRQuickStart.Build.cs @@ -25,7 +25,7 @@ public class RWTHVRQuickStart : ModuleRules PublicDependencyModuleNames.AddRange( new string[] { - "Core", + "Core", "Engine", "Engine", // ... add other public dependencies that you statically link with here ... } ); @@ -37,7 +37,7 @@ public class RWTHVRQuickStart : ModuleRules "CoreUObject", "Engine", "Slate", - "SlateCore", + "SlateCore", "RWTHVRToolkit", "RWTHVRToolkit", // ... add private dependencies that you statically link with here ... } );