diff --git a/Config/DefaultRWTHVRToolkit.ini b/Config/DefaultRWTHVRToolkit.ini index e9585a9f68eb75f31e4e7455de23095ddf501da1..5e0f330705848d2342828dce06c75fa670d760d4 100644 --- a/Config/DefaultRWTHVRToolkit.ini +++ b/Config/DefaultRWTHVRToolkit.ini @@ -20,7 +20,13 @@ +PropertyRedirects = (OldName="/Script/RWTHVRToolkit.DirectInteractionComponent.PreviousGrabBehavioursInRange",NewName="/Script/RWTHVRToolkit.DirectInteractionComponent.PreviousInteractableComponentsInRange") +PropertyRedirects = (OldName="/Script/RWTHVRToolkit.DirectInteractionComponent.CurrentGrabBehavioursInRange",NewName="/Script/RWTHVRToolkit.DirectInteractionComponent.CurrentInteractableComponentsInRange") +PropertyRedirects = (OldName="/Script/RWTHVRToolkit.TurnComponent.DesktopRotation",NewName="/Script/RWTHVRToolkit.TurnComponent.DesktopTurnCondition") -+PropertyRedirects = (OldName="/Script/RWTHVRToolkit.TurnComponent.Turn",NewName="/Script/RWTHVRToolkit.TurnComponent.XRTurn") -+PropertyRedirects = (OldName="/Script/RWTHVRToolkit.RWTHVRWidgetInteractionComponent.WidgetClickInputAction",NewName="/Script/RWTHVRToolkit.RWTHVRWidgetInteractionComponent.WidgetLeftClickInputAction") -+FunctionRedirects = (OldName="/Script/RWTHVRToolkit.RWTHVRWidgetInteractionComponent.OnBeginClick",NewName="/Script/RWTHVRToolkit.RWTHVRWidgetInteractionComponent.OnBeginLeftClick") -+FunctionRedirects = (OldName="/Script/RWTHVRToolkit.RWTHVRWidgetInteractionComponent.OnEndClick",NewName="/Script/RWTHVRToolkit.RWTHVRWidgetInteractionComponent.OnEndLeftClick") \ No newline at end of file ++PropertyRedirects = (OldName="/Script/RWTHVRToolkit.TurnComponent.Turn",NewName="/Script/RWTHVRToolkit.TurnComponent.XRTurn") ++PropertyRedirects = (OldName="/Script/RWTHVRToolkit.RWTHVRWidgetInteractionComponent.WidgetClickInputAction",NewName="/Script/RWTHVRToolkit.RWTHVRWidgetInteractionComponent.WidgetLeftClickInputAction") ++FunctionRedirects = (OldName="/Script/RWTHVRToolkit.RWTHVRWidgetInteractionComponent.OnBeginClick",NewName="/Script/RWTHVRToolkit.RWTHVRWidgetInteractionComponent.OnBeginLeftClick") ++FunctionRedirects = (OldName="/Script/RWTHVRToolkit.RWTHVRWidgetInteractionComponent.OnEndClick",NewName="/Script/RWTHVRToolkit.RWTHVRWidgetInteractionComponent.OnEndLeftClick") ++FunctionRedirects=(OldName="/Script/RWTHVRToolkit.UBaseInteractionComponent.OnBeginInteraction",NewName="/Script/RWTHVRToolkit.UBaseInteractionComponent.OnBeginInteractionInputAction") ++FunctionRedirects=(OldName="/Script/RWTHVRToolkit.UBaseInteractionComponent.OnEndInteraction",NewName="/Script/RWTHVRToolkit.UBaseInteractionComponent.OnEndInteractionInputAction") ++FunctionRedirects=(OldName="/Script/RWTHVRToolkit.UBaseInteractionComponent.MulticastHoverBehaviourStartRpc",NewName="/Script/RWTHVRToolkit.UBaseInteractionComponent.MulticastHoverBehaviourReplicationStartRpc") ++FunctionRedirects=(OldName="/Script/RWTHVRToolkit.UBaseInteractionComponent.MulticastActionBehaviourStartRpc",NewName="/Script/RWTHVRToolkit.UBaseInteractionComponent.MulticastActionBehaviourReplicationStartRpc") ++FunctionRedirects=(OldName="/Script/RWTHVRToolkit.ActionBehaviour.OnActionStart",NewName="/Script/RWTHVRToolkit.ActionBehaviour.OnActionEvent") ++PropertyRedirects=(OldName="/Script/RWTHVRToolkit.ActionBehaviour.OnActionBeginEvent",NewName="/Script/RWTHVRToolkit.ActionBehaviour.OnActionEventEvent") \ No newline at end of file diff --git a/Content/Input/Default_IMC/IMC_General.uasset b/Content/Input/Default_IMC/IMC_General.uasset index 86a5cc420d316fec6cfe82086febf9292630569a..bfc100b8d7b66753e20343576c91f76dc51cfaac 100644 Binary files a/Content/Input/Default_IMC/IMC_General.uasset and b/Content/Input/Default_IMC/IMC_General.uasset differ diff --git a/Source/RWTHVRToolkit/Private/Interaction/Interactables/ActionBehaviour.cpp b/Source/RWTHVRToolkit/Private/Interaction/Interactables/ActionBehaviour.cpp index 4e9c3fd662dd3baa2a30fddd8f516022e4efa9ba..ff1d027208025476ce073a15bd224ca5c9ccb9f0 100644 --- a/Source/RWTHVRToolkit/Private/Interaction/Interactables/ActionBehaviour.cpp +++ b/Source/RWTHVRToolkit/Private/Interaction/Interactables/ActionBehaviour.cpp @@ -6,27 +6,14 @@ // We disable ticking here, as we are mainly interested in the events UActionBehaviour::UActionBehaviour() { PrimaryComponentTick.bCanEverTick = false; } -void UActionBehaviour::OnActionStart(USceneComponent* TriggeredComponent, const UInputAction* InputAction, +void UActionBehaviour::OnActionEvent(USceneComponent* TriggerComponent, const EInteractionEventType EventType, const FInputActionValue& Value) { } -void UActionBehaviour::OnActionEnd(USceneComponent* TriggeredComponent, const UInputAction* InputAction, - const FInputActionValue& Value) -{ -} - void UActionBehaviour::BeginPlay() { Super::BeginPlay(); - OnActionBeginEvent.AddDynamic(this, &UActionBehaviour::OnActionStart); - OnActionEndEvent.AddDynamic(this, &UActionBehaviour::OnActionEnd); -} - -// Called every frame -void UActionBehaviour::TickComponent(float DeltaTime, ELevelTick TickType, - FActorComponentTickFunction* ThisTickFunction) -{ - Super::TickComponent(DeltaTime, TickType, ThisTickFunction); + OnActionEventEvent.AddDynamic(this, &UActionBehaviour::OnActionEvent); } diff --git a/Source/RWTHVRToolkit/Private/Interaction/Interactables/BaseBehaviour.cpp b/Source/RWTHVRToolkit/Private/Interaction/Interactables/BaseBehaviour.cpp new file mode 100644 index 0000000000000000000000000000000000000000..95488260668c095eb5dece6895555ce808e2fd11 --- /dev/null +++ b/Source/RWTHVRToolkit/Private/Interaction/Interactables/BaseBehaviour.cpp @@ -0,0 +1,4 @@ +// Fill out your copyright notice in the Description page of Project Settings. + + +#include "Interaction/Interactables/BaseBehaviour.h" diff --git a/Source/RWTHVRToolkit/Private/Interaction/Interactables/GrabBehavior.cpp b/Source/RWTHVRToolkit/Private/Interaction/Interactables/GrabBehavior.cpp index 1537243da0b6c92a901decadce793bc8bd819bbb..502a4be532df57e6f3214e859db8eb4729b15482 100644 --- a/Source/RWTHVRToolkit/Private/Interaction/Interactables/GrabBehavior.cpp +++ b/Source/RWTHVRToolkit/Private/Interaction/Interactables/GrabBehavior.cpp @@ -3,10 +3,26 @@ #include "Interaction/Interactables/GrabBehavior.h" #include "Interaction/Interactables/InteractableComponent.h" +#include "Interaction/Interactables/InteractionEventType.h" #include "Logging/StructuredLog.h" +#include "Pawn/Navigation/CollisionHandlingMovement.h" #include "Serialization/JsonTypes.h" #include "Utility/RWTHVRUtilities.h" +UGrabBehavior::UGrabBehavior() +{ + SetIsReplicatedByDefault(true); + bExecuteOnServer = true; + bExecuteOnAllClients = false; +} + +void UGrabBehavior::BeginPlay() +{ + Super::BeginPlay(); + + OnActionReplicationStartedOriginatorEvent.AddDynamic(this, &UGrabBehavior::ReplicationOriginaterClientCallback); +} + UPrimitiveComponent* UGrabBehavior::GetFirstComponentSimulatingPhysics(const AActor* TargetActor) { TArray<UPrimitiveComponent*> PrimitiveComponents; @@ -33,16 +49,79 @@ UPrimitiveComponent* UGrabBehavior::GetHighestParentSimulatingPhysics(UPrimitive return Comp; } +void UGrabBehavior::ReplicationOriginaterClientCallback(USceneComponent* TriggerComponent, + const EInteractionEventType EventType, + const FInputActionValue& Value) +{ + const USceneComponent* CurrentAttachParent = Cast<USceneComponent>(TriggerComponent->GetAttachParent()); + HandleCollisionHandlingMovement(CurrentAttachParent, EventType); +} -void UGrabBehavior::OnActionStart(USceneComponent* TriggeredComponent, const UInputAction* InputAction, +void UGrabBehavior::HandleCollisionHandlingMovement(const USceneComponent* CurrentAttachParent, + const EInteractionEventType EventType) +{ + auto CHM = CurrentAttachParent->GetOwner()->GetComponentByClass<UCollisionHandlingMovement>(); + if (!CHM) + return; + + if (EventType == EInteractionEventType::InteractionStart) + { + // Add to ignore list for collision handling movement, if it exists + if (bIgnoreGrabbedActorInCollisionMovement) + { + bWasAddedToIgnore = CHM->AddActorToIgnore(GetOwner()); + } + } + else + { + // If our attach parent has a collision handling component, remove + if (bWasAddedToIgnore) + { + CHM->RemoveActorFromIgnore(GetOwner()); + } + } +} +void UGrabBehavior::OnActionEvent(USceneComponent* TriggerComponent, const EInteractionEventType EventType, const FInputActionValue& Value) +{ + if (EventType == EInteractionEventType::InteractionStart) + { + StartGrab(TriggerComponent); + } + else + { + EndGrab(TriggerComponent); + } +} + +bool UGrabBehavior::TryRelease() +{ + if (!bObjectGrabbed) + { + UE_LOGFMT(Toolkit, Display, "UGrabBehavior::TryRelease: bObjectGrabbed was false!"); + return false; + } + + if (MyPhysicsComponent) + { + MyPhysicsComponent->DetachFromComponent(FDetachmentTransformRules::KeepWorldTransform); + MyPhysicsComponent->SetSimulatePhysics(bWasSimulatingPhysics); + } + else + { + GetOwner()->GetRootComponent()->DetachFromComponent(FDetachmentTransformRules::KeepWorldTransform); + } + bObjectGrabbed = false; + return true; +} +void UGrabBehavior::StartGrab(USceneComponent* TriggerComponent) { if (bObjectGrabbed) { return; } - USceneComponent* CurrentAttachParent = Cast<USceneComponent>(TriggeredComponent->GetAttachParent()); + USceneComponent* CurrentAttachParent = Cast<USceneComponent>(TriggerComponent->GetAttachParent()); const FAttachmentTransformRules Rules = FAttachmentTransformRules(EAttachmentRule::KeepWorld, false); if (MyPhysicsComponent = GetFirstComponentSimulatingPhysics(GetOwner()); MyPhysicsComponent != nullptr) @@ -72,18 +151,19 @@ void UGrabBehavior::OnActionStart(USceneComponent* TriggeredComponent, const UIn GetOwner()->GetComponents<UInteractableComponent>(Interactables, false); for (UInteractableComponent* Interactable : Interactables) { - Interactable->RestrictInteractionToComponent(TriggeredComponent); + Interactable->RestrictInteractionToComponent(TriggerComponent); } } OnGrabStartEvent.Broadcast(CurrentAttachParent, MyPhysicsComponent); + + // Add to ignore list for collision handling movement, if it exists + HandleCollisionHandlingMovement(CurrentAttachParent, InteractionStart); } -void UGrabBehavior::OnActionEnd(USceneComponent* TriggeredComponent, const UInputAction* InputAction, - const FInputActionValue& Value) +void UGrabBehavior::EndGrab(USceneComponent* TriggerComponent) { - - USceneComponent* CurrentAttachParent = Cast<USceneComponent>(TriggeredComponent->GetAttachParent()); + USceneComponent* CurrentAttachParent = Cast<USceneComponent>(TriggerComponent->GetAttachParent()); // We try to release the attached component. If it is not succesful we log and return. Otherwise, we continue. if (!TryRelease()) @@ -106,24 +186,7 @@ void UGrabBehavior::OnActionEnd(USceneComponent* TriggeredComponent, const UInpu Interactable->ResetRestrictInteraction(); } } -} -bool UGrabBehavior::TryRelease() -{ - if (!bObjectGrabbed) - { - return false; - } - - if (MyPhysicsComponent) - { - MyPhysicsComponent->DetachFromComponent(FDetachmentTransformRules::KeepWorldTransform); - MyPhysicsComponent->SetSimulatePhysics(bWasSimulatingPhysics); - } - else - { - GetOwner()->GetRootComponent()->DetachFromComponent(FDetachmentTransformRules::KeepWorldTransform); - } - bObjectGrabbed = false; - return true; + // If our attach parent has a collision handling component, remove + HandleCollisionHandlingMovement(CurrentAttachParent, InteractionEnd); } diff --git a/Source/RWTHVRToolkit/Private/Interaction/Interactables/HoverBehaviour.cpp b/Source/RWTHVRToolkit/Private/Interaction/Interactables/HoverBehaviour.cpp index 96897613cd2f536cb9625a9b395ce49181656e70..f78e75fabfa434b1396d7bffc1354adee7f49441 100644 --- a/Source/RWTHVRToolkit/Private/Interaction/Interactables/HoverBehaviour.cpp +++ b/Source/RWTHVRToolkit/Private/Interaction/Interactables/HoverBehaviour.cpp @@ -3,14 +3,13 @@ #include "Interaction/Interactables/HoverBehaviour.h" -void UHoverBehaviour::OnHoverStart(const USceneComponent* TriggeredComponent, FHitResult Hit) {} - -void UHoverBehaviour::OnHoverEnd(const USceneComponent* TriggeredComponent) {} +void UHoverBehaviour::OnHoverEvent(const USceneComponent* TriggerComponent, EInteractionEventType EventType, + const FHitResult& Hit) +{ +} void UHoverBehaviour::BeginPlay() { Super::BeginPlay(); - - OnHoverStartEvent.AddDynamic(this, &UHoverBehaviour::OnHoverStart); - OnHoverEndEvent.AddDynamic(this, &UHoverBehaviour::OnHoverEnd); + OnHoverEventEvent.AddDynamic(this, &UHoverBehaviour::OnHoverEvent); } diff --git a/Source/RWTHVRToolkit/Private/Interaction/Interactables/IntenSelect/IntenSelectable.cpp b/Source/RWTHVRToolkit/Private/Interaction/Interactables/IntenSelect/IntenSelectable.cpp index 90a4e2956a3d06f1cb42d6219e4f7f1ff31ca050..276269b3dc0f389621e6415a71887cf3170a9e79 100644 --- a/Source/RWTHVRToolkit/Private/Interaction/Interactables/IntenSelect/IntenSelectable.cpp +++ b/Source/RWTHVRToolkit/Private/Interaction/Interactables/IntenSelect/IntenSelectable.cpp @@ -62,7 +62,8 @@ void UIntenSelectable::HandleOnSelectStartEvents(const UIntenSelectComponent* In { for (const UHoverBehaviour* CurrentHoverBehaviour : OnHoverBehaviours) { - CurrentHoverBehaviour->OnHoverStartEvent.Broadcast(IntenSelect, HitResult); + CurrentHoverBehaviour->OnHoverEventEvent.Broadcast(IntenSelect, EInteractionEventType::InteractionStart, + HitResult); } } @@ -70,7 +71,9 @@ void UIntenSelectable::HandleOnSelectEndEvents(const UIntenSelectComponent* Inte { for (const UHoverBehaviour* CurrentHoverBehaviour : OnHoverBehaviours) { - CurrentHoverBehaviour->OnHoverEndEvent.Broadcast(IntenSelect); + FHitResult EmptyHit; + CurrentHoverBehaviour->OnHoverEventEvent.Broadcast(IntenSelect, EInteractionEventType::InteractionEnd, + EmptyHit); } } @@ -79,8 +82,8 @@ void UIntenSelectable::HandleOnClickStartEvents(UIntenSelectComponent* IntenSele for (const UActionBehaviour* CurrentActionBehaviour : OnActionBehaviours) { FInputActionValue EmptyInputActionValue{}; - const UInputAction* EmptyInputAction{}; - CurrentActionBehaviour->OnActionBeginEvent.Broadcast(IntenSelect, EmptyInputAction, EmptyInputActionValue); + CurrentActionBehaviour->OnActionEventEvent.Broadcast(IntenSelect, EInteractionEventType::InteractionStart, + EmptyInputActionValue); } } @@ -88,8 +91,8 @@ void UIntenSelectable::HandleOnClickEndEvents(UIntenSelectComponent* IntenSelect { for (const UActionBehaviour* CurrentActionBehaviour : OnActionBehaviours) { - const UInputAction* EmptyInputActionValue{}; - CurrentActionBehaviour->OnActionEndEvent.Broadcast(IntenSelect, EmptyInputActionValue, InputValue); + CurrentActionBehaviour->OnActionEventEvent.Broadcast(IntenSelect, EInteractionEventType::InteractionEnd, + InputValue); } } diff --git a/Source/RWTHVRToolkit/Private/Interaction/Interactables/InteractableComponent.cpp b/Source/RWTHVRToolkit/Private/Interaction/Interactables/InteractableComponent.cpp index 29ee75b08e1d37147671228e167c09e4cde462f8..4f1c256218355ce5c142d010bcc3a460491f08f8 100644 --- a/Source/RWTHVRToolkit/Private/Interaction/Interactables/InteractableComponent.cpp +++ b/Source/RWTHVRToolkit/Private/Interaction/Interactables/InteractableComponent.cpp @@ -4,6 +4,9 @@ #include "Interaction/Interactables/InteractableComponent.h" #include "Interaction/Interactables/ActionBehaviour.h" #include "Interaction/Interactables/HoverBehaviour.h" +#include "Interaction/Interactors/UBaseInteractionComponent.h" +#include "Logging/StructuredLog.h" +#include "Utility/RWTHVRUtilities.h" void UInteractableComponent::RestrictInteractionToComponents(const TArray<USceneComponent*>& Components) { @@ -39,9 +42,8 @@ void UInteractableComponent::BeginPlay() InitDefaultBehaviourReferences(); } -// This functions dispatches the HoverStart Event to the attached Hover Behaviour Components -void UInteractableComponent::HandleOnHoverStartEvents(USceneComponent* TriggerComponent, - const EInteractorType Interactor) +void UInteractableComponent::HandleOnHoverEvents(USceneComponent* TriggerComponent, const EInteractorType Interactor, + const EInteractionEventType EventType) { // We early return if there the InteractorFilter is set and the Interactor is allowed. if (!(InteractorFilter == EInteractorType::None || InteractorFilter & Interactor)) @@ -52,34 +54,47 @@ void UInteractableComponent::HandleOnHoverStartEvents(USceneComponent* TriggerCo return; // Broadcast event to all HoverBehaviours - for (const UHoverBehaviour* b : OnHoverBehaviours) + for (UHoverBehaviour* HoverBehaviour : OnHoverBehaviours) { - b->OnHoverStartEvent.Broadcast(TriggerComponent, HitResult); - } -} - -// This functions dispatches the HoverEnd Event to the attached Hover Behaviour Components -void UInteractableComponent::HandleOnHoverEndEvents(USceneComponent* TriggerComponent, const EInteractorType Interactor) -{ - // We early return if there the InteractorFilter is set and the Interactor is allowed. - if (!(InteractorFilter == EInteractorType::None || InteractorFilter & Interactor)) - return; - - // We early return if the source Interactor is not part of the allowed components - if (!IsComponentAllowed(TriggerComponent)) - return; - - // Broadcast event to all HoverBehaviours - for (const UHoverBehaviour* b : OnHoverBehaviours) - { - b->OnHoverEndEvent.Broadcast(TriggerComponent); + // Check if we need to replicate the action to the server + if (HoverBehaviour->bExecuteOnServer) + { + // Request the server to execute our behaviour. + // As we can only execute RPCs from an owned object, and we don't necessarily own this actor, pipe it + // through the interactor. Because behaviours can also be triggered from non-interactors, only do this on a + // successful cast + + auto* InteractorComponent = Cast<UUBaseInteractionComponent>(TriggerComponent); + if (!InteractorComponent) + { + UE_LOGFMT(Toolkit, Warning, + "Interaction: TriggerComponent {TriggerComponent} is not a UUBaseInteractionComponent. Only " + "UUBaseInteractionComponent TriggerComponents can be replicated.", + TriggerComponent->GetName()); + return; + } + InteractorComponent->RequestHoverBehaviourReplicationStart(HoverBehaviour, EventType, HitResult); + + // Broadcast local callback + HoverBehaviour->OnHoverReplicationStartedOriginatorEvent.Broadcast(TriggerComponent, EventType, HitResult); + } + else if (HoverBehaviour->bExecuteOnAllClients) + { + UE_LOGFMT(Toolkit, Warning, + "Interaction: Behaviour {BehaviourName} has bExecuteOnAllClients=true, which requires " + "bExecuteOnServer also set to true, which is not the case.", + HoverBehaviour->GetName()); + } + else // skip replication altogether (default case) + { + HoverBehaviour->OnHoverEventEvent.Broadcast(TriggerComponent, EventType, HitResult); + } } } // This functions dispatches the ActionStart Event to the attached Action Behaviour Components -void UInteractableComponent::HandleOnActionStartEvents(USceneComponent* TriggerComponent, - const UInputAction* InputAction, const FInputActionValue& Value, - const EInteractorType Interactor) +void UInteractableComponent::HandleOnActionEvents(USceneComponent* TriggerComponent, const EInteractorType Interactor, + const EInteractionEventType EventType, const FInputActionValue& Value) { // We early return if there the InteractorFilter is set and the Interactor is allowed. if (!(InteractorFilter == EInteractorType::None || InteractorFilter & Interactor)) @@ -90,28 +105,41 @@ void UInteractableComponent::HandleOnActionStartEvents(USceneComponent* TriggerC return; // Broadcast event to all ActionBehaviours - for (const UActionBehaviour* b : OnActionBehaviours) + for (UActionBehaviour* ActionBehaviour : OnActionBehaviours) { - b->OnActionBeginEvent.Broadcast(TriggerComponent, InputAction, Value); - } -} - -// This functions dispatches the ActionEnd Event to the attached Action Behaviour Components -void UInteractableComponent::HandleOnActionEndEvents(USceneComponent* TriggerComponent, const UInputAction* InputAction, - const FInputActionValue& Value, const EInteractorType Interactor) -{ - // We early return if there the InteractorFilter is set and the Interactor is allowed. - if (!(InteractorFilter == EInteractorType::None || InteractorFilter & Interactor)) - return; - - // We early return if the source Interactor is not part of the allowed components - if (!IsComponentAllowed(TriggerComponent)) - return; - - // Broadcast event to all ActionBehaviours - for (const UActionBehaviour* b : OnActionBehaviours) - { - b->OnActionEndEvent.Broadcast(TriggerComponent, InputAction, Value); + // Check if we need to replicate the action to the server + if (ActionBehaviour->bExecuteOnServer) + { + // Request the server to execute our behaviour. + // As we can only execute RPCs from an owned object, and we don't necessarily own this actor, pipe it + // through the interactor. Because behaviours can also be triggered from non-interactors, only do this on a + // successful cast + + auto* InteractorComponent = Cast<UUBaseInteractionComponent>(TriggerComponent); + if (!InteractorComponent) + { + UE_LOGFMT(Toolkit, Warning, + "Interaction: TriggerComponent {TriggerComponent} is not a UUBaseInteractionComponent. Only " + "UUBaseInteractionComponent TriggerComponents can be replicated.", + TriggerComponent->GetName()); + return; + } + InteractorComponent->RequestActionBehaviourReplicationStart(ActionBehaviour, EventType, Value); + + // Broadcast local callback + ActionBehaviour->OnActionReplicationStartedOriginatorEvent.Broadcast(TriggerComponent, EventType, Value); + } + else if (ActionBehaviour->bExecuteOnAllClients) + { + UE_LOGFMT(Toolkit, Warning, + "Interaction: Behaviour {BehaviourName} has bExecuteOnAllClients=true, which requires " + "bExecuteOnServer also set to true, which is not the case.", + ActionBehaviour->GetName()); + } + else // skip replication altogether (default case) + { + ActionBehaviour->OnActionEventEvent.Broadcast(TriggerComponent, EventType, Value); + } } } @@ -150,4 +178,4 @@ bool UInteractableComponent::IsComponentAllowed(USceneComponent* Component) cons } } return true; -} +} \ No newline at end of file diff --git a/Source/RWTHVRToolkit/Private/Interaction/Interactors/DirectInteractionComponent.cpp b/Source/RWTHVRToolkit/Private/Interaction/Interactors/DirectInteractionComponent.cpp index 7c81cb805f86e3d9a4de4bfa2c594ab02a683719..18e515817875cb60c35090b4dbfb4300051b8dac 100644 --- a/Source/RWTHVRToolkit/Private/Interaction/Interactors/DirectInteractionComponent.cpp +++ b/Source/RWTHVRToolkit/Private/Interaction/Interactors/DirectInteractionComponent.cpp @@ -6,6 +6,7 @@ #include "EnhancedInputComponent.h" #include "Interaction/Interactables/InteractableComponent.h" #include "Interaction/Interactables/InteractionBitSet.h" +#include "Interaction/Interactables/InteractionEventType.h" #include "Kismet/GameplayStatics.h" #include "Logging/StructuredLog.h" @@ -30,7 +31,7 @@ void UDirectInteractionComponent::TickComponent(float DeltaTime, ELevelTick Tick TArray<FHitResult> OutHits; const ETraceTypeQuery TraceType = UEngineTypes::ConvertToTraceType(ECollisionChannel::ECC_PhysicsBody); - auto DebugTrace = bShowDebugTrace ? EDrawDebugTrace::ForOneFrame : EDrawDebugTrace::None; + const auto DebugTrace = bShowDebugTrace ? EDrawDebugTrace::ForOneFrame : EDrawDebugTrace::None; UKismetSystemLibrary::SphereTraceMulti(GetWorld(), GetAttachParent()->GetComponentLocation(), GetAttachParent()->GetComponentLocation(), InteractionSphereRadius, @@ -59,7 +60,8 @@ void UDirectInteractionComponent::TickComponent(float DeltaTime, ELevelTick Tick if (!PreviousInteractableComponentsInRange.Contains(CurrentInteractableComp)) { PreviousInteractableComponentsInRange.AddUnique(CurrentInteractableComp); - CurrentInteractableComp->HandleOnHoverStartEvents(this, EInteractorType::Direct); + CurrentInteractableComp->HandleOnHoverEvents(this, EInteractorType::Direct, + EInteractionEventType::InteractionStart); } } @@ -71,7 +73,8 @@ void UDirectInteractionComponent::TickComponent(float DeltaTime, ELevelTick Tick if (!CurrentInteractableCompsInRange.Contains(PrevInteractableComp)) { ComponentsToRemove.AddUnique(PrevInteractableComp); - PrevInteractableComp->HandleOnHoverEndEvents(this, EInteractorType::Direct); + PrevInteractableComp->HandleOnHoverEvents(this, EInteractorType::Direct, + EInteractionEventType::InteractionEnd); } } @@ -97,12 +100,12 @@ void UDirectInteractionComponent::SetupPlayerInput(UInputComponent* PlayerInputC return; EI->BindAction(InteractionInputAction, ETriggerEvent::Started, this, - &UDirectInteractionComponent::OnBeginInteraction); + &UDirectInteractionComponent::OnBeginInteractionInputAction); EI->BindAction(InteractionInputAction, ETriggerEvent::Completed, this, - &UDirectInteractionComponent::OnEndInteraction); + &UDirectInteractionComponent::OnEndInteractionInputAction); } -void UDirectInteractionComponent::OnBeginInteraction(const FInputActionValue& Value) +void UDirectInteractionComponent::OnBeginInteractionInputAction(const FInputActionValue& Value) { const FVector InteractionLocation = GetAttachParent()->GetComponentLocation(); @@ -115,7 +118,7 @@ void UDirectInteractionComponent::OnBeginInteraction(const FInputActionValue& Va CurrentInteractableComponentsInRange, [&](auto Element) { return FVector(Element->GetOwner()->GetActorLocation() - InteractionLocation).Size(); }); - MinElement->HandleOnActionStartEvents(this, InteractionInputAction, Value, EInteractorType::Direct); + MinElement->HandleOnActionEvents(this, EInteractorType::Direct, EInteractionEventType::InteractionStart, Value); CurrentlyInteractedComponents = {MinElement}; } else @@ -125,19 +128,21 @@ void UDirectInteractionComponent::OnBeginInteraction(const FInputActionValue& Va CurrentInteractableComponentsInRange.Num()); for (UInteractableComponent* InteractableComp : CurrentInteractableComponentsInRange) { - InteractableComp->HandleOnActionStartEvents(this, InteractionInputAction, Value, EInteractorType::Direct); + InteractableComp->HandleOnActionEvents(this, EInteractorType::Direct, + EInteractionEventType::InteractionStart, Value); CurrentlyInteractedComponents.Add(InteractableComp); } } } -void UDirectInteractionComponent::OnEndInteraction(const FInputActionValue& Value) +void UDirectInteractionComponent::OnEndInteractionInputAction(const FInputActionValue& Value) { for (auto& Component : CurrentlyInteractedComponents) { if (Component.IsValid()) { - Component->HandleOnActionEndEvents(this, InteractionInputAction, Value, EInteractorType::Direct); + Component->HandleOnActionEvents(this, EInteractorType::Direct, EInteractionEventType::InteractionEnd, + Value); } } CurrentlyInteractedComponents.Empty(); diff --git a/Source/RWTHVRToolkit/Private/Interaction/Interactors/RaycastInteractionComponent.cpp b/Source/RWTHVRToolkit/Private/Interaction/Interactors/RaycastInteractionComponent.cpp index 94afba4b4426fe1b861d787826f0e1cc11d6583b..42e1aeea61a94302b199769e8f79d50314c5cc89 100644 --- a/Source/RWTHVRToolkit/Private/Interaction/Interactors/RaycastInteractionComponent.cpp +++ b/Source/RWTHVRToolkit/Private/Interaction/Interactors/RaycastInteractionComponent.cpp @@ -5,6 +5,7 @@ #include "EnhancedInputComponent.h" #include "Interaction/Interactables/InteractableComponent.h" +#include "Interaction/Interactables/InteractionEventType.h" #include "Kismet/KismetSystemLibrary.h" // Sets default values for this component's properties @@ -25,14 +26,13 @@ void URaycastInteractionComponent::TickComponent(float DeltaTime, ELevelTick Tic UInteractableComponent* NewInteractableComponent = nullptr; - - TArray<AActor*> ActorsToIgnore; FHitResult Hit; const ETraceTypeQuery TraceType = UEngineTypes::ConvertToTraceType(ECollisionChannel::ECC_PhysicsBody); - FVector TraceStart = GetAttachParent()->GetComponentLocation(); - FVector TraceEnd = GetAttachParent()->GetComponentLocation() + TraceLength * GetAttachParent()->GetForwardVector(); + const FVector TraceStart = GetAttachParent()->GetComponentLocation(); + const FVector TraceEnd = + GetAttachParent()->GetComponentLocation() + TraceLength * GetAttachParent()->GetForwardVector(); - auto DebugTrace = bShowDebugTrace ? EDrawDebugTrace::ForOneFrame : EDrawDebugTrace::None; + const auto DebugTrace = bShowDebugTrace ? EDrawDebugTrace::ForOneFrame : EDrawDebugTrace::None; UKismetSystemLibrary::LineTraceSingle(GetWorld(), TraceStart, TraceEnd, TraceType, true, ActorsToIgnore, DebugTrace, Hit, true, FColor::Green); @@ -53,24 +53,28 @@ void URaycastInteractionComponent::TickComponent(float DeltaTime, ELevelTick Tic if (CurrentInteractable != PreviousInteractable) { if (CurrentInteractable && CurrentInteractable->HasInteractionTypeFlag(EInteractorType::Raycast)) - CurrentInteractable->HandleOnHoverStartEvents(this, EInteractorType::Raycast); + CurrentInteractable->HandleOnHoverEvents(this, EInteractorType::Raycast, + EInteractionEventType::InteractionStart); if (PreviousInteractable && PreviousInteractable->HasInteractionTypeFlag(EInteractorType::Raycast)) - PreviousInteractable->HandleOnHoverEndEvents(this, EInteractorType::Raycast); + PreviousInteractable->HandleOnHoverEvents(this, EInteractorType::Raycast, + EInteractionEventType::InteractionEnd); } PreviousInteractable = CurrentInteractable; } -void URaycastInteractionComponent::OnBeginInteraction(const FInputActionValue& Value) +void URaycastInteractionComponent::OnBeginInteractionInputAction(const FInputActionValue& Value) { if (CurrentInteractable && CurrentInteractable->HasInteractionTypeFlag(EInteractorType::Raycast)) - CurrentInteractable->HandleOnActionStartEvents(this, InteractionInputAction, Value, EInteractorType::Raycast); + CurrentInteractable->HandleOnActionEvents(this, EInteractorType::Raycast, + EInteractionEventType::InteractionStart, Value); } -void URaycastInteractionComponent::OnEndInteraction(const FInputActionValue& Value) +void URaycastInteractionComponent::OnEndInteractionInputAction(const FInputActionValue& Value) { if (CurrentInteractable && CurrentInteractable->HasInteractionTypeFlag(EInteractorType::Raycast)) - CurrentInteractable->HandleOnActionEndEvents(this, InteractionInputAction, Value, EInteractorType::Raycast); + CurrentInteractable->HandleOnActionEvents(this, EInteractorType::Raycast, EInteractionEventType::InteractionEnd, + Value); } void URaycastInteractionComponent::SetupPlayerInput(UInputComponent* PlayerInputComponent) @@ -86,7 +90,7 @@ void URaycastInteractionComponent::SetupPlayerInput(UInputComponent* PlayerInput return; EI->BindAction(InteractionInputAction, ETriggerEvent::Started, this, - &URaycastInteractionComponent::OnBeginInteraction); + &URaycastInteractionComponent::OnBeginInteractionInputAction); EI->BindAction(InteractionInputAction, ETriggerEvent::Completed, this, - &URaycastInteractionComponent::OnEndInteraction); + &URaycastInteractionComponent::OnEndInteractionInputAction); } diff --git a/Source/RWTHVRToolkit/Private/Interaction/Interactors/UBaseInteractionComponent.cpp b/Source/RWTHVRToolkit/Private/Interaction/Interactors/UBaseInteractionComponent.cpp new file mode 100644 index 0000000000000000000000000000000000000000..67d718c90cdff0dce7dab7cf7bff226f778877c9 --- /dev/null +++ b/Source/RWTHVRToolkit/Private/Interaction/Interactors/UBaseInteractionComponent.cpp @@ -0,0 +1,152 @@ +// Fill out your copyright notice in the Description page of Project Settings. + + +#include "Interaction/Interactors/UBaseInteractionComponent.h" + +#include "Interaction/Interactables/ActionBehaviour.h" +#include "Interaction/Interactables/HoverBehaviour.h" +#include "Logging/StructuredLog.h" +#include "Utility/RWTHVRUtilities.h" + +// Sets default values for this component's properties +UUBaseInteractionComponent::UUBaseInteractionComponent() +{ + // 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; + SetIsReplicatedByDefault(true); + // ... +} + +void UUBaseInteractionComponent::SetupPlayerInput(UInputComponent* PlayerInputComponent) +{ + IInputExtensionInterface::SetupPlayerInput(PlayerInputComponent); +} + +void UUBaseInteractionComponent::OnBeginInteractionInputAction(const FInputActionValue& Value) {} + +void UUBaseInteractionComponent::OnEndInteractionInputAction(const FInputActionValue& Value) {} + +void UUBaseInteractionComponent::RequestHoverBehaviourReplicationStart(UHoverBehaviour* Behaviour, + const EInteractionEventType EventType, + const FHitResult& Hit) +{ + if (GetOwner()->HasLocalNetOwner()) + { + if (GetOwner()->HasAuthority()) + { + HoverBehaviourReplicationStart(Behaviour, EventType, Hit); + } + else + { + // RPC + ServerRequestHoverBehaviourReplicationStartRpc(Behaviour, EventType, Hit); + } + } + else + { + UE_LOGFMT(Toolkit, Error, + "Interaction: Trying to replicate HoverBehaviour {HoverBehaviour} in InteractionComponent " + "{InteractionComponent} which has no local net owner!", + Behaviour->GetName(), this->GetName()); + } +} + +void UUBaseInteractionComponent::HoverBehaviourReplicationStart(UHoverBehaviour* Behaviour, + const EInteractionEventType EventType, + const FHitResult& Hit) +{ + if (!Behaviour->bExecuteOnServer) // this should never happen + { + UE_LOGFMT(Toolkit, Error, + "Interaction: Trying to execute HoverBehaviour {HoverBehaviour} in InteractionComponent " + "{InteractionComponent} on the server, but bExecuteOnServer is false!", + Behaviour->GetName(), this->GetName()); + return; + } + + // If desired, multicast to all clients (including originator) + if (Behaviour->bExecuteOnAllClients) + { + MulticastHoverBehaviourReplicationStartRpc(Behaviour, EventType, Hit); + } + else + { + // Execute the behaviour only on the server + Behaviour->OnHoverEventEvent.Broadcast(this, EventType, Hit); + } +} + +void UUBaseInteractionComponent::ServerRequestHoverBehaviourReplicationStartRpc_Implementation( + UHoverBehaviour* Behaviour, const EInteractionEventType EventType, const FHitResult& Hit) +{ + HoverBehaviourReplicationStart(Behaviour, EventType, Hit); +} + +void UUBaseInteractionComponent::MulticastHoverBehaviourReplicationStartRpc_Implementation( + UHoverBehaviour* Behaviour, const EInteractionEventType EventType, const FHitResult& Hit) +{ + Behaviour->OnHoverEventEvent.Broadcast(this, EventType, Hit); +} + +void UUBaseInteractionComponent::RequestActionBehaviourReplicationStart(UActionBehaviour* Behaviour, + const EInteractionEventType EventType, + const FInputActionValue& Value) +{ + if (GetOwner()->HasLocalNetOwner()) + { + if (GetOwner()->HasAuthority()) + { + ActionBehaviourReplicationStart(Behaviour, EventType, Value); + } + else + { + // RPC + ServerRequestActionBehaviourReplicationStartRpc(Behaviour, EventType, Value); + } + } + else + { + UE_LOGFMT(Toolkit, Error, + "Interaction: Trying to replicate HoverBehaviour {HoverBehaviour} in InteractionComponent " + "{InteractionComponent} which has no local net owner!", + Behaviour->GetName(), this->GetName()); + } +} + +void UUBaseInteractionComponent::ActionBehaviourReplicationStart(UActionBehaviour* Behaviour, + const EInteractionEventType EventType, + const FInputActionValue& Value) +{ + if (!Behaviour->bExecuteOnServer) // this should never happen + { + UE_LOGFMT(Toolkit, Error, + "Interaction: Trying to execute ActionBehaviour {ActionBehaviour} in InteractionComponent " + "{InteractionComponent} on the server, but bExecuteOnServer is false!", + Behaviour->GetName(), this->GetName()); + return; + } + + // If desired, multicast to all clients (including originator) + if (Behaviour->bExecuteOnAllClients) + { + MulticastActionBehaviourReplicationStartRpc(Behaviour, EventType, Value); + } + else + { + // Execute the behaviour only on the server + Behaviour->OnActionEventEvent.Broadcast(this, EventType, Value); + } +} + +void UUBaseInteractionComponent::ServerRequestActionBehaviourReplicationStartRpc_Implementation( + UActionBehaviour* Behaviour, const EInteractionEventType EventType, const FInputActionValue& Value) +{ + ActionBehaviourReplicationStart(Behaviour, EventType, Value); +} + +void UUBaseInteractionComponent::MulticastActionBehaviourReplicationStartRpc_Implementation( + UActionBehaviour* Behaviour, const EInteractionEventType EventType, const FInputActionValue& Value) +{ + Behaviour->OnActionEventEvent.Broadcast(this, EventType, Value); +} diff --git a/Source/RWTHVRToolkit/Private/Pawn/Navigation/CollisionHandlingMovement.cpp b/Source/RWTHVRToolkit/Private/Pawn/Navigation/CollisionHandlingMovement.cpp index d75c4d401caf16d8485458db2ed4f4e78c4689d3..62bfa4602491506185646dfd769a3876ca4b8ccb 100644 --- a/Source/RWTHVRToolkit/Private/Pawn/Navigation/CollisionHandlingMovement.cpp +++ b/Source/RWTHVRToolkit/Private/Pawn/Navigation/CollisionHandlingMovement.cpp @@ -1,6 +1,8 @@ #include "Pawn/Navigation/CollisionHandlingMovement.h" #include "Kismet/KismetSystemLibrary.h" +#include "Logging/StructuredLog.h" +#include "Utility/RWTHVRUtilities.h" UCollisionHandlingMovement::UCollisionHandlingMovement(const FObjectInitializer& ObjectInitializer) : Super(ObjectInitializer) @@ -33,61 +35,76 @@ void UCollisionHandlingMovement::BeginPlay() void UCollisionHandlingMovement::TickComponent(float DeltaTime, enum ELevelTick TickType, FActorComponentTickFunction* ThisTickFunction) { - SetCapsuleColliderToUserSize(); - FVector InputVector = GetPendingInputVector(); - - if (NavigationMode == EVRNavigationModes::NAV_WALK) + if (ShouldSkipUpdate(DeltaTime)) { - // you are only allowed to move horizontally in NAV_WALK - // everything else will be handled by stepping-up/gravity - // so rotate the input vector onto horizontal plane - const FRotator InputRot = FRotator(InputVector.Rotation()); - const FRotator InputYaw = FRotator(0, InputRot.Yaw, 0); - InputVector = InputRot.UnrotateVector(InputVector); - InputVector = InputYaw.RotateVector(InputVector); - ConsumeInputVector(); - AddInputVector(InputVector); + return; } - - if (NavigationMode == EVRNavigationModes::NAV_FLY || NavigationMode == EVRNavigationModes::NAV_WALK) + const AController* Controller = PawnOwner->GetController(); + if (Controller && Controller->IsLocalController()) { - // if me managed to get into a collision revert the movement since last Tick - CheckAndRevertCollisionSinceLastTick(); - // check whether we are still in collision e.g. if an object has moved and got us into collision - MoveOutOfNewDynamicCollisions(); + SetCapsuleColliderToUserSize(); - if (InputVector.Size() > 0.001) + FVector InputVector = GetPendingInputVector(); + + if (NavigationMode == EVRNavigationModes::NAV_WALK) { - const FVector SafeSteeringInput = GetCollisionSafeVirtualSteeringVec(InputVector, DeltaTime); - if (SafeSteeringInput != InputVector) + // you are only allowed to move horizontally in NAV_WALK + // everything else will be handled by stepping-up/gravity + // so rotate the input vector onto horizontal plane + const FRotator InputRot = FRotator(InputVector.Rotation()); + const FRotator InputYaw = FRotator(0, InputRot.Yaw, 0); + InputVector = InputRot.UnrotateVector(InputVector); + InputVector = InputYaw.RotateVector(InputVector); + ConsumeInputVector(); + AddInputVector(InputVector); + } + + if (NavigationMode == EVRNavigationModes::NAV_FLY || NavigationMode == EVRNavigationModes::NAV_WALK) + { + // if me managed to get into a collision revert the movement since last Tick + CheckAndRevertCollisionSinceLastTick(); + // check whether we are still in collision e.g. if an object has moved and got us into collision + MoveOutOfNewDynamicCollisions(); + + if (InputVector.Size() > 0.001) + { + const FVector SafeSteeringInput = GetCollisionSafeVirtualSteeringVec(InputVector, DeltaTime); + if (SafeSteeringInput != InputVector) + { + // if we would move into something if we apply this input (estimating distance by max speed) + // we only apply its perpendicular part (unless it is facing away from the collision) + ConsumeInputVector(); + AddInputVector(SafeSteeringInput); + } + } + + // in case we are in a collision and collision checks are temporarily deactivated, we only allow physical + // movement without any checks, otherwise check collision during physical movement + if (bCollisionChecksTemporarilyDeactivated) { - // if we would move into something if we apply this input (estimating distance by max speed) - // we only apply its perpendicular part (unless it is facing away from the collision) ConsumeInputVector(); - AddInputVector(SafeSteeringInput); + } + else + { + // so we add stepping-up (for both walk and fly) + // and gravity for walking only + MoveByGravityOrStepUp(DeltaTime); + + // if we physically (in the tracking space) walked into something, move the world away (by moving the + // pawn) + CheckForPhysWalkingCollision(); } } - // in case we are in a collision and collision checks are temporarily deactivated. - if (!bCollisionChecksTemporarilyDeactivated) + if (NavigationMode == EVRNavigationModes::NAV_NONE) { - // so we add stepping-up (for both walk and fly) - // and gravity for walking only - MoveByGravityOrStepUp(DeltaTime); - - // if we physically (in the tracking space) walked into something, move the world away (by moving the pawn) - CheckForPhysWalkingCollision(); + // just remove whatever input is there + ConsumeInputVector(); } } - if (NavigationMode == EVRNavigationModes::NAV_NONE) - { - // just remove whatever input is there - ConsumeInputVector(); - } - Super::TickComponent(DeltaTime, TickType, ThisTickFunction); } @@ -100,6 +117,34 @@ void UCollisionHandlingMovement::SetHeadComponent(USceneComponent* NewHeadCompon CapsuleColliderComponent->SetWorldLocation(FVector(0.0f, 0.0f, -HalfHeight)); } +bool UCollisionHandlingMovement::AddActorToIgnore(AActor* ActorToIgnore) +{ + if (ActorToIgnore && IsValid(ActorToIgnore)) + { + ActorsToIgnore.AddUnique(ActorToIgnore); + return true; + } + else + { + UE_LOGFMT(Toolkit, Warning, "UCollisionHandlingMovement::AddActorToIgnore: Cannot add invalid actor"); + return false; + } +} + +bool UCollisionHandlingMovement::RemoveActorFromIgnore(AActor* ActorToIgnore) +{ + if (ActorToIgnore && IsValid(ActorToIgnore)) + { + const int32 NumRemoved = ActorsToIgnore.Remove(ActorToIgnore); + return NumRemoved > 0; + } + else + { + UE_LOGFMT(Toolkit, Warning, "UCollisionHandlingMovement::RemoveActorFromIgnore: Cannot remove invalid actor"); + return false; + } +} + void UCollisionHandlingMovement::SetCapsuleColliderToUserSize() const { // the collider should be placed diff --git a/Source/RWTHVRToolkit/Public/Interaction/Interactables/ActionBehaviour.h b/Source/RWTHVRToolkit/Public/Interaction/Interactables/ActionBehaviour.h index 755d876dc3205b0538f6bc394352def55b2da670..bf3afea3a1246994a0bcbfb1d96317083e2770e3 100644 --- a/Source/RWTHVRToolkit/Public/Interaction/Interactables/ActionBehaviour.h +++ b/Source/RWTHVRToolkit/Public/Interaction/Interactables/ActionBehaviour.h @@ -3,21 +3,18 @@ #pragma once #include "CoreMinimal.h" -#include "InputAction.h" +#include "BaseBehaviour.h" #include "Components/SceneComponent.h" #include "InputActionValue.h" -#include "InteractionBitSet.h" #include "ActionBehaviour.generated.h" +enum EInteractionEventType : uint8; -DECLARE_DYNAMIC_MULTICAST_DELEGATE_ThreeParams(FOnActionBegin, USceneComponent*, TriggeredComponent, - const UInputAction*, InputAction, const FInputActionValue&, Value); - -DECLARE_DYNAMIC_MULTICAST_DELEGATE_ThreeParams(FOnActionEnd, USceneComponent*, TriggeredComponent, const UInputAction*, - InputAction, const FInputActionValue&, Value); +DECLARE_DYNAMIC_MULTICAST_DELEGATE_ThreeParams(FOnActionBegin, USceneComponent*, TriggerComponent, + const EInteractionEventType, EventType, const FInputActionValue&, Value); UCLASS(ClassGroup = (Custom), meta = (BlueprintSpawnableComponent)) -class RWTHVRTOOLKIT_API UActionBehaviour : public USceneComponent +class RWTHVRTOOLKIT_API UActionBehaviour : public UBaseBehaviour { GENERATED_BODY() @@ -26,22 +23,23 @@ public: UActionBehaviour(); UPROPERTY(BlueprintAssignable) - FOnActionBegin OnActionBeginEvent; - + FOnActionBegin OnActionEventEvent; + + /** + * Replication specific: + * This is only executed on the local client which processed the interaction and requested the replication process + * to be started. Can be used e.g. for local effects or things that should be done both on the server and local + * client. Broadcast by UInteractableComponent when the originating client sends a server rpc to start the + * interaction replication. + */ UPROPERTY(BlueprintAssignable) - FOnActionEnd OnActionEndEvent; + FOnActionBegin OnActionReplicationStartedOriginatorEvent; + protected: UFUNCTION() - virtual void OnActionStart(USceneComponent* TriggeredComponent, const UInputAction* InputAction, + virtual void OnActionEvent(USceneComponent* TriggerComponent, const EInteractionEventType EventType, const FInputActionValue& Value); - UFUNCTION() - virtual void OnActionEnd(USceneComponent* TriggeredComponent, const UInputAction* InputAction, - const FInputActionValue& Value); - virtual void BeginPlay() override; - - virtual void TickComponent(float DeltaTime, ELevelTick TickType, - FActorComponentTickFunction* ThisTickFunction) override; }; diff --git a/Source/RWTHVRToolkit/Public/Interaction/Interactables/BaseBehaviour.h b/Source/RWTHVRToolkit/Public/Interaction/Interactables/BaseBehaviour.h new file mode 100644 index 0000000000000000000000000000000000000000..d1f379bb9431749786d2ac871d119e82f5bea46e --- /dev/null +++ b/Source/RWTHVRToolkit/Public/Interaction/Interactables/BaseBehaviour.h @@ -0,0 +1,34 @@ +// Fill out your copyright notice in the Description page of Project Settings. + +#pragma once + +#include "CoreMinimal.h" +#include "Components/SceneComponent.h" +#include "BaseBehaviour.generated.h" + + +UCLASS(ClassGroup = (Custom), meta = (BlueprintSpawnableComponent)) +class RWTHVRTOOLKIT_API UBaseBehaviour : public USceneComponent +{ + GENERATED_BODY() + +public: + /** + * Replication part + */ + + /** + * Defines whether this behaviour will be executed on the server instead of the local client. + * If set to true, an RPC is sent to the server and it will not be run locally. + * */ + UPROPERTY(EditAnywhere, BlueprintReadWrite) + bool bExecuteOnServer = false; + + /** + * Defines whether this behaviour will be executed on all connected clients that are relevant, including the + * local originator client. This only has an effect if bExecuteOnServer is true, as only the server can multicast + * to all other clients. + */ + UPROPERTY(EditAnywhere, BlueprintReadWrite, meta = (EditCondition = "bExecuteOnServer")) + bool bExecuteOnAllClients = false; +}; diff --git a/Source/RWTHVRToolkit/Public/Interaction/Interactables/GrabBehavior.h b/Source/RWTHVRToolkit/Public/Interaction/Interactables/GrabBehavior.h index d10affdff4aa25e2e4c22818219375580f342c8f..65118646b4ca6edae19772cb3d5bf1df875534ee 100644 --- a/Source/RWTHVRToolkit/Public/Interaction/Interactables/GrabBehavior.h +++ b/Source/RWTHVRToolkit/Public/Interaction/Interactables/GrabBehavior.h @@ -20,14 +20,13 @@ class RWTHVRTOOLKIT_API UGrabBehavior : public UActionBehaviour GENERATED_BODY() public: + UGrabBehavior(); + UPROPERTY(EditAnywhere, Category = "Grabbing") bool bBlockOtherInteractionsWhileGrabbed = true; - virtual void OnActionStart(USceneComponent* TriggeredComponent, const UInputAction* InputAction, - const FInputActionValue& Value) override; - virtual void OnActionEnd(USceneComponent* TriggeredComponent, const UInputAction* InputAction, - const FInputActionValue& Value) override; - + UPROPERTY(EditAnywhere, Category = "Grabbing") + bool bIgnoreGrabbedActorInCollisionMovement = true; /** * Called after the object was successfully attached to the hand @@ -41,13 +40,25 @@ public: UPROPERTY(BlueprintAssignable) FOnGrabEnd OnGrabEndEvent; + UPROPERTY() + UPrimitiveComponent* MyPhysicsComponent; + + virtual void BeginPlay() override; + UPrimitiveComponent* GetFirstComponentSimulatingPhysics(const AActor* TargetActor); // recursively goes up the hierarchy and returns the highest parent simulating physics UPrimitiveComponent* GetHighestParentSimulatingPhysics(UPrimitiveComponent* Comp); - UPROPERTY() - UPrimitiveComponent* MyPhysicsComponent; + UFUNCTION() + void ReplicationOriginaterClientCallback(USceneComponent* TriggerComponent, const EInteractionEventType EventType, + const FInputActionValue& Value); + + void HandleCollisionHandlingMovement(const USceneComponent* CurrentAttachParent, + const EInteractionEventType EventType); + + virtual void OnActionEvent(USceneComponent* TriggerComponent, const EInteractionEventType EventType, + const FInputActionValue& Value) override; UFUNCTION(BlueprintPure) bool IsObjectGrabbed() const { return bObjectGrabbed; } @@ -61,7 +72,13 @@ private: */ bool TryRelease(); + void StartGrab(USceneComponent* TriggerComponent); + + void EndGrab(USceneComponent* TriggerComponent); + bool bObjectGrabbed = false; bool bWasSimulatingPhysics; + + bool bWasAddedToIgnore = false; }; diff --git a/Source/RWTHVRToolkit/Public/Interaction/Interactables/HoverBehaviour.h b/Source/RWTHVRToolkit/Public/Interaction/Interactables/HoverBehaviour.h index 7557653fbf02dc8d1780bbb32804c583324281ad..efdd6eafd797bf60bc07847565bf216537309358 100644 --- a/Source/RWTHVRToolkit/Public/Interaction/Interactables/HoverBehaviour.h +++ b/Source/RWTHVRToolkit/Public/Interaction/Interactables/HoverBehaviour.h @@ -3,16 +3,16 @@ #pragma once #include "CoreMinimal.h" +#include "BaseBehaviour.h" +#include "InteractionEventType.h" #include "Components/SceneComponent.h" #include "HoverBehaviour.generated.h" - -DECLARE_DYNAMIC_MULTICAST_DELEGATE_TwoParams(FOnHoverStart, const USceneComponent*, TriggeredComponent, FHitResult, - Hit); -DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnHoverEnd, const USceneComponent*, TriggeredComponent); +DECLARE_DYNAMIC_MULTICAST_DELEGATE_ThreeParams(FOnHoverEvent, const USceneComponent*, TriggeredComponent, + const EInteractionEventType, EventType, const FHitResult&, Hit); UCLASS(ClassGroup = (Custom), meta = (BlueprintSpawnableComponent)) -class RWTHVRTOOLKIT_API UHoverBehaviour : public USceneComponent +class RWTHVRTOOLKIT_API UHoverBehaviour : public UBaseBehaviour { GENERATED_BODY() @@ -22,16 +22,22 @@ public: * VRPawn) Hit: Hit Result of the trace to get access to e.g. contact point/normals etc. */ UPROPERTY(BlueprintAssignable) - FOnHoverStart OnHoverStartEvent; + FOnHoverEvent OnHoverEventEvent; + /** + * Replication specific: + * This is only executed on the local client which processed the interaction and requested the replication process + * to be started. Can be used e.g. for local effects or things that should be done both on the server and local + * client. Broadcast by UInteractableComponent when the originating client sends a server rpc to start the + * interaction replication. + */ UPROPERTY(BlueprintAssignable) - FOnHoverEnd OnHoverEndEvent; + FOnHoverEvent OnHoverReplicationStartedOriginatorEvent; protected: UFUNCTION() - virtual void OnHoverStart(const USceneComponent* TriggeredComponent, FHitResult Hit); - UFUNCTION() - virtual void OnHoverEnd(const USceneComponent* TriggeredComponent); + virtual void OnHoverEvent(const USceneComponent* TriggerComponent, EInteractionEventType EventType, + const FHitResult& Hit); virtual void BeginPlay() override; }; diff --git a/Source/RWTHVRToolkit/Public/Interaction/Interactables/InteractableComponent.h b/Source/RWTHVRToolkit/Public/Interaction/Interactables/InteractableComponent.h index da85ad8b003814098fc21e112de423a097a80535..7ef7078b931eebdc43d94932ebe6d615a8a7a442 100644 --- a/Source/RWTHVRToolkit/Public/Interaction/Interactables/InteractableComponent.h +++ b/Source/RWTHVRToolkit/Public/Interaction/Interactables/InteractableComponent.h @@ -9,6 +9,8 @@ #include "InteractableComponent.generated.h" +enum EInteractionEventType : uint8; +class UBaseBehaviour; struct FInputActionValue; class UActionBehaviour; class UHoverBehaviour; @@ -53,7 +55,7 @@ public: bool bAllowInteractionFromChildGeometry = true; UFUNCTION(BlueprintCallable) - FORCEINLINE bool HasInteractionTypeFlag(EInteractorType type) { return type & InteractorFilter; } + FORCEINLINE bool HasInteractionTypeFlag(EInteractorType Type) { return Type & InteractorFilter; } /** * @brief Restrict interactability to given components (e.g. if an object is grabbed, block interactions from other @@ -74,12 +76,10 @@ protected: virtual void BeginPlay() override; public: - void HandleOnHoverStartEvents(USceneComponent* TriggerComponent, const EInteractorType Interactor); - void HandleOnHoverEndEvents(USceneComponent* TriggerComponent, const EInteractorType Interactor); - void HandleOnActionStartEvents(USceneComponent* TriggerComponent, const UInputAction* InputAction, - const FInputActionValue& Value, const EInteractorType Interactor); - void HandleOnActionEndEvents(USceneComponent* TriggerComponent, const UInputAction* InputAction, - const FInputActionValue& Value, const EInteractorType Interactor); + void HandleOnHoverEvents(USceneComponent* TriggerComponent, const EInteractorType Interactor, + const EInteractionEventType EventType); + void HandleOnActionEvents(USceneComponent* TriggerComponent, const EInteractorType Interactor, + const EInteractionEventType EventType, const FInputActionValue& Value); /** * @brief If hover and action behaviors are not explicitly specified, load all existing ones diff --git a/Source/RWTHVRToolkit/Public/Interaction/Interactables/InteractionEventType.h b/Source/RWTHVRToolkit/Public/Interaction/Interactables/InteractionEventType.h new file mode 100644 index 0000000000000000000000000000000000000000..9e24c211276613c6ea0e7b11037d59e05caefba0 --- /dev/null +++ b/Source/RWTHVRToolkit/Public/Interaction/Interactables/InteractionEventType.h @@ -0,0 +1,8 @@ +#pragma once + +UENUM(BlueprintType) +enum EInteractionEventType : uint8 +{ + InteractionStart = 0, + InteractionEnd = 1, +}; diff --git a/Source/RWTHVRToolkit/Public/Interaction/Interactors/DirectInteractionComponent.h b/Source/RWTHVRToolkit/Public/Interaction/Interactors/DirectInteractionComponent.h index 69ef74ff844a727c11a333047a7f011a12d01241..46d2d160358d991a70e232bce73bc6c9a57c52ef 100644 --- a/Source/RWTHVRToolkit/Public/Interaction/Interactors/DirectInteractionComponent.h +++ b/Source/RWTHVRToolkit/Public/Interaction/Interactors/DirectInteractionComponent.h @@ -3,13 +3,13 @@ #pragma once #include "CoreMinimal.h" +#include "UBaseInteractionComponent.h" #include "Components/SceneComponent.h" #include "Interaction/Interactables/InteractableComponent.h" -#include "Pawn/InputExtensionInterface.h" #include "DirectInteractionComponent.generated.h" UCLASS(Abstract, Blueprintable) -class RWTHVRTOOLKIT_API UDirectInteractionComponent : public USceneComponent, public IInputExtensionInterface +class RWTHVRTOOLKIT_API UDirectInteractionComponent : public UUBaseInteractionComponent { GENERATED_BODY() @@ -20,29 +20,18 @@ public: virtual void TickComponent(float DeltaTime, ELevelTick TickType, FActorComponentTickFunction* ThisTickFunction) override; - UPROPERTY(EditAnywhere, Category = "Input") - class UInputAction* InteractionInputAction; - UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Direct Interaction") float InteractionSphereRadius = 15.0; - UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Direct Interaction") - bool bShowDebugTrace = false; - UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Direct Interaction") bool bOnlyInteractWithClosestActor = false; - UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Direct Interaction") - TArray<AActor*> ActorsToIgnore; - virtual void SetupPlayerInput(UInputComponent* PlayerInputComponent) override; private: - UFUNCTION() - void OnBeginInteraction(const FInputActionValue& Value); + virtual void OnBeginInteractionInputAction(const FInputActionValue& Value) override; - UFUNCTION() - void OnEndInteraction(const FInputActionValue& Value); + virtual void OnEndInteractionInputAction(const FInputActionValue& Value) override; UPROPERTY() TArray<UInteractableComponent*> PreviousInteractableComponentsInRange; diff --git a/Source/RWTHVRToolkit/Public/Interaction/Interactors/RaycastInteractionComponent.h b/Source/RWTHVRToolkit/Public/Interaction/Interactors/RaycastInteractionComponent.h index f5c93123e8ba136e747796825f93309b285109b0..31441d516c5a34923c2ca6d26665a50a9f6069ed 100644 --- a/Source/RWTHVRToolkit/Public/Interaction/Interactors/RaycastInteractionComponent.h +++ b/Source/RWTHVRToolkit/Public/Interaction/Interactors/RaycastInteractionComponent.h @@ -2,16 +2,15 @@ #pragma once -#include <Pawn/InputExtensionInterface.h> - #include "CoreMinimal.h" +#include "UBaseInteractionComponent.h" #include "Components/SceneComponent.h" #include "Interaction/Interactables/InteractableComponent.h" #include "RaycastInteractionComponent.generated.h" UCLASS(Abstract, Blueprintable) -class RWTHVRTOOLKIT_API URaycastInteractionComponent : public USceneComponent, public IInputExtensionInterface +class RWTHVRTOOLKIT_API URaycastInteractionComponent : public UUBaseInteractionComponent { GENERATED_BODY() @@ -23,20 +22,13 @@ public: virtual void TickComponent(float DeltaTime, ELevelTick TickType, FActorComponentTickFunction* ThisTickFunction) override; - UPROPERTY(EditAnywhere, Category = "Input") - class UInputAction* InteractionInputAction; - UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Raycast") float TraceLength = 3000.0; - UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Raycast") - bool bShowDebugTrace = false; private: - UFUNCTION() - void OnBeginInteraction(const FInputActionValue& Value); + virtual void OnBeginInteractionInputAction(const FInputActionValue& Value) override; - UFUNCTION() - void OnEndInteraction(const FInputActionValue& Value); + virtual void OnEndInteractionInputAction(const FInputActionValue& Value) override; public: virtual void SetupPlayerInput(UInputComponent* PlayerInputComponent) override; diff --git a/Source/RWTHVRToolkit/Public/Interaction/Interactors/UBaseInteractionComponent.h b/Source/RWTHVRToolkit/Public/Interaction/Interactors/UBaseInteractionComponent.h new file mode 100644 index 0000000000000000000000000000000000000000..ca97fd7e36f1359273734f7b88da77777fdfa7b0 --- /dev/null +++ b/Source/RWTHVRToolkit/Public/Interaction/Interactors/UBaseInteractionComponent.h @@ -0,0 +1,103 @@ +// Fill out your copyright notice in the Description page of Project Settings. + +#pragma once + +#include "CoreMinimal.h" +#include "Components/SceneComponent.h" +#include "Pawn/InputExtensionInterface.h" +#include "UBaseInteractionComponent.generated.h" + + +struct FInputActionValue; +enum EInteractionEventType : uint8; +class UActionBehaviour; +class UHoverBehaviour; + +UCLASS(ClassGroup = (Custom), meta = (BlueprintSpawnableComponent)) +class RWTHVRTOOLKIT_API UUBaseInteractionComponent : public USceneComponent, public IInputExtensionInterface +{ + GENERATED_BODY() + +public: + // Sets default values for this component's properties + UUBaseInteractionComponent(); + + virtual void SetupPlayerInput(UInputComponent* PlayerInputComponent) override; + + UPROPERTY(EditAnywhere, Category = "Input") + class UInputAction* InteractionInputAction; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Base Interaction") + TArray<AActor*> ActorsToIgnore; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Base Interaction") + bool bShowDebugTrace = false; + + /* + * Replication part + */ + + + /** + * Requests the start of the replication process for the given HoverBehaviour, EventType and Hit. + * Depending on authority, this executes the replication of the behaviour directly or requests it via a server RPC. + */ + void RequestHoverBehaviourReplicationStart(UHoverBehaviour* Behaviour, const EInteractionEventType EventType, + const FHitResult& Hit); + + /** + * This is executed on the server/authority. The behaviour is actually executed directly on the server, or + * multicast to all connected clients. The multicast then executes the behaviour. + */ + void HoverBehaviourReplicationStart(UHoverBehaviour* Behaviour, const EInteractionEventType EventType, + const FHitResult& Hit); + + /** + * This is only executed on the local client which processed the interaction and requested the replication process + * to be started. Can be used e.g. for local effects or things that should be done both on the server and local + * client. + */ + void HoverBehaviourReplicationOriginatorCallback(UHoverBehaviour* Behaviour, const EInteractionEventType EventType, + const FHitResult& Hit); + + /** + * Requests the start of the replication process for the given ActionBehaviour, EventType and the Value of the Input + * Action. Depending on authority, this executes the replication of the behaviour directly or requests it via a + * server RPC. + */ + void RequestActionBehaviourReplicationStart(UActionBehaviour* Behaviour, const EInteractionEventType EventType, + const FInputActionValue& Value); + + /** + * This is executed on the server/authority. The behaviour is actually executed directly on the server, or + * multicast to all connected clients. The multicast then executes the behaviour. + */ + void ActionBehaviourReplicationStart(UActionBehaviour* Behaviour, const EInteractionEventType EventType, + const FInputActionValue& Value); + + + // RPCs + UFUNCTION(Server, Reliable) + void ServerRequestHoverBehaviourReplicationStartRpc(UHoverBehaviour* Behaviour, + const EInteractionEventType EventType, const FHitResult& Hit); + + UFUNCTION(NetMulticast, Reliable) + void MulticastHoverBehaviourReplicationStartRpc(UHoverBehaviour* Behaviour, const EInteractionEventType EventType, + const FHitResult& Hit); + + UFUNCTION(Server, Reliable) + void ServerRequestActionBehaviourReplicationStartRpc(UActionBehaviour* Behaviour, + const EInteractionEventType EventType, + const FInputActionValue& Value); + + UFUNCTION(NetMulticast, Reliable) + void MulticastActionBehaviourReplicationStartRpc(UActionBehaviour* Behaviour, const EInteractionEventType EventType, + const FInputActionValue& Value); + +private: + UFUNCTION() + virtual void OnBeginInteractionInputAction(const FInputActionValue& Value); + + UFUNCTION() + virtual void OnEndInteractionInputAction(const FInputActionValue& Value); +}; diff --git a/Source/RWTHVRToolkit/Public/Pawn/Navigation/CollisionHandlingMovement.h b/Source/RWTHVRToolkit/Public/Pawn/Navigation/CollisionHandlingMovement.h index 6166c84a1c6f2aee1adfdd2d7b5080ac027d23cd..b9cfad134d1b57e12d6be08a82b56935f3d4674b 100644 --- a/Source/RWTHVRToolkit/Public/Pawn/Navigation/CollisionHandlingMovement.h +++ b/Source/RWTHVRToolkit/Public/Pawn/Navigation/CollisionHandlingMovement.h @@ -42,6 +42,12 @@ public: void SetHeadComponent(USceneComponent* NewHeadComponent); + UFUNCTION(BlueprintCallable) + bool AddActorToIgnore(AActor* ActorToIgnore); + + UFUNCTION(BlueprintCallable) + bool RemoveActorFromIgnore(AActor* ActorToIgnore); + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "VR Movement") EVRNavigationModes NavigationMode = EVRNavigationModes::NAV_WALK;