Skip to content
Snippets Groups Projects
Commit b7068808 authored by David Gilbert's avatar David Gilbert :bug:
Browse files

refactor(interaction): Restructures the widget interaction to work with the current system

parent 22e13beb
No related branches found
No related tags found
1 merge request!52refactor(pawn, interaction): removes legacy fire & toggle mode input actions
No preview for this file type
File added
File added
File added
// Fill out your copyright notice in the Description page of Project Settings.
#include "Interaction/Interactors/VRWidgetInteractionComponent.h"
#include "EnhancedInputComponent.h"
#include "EnhancedInputSubsystems.h"
#include "Interaction/Interactors/GrabComponent.h"
#include "Misc/Optional.h"
#include "Utility/VirtualRealityUtilities.h"
UVRWidgetInteractionComponent::UVRWidgetInteractionComponent()
{
PrimaryComponentTick.bCanEverTick = true;
}
void UVRWidgetInteractionComponent::SetupPlayerInput(UInputComponent* PlayerInputComponent)
{
IInputExtensionInterface::SetupPlayerInput(PlayerInputComponent);
const APawn* Pawn = Cast<APawn>(GetOwner());
if (!Pawn)
return;
auto* InputSubsystem = GetEnhancedInputLocalPlayerSubsystem(Pawn);
if (!InputSubsystem)
return;
// Because we cannot use the regular debug ray (only works in editor), we set up our own (mesh) ray.
SetupInteractionRay();
// add Input Mapping context
InputSubsystem->AddMappingContext(IMCWidgetInteraction, 0);
UEnhancedInputComponent* EI = Cast<UEnhancedInputComponent>(Pawn->InputComponent);
if (EI == nullptr)
return;
EI->BindAction(WidgetClickInputAction, ETriggerEvent::Started, this, &UVRWidgetInteractionComponent::OnBeginClick);
EI->BindAction(WidgetClickInputAction, ETriggerEvent::Completed, this, &UVRWidgetInteractionComponent::OnEndClick);
}
// Called every frame
void UVRWidgetInteractionComponent::TickComponent(float DeltaTime, ELevelTick TickType,
FActorComponentTickFunction* ThisTickFunction)
{
Super::TickComponent(DeltaTime, TickType, ThisTickFunction);
// only raytrace for targetable objects if user wants to enable this feature
if (!bCanRaytraceEveryTick)
return;
// Disable/enable ray on hover
if (InteractionRayVisibility == EInteractionRayVisibility::VisibleOnHoverOnly)
{
if (IsOverInteractableWidget())
{
InteractionRay->SetVisibility(true);
}
else
{
InteractionRay->SetVisibility(false);
}
}
}
void UVRWidgetInteractionComponent::SetInteractionRayVisibility(EInteractionRayVisibility NewVisibility)
{
InteractionRayVisibility = NewVisibility;
InteractionRay->SetVisibility(NewVisibility == Visible);
if (InteractionRayVisibility == EInteractionRayVisibility::VisibleOnHoverOnly && !bCanRaytraceEveryTick)
{
UE_LOG(Toolkit, Warning,
TEXT("VisibleOnHoverOnly needs bCanRaytraceEveryTick=true, so this is forcibly set!"));
bCanRaytraceEveryTick = true;
}
if (InteractionRayVisibility == EInteractionRayVisibility::Visible && !bCanRaytraceEveryTick)
{
UE_LOG(Toolkit, Warning,
TEXT(
"VisibleOnHoverOnly will need two clicks to interact with widgets if bCanRaytraceEveryTick is not set!"
));
}
}
// Forward the click to the WidgetInteraction
void UVRWidgetInteractionComponent::OnBeginClick(const FInputActionValue& Value)
{
PressPointerKey(EKeys::LeftMouseButton);
}
// Forward the end click to the WidgetInteraction
void UVRWidgetInteractionComponent::OnEndClick(const FInputActionValue& Value)
{
ReleasePointerKey(EKeys::LeftMouseButton);
}
void UVRWidgetInteractionComponent::SetupInteractionRay()
{
// Only create a new static mesh component if we haven't gotten one already
if (!InteractionRay)
{
// Create the new component
InteractionRay = NewObject<UStaticMeshComponent>(this);
// If we're not already attached, attach. Not sure why this is needed, taken from engine code.
if (!InteractionRay->GetAttachParent() && !InteractionRay->IsAttachedTo(this))
{
const AActor* Owner = GetOwner();
// Copied from engine code - I don't think this case will ever happen
if (!Owner || !Owner->GetWorld())
{
if (UWorld* World = GetWorld())
{
InteractionRay->RegisterComponentWithWorld(World);
InteractionRay->AttachToComponent(this, FAttachmentTransformRules::SnapToTargetNotIncludingScale);
}
else
{
InteractionRay->SetupAttachment(this);
}
}
else
{
// Usual case, just attach it to ourselves.
InteractionRay->AttachToComponent(this, FAttachmentTransformRules::SnapToTargetNotIncludingScale);
InteractionRay->RegisterComponent();
}
}
}
if (InteractionRay)
{
// Setup the interaction ray.
// The static mesh reference is set in the blueprint, avoid ConstructorHelpers and hardcoded paths!
// this ray model has an inlayed cross with flipped normals so it can be seen as a cross in desktop mode
// where the right hand is attached to the head
if (InteractionRayMesh)
{
InteractionRay->SetStaticMesh(InteractionRayMesh);
}
InteractionRay->SetCastShadow(false);
// turns off collisions as the InteractionRay is only meant to visualize the ray
InteractionRay->SetCollisionProfileName(TEXT("NoCollision"));
//the ray model has a length of 100cm (and is a bit too big in Y/Z dir)
InteractionRay->SetRelativeScale3D(FVector(InteractionDistance / 100.0f, 0.5f, 0.5f));
SetInteractionRayVisibility(InteractionRayVisibility);
}
}
// Fill out your copyright notice in the Description page of Project Settings.
#include "Pawn/BasicVRInteractionComponent.h"
#include "Misc/Optional.h"
DEFINE_LOG_CATEGORY(LogVRInteractionComponent);
// Sets default values for this component's properties
UBasicVRInteractionComponent::UBasicVRInteractionComponent()
{
// Set this component to be initialized when the game starts, and to be ticked every frame. You can turn these features
// off to improve performance if you don't need them.
PrimaryComponentTick.bCanEverTick = true;
// Setup the interaction ray.
InteractionRay = CreateDefaultSubobject<UStaticMeshComponent>(TEXT("Interaction Ray"));
InteractionRay->SetCastShadow(false);
// turns off collisions as the InteractionRay is only meant to visualize the ray
InteractionRay->SetCollisionProfileName(TEXT("NoCollision"));
// The static mesh reference is set in the blueprint, avoid ConstructorHelpers and hardcoded paths!
// this ray model has an inlayed cross with flipped normals so it can be seen as a cross in desktop mode where the right hand is attached to the head
bShowDebug = false; //otherwise the WidgetInteractionComponent debug vis is shown
InteractionSource = EWidgetInteractionSource::Custom;
//can also be kept at default (World), this way, however, we efficiently reuse the line traces
}
void UBasicVRInteractionComponent::BeginPlay()
{
Super::BeginPlay();
//WidgetInteractionComponent
InteractionDistance = MaxClickDistance;
InteractionRay->SetRelativeScale3D(FVector(MaxClickDistance / 100.0f, 0.5f, 0.5f));
//the ray model has a length of 100cm (and is a bit too big in Y/Z dir)
SetInteractionRayVisibility(InteractionRayVisibility);
}
void UBasicVRInteractionComponent::BeginInteraction()
{
if (!InteractionRayEmitter) return;
// start and end point for raytracing
const FTwoVectors StartEnd = GetHandRay(MaxClickDistance);
TOptional<FHitResult> Hit = RaytraceForFirstHit(StartEnd);
if (!Hit.IsSet())
return;
AActor* HitActor = Hit->GetActor();
//trigger interaction of WidgetInteractionComponent
SetCustomHitResult(Hit.GetValue());
//if !bCanRaytraceEveryTick, you have to click twice, since the first tick it only highlights and can't directly click
PressPointerKey(EKeys::LeftMouseButton);
}
void UBasicVRInteractionComponent::EndInteraction()
{
if (!InteractionRayEmitter) return;
//end interaction of WidgetInteractionComponent
ReleasePointerKey(EKeys::LeftMouseButton);
}
// Called every frame
void UBasicVRInteractionComponent::TickComponent(float DeltaTime, ELevelTick TickType,
FActorComponentTickFunction* ThisTickFunction)
{
Super::TickComponent(DeltaTime, TickType, ThisTickFunction);
if (!InteractionRayEmitter) return;
// only raytrace for targetable objects if bool user wants to enable this feature
if (!bCanRaytraceEveryTick)
return;
const FTwoVectors StartEnd = GetHandRay(MaxClickDistance);
TOptional<FHitResult> Hit = RaytraceForFirstHit(StartEnd);
if (!Hit.IsSet() || !Hit->GetActor())
{
if (InteractionRayVisibility == EInteractionRayVisibility::VisibleOnHoverOnly)
{
InteractionRay->SetVisibility(false);
}
return;
}
AActor* HitActor = Hit->GetActor();
// widget interaction
SetCustomHitResult(Hit.GetValue());
if (InteractionRayVisibility == EInteractionRayVisibility::VisibleOnHoverOnly)
{
if (IsOverInteractableWidget())
{
InteractionRay->SetVisibility(true);
}
else
{
InteractionRay->SetVisibility(false);
}
}
LastActorHit = HitActor; // Store the actor that was hit to have access to it in the next frame as well
}
void UBasicVRInteractionComponent::Initialize(USceneComponent* RayEmitter)
{
if (InteractionRayEmitter) return; /* Return if already initialized */
InteractionRayEmitter = RayEmitter;
InteractionRay->AttachToComponent(RayEmitter, FAttachmentTransformRules::KeepRelativeTransform);
this->AttachToComponent(RayEmitter, FAttachmentTransformRules::KeepRelativeTransform);
}
void UBasicVRInteractionComponent::SetInteractionRayVisibility(EInteractionRayVisibility NewVisibility)
{
InteractionRayVisibility = NewVisibility;
if (InteractionRay)
{
switch (InteractionRayVisibility)
{
case Visible:
InteractionRay->SetVisibility(true);
break;
case VisibleOnHoverOnly:
case Invisible:
InteractionRay->SetVisibility(false);
break;
default: ;
}
}
if (InteractionRayVisibility == EInteractionRayVisibility::VisibleOnHoverOnly && !bCanRaytraceEveryTick)
{
UE_LOG(LogVRInteractionComponent, Warning,
TEXT("VisibleOnHoverOnly needs bCanRaytraceEveryTick=true, so this is set!"));
bCanRaytraceEveryTick = true;
}
if (InteractionRayVisibility == EInteractionRayVisibility::Visible && !bCanRaytraceEveryTick)
{
UE_LOG(LogVRInteractionComponent, Warning,
TEXT(
"VisibleOnHoverOnly will need two clicks to interact with widgets if bCanRaytraceEveryTick is not set!"
));
}
}
FTwoVectors UBasicVRInteractionComponent::GetHandRay(const float Length) const
{
const FVector Start = InteractionRayEmitter->GetComponentLocation();
const FVector Direction = InteractionRayEmitter->GetForwardVector();
const FVector End = Start + Length * Direction;
return FTwoVectors(Start, End);
}
TOptional<FHitResult> UBasicVRInteractionComponent::RaytraceForFirstHit(const FTwoVectors& Ray) const
{
const FVector Start = Ray.v1;
const FVector End = Ray.v2;
// will be filled by the Line Trace Function
FHitResult Hit;
FCollisionQueryParams Params;
Params.AddIgnoredActor(GetOwner()->GetUniqueID()); // prevents actor hitting itself
if (GetWorld()->LineTraceSingleByChannel(Hit, Start, End, ECollisionChannel::ECC_Visibility, Params))
return {Hit};
else
return {};
}
......@@ -36,9 +36,6 @@ AVirtualRealityPawn::AVirtualRealityPawn(const FObjectInitializer& ObjectInitial
LeftHand = CreateDefaultSubobject<UReplicatedMotionControllerComponent>(TEXT("Left Hand MCC"));
LeftHand->SetupAttachment(RootComponent);
BasicVRInteraction = CreateDefaultSubobject<UBasicVRInteractionComponent>(TEXT("Basic VR Interaction"));
BasicVRInteraction->Initialize(RightHand);
}
void AVirtualRealityPawn::Tick(float DeltaSeconds)
......
......@@ -4,11 +4,8 @@
#include "CoreMinimal.h"
#include "Components/WidgetInteractionComponent.h"
#include "BasicVRInteractionComponent.generated.h"
DECLARE_LOG_CATEGORY_EXTERN(LogVRInteractionComponent, Log, All);
class UGrabbingBehaviorComponent;
#include "Pawn/InputExtensionInterface.h"
#include "VRWidgetInteractionComponent.generated.h"
UENUM()
enum EInteractionRayVisibility
......@@ -21,46 +18,28 @@ enum EInteractionRayVisibility
};
UCLASS(ClassGroup=(Custom), meta=(BlueprintSpawnableComponent))
class RWTHVRTOOLKIT_API UBasicVRInteractionComponent : public UWidgetInteractionComponent
UCLASS(Blueprintable, Abstract, ClassGroup=(Custom), meta=(BlueprintSpawnableComponent))
class RWTHVRTOOLKIT_API UVRWidgetInteractionComponent : public UWidgetInteractionComponent,
public IInputExtensionInterface
{
GENERATED_BODY()
public:
// Sets default values for this component's properties
UBasicVRInteractionComponent();
UVRWidgetInteractionComponent();
virtual void BeginPlay() override;
virtual void SetupPlayerInput(UInputComponent* PlayerInputComponent) override;
virtual void TickComponent(float DeltaTime, ELevelTick TickType,
FActorComponentTickFunction* ThisTickFunction) override;
UFUNCTION(BlueprintCallable)
void BeginInteraction();
UFUNCTION(BlueprintCallable)
void EndInteraction();
UFUNCTION(BlueprintCallable)
void Initialize(USceneComponent* RayEmitter);
UFUNCTION(BlueprintCallable, BlueprintPure)
AActor* GetGrabbedActor() const { return GrabbedActor; }
UFUNCTION(BlueprintCallable, BlueprintPure)
USceneComponent* GetInteractionRayEmitter() const { return InteractionRayEmitter; }
UFUNCTION(BlueprintCallable)
void SetInteractionRayVisibility(EInteractionRayVisibility NewVisibility);
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Pawn|Interaction")
UStaticMeshComponent* InteractionRay;
UPROPERTY(EditAnywhere, BlueprintReadWrite)
float MaxGrabDistance = 50;
UPROPERTY(EditAnywhere)
UStaticMesh* InteractionRayMesh;
UPROPERTY(EditAnywhere, BlueprintReadWrite)
float MaxClickDistance = 500;
UPROPERTY(BlueprintReadOnly)
UStaticMeshComponent* InteractionRay;
// Enable this if you want to interact with Targetable classes or use EInteractionRayVisibility::VisibleOnHoverOnly
UPROPERTY(EditAnywhere)
......@@ -69,22 +48,18 @@ public:
UPROPERTY(EditAnywhere)
TEnumAsByte<EInteractionRayVisibility> InteractionRayVisibility = EInteractionRayVisibility::Invisible;
private:
/* Holding a reference to the actor that is currently being grabbed */
UPROPERTY()
AActor* GrabbedActor;
UPROPERTY(EditDefaultsOnly, Category = "Input")
class UInputMappingContext* IMCWidgetInteraction;
/* Holds a reference to the grabbed actors physics simulating component if there was one*/
UPROPERTY()
UPrimitiveComponent* ComponentSimulatingPhysics = nullptr;
UPROPERTY(EditAnywhere, Category = "Input")
class UInputAction* WidgetClickInputAction;
UPROPERTY()
USceneComponent* InteractionRayEmitter = nullptr;
private:
UFUNCTION()
void OnBeginClick(const FInputActionValue& Value);
/* Stores the reference of the Actor that was hit in the last frame*/
UPROPERTY()
AActor* LastActorHit = nullptr;
UFUNCTION()
void OnEndClick(const FInputActionValue& Value);
FTwoVectors GetHandRay(float Length) const;
TOptional<FHitResult> RaytraceForFirstHit(const FTwoVectors& Ray) const;
void SetupInteractionRay();
};
......@@ -2,7 +2,6 @@
#pragma once
#include "BasicVRInteractionComponent.h"
#include "CoreMinimal.h"
#include "LiveLinkRole.h"
#include "Pawn/Navigation/VRPawnMovement.h"
......@@ -35,10 +34,6 @@ public:
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Pawn|MotionControllers")
UMotionControllerComponent* LeftHand;
/* Interaction */
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Pawn|Interaction")
UBasicVRInteractionComponent* BasicVRInteraction;
/* Movement */
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Pawn|Movement")
UVRPawnMovement* PawnMovement;
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment