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

Merge branch 'dev/5.3' into 'dev/5.3'

feature(Interaction): improve grabbing

See merge request !56
parents 487a5db5 ac6de687
No related branches found
No related tags found
2 merge requests!80UE5.3-2023.1-rc2,!56feature(Interaction): improve grabbing
Showing
with 194 additions and 43 deletions
File deleted
No preview for this file type
No preview for this file type
No preview for this file type
File added
No preview for this file type
...@@ -3,8 +3,9 @@ ...@@ -3,8 +3,9 @@
#include "Interaction/Interactables/GrabBehavior.h" #include "Interaction/Interactables/GrabBehavior.h"
#include "Interaction/Interactables/InteractableComponent.h" #include "Interaction/Interactables/InteractableComponent.h"
#include "Kismet/GameplayStatics.h" #include "Logging/StructuredLog.h"
#include "Serialization/JsonTypes.h" #include "Serialization/JsonTypes.h"
#include "Utility/RWTHVRUtilities.h"
UPrimitiveComponent* UGrabBehavior::GetFirstComponentSimulatingPhysics(const AActor* TargetActor) UPrimitiveComponent* UGrabBehavior::GetFirstComponentSimulatingPhysics(const AActor* TargetActor)
{ {
...@@ -29,34 +30,42 @@ UPrimitiveComponent* UGrabBehavior::GetHighestParentSimulatingPhysics(UPrimitive ...@@ -29,34 +30,42 @@ UPrimitiveComponent* UGrabBehavior::GetHighestParentSimulatingPhysics(UPrimitive
{ {
return GetHighestParentSimulatingPhysics(Cast<UPrimitiveComponent>(Comp->GetAttachParent())); return GetHighestParentSimulatingPhysics(Cast<UPrimitiveComponent>(Comp->GetAttachParent()));
} }
else
{
return Comp; return Comp;
} }
}
void UGrabBehavior::OnActionStart(USceneComponent* TriggeredComponent, const UInputAction* InputAction, void UGrabBehavior::OnActionStart(USceneComponent* TriggeredComponent, const UInputAction* InputAction,
const FInputActionValue& Value) const FInputActionValue& Value)
{ {
const APawn* Player = UGameplayStatics::GetPlayerPawn(GetWorld(), 0); if (bObjectGrabbed)
{
USceneComponent* Hand = Cast<USceneComponent>(TriggeredComponent->GetAttachParent()); return;
}
USceneComponent* CurrentAttachParent = Cast<USceneComponent>(TriggeredComponent->GetAttachParent());
const FAttachmentTransformRules Rules = FAttachmentTransformRules(EAttachmentRule::KeepWorld, false); const FAttachmentTransformRules Rules = FAttachmentTransformRules(EAttachmentRule::KeepWorld, false);
MyPhysicsComponent = GetFirstComponentSimulatingPhysics(GetOwner()); if (MyPhysicsComponent = GetFirstComponentSimulatingPhysics(GetOwner()); MyPhysicsComponent != nullptr)
if (MyPhysicsComponent)
{ {
bWasSimulatingPhysics = MyPhysicsComponent->IsSimulatingPhysics();
MyPhysicsComponent->SetSimulatePhysics(false); MyPhysicsComponent->SetSimulatePhysics(false);
MyPhysicsComponent->AttachToComponent(Hand, Rules); bObjectGrabbed = MyPhysicsComponent->AttachToComponent(CurrentAttachParent, Rules);
} }
else else
{ {
GetOwner()->GetRootComponent()->AttachToComponent(Hand, Rules); bObjectGrabbed = GetOwner()->GetRootComponent()->AttachToComponent(CurrentAttachParent, Rules);
} }
if (!bObjectGrabbed)
{
UE_LOGFMT(Toolkit, Warning, "Grab failed! Cannot attach grabbed component to attach parent ({Parent})",
CurrentAttachParent->GetName());
return;
}
// If we want to restrict other interactions while this component is grabbed we add the component
// that triggered the interaction to the whitelist of all interactables that are attached to the
// affected actor
if (bBlockOtherInteractionsWhileGrabbed) if (bBlockOtherInteractionsWhileGrabbed)
{ {
TArray<UInteractableComponent*> Interactables; TArray<UInteractableComponent*> Interactables;
...@@ -66,21 +75,28 @@ void UGrabBehavior::OnActionStart(USceneComponent* TriggeredComponent, const UIn ...@@ -66,21 +75,28 @@ void UGrabBehavior::OnActionStart(USceneComponent* TriggeredComponent, const UIn
Interactable->RestrictInteractionToComponent(TriggeredComponent); Interactable->RestrictInteractionToComponent(TriggeredComponent);
} }
} }
OnGrabStartEvent.Broadcast(CurrentAttachParent, MyPhysicsComponent);
} }
void UGrabBehavior::OnActionEnd(USceneComponent* TriggeredComponent, const UInputAction* InputAction, void UGrabBehavior::OnActionEnd(USceneComponent* TriggeredComponent, const UInputAction* InputAction,
const FInputActionValue& Value) const FInputActionValue& Value)
{ {
if (MyPhysicsComponent)
{ USceneComponent* CurrentAttachParent = Cast<USceneComponent>(TriggeredComponent->GetAttachParent());
MyPhysicsComponent->DetachFromComponent(FDetachmentTransformRules::KeepWorldTransform);
MyPhysicsComponent->SetSimulatePhysics(true); // We try to release the attached component. If it is not succesful we log and return. Otherwise, we continue.
} if (!TryRelease())
else
{ {
GetOwner()->GetRootComponent()->DetachFromComponent(FDetachmentTransformRules::KeepWorldTransform); UE_LOGFMT(Toolkit, Display,
"UGrabBehavior::OnActionEnd: TryRelease failed to release with AttachParent {Parent}",
CurrentAttachParent->GetName());
return;
} }
OnGrabEndEvent.Broadcast(CurrentAttachParent, MyPhysicsComponent);
// Release the interation restriction on all component
if (bBlockOtherInteractionsWhileGrabbed) if (bBlockOtherInteractionsWhileGrabbed)
{ {
TArray<UInteractableComponent*> Interactables; TArray<UInteractableComponent*> Interactables;
...@@ -91,3 +107,23 @@ void UGrabBehavior::OnActionEnd(USceneComponent* TriggeredComponent, const UInpu ...@@ -91,3 +107,23 @@ void UGrabBehavior::OnActionEnd(USceneComponent* TriggeredComponent, const UInpu
} }
} }
} }
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;
}
...@@ -8,6 +8,8 @@ ...@@ -8,6 +8,8 @@
#include "Interaction/Interactables/InteractionBitSet.h" #include "Interaction/Interactables/InteractionBitSet.h"
#include "Kismet/GameplayStatics.h" #include "Kismet/GameplayStatics.h"
#include "Logging/StructuredLog.h"
#include "Utility/RWTHVRUtilities.h"
// Sets default values for this component's properties // Sets default values for this component's properties
UGrabComponent::UGrabComponent() UGrabComponent::UGrabComponent()
...@@ -15,7 +17,6 @@ UGrabComponent::UGrabComponent() ...@@ -15,7 +17,6 @@ UGrabComponent::UGrabComponent()
// Set this component to be initialized when the game starts, and to be ticked every frame. You can turn these // 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. // features off to improve performance if you don't need them.
PrimaryComponentTick.bCanEverTick = true; PrimaryComponentTick.bCanEverTick = true;
// ... // ...
} }
...@@ -23,10 +24,8 @@ void UGrabComponent::TickComponent(float DeltaTime, ELevelTick TickType, FActorC ...@@ -23,10 +24,8 @@ void UGrabComponent::TickComponent(float DeltaTime, ELevelTick TickType, FActorC
{ {
Super::TickComponent(DeltaTime, TickType, ThisTickFunction); Super::TickComponent(DeltaTime, TickType, ThisTickFunction);
TArray<UInteractableComponent*> CurrentGrabCompsInRange; TArray<UInteractableComponent*> CurrentGrabCompsInRange;
TArray<AActor*> ActorsToIgnore;
TArray<FHitResult> OutHits; TArray<FHitResult> OutHits;
const ETraceTypeQuery TraceType = UEngineTypes::ConvertToTraceType(ECollisionChannel::ECC_PhysicsBody); const ETraceTypeQuery TraceType = UEngineTypes::ConvertToTraceType(ECollisionChannel::ECC_PhysicsBody);
...@@ -41,42 +40,42 @@ void UGrabComponent::TickComponent(float DeltaTime, ELevelTick TickType, FActorC ...@@ -41,42 +40,42 @@ void UGrabComponent::TickComponent(float DeltaTime, ELevelTick TickType, FActorC
AActor* HitActor = Hit.GetActor(); AActor* HitActor = Hit.GetActor();
if (HitActor) if (HitActor)
{ {
UInteractableComponent* Grabbable = HitActor->FindComponentByClass<UInteractableComponent>(); UInteractableComponent* Grabbable = SearchForInteractable(HitActor);
if (Grabbable && Grabbable->HasInteractionTypeFlag(EInteractorType::Grab) && Grabbable->IsInteractable) if (Grabbable && Grabbable->HasInteractionTypeFlag(EInteractorType::Grab) && Grabbable->IsInteractable)
{ {
Grabbable->HitResult = Hit; Grabbable->HitResult = Hit;
CurrentGrabCompsInRange.Add(Grabbable); CurrentGrabCompsInRange.AddUnique(Grabbable);
} }
} }
} }
CurrentGrabbableInRange = CurrentGrabCompsInRange; CurrentGrabBehavioursInRange = CurrentGrabCompsInRange;
// Call hover start events on all components that were not in range before // Call hover start events on all components that were not in range before
for (UInteractableComponent* CurrentGrabbale : CurrentGrabCompsInRange) for (UInteractableComponent* CurrentGrabbable : CurrentGrabCompsInRange)
{ {
if (!PreviousGrabbablesInRange.Contains(CurrentGrabbale)) if (!PreviousGrabBehavioursInRange.Contains(CurrentGrabbable))
{ {
PreviousGrabbablesInRange.Add(CurrentGrabbale); PreviousGrabBehavioursInRange.AddUnique(CurrentGrabbable);
CurrentGrabbale->HandleOnHoverStartEvents(this, EInteractorType::Grab); CurrentGrabbable->HandleOnHoverStartEvents(this, EInteractorType::Grab);
} }
} }
TArray<UInteractableComponent*> ComponentsToRemove; TArray<UInteractableComponent*> ComponentsToRemove;
// Call hover end events on all components that were previously in range, but not anymore // Call hover end events on all components that were previously in range, but not anymore
for (UInteractableComponent* PrevGrabbale : PreviousGrabbablesInRange) for (UInteractableComponent* PrevGrabbale : PreviousGrabBehavioursInRange)
{ {
if (!CurrentGrabCompsInRange.Contains(PrevGrabbale)) if (!CurrentGrabCompsInRange.Contains(PrevGrabbale))
{ {
ComponentsToRemove.Add(PrevGrabbale); ComponentsToRemove.AddUnique(PrevGrabbale);
PrevGrabbale->HandleOnHoverEndEvents(this, EInteractorType::Grab); PrevGrabbale->HandleOnHoverEndEvents(this, EInteractorType::Grab);
} }
} }
for (UInteractableComponent* CompToRemove : ComponentsToRemove) for (UInteractableComponent* CompToRemove : ComponentsToRemove)
{ {
PreviousGrabbablesInRange.Remove(CompToRemove); PreviousGrabBehavioursInRange.Remove(CompToRemove);
} }
} }
...@@ -88,6 +87,9 @@ void UGrabComponent::SetupPlayerInput(UInputComponent* PlayerInputComponent) ...@@ -88,6 +87,9 @@ void UGrabComponent::SetupPlayerInput(UInputComponent* PlayerInputComponent)
if (!Pawn) if (!Pawn)
return; return;
// Probably not the best place to add this.
ActorsToIgnore.AddUnique(GetOwner());
UEnhancedInputComponent* EI = Cast<UEnhancedInputComponent>(Pawn->InputComponent); UEnhancedInputComponent* EI = Cast<UEnhancedInputComponent>(Pawn->InputComponent);
if (EI == nullptr) if (EI == nullptr)
return; return;
...@@ -98,16 +100,79 @@ void UGrabComponent::SetupPlayerInput(UInputComponent* PlayerInputComponent) ...@@ -98,16 +100,79 @@ void UGrabComponent::SetupPlayerInput(UInputComponent* PlayerInputComponent)
void UGrabComponent::OnBeginGrab(const FInputActionValue& Value) void UGrabComponent::OnBeginGrab(const FInputActionValue& Value)
{ {
for (UInteractableComponent* Grabbale : CurrentGrabbableInRange) const FVector GrabLocation = GetAttachParent()->GetComponentLocation();
if (CurrentGrabBehavioursInRange.IsEmpty())
return;
if (bOnlyGrabClosestActor)
{
auto MinElement = *Algo::MinElementBy(
CurrentGrabBehavioursInRange,
[&](auto Element) { return FVector(Element->GetOwner()->GetActorLocation() - GrabLocation).Size(); });
MinElement->HandleOnActionStartEvents(this, GrabInputAction, Value, EInteractorType::Grab);
CurrentlyGrabbedComponents = {MinElement};
}
else
{
CurrentlyGrabbedComponents.Reserve(CurrentlyGrabbedComponents.Num() + CurrentGrabBehavioursInRange.Num());
for (UInteractableComponent* Grabbable : CurrentGrabBehavioursInRange)
{ {
Grabbale->HandleOnActionStartEvents(this, GrabInputAction, Value, EInteractorType::Grab); Grabbable->HandleOnActionStartEvents(this, GrabInputAction, Value, EInteractorType::Grab);
CurrentlyGrabbedComponents.Add(Grabbable);
}
} }
} }
void UGrabComponent::OnEndGrab(const FInputActionValue& Value) void UGrabComponent::OnEndGrab(const FInputActionValue& Value)
{ {
for (UInteractableComponent* Grabbale : CurrentGrabbableInRange) for (auto& Component : CurrentlyGrabbedComponents)
{
if (Component.IsValid())
{
Component->HandleOnActionEndEvents(this, GrabInputAction, Value, EInteractorType::Grab);
}
}
}
UInteractableComponent* UGrabComponent::SearchForInteractable(AActor* HitActor)
{
UInteractableComponent* Grabbable = nullptr;
if (!HitActor)
{
UE_LOGFMT(Toolkit, Warning, "UGrabComponent::SearchForInteractable: HitActor was nullptr, returning nullptr");
return nullptr;
}
if (HitActor->IsChildActor())
{
// search for UInteractable upwards from hit geometry and return first one found
Grabbable = HitActor->FindComponentByClass<UInteractableComponent>();
// if Grabbable is not valid search at parent
if (!Grabbable)
{
HitActor = HitActor->GetParentActor();
if (HitActor)
{
bSearchAtParent = true;
return SearchForInteractable(HitActor);
}
}
}
else if (!HitActor->IsChildActor())
{
Grabbable = HitActor->FindComponentByClass<UInteractableComponent>();
}
if (Grabbable)
{ {
Grabbale->HandleOnActionEndEvents(this, GrabInputAction, Value, EInteractorType::Grab); // in the case, were we had to iterate up the hierarchy, check if we are allowed
// to grab the parent via child geometry
if (bSearchAtParent && !Grabbable->bAllowInteractionFromChildGeometry)
{
Grabbable = nullptr;
}
} }
bSearchAtParent = false;
return Grabbable;
} }
...@@ -8,6 +8,12 @@ ...@@ -8,6 +8,12 @@
#include "GrabBehavior.generated.h" #include "GrabBehavior.generated.h"
DECLARE_DYNAMIC_MULTICAST_DELEGATE_TwoParams(FOnGrabStart, USceneComponent*, NewAttachParent, UPrimitiveComponent*,
HeldComponent);
DECLARE_DYNAMIC_MULTICAST_DELEGATE_TwoParams(FOnGrabEnd, USceneComponent*, PreviousAttachParent, UPrimitiveComponent*,
HeldComponent);
UCLASS(ClassGroup = (Custom), meta = (BlueprintSpawnableComponent)) UCLASS(ClassGroup = (Custom), meta = (BlueprintSpawnableComponent))
class RWTHVRTOOLKIT_API UGrabBehavior : public UActionBehaviour class RWTHVRTOOLKIT_API UGrabBehavior : public UActionBehaviour
{ {
...@@ -22,6 +28,19 @@ public: ...@@ -22,6 +28,19 @@ public:
virtual void OnActionEnd(USceneComponent* TriggeredComponent, const UInputAction* InputAction, virtual void OnActionEnd(USceneComponent* TriggeredComponent, const UInputAction* InputAction,
const FInputActionValue& Value) override; const FInputActionValue& Value) override;
/**
* Called after the object was successfully attached to the hand
*/
UPROPERTY(BlueprintAssignable)
FOnGrabStart OnGrabStartEvent;
/**
* Called after the object was successfully detached from the hand
*/
UPROPERTY(BlueprintAssignable)
FOnGrabEnd OnGrabEndEvent;
UPrimitiveComponent* GetFirstComponentSimulatingPhysics(const AActor* TargetActor); UPrimitiveComponent* GetFirstComponentSimulatingPhysics(const AActor* TargetActor);
// recursively goes up the hierarchy and returns the highest parent simulating physics // recursively goes up the hierarchy and returns the highest parent simulating physics
...@@ -29,4 +48,20 @@ public: ...@@ -29,4 +48,20 @@ public:
UPROPERTY() UPROPERTY()
UPrimitiveComponent* MyPhysicsComponent; UPrimitiveComponent* MyPhysicsComponent;
UFUNCTION(BlueprintPure)
bool IsObjectGrabbed() const { return bObjectGrabbed; }
private:
/**
* Try to detach the object from the hand. Keep this private for now as this does not broadcast the GrabEnd Event
* correctly.
* @return true if object was successfully detached. If detachment failed or if object was not grabbed before,
* return false.
*/
bool TryRelease();
bool bObjectGrabbed = false;
bool bWasSimulatingPhysics;
}; };
...@@ -46,6 +46,12 @@ public: ...@@ -46,6 +46,12 @@ public:
UPROPERTY(EditAnywhere, BlueprintReadWrite) UPROPERTY(EditAnywhere, BlueprintReadWrite)
TArray<UActionBehaviour*> OnActionBehaviours; TArray<UActionBehaviour*> OnActionBehaviours;
/**
* If true, allow a grab to be triggered by the geometry of a child actor.
*/
UPROPERTY(EditAnywhere, BlueprintReadWrite)
bool bAllowInteractionFromChildGeometry = true;
UFUNCTION(BlueprintCallable) UFUNCTION(BlueprintCallable)
FORCEINLINE bool HasInteractionTypeFlag(EInteractorType type) { return type & InteractorFilter; } FORCEINLINE bool HasInteractionTypeFlag(EInteractorType type) { return type & InteractorFilter; }
...@@ -76,7 +82,7 @@ public: ...@@ -76,7 +82,7 @@ public:
const FInputActionValue& Value, const EInteractorType Interactor); const FInputActionValue& Value, const EInteractorType Interactor);
/** /**
* @brief If click and grab behaviors are not explicitly specified, load all existing ones * @brief If hover and action behaviors are not explicitly specified, load all existing ones
*/ */
void InitDefaultBehaviourReferences(); void InitDefaultBehaviourReferences();
...@@ -91,7 +97,4 @@ public: ...@@ -91,7 +97,4 @@ public:
TArray<USceneComponent*> AllowedComponents; TArray<USceneComponent*> AllowedComponents;
bool IsComponentAllowed(USceneComponent* Component) const; bool IsComponentAllowed(USceneComponent* Component) const;
private:
bool bInitOnce = true;
}; };
...@@ -31,6 +31,12 @@ public: ...@@ -31,6 +31,12 @@ public:
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Grabbing") UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Grabbing")
bool bShowDebugTrace = false; bool bShowDebugTrace = false;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Grabbing")
bool bOnlyGrabClosestActor = false;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Grabbing")
TArray<AActor*> ActorsToIgnore;
virtual void SetupPlayerInput(UInputComponent* PlayerInputComponent) override; virtual void SetupPlayerInput(UInputComponent* PlayerInputComponent) override;
private: private:
...@@ -41,8 +47,14 @@ private: ...@@ -41,8 +47,14 @@ private:
void OnEndGrab(const FInputActionValue& Value); void OnEndGrab(const FInputActionValue& Value);
UPROPERTY() UPROPERTY()
TArray<UInteractableComponent*> PreviousGrabbablesInRange; TArray<UInteractableComponent*> PreviousGrabBehavioursInRange;
UPROPERTY() UPROPERTY()
TArray<UInteractableComponent*> CurrentGrabbableInRange; TArray<UInteractableComponent*> CurrentGrabBehavioursInRange;
TArray<TWeakObjectPtr<UInteractableComponent>> CurrentlyGrabbedComponents;
UInteractableComponent* SearchForInteractable(AActor* HitActor);
bool bSearchAtParent = false;
}; };
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment