├── .gitattributes ├── Resources └── Icon128.png ├── Source └── InteractionPlugin │ ├── Private │ ├── InteractionDataTypes.cpp │ ├── Interface │ │ └── InteractionInterface.cpp │ ├── InteractionPlugin.cpp │ ├── InteractionComponents │ │ ├── InteractionComponent_Instant.cpp │ │ ├── InteractionComponent_Hold.cpp │ │ └── InteractionComponent.cpp │ └── InteractorComponents │ │ └── InteractorComponent.cpp │ ├── Public │ ├── InteractionPlugin.h │ ├── InteractionDataTypes.h │ ├── InteractionComponents │ │ ├── InteractionComponent_Instant.h │ │ ├── InteractionComponent_Hold.h │ │ └── InteractionComponent.h │ ├── Interface │ │ └── InteractionInterface.h │ └── InteractorComponents │ │ └── InteractorComponent.h │ └── InteractionPlugin.Build.cs ├── InteractionPlugin.uplugin ├── .gitignore ├── LICENSE └── README.md /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto -------------------------------------------------------------------------------- /Resources/Icon128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Amirans/InteractionPlugin/HEAD/Resources/Icon128.png -------------------------------------------------------------------------------- /Source/InteractionPlugin/Private/InteractionDataTypes.cpp: -------------------------------------------------------------------------------- 1 | // Fill out your copyright notice in the Description page of Project Settings. 2 | 3 | #include "InteractionDataTypes.h" -------------------------------------------------------------------------------- /Source/InteractionPlugin/Private/Interface/InteractionInterface.cpp: -------------------------------------------------------------------------------- 1 | // Fill out your copyright notice in the Description page of Project Settings. 2 | 3 | #include "InteractionInterface.h" 4 | 5 | // Add default functionality here for any IInteractionInterface functions that are not pure virtual. 6 | -------------------------------------------------------------------------------- /Source/InteractionPlugin/Public/InteractionPlugin.h: -------------------------------------------------------------------------------- 1 | // Copyright 1998-2018 Epic Games, Inc. All Rights Reserved. 2 | 3 | #pragma once 4 | 5 | #include "CoreMinimal.h" 6 | #include "Modules/ModuleManager.h" 7 | 8 | class FInteractionPluginModule : public IModuleInterface 9 | { 10 | public: 11 | 12 | /** IModuleInterface implementation */ 13 | virtual void StartupModule() override; 14 | virtual void ShutdownModule() override; 15 | }; 16 | -------------------------------------------------------------------------------- /InteractionPlugin.uplugin: -------------------------------------------------------------------------------- 1 | { 2 | "FileVersion": 3, 3 | "Version": 1, 4 | "VersionName": "1.0", 5 | "FriendlyName": "InteractionPlugin", 6 | "Description": "", 7 | "Category": "Other", 8 | "CreatedBy": "Amir Ansari", 9 | "CreatedByURL": "", 10 | "DocsURL": "", 11 | "MarketplaceURL": "", 12 | "SupportURL": "https://github.com/Amirans/InteractionPlugin/", 13 | "CanContainContent": true, 14 | "IsBetaVersion": true, 15 | "Installed": false, 16 | "Modules": [ 17 | { 18 | "Name": "InteractionPlugin", 19 | "Type": "Runtime", 20 | "LoadingPhase": "Default" 21 | } 22 | ] 23 | } -------------------------------------------------------------------------------- /Source/InteractionPlugin/Private/InteractionPlugin.cpp: -------------------------------------------------------------------------------- 1 | // Copyright 1998-2018 Epic Games, Inc. All Rights Reserved. 2 | 3 | #include "InteractionPlugin.h" 4 | 5 | #define LOCTEXT_NAMESPACE "FInteractionPluginModule" 6 | 7 | void FInteractionPluginModule::StartupModule() 8 | { 9 | // This code will execute after your module is loaded into memory; the exact timing is specified in the .uplugin file per-module 10 | } 11 | 12 | void FInteractionPluginModule::ShutdownModule() 13 | { 14 | // This function may be called during shutdown to clean up your module. For modules that support dynamic reloading, 15 | // we call this function before unloading the module. 16 | } 17 | 18 | #undef LOCTEXT_NAMESPACE 19 | 20 | IMPLEMENT_MODULE(FInteractionPluginModule, InteractionPlugin) -------------------------------------------------------------------------------- /Source/InteractionPlugin/Private/InteractionComponents/InteractionComponent_Instant.cpp: -------------------------------------------------------------------------------- 1 | // Fill out your copyright notice in the Description page of Project Settings. 2 | 3 | #include "InteractionComponent_Instant.h" 4 | #include "InteractorComponents/InteractorComponent.h" 5 | #include "InteractionDataTypes.h" 6 | 7 | UInteractionComponent_Instant::UInteractionComponent_Instant() 8 | { 9 | PrimaryComponentTick.bCanEverTick = false; 10 | InteractionType = EInteractionType::IT_Instant; 11 | } 12 | 13 | bool UInteractionComponent_Instant::StartInteraction(UInteractorComponent* InteractorComp) 14 | { 15 | /* Exit If Super Returns False */ 16 | if (!Super::StartInteraction(InteractorComp)) 17 | { 18 | return false; 19 | } 20 | 21 | /* Complete the Interaction Immediately*/ 22 | CompleteInteraction(EInteractionResult::IR_Successful,InteractorComp); 23 | 24 | return true; 25 | } 26 | -------------------------------------------------------------------------------- /Source/InteractionPlugin/Public/InteractionDataTypes.h: -------------------------------------------------------------------------------- 1 | // Fill out your copyright notice in the Description page of Project Settings. 2 | #pragma once 3 | 4 | #include "CoreMinimal.h" 5 | 6 | UENUM(BlueprintType) 7 | enum class EInteractionResult : uint8 8 | { 9 | IR_None UMETA(DisplayName = "None"), 10 | IR_Started UMETA(DisplayName = "Started"), 11 | IR_Successful UMETA(DisplayName = "Successful"), 12 | IR_Failed UMETA(DisplayName = "Failed"), 13 | IR_Interrupted UMETA(DisplayName = "Interrupted") 14 | }; 15 | 16 | UENUM(BlueprintType) 17 | enum class EInteractionType : uint8 18 | { 19 | IT_None UMETA(DisplayName = "None"), 20 | IT_Instant UMETA(DisplayName = "Instant"), 21 | IT_Hold UMETA(DisplayName = "Hold") 22 | }; 23 | 24 | UENUM(BlueprintType) 25 | enum class EInteractionNetMode : uint8 26 | { 27 | INM_None UMETA(DisplayName="None"), 28 | INM_OwnerOnly UMETA(DisplayName = "OwnerOnly"), 29 | INM_All UMETA(DisplayName = "All") 30 | }; 31 | 32 | -------------------------------------------------------------------------------- /Source/InteractionPlugin/Public/InteractionComponents/InteractionComponent_Instant.h: -------------------------------------------------------------------------------- 1 | // Fill out your copyright notice in the Description page of Project Settings. 2 | 3 | #pragma once 4 | 5 | #include "CoreMinimal.h" 6 | #include "InteractionComponents/InteractionComponent.h" 7 | #include "InteractionComponent_Instant.generated.h" 8 | 9 | UCLASS( ClassGroup=(Custom), meta=(BlueprintSpawnableComponent) ) 10 | class INTERACTIONPLUGIN_API UInteractionComponent_Instant : public UInteractionComponent 11 | { 12 | GENERATED_BODY() 13 | 14 | public: 15 | // Sets default values for this component's properties 16 | UInteractionComponent_Instant(); 17 | 18 | /** 19 | * [Overridden] Starts Interaction for a Given Interactor Component 20 | * 21 | * @param InteractorComp - Interactor Component Trying to Interact 22 | * @returns True Whether Interaction Initiated Successfully 23 | */ 24 | bool StartInteraction(UInteractorComponent* InteractorComp) override; 25 | 26 | }; 27 | -------------------------------------------------------------------------------- /Source/InteractionPlugin/Public/Interface/InteractionInterface.h: -------------------------------------------------------------------------------- 1 | // Fill out your copyright notice in the Description page of Project Settings. 2 | 3 | #pragma once 4 | 5 | #include "CoreMinimal.h" 6 | #include "UObject/Interface.h" 7 | #include "InteractionInterface.generated.h" 8 | 9 | // This class does not need to be modified. 10 | UINTERFACE(MinimalAPI, BlueprintType) 11 | class UInteractionInterface : public UInterface 12 | { 13 | GENERATED_BODY() 14 | }; 15 | 16 | /** 17 | * 18 | */ 19 | class INTERACTIONPLUGIN_API IInteractionInterface 20 | { 21 | GENERATED_BODY() 22 | 23 | // Add interface functions to this class. This is the class that will be inherited to implement this interface. 24 | public: 25 | 26 | /** 27 | * Interface Invoked By Interactor and Interaction Component before an Interaction Process to Custom Conditions 28 | * 29 | * @param OtherOwner - Interaction or Interactor Owner Trying to Interact With 30 | * @return True If Interaction is Allowed else Interaction will not Start 31 | */ 32 | UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category = InteractionInterface) 33 | bool ICanInteractWith(AActor* OtherOwner); 34 | }; 35 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Visual Studio 2015 user specific files 2 | .vs/ 3 | 4 | # Visual Studio 2015 database file 5 | *.VC.db 6 | 7 | # Compiled Object files 8 | *.slo 9 | *.lo 10 | *.o 11 | *.obj 12 | 13 | # Precompiled Headers 14 | *.gch 15 | *.pch 16 | 17 | # Compiled Dynamic libraries 18 | *.so 19 | *.dylib 20 | 21 | # Fortran module files 22 | *.mod 23 | 24 | # Compiled Static libraries 25 | *.lai 26 | *.la 27 | *.a 28 | *.lib 29 | 30 | # Executables 31 | *.exe 32 | *.out 33 | *.app 34 | *.ipa 35 | 36 | # These project files can be generated by the engine 37 | *.xcodeproj 38 | *.xcworkspace 39 | *.sln 40 | *.suo 41 | *.opensdf 42 | *.sdf 43 | *.VC.db 44 | *.VC.opendb 45 | 46 | # Precompiled Assets 47 | SourceArt/**/*.png 48 | SourceArt/**/*.tga 49 | 50 | # Builds 51 | Build/* 52 | 53 | # Whitelist PakBlacklist-.txt files 54 | !Build/*/ 55 | Build/*/** 56 | !Build/*/PakBlacklist*.txt 57 | 58 | # Don't ignore icon files in Build 59 | !Build/**/*.ico 60 | 61 | # Built data for maps 62 | *_BuiltData.uasset 63 | 64 | # Configuration files generated by the Editor 65 | Saved/* 66 | 67 | # Compiled source files for the engine to use 68 | Intermediate/* 69 | 70 | # Cache files for the editor to use 71 | DerivedDataCache/* 72 | /Binaries/Win64/*.pdb 73 | 74 | /Binaries 75 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | This is free and unencumbered software released into the public domain. 2 | 3 | Anyone is free to copy, modify, publish, use, compile, sell, or 4 | distribute this software, either in source code form or as a compiled 5 | binary, for any purpose, commercial or non-commercial, and by any 6 | means. 7 | 8 | In jurisdictions that recognize copyright laws, the author or authors 9 | of this software dedicate any and all copyright interest in the 10 | software to the public domain. We make this dedication for the benefit 11 | of the public at large and to the detriment of our heirs and 12 | successors. We intend this dedication to be an overt act of 13 | relinquishment in perpetuity of all present and future rights to this 14 | software under copyright law. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 19 | IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR 20 | OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 21 | ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 22 | OTHER DEALINGS IN THE SOFTWARE. 23 | 24 | For more information, please refer to 25 | -------------------------------------------------------------------------------- /Source/InteractionPlugin/InteractionPlugin.Build.cs: -------------------------------------------------------------------------------- 1 | // Copyright 1998-2018 Epic Games, Inc. All Rights Reserved. 2 | 3 | using UnrealBuildTool; 4 | 5 | public class InteractionPlugin : ModuleRules 6 | { 7 | public InteractionPlugin(ReadOnlyTargetRules Target) : base(Target) 8 | { 9 | PCHUsage = ModuleRules.PCHUsageMode.UseExplicitOrSharedPCHs; 10 | 11 | PublicIncludePaths.AddRange( 12 | new string[] { 13 | // ... add public include paths required here ... 14 | } 15 | ); 16 | 17 | 18 | PrivateIncludePaths.AddRange( 19 | new string[] { 20 | // ... add other private include paths required here ... 21 | } 22 | ); 23 | 24 | 25 | PublicDependencyModuleNames.AddRange( 26 | new string[] 27 | { 28 | "Core", 29 | // ... add other public dependencies that you statically link with here ... 30 | } 31 | ); 32 | 33 | 34 | PrivateDependencyModuleNames.AddRange( 35 | new string[] 36 | { 37 | "CoreUObject", 38 | "Engine", 39 | "Slate", 40 | "SlateCore", 41 | // ... add private dependencies that you statically link with here ... 42 | } 43 | ); 44 | 45 | 46 | DynamicallyLoadedModuleNames.AddRange( 47 | new string[] 48 | { 49 | // ... add any modules that your module loads dynamically here ... 50 | } 51 | ); 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /Source/InteractionPlugin/Private/InteractionComponents/InteractionComponent_Hold.cpp: -------------------------------------------------------------------------------- 1 | // Fill out your copyright notice in the Description page of Project Settings. 2 | 3 | #include "InteractionComponent_Hold.h" 4 | #include "Engine/World.h" 5 | #include "InteractorComponents/InteractorComponent.h" 6 | 7 | UInteractionComponent_Hold::UInteractionComponent_Hold() 8 | :InteractionDuration(10.0f) 9 | { 10 | PrimaryComponentTick.bCanEverTick = false; 11 | InteractionType = EInteractionType::IT_Hold; 12 | } 13 | 14 | bool UInteractionComponent_Hold::StartInteraction(UInteractorComponent* InteractorComp) 15 | { 16 | /* Exit If Super Returns False */ 17 | if (!Super::StartInteraction(InteractorComp)) 18 | { 19 | return false; 20 | } 21 | 22 | /* Record Initiation Time */ 23 | AddInteractor(InteractorComp); 24 | 25 | return true; 26 | } 27 | 28 | void UInteractionComponent_Hold::OnHoldCompleted(UInteractorComponent* InteractorComp) 29 | { 30 | if (IsInteractionTimeOver(InteractorComp)) 31 | { 32 | /* Complete the Interaction */ 33 | CompleteInteraction(EInteractionResult::IR_Successful, InteractorComp); 34 | } 35 | else 36 | { 37 | /* Fail Interaction If Time Not Over */ 38 | CompleteInteraction(EInteractionResult::IR_Failed, InteractorComp); 39 | } 40 | 41 | /* Remove the Interactor from List */ 42 | Interactors.Remove(InteractorComp); 43 | } 44 | 45 | bool UInteractionComponent_Hold::StopInteraction(UInteractorComponent* InteractorComp) 46 | { 47 | /* Exit If Super Returns False */ 48 | if (!Super::StopInteraction(InteractorComp)) 49 | { 50 | return false; 51 | } 52 | 53 | /* Notify Interactor of Interaction Interruption */ 54 | if (Interactors.Contains(InteractorComp)) 55 | { 56 | CompleteInteraction(EInteractionResult::IR_Interrupted, InteractorComp); 57 | } 58 | 59 | /* Remove the Interactor from List */ 60 | Interactors.Remove(InteractorComp); 61 | 62 | return true; 63 | } 64 | 65 | bool UInteractionComponent_Hold::CanInteractWith(UInteractorComponent* InteractoComp) 66 | { 67 | return Super::CanInteractWith(InteractoComp) && bMultipleInteraction || Interactors.Num() == 0; 68 | } 69 | 70 | bool UInteractionComponent_Hold::IsInteractionTimeOver(const UInteractorComponent* InteractorComponent) const 71 | { 72 | if (Interactors.Contains(InteractorComponent)) 73 | { 74 | const UWorld* World = GetWorld(); 75 | 76 | /* Interaction Duration With Addition Error Tolerance of 0.5 */ 77 | const float ErrorToleranceDuration = InteractionDuration - 0.5f; 78 | 79 | return IsValid(World) ? Interactors[InteractorComponent] + ErrorToleranceDuration <= World->GetTimeSeconds() : false; 80 | 81 | } 82 | 83 | return false; 84 | } 85 | 86 | void UInteractionComponent_Hold::AddInteractor(UInteractorComponent* InteractorComponent) 87 | { 88 | /* Get World and Time Seconds */ 89 | const UWorld* World = GetWorld(); 90 | 91 | Interactors.Add(InteractorComponent, IsValid(World) ? World->GetTimeSeconds() : 0.0f); 92 | } 93 | 94 | void UInteractionComponent_Hold::EndPlay(const EEndPlayReason::Type EndPlayReason) 95 | { 96 | Super::EndPlay(EndPlayReason); 97 | 98 | if (EndPlayReason == EEndPlayReason::Destroyed || 99 | EndPlayReason == EEndPlayReason::RemovedFromWorld) 100 | { 101 | for (auto EachInteractor : Interactors) 102 | { 103 | CompleteInteraction(EInteractionResult::IR_Interrupted, EachInteractor.Key); 104 | } 105 | 106 | Interactors.Empty(); 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /Source/InteractionPlugin/Public/InteractionComponents/InteractionComponent_Hold.h: -------------------------------------------------------------------------------- 1 | // Fill out your copyright notice in the Description page of Project Settings. 2 | 3 | #pragma once 4 | 5 | #include "CoreMinimal.h" 6 | #include "InteractionComponents/InteractionComponent.h" 7 | #include "InteractionComponent_Hold.generated.h" 8 | 9 | UCLASS( ClassGroup=(Custom), meta=(BlueprintSpawnableComponent) ) 10 | class INTERACTIONPLUGIN_API UInteractionComponent_Hold : public UInteractionComponent 11 | { 12 | GENERATED_BODY() 13 | 14 | public: 15 | 16 | // Sets default values for this component's properties 17 | UInteractionComponent_Hold(); 18 | 19 | /** 20 | * Ends Gameplay For this Component. Allows the Interaction to begin Asynchronous Cleanup 21 | */ 22 | void EndPlay(const EEndPlayReason::Type EndPlayReason) override; 23 | 24 | /** 25 | * [Overridden] Starts Interaction for a Given Interactor Component 26 | * 27 | * @param InteractorComp - Interactor Component Trying to Interact 28 | * @returns True Whether Interaction Initiated Successfully 29 | */ 30 | bool StartInteraction(UInteractorComponent* InteractorComp) override; 31 | 32 | /** 33 | * Tries to Complete the Hold Interaction for the Interactor Component 34 | * Invoked By Interactor on Timer Completion 35 | * 36 | * @param InteractorComp - Interactor Component Trying to Complete the Hold 37 | */ 38 | void OnHoldCompleted(UInteractorComponent* InteractorComp); 39 | 40 | /** 41 | * [Overridden] Stops an Interaction In Progress for a Given Interactor Component 42 | * 43 | * @param InteractorComp - Interactor Component Trying to Interact 44 | * @returns True Whether Interaction Stopped Successfully 45 | */ 46 | bool StopInteraction(UInteractorComponent* InteractorComp) override; 47 | 48 | /** 49 | * [Overridden] Validates Condition and Returns Whether Interaction is Allowed 50 | */ 51 | bool CanInteractWith(UInteractorComponent* InteractoComp) override; 52 | 53 | /** 54 | * Getter for the Interaction Duration 55 | */ 56 | UFUNCTION(BlueprintGetter, Category = InteractionConfig) 57 | float GetInteractionDuration() const 58 | { 59 | return InteractionDuration; 60 | }; 61 | 62 | /** 63 | * Setter for the Interaction Duration 64 | */ 65 | UFUNCTION(BlueprintSetter, Category = InteractionConfig) 66 | void SetInteractionDuration(const float NewInteractionDuration) 67 | { 68 | InteractionDuration = NewInteractionDuration; 69 | }; 70 | 71 | protected: 72 | 73 | /** Duration Required for the Interaction to be Completed */ 74 | UPROPERTY(EditAnywhere, Category = "InteractionComponent|Config", BlueprintGetter = GetInteractionDuration, BlueprintSetter = SetInteractionDuration,meta = (ClampMin = 0.0f)) 75 | float InteractionDuration; 76 | 77 | private: 78 | 79 | /** 80 | * Calculates and Returns If the Initiated Interaction Time Plus the Interaction Required Duration has already been passed. 81 | * 82 | * @param InteractorComponent - Interactor to Check Against 83 | * @returns True If Duration is Over 84 | */ 85 | UFUNCTION() 86 | inline bool IsInteractionTimeOver(const UInteractorComponent* InteractorComponent) const; 87 | 88 | /** 89 | * Adds Interactor and the Current Game World Time to the Interactor Map 90 | */ 91 | UFUNCTION() 92 | void AddInteractor(UInteractorComponent* InteractorComponent); 93 | 94 | /** 95 | * Map of Interactors to Interaction Initiation Game Time 96 | */ 97 | UPROPERTY() 98 | TMap Interactors; 99 | 100 | }; 101 | -------------------------------------------------------------------------------- /Source/InteractionPlugin/Private/InteractionComponents/InteractionComponent.cpp: -------------------------------------------------------------------------------- 1 | // Fill out your copyright notice in the Description page of Project Settings. 2 | 3 | #include "InteractionComponent.h" 4 | #include "InteractorComponents/InteractorComponent.h" 5 | #include "Interface/InteractionInterface.h" 6 | 7 | DEFINE_LOG_CATEGORY(LogInteraction); 8 | 9 | UInteractionComponent::UInteractionComponent() 10 | :InteractionType(EInteractionType::IT_None) 11 | ,bMultipleInteraction(true) 12 | ,InteractionStateNetMode(EInteractionNetMode::INM_OwnerOnly) 13 | ,bOnlyFaceInteraction(false) 14 | { 15 | this->SetIsReplicated(true); 16 | } 17 | 18 | void UInteractionComponent::SetInteractionFocusState(bool bNewFocus, UInteractorComponent* NewInteractorComponent /* = nullptr */) 19 | { 20 | if (OnInteractionFocusChanged.IsBound()) 21 | { 22 | OnInteractionFocusChanged.Broadcast(bNewFocus); 23 | } 24 | 25 | /* Set Focusing Interactor */ 26 | FocusingInteractor = NewInteractorComponent; 27 | } 28 | 29 | bool UInteractionComponent::StartInteraction(UInteractorComponent* InteractorComp) 30 | { 31 | /* Validate Interactor Comp */ 32 | if (!IsValid(InteractorComp)) 33 | { 34 | UE_LOG(LogInteraction, Warning, TEXT("Failed to Start Interaction Due to Invalid Interactor Component")); 35 | return false; 36 | } 37 | 38 | return true; 39 | } 40 | 41 | bool UInteractionComponent::StopInteraction(UInteractorComponent* InteractorComp) 42 | { 43 | /* Validate Interactor Comp */ 44 | if (!IsValid(InteractorComp)) 45 | { 46 | UE_LOG(LogInteraction, Warning, TEXT("Failed to Stop Interaction Due to Invalid Interactor Component")); 47 | return false; 48 | } 49 | 50 | return true; 51 | } 52 | 53 | bool UInteractionComponent::CanInteractWith(UInteractorComponent* InteractorComp) 54 | { 55 | 56 | /* Get Owner */ 57 | AActor* Owner = GetOwner(); 58 | 59 | /* Check for Interaction Interface and Execute If Owner Implements */ 60 | if (IsValid(Owner) && 61 | Owner->GetClass()->ImplementsInterface(UInteractionInterface::StaticClass())) 62 | { 63 | return IInteractionInterface::Execute_ICanInteractWith(Owner, InteractorComp->GetOwner()); 64 | } 65 | 66 | return true; 67 | } 68 | 69 | void UInteractionComponent::CompleteInteraction(EInteractionResult InteractionResult, UInteractorComponent* InteractorComp) 70 | { 71 | /* Validate Interactor Comp */ 72 | if (IsValid(InteractorComp)) 73 | { 74 | /* Invoke Interactor End Interaction */ 75 | InteractorComp->EndInteraction(InteractionResult, this); 76 | 77 | /* Notify Interaction State */ 78 | NotifyInteraction(InteractionResult, InteractorComp); 79 | } 80 | } 81 | 82 | void UInteractionComponent::NotifyInteraction(EInteractionResult NewInteractionResult, UInteractorComponent* NewInteractionComponent) 83 | { 84 | /** 85 | * @note: OwnerOnly Notifications are Handled and Recieved By the Interactor Component 86 | */ 87 | 88 | if (InteractionStateNetMode == EInteractionNetMode::INM_All) 89 | { 90 | Multi_NotifyInteraction(NewInteractionResult, NewInteractionComponent); 91 | } 92 | } 93 | 94 | void UInteractionComponent::ClientNotifyInteraction(EInteractionResult NewInteractionResult, UInteractorComponent* NewInteractionComponent) 95 | { 96 | if (OnInteractionStateChanged.IsBound()) 97 | { 98 | OnInteractionStateChanged.Broadcast( 99 | NewInteractionResult, 100 | IsValid(NewInteractionComponent) ? NewInteractionComponent->GetOwner() : nullptr); 101 | } 102 | } 103 | 104 | void UInteractionComponent::Multi_NotifyInteraction_Implementation(EInteractionResult NewInteractionResult, UInteractorComponent* NewInteractionComponent) 105 | { 106 | if (OnInteractionStateChanged.IsBound()) 107 | { 108 | OnInteractionStateChanged.Broadcast( 109 | NewInteractionResult, 110 | IsValid(NewInteractionComponent) ? NewInteractionComponent->GetOwner() : nullptr); 111 | } 112 | } 113 | 114 | 115 | void UInteractionComponent::EndPlay(const EEndPlayReason::Type EndPlayReason) 116 | { 117 | Super::EndPlay(EndPlayReason); 118 | 119 | if (IsValid(FocusingInteractor)) 120 | { 121 | FocusingInteractor->LocalEndInteractionFocus(this); 122 | } 123 | } 124 | -------------------------------------------------------------------------------- /Source/InteractionPlugin/Public/InteractionComponents/InteractionComponent.h: -------------------------------------------------------------------------------- 1 | // Fill out your copyright notice in the Description page of Project Settings. 2 | 3 | #pragma once 4 | 5 | #include "CoreMinimal.h" 6 | #include "Components/SceneComponent.h" 7 | #include "InteractionDataTypes.h" 8 | #include "InteractionComponent.generated.h" 9 | 10 | DECLARE_DYNAMIC_MULTICAST_DELEGATE_TwoParams(FOnInteractionStateChanged, EInteractionResult, InteractionResult, AActor*, InteractionActor); 11 | DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnInteractionFocusChanged, bool, bInFocus); 12 | 13 | 14 | DECLARE_LOG_CATEGORY_EXTERN(LogInteraction, Log, All); 15 | 16 | class UInteractorComponent; 17 | 18 | UCLASS( ClassGroup=(Custom), Abstract) 19 | class INTERACTIONPLUGIN_API UInteractionComponent : public USceneComponent 20 | { 21 | GENERATED_BODY() 22 | 23 | public: 24 | 25 | // Sets default values for this component's properties 26 | UInteractionComponent(); 27 | 28 | /** 29 | * Ends Gameplay For this Component. Allows the Interaction to begin Asynchronous Cleanup 30 | */ 31 | void EndPlay(const EEndPlayReason::Type EndPlayReason) override; 32 | 33 | /* Delegate to Notify Interactor Focus Changed */ 34 | UPROPERTY(BlueprintAssignable) 35 | FOnInteractionFocusChanged OnInteractionFocusChanged; 36 | 37 | /** 38 | * Delegate to Notify Interaction State Changes 39 | */ 40 | UPROPERTY(BlueprintAssignable) 41 | FOnInteractionStateChanged OnInteractionStateChanged; 42 | 43 | /** 44 | * [Config] Configuration to Determine Interaction State Over Net 45 | * 46 | * None : No Clients Receive the Interaction State Update 47 | * OwnerOnly : Only the Local Owner of the Interaction Component Will Receive the Update 48 | * All : All Clients With this Instance of the Interaction Component Will Receive the Update 49 | */ 50 | UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "InteractionComponent|Config", meta = (DisplayName = "State Net Mode")) 51 | EInteractionNetMode InteractionStateNetMode; 52 | 53 | /** 54 | * [Config] Boolean to Allow Multiple Interaction at Once 55 | * @note Does Not Apply to Instant Interaction 56 | */ 57 | UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "InteractionComponent|Config", meta = (DisplayName = "Allow Multiple Interaction")) 58 | bool bMultipleInteraction; 59 | 60 | /** 61 | * [Config] Boolean to Allow Interaction Only If Interactor Is Looking at the Face/Fron of the Object 62 | * @note Set to True If Interactor can Only Interact From the front of this Object 63 | */ 64 | UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "InteractionComponent|Config") 65 | bool bOnlyFaceInteraction; 66 | 67 | 68 | 69 | /** 70 | * Starts Interaction for a Given Interactor Component 71 | * 72 | * @param InteractorComp - Interactor Component Trying to Interact 73 | * @returns True Whether Interaction Initiated Successfully 74 | */ 75 | UFUNCTION(BlueprintCallable, Category = InteractionComponent) 76 | virtual bool StartInteraction(UInteractorComponent* InteractorComp); 77 | 78 | /** 79 | * Stops an Interaction In Progress for a Given Interactor Component 80 | * 81 | * @param InteractorComp - Interactor Component Trying to Interact 82 | * @returns True Whether Interaction Stopped Successfully 83 | */ 84 | UFUNCTION(BlueprintCallable, Category = InteractionComponent) 85 | virtual bool StopInteraction(UInteractorComponent* InteractorComp); 86 | 87 | 88 | /** 89 | * Validates Condition and Returns Whether Interaction is Allowed 90 | */ 91 | UFUNCTION(BlueprintCallable, Category = InteractionComponent) 92 | virtual bool CanInteractWith(UInteractorComponent* InteractorComp); 93 | 94 | /** 95 | * Invoked By Interactor to Notify Interaction is Under Focus 96 | */ 97 | UFUNCTION() 98 | void SetInteractionFocusState(bool bNewFocus, UInteractorComponent* NewInteractorComponent = nullptr); 99 | 100 | /** 101 | * Owner Only Interaction State Notification 102 | * 103 | * @note Owner Only Interaction State is Handled and Received by Interactor 104 | * @param InteractionResult - Result of the Interaction 105 | * @param InteractionType - Type of Interaction 106 | */ 107 | UFUNCTION() 108 | void ClientNotifyInteraction(EInteractionResult NewInteractionResult, UInteractorComponent* NewInteractionComponent); 109 | 110 | /** 111 | * Getter For Interaction Component Interaction Type 112 | */ 113 | UFUNCTION(BlueprintGetter, Category = InteractionComponent) 114 | EInteractionType GetInteractionType() const 115 | { 116 | return InteractionType; 117 | } 118 | 119 | protected: 120 | 121 | /** 122 | * Interaction Component Interaction Type 123 | */ 124 | UPROPERTY(BlueprintGetter = GetInteractionType, Category = InteractionComponent) 125 | EInteractionType InteractionType; 126 | 127 | /** 128 | * Interactor Component Reference Currently Focusing on this Interaction 129 | */ 130 | UPROPERTY() 131 | UInteractorComponent* FocusingInteractor; 132 | 133 | /** 134 | * Completes an Interaction With an Interactor 135 | * 136 | * @note Must be the End Point For All the Interaction Completions 137 | * @param InteractionResult - Result of Interaction 138 | * @param InteractorComp - Interactor Component to Notify 139 | */ 140 | UFUNCTION() 141 | virtual void CompleteInteraction(EInteractionResult InteractionResult, UInteractorComponent* InteractorComp); 142 | 143 | /** 144 | * Handles Interaction Notification Based on Config 145 | * 146 | * @param InteractionResult - Result of the Interaction 147 | * @param InteractionType - Type of Interaction 148 | */ 149 | UFUNCTION() 150 | void NotifyInteraction(EInteractionResult NewInteractionResult, UInteractorComponent* NewInteractionComponent); 151 | 152 | /** 153 | * Multi Cast Call to all Clients Notifying Interaction State 154 | * 155 | * @param InteractionResult - Result of the Interaction 156 | * @param InteractionType - Type of Interaction 157 | */ 158 | UFUNCTION(NetMulticast, Reliable) 159 | void Multi_NotifyInteraction(EInteractionResult NewInteractionResult, UInteractorComponent* NewInteractionComponent); 160 | }; 161 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Interaction plug-in for the Unreal Engine. 2 | 3 | The Goal of this plug-in is to handle the interaction between player and objects/actors in the game by implementing a **component based architecture**. There are two main components, **Interactor Component** which is added to the player and **Interaction Component** for the Intractable Objects. 4 | 5 | ![Instant Interaction](https://i.imgur.com/7fk4zfo.gif ) 6 | 7 | ![Hold Interaction](https://i.imgur.com/uGBkTiW.gif) 8 | 9 | ## Introduction 10 | 11 | The Interaction System is designed and developed based on a Component to Component communication architecture. All of the required logic during the interaction process is handled and processed by the components attached to the owners such as Characters and Interactive objects. The system is mainly made of two important components, Interactor Component which is added to the character or the player pawn and interaction component that is required by an interactive object. 12 | 13 | ## Road map / Features: 14 | 1. Interaction Types 15 | - Instant [DONE] 16 | - Hold [DONE] 17 | 2. Condition Based Interaction 18 | - Single Interaction [DONE] 19 | - Multiple Interaction [DONE] 20 | - Custom Defined Condition E.g Team Id [DONE] 21 | 3. Multiplayer/Network Support 22 | - Interaction States Replication [DONE] 23 | - Interaction Process [DONE] 24 | - Notifications for Animations [DONE] 25 | 4. Documentation [DONE] 26 | - Showcase Level [DONE] 27 | 28 | ## Getting up and Running 29 | 1. Add an **Interactor Component** to the Character or Player Pawn 30 | 2. Add an **Interaction Component** to the Interactive Object (e.g Door) 31 | 3. Setup an Input key to Invoke **TryStartInteraction** on the **Interactor Component** 32 | ![Setup Interactor](https://i.imgur.com/kkyUdWZ.png) 33 | 4. Bind to the **OnInteractorStateChanged** Delegate on **Interactor Component** to Receive Interaction Results 34 | 35 | ## Interaction Components 36 | ### Class: UInteractionComponent 37 | Interaction Component is added to an Interact-able object (E.g Doors,Pickup). There are currently two types of Interaction Components. 38 | 39 | - **InteractionComponent_Instant:** 40 | Class: UInteractionComponent_Instant 41 | Interaction is Instantly Completed upon Initiation by an Interactor. 42 | - **InteractionComponent_Hold:** 43 | Class: UInteractionComponent_Hold 44 | Hold Interaction is a Duration based interaction that requires the Interactor to actively interact with the object for the duration. 45 | 46 | ## Multiple Interaction 47 | Interaction Components can allow multiple interactions simultaneously. A configuration Boolean on the interaction component named **bMultipleInteraction**, controls and determines whether Interaction components can allow simultaneous interaction or only one interaction at a time. This does not imply to Instant interaction, as the interaction is completed instantly. 48 | 49 | ## Condition Based Interaction 50 | ### Class: IInteractionInterface 51 | At times, **Custom Conditions** are required to be met before starting an interaction, for example, a lock system on a chest or team only buildings and equipment. In order to handle such custom conditions both Interactor and Interaction Components will Execute an **Interface** call on their owners `ICanInteractWith(Actor* OtherOwner)` Passing the other party actor, this interface then returns a **Boolean** determining whether the interaction can be initiated or not. ** However This does not mean that the interface has to be always implemented on the owner even if custom conditions are not required, the components will simply ignore the interface call if the owner does not implement it.** 52 | 53 | **E.g:** In order to implement a team only chest, we simply add an Interaction Interface on the chest actor. Then we override the ICanInteractWith Function in the interface tab. Then inside the function, we get the team id of the OtherOwner then return true if the team id is equal to the chest team id. 54 | 55 | ![Example of Interaction Interface](https://i.imgur.com/L0XxKLZ.png) 56 | 57 | ## Interaction Result 58 | Both Interaction and Interactor Components implement Interaction **Results** and **State**. Interaction Results are enums that are meant to provide more information during the interaction process. These results are **broadcasted** through delegates when an Interaction is initiated up to completion. 59 | 60 | #### EInteractionResult: 61 | - **None [IR_None]:** Unknown Result 62 | - **Started [IR_Started]**: Interaction Started 63 | - **Successful [IR_Successful]**: Interaction Successfully Completed 64 | - **Failed [IR_Failed]**: Interaction Failed due to Conditions Returning False 65 | - **Interrupted [IR_Interrupted]**: Interaction Interrupted due to Player Pawn or Character Looking away and Going out of Reach during Interaction. Moreover, Interruptions Happen after removal of Interaction or Interactor Components during Interaction process. 66 | 67 | These Interaction Results are received and broadcasted on Both Components through delegates (Blueprint Event Dispatcher). 68 | 69 | - **Interaction Component**: OnInteractionStateChanged 70 | - **Interactor Component**: OnInteractorStateChanged 71 | 72 | In a networked Environments, These results are send to the clients through **Remote Procedure Calls (RPC)**. But in Some cases this Information may not be relevant to all the clients or on the other hand, all the clients should be aware of these results. This can be controlled and configured on each component by changing the InteractorStateNetMode or InteractionStateNetMode. 73 | - **None** : None of the Clients Receive the Result Update 74 | - **OwnerOnly** : Only the Local Owner of the Component Will Receive the Update 75 | - **All** : All Clients With this Instance of the Component Will Receive the Update 76 | 77 | ## Interaction Focus 78 | It is important to be able to notify and inform the player of an interactive object or even show and interaction widget (Press E to Interact). This can be **easily** implemented by binding/listening to any of these delegates. 79 | 80 | **OnNewInteraction**: This Delegate is broadcasted by the **Interactor Component** when a New Interactive object comes into the focus or leaves the reach of the player. 81 | 82 | **OnInteractionFocusChanged**: Delegate Implemented by the **Interaction Component**. Broadcasted whenever the interaction object comes into the focus of a player. 83 | 84 | ## Interaction Direction 85 | In Some Cases, the direction of the interaction is important. Some Interactive Objects may require the Players to look at the face of the object in order to be able to interact. But for other interactive objects this may not be a requirement. This behavior can be configured on each Interaction Component Config setting by changing the Boolean variable named **OnlyFaceInteraction**. Setting this variable to **true** will require the player to look at the face of the object. 86 | 87 | ## Showcase 88 | You can Download the Showcase Level [here](https://drive.google.com/drive/u/0/folders/1WHRh8U5XVVyyZ5DnwHTg_XufNqroyj-w). 89 | 90 | ## Author 91 | Contact me on [Twitter](https://twitter.com/amiransari09). 92 | -------------------------------------------------------------------------------- /Source/InteractionPlugin/Public/InteractorComponents/InteractorComponent.h: -------------------------------------------------------------------------------- 1 | // Fill out your copyright notice in the Description page of Project Settings. 2 | 3 | #pragma once 4 | 5 | #include "CoreMinimal.h" 6 | #include "Components/ActorComponent.h" 7 | #include "InteractionDataTypes.h" 8 | #include "InteractorComponent.generated.h" 9 | 10 | DECLARE_LOG_CATEGORY_EXTERN(LogInteractor, Log, All); 11 | 12 | class UInteractionComponent; 13 | 14 | DECLARE_DYNAMIC_MULTICAST_DELEGATE_FourParams(FOnInteractorStateChanged, EInteractionResult, InteractionResult, EInteractionType, InteractionType, float , InteractionDuration, AActor*, InteractionActor); 15 | 16 | DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnInteractingChanged, bool, bIsInteracting); 17 | 18 | DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnNewInteraction, UInteractionComponent*, NewInteraction); 19 | 20 | UCLASS( ClassGroup=(Custom), meta=(BlueprintSpawnableComponent) ) 21 | class INTERACTIONPLUGIN_API UInteractorComponent : public UActorComponent 22 | { 23 | GENERATED_BODY() 24 | 25 | public: 26 | 27 | // Sets default values for this component's properties 28 | UInteractorComponent(); 29 | 30 | /** 31 | * Returns the properties used for network replication 32 | */ 33 | virtual void GetLifetimeReplicatedProps(TArray< FLifetimeProperty > & OutLifetimeProps) const override; 34 | 35 | /** 36 | * Ends Gameplay For this Component. Allows the Interaction to begin Asynchronous Cleanup 37 | */ 38 | void EndPlay(const EEndPlayReason::Type EndPlayReason) override; 39 | 40 | /** 41 | * Delegate to Notify Interaction State Changes 42 | */ 43 | UPROPERTY(BlueprintAssignable) 44 | FOnInteractorStateChanged OnInteractorStateChanged; 45 | 46 | /** 47 | * Delegate to Notify New Interaction Component in Focus 48 | */ 49 | UPROPERTY(BlueprintAssignable) 50 | FOnNewInteraction OnNewInteraction; 51 | 52 | /** 53 | * Delegate to Notify Interacting Status Changes 54 | */ 55 | UPROPERTY(BlueprintAssignable) 56 | FOnInteractingChanged OnInteractingChanged; 57 | 58 | /** 59 | * [Config] Configuration to Determine Interaction State Over Net 60 | * 61 | * None : No Clients Receive the Interaction State Update 62 | * OwnerOnly : Only the Local Owner of the Interactor Component Will Receive the Update 63 | * All : All Clients With this Instance of the Interactor Component Will Receive the Update 64 | */ 65 | UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "Interactor|Config", meta = (DisplayName = "State Net Mode")) 66 | EInteractionNetMode InteractorStateNetMode; 67 | 68 | /** 69 | * [Config] Define the Distance Which an Interaction Component Will be Detected and be In Focus 70 | */ 71 | UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "Interactor|Config") 72 | float InteractorReachLength; 73 | 74 | 75 | #if WITH_EDITORONLY_DATA 76 | 77 | /** 78 | * [Editor Only] Debug Boolean to Determine Whether Trace Line Should be Drawn 79 | */ 80 | UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "Interactor|Debug") 81 | bool bDrawInteractorTrace; 82 | 83 | #endif 84 | 85 | /** 86 | * Tries to Start Interaction On Authority Side of the Interactor 87 | * Gets Interaction On Server Side to Start 88 | */ 89 | UFUNCTION(BlueprintCallable, Category = Interactor) 90 | void TryStartInteraction(); 91 | 92 | /** 93 | * Tries to Stop an Interaction on Authority Side of the Interaction 94 | */ 95 | UFUNCTION(BlueprintCallable, Category = Interactor) 96 | void TryStopInteraction(); 97 | 98 | /** 99 | * Ends the Interaction with Result 100 | * 101 | * @param InteractionResult - Result of the Interaction Process 102 | */ 103 | UFUNCTION() 104 | void EndInteraction(EInteractionResult InteractionResult,UInteractionComponent* InteractionComponent); 105 | 106 | /** 107 | * Ends Interaction Focus, Usually Invoked by the Interaction when Destroying 108 | */ 109 | UFUNCTION() 110 | void LocalEndInteractionFocus(UInteractionComponent* InteractionComponent); 111 | 112 | /** 113 | * Validates Condition and Returns Whether Interaction is Allowed 114 | */ 115 | UFUNCTION(BlueprintCallable, Category = Interactor) 116 | bool CanInteractWith(UInteractionComponent* InteractionComponent); 117 | 118 | /** 119 | * Returns the Component Owner Role 120 | */ 121 | UFUNCTION(BlueprintPure, Category = Interactor) 122 | FORCEINLINE ENetRole GetInteractorRole() const 123 | { 124 | return IsValid(GetOwner()) ? GetOwner()->GetLocalRole() : ENetRole::ROLE_None; 125 | }; 126 | 127 | /** 128 | * Returns the Component Owner Remote Role 129 | */ 130 | UFUNCTION(BlueprintPure, Category = Interactor) 131 | FORCEINLINE ENetRole GetInteractorRemoteRole() const 132 | { 133 | return IsValid(GetOwner()) ? GetOwner()->GetRemoteRole() : ENetRole::ROLE_None; 134 | }; 135 | 136 | 137 | /** 138 | * Returns True If the Current Invoking is the Local Interactor 139 | */ 140 | UFUNCTION(BlueprintPure, Category = Interactor) 141 | FORCEINLINE bool IsLocalInteractor() const 142 | { 143 | const ENetMode NetMode = GetNetMode(); 144 | const ENetRole Role = GetInteractorRole(); 145 | 146 | if (NetMode == NM_Standalone) 147 | { 148 | return true; 149 | } 150 | 151 | if (NetMode == NM_Client && Role == ROLE_AutonomousProxy) 152 | { 153 | return true; 154 | } 155 | 156 | if (GetInteractorRemoteRole() != ROLE_AutonomousProxy && Role == ROLE_Authority) 157 | { 158 | return true; 159 | } 160 | 161 | return false; 162 | }; 163 | 164 | /** 165 | * Performs an Interaction Trace and an Interaction Direction Validation 166 | * 167 | * @param OutInteractionComponent - Out Interaction Component If Found 168 | * @returns True If Interaction Exists and has Valid Direction 169 | */ 170 | UFUNCTION() 171 | FORCEINLINE bool TryGetInteraction(UInteractionComponent*& OutInteractionComponent) 172 | { 173 | OutInteractionComponent = GetInteractionTrace(); 174 | return ValidateDirection(OutInteractionComponent); 175 | } 176 | 177 | 178 | protected: 179 | 180 | /** 181 | * Boolean to Determine Whether the Interactor is Interacting or Not 182 | */ 183 | UPROPERTY(BlueprintReadOnly, ReplicatedUsing = OnRep_bInteracting) 184 | bool bInteracting; 185 | 186 | /** 187 | * Currently Interacting Interaction Component 188 | */ 189 | UPROPERTY(BlueprintReadOnly) 190 | UInteractionComponent* InteractionCandidate; 191 | 192 | // Called when the game starts 193 | virtual void BeginPlay() override; 194 | 195 | /** 196 | * Traces From the Eye Point of View 197 | * 198 | * @returns Returns the Interactable Components 199 | */ 200 | UFUNCTION() 201 | UInteractionComponent* GetInteractionTrace(); 202 | 203 | /** 204 | * Validates the Direction Towards Interaction Component 205 | */ 206 | UFUNCTION() 207 | bool ValidateDirection(const UInteractionComponent* InteractionComponent) const; 208 | 209 | protected: 210 | 211 | 212 | /** 213 | * Starts The Interaction With the Interaction Component 214 | * @note Should Not Be Called Directly, @see TryStartInteraction 215 | */ 216 | UFUNCTION() 217 | void StartInteraction(); 218 | 219 | /** 220 | * Starts The Interactor Timer for a Given Duration 221 | * 222 | * @param NewInteractionDuration - Duration to Start the Interactor Duration With 223 | */ 224 | UFUNCTION() 225 | void ToggleInteractorTimer(bool bStartTImer = true, float NewInteractionDuration = 0.1f); 226 | 227 | /** 228 | * Function Invoked By Interactor Timer On Timer Ends 229 | */ 230 | UFUNCTION() 231 | void OnInteractorTimerCompleted(); 232 | 233 | /** 234 | * On Rep To Notify of bInteracting Changes 235 | */ 236 | UFUNCTION() 237 | void OnRep_bInteracting(); 238 | 239 | /** 240 | * Sets bInteracting Value and Calls Related On Rep 241 | */ 242 | UFUNCTION() 243 | void SetInteracting(bool bNewInteracting) 244 | { 245 | bInteracting = bNewInteracting; 246 | 247 | if (GetNetMode() != ENetMode::NM_DedicatedServer) 248 | { 249 | OnRep_bInteracting(); 250 | } 251 | }; 252 | 253 | private: 254 | 255 | /** 256 | * Timer to Handle Interaction Times 257 | */ 258 | UPROPERTY() 259 | FTimerHandle InteractorTimer; 260 | 261 | /** 262 | * RPC to Server To Start the Interaction 263 | */ 264 | UFUNCTION(Server, Reliable, WithValidation) 265 | void Server_TryStartInteraction(); 266 | bool Server_TryStartInteraction_Validate() { return true; }; 267 | 268 | /** 269 | * RPC to Server To Stop the Interaction 270 | */ 271 | UFUNCTION(Server, Reliable, WithValidation) 272 | void Server_TryStopInteraction(); 273 | bool Server_TryStopInteraction_Validate() { return true; }; 274 | 275 | /** 276 | * Invoked When a New Interaction Component is Valid Candidate 277 | * 278 | * @param NewInteraction - New Interaction Component 279 | */ 280 | UFUNCTION() 281 | void RegisterNewInteraction(UInteractionComponent* NewInteraction); 282 | 283 | /** 284 | * Invoked When Interaction is Not Valid for Candidate 285 | * 286 | * Removes the Current Interaction Candidate, Requests to Cancel any Interaction in Progress 287 | */ 288 | UFUNCTION() 289 | void DeRegisterInteraction(); 290 | 291 | /** 292 | * Determines Whether this Interactor Instance Should Tick 293 | * 294 | * @return True If Local is Owner Or Instance is on Server 295 | */ 296 | UFUNCTION() 297 | FORCEINLINE bool ShouldTickInstance() const 298 | { 299 | return GetNetMode() == NM_Client && GetInteractorRole() != ROLE_AutonomousProxy ? false : true; 300 | } 301 | 302 | /** 303 | * Handles Interaction Notification Based on Config 304 | * 305 | * @param InteractionResult - Result of the Interaction 306 | * @param InteractionType - Type of Interaction 307 | */ 308 | UFUNCTION() 309 | void NotifyInteraction(EInteractionResult NewInteractionResult, EInteractionType NewInteractionType); 310 | 311 | /** 312 | * Owner Only Interaction State Notification 313 | * 314 | * @param InteractionResult - Result of the Interaction 315 | * @param InteractionType - Type of Interaction 316 | */ 317 | UFUNCTION(Client, Reliable) 318 | void Client_NotifyInteraction(EInteractionResult NewInteractionResult, EInteractionType NewInteractionType); 319 | 320 | /** 321 | * Multi Cast Call to all Clients Notifying Interaction State 322 | * 323 | * @param InteractionResult - Result of the Interaction 324 | * @param InteractionType - Type of Interaction 325 | */ 326 | UFUNCTION(NetMulticast, Reliable) 327 | void Multi_NotifyInteraction(EInteractionResult NewInteractionResult, EInteractionType NewInteractionType); 328 | 329 | 330 | 331 | 332 | public: 333 | // Called every frame 334 | virtual void TickComponent(float DeltaTime, ELevelTick TickType, FActorComponentTickFunction* ThisTickFunction) override; 335 | }; 336 | -------------------------------------------------------------------------------- /Source/InteractionPlugin/Private/InteractorComponents/InteractorComponent.cpp: -------------------------------------------------------------------------------- 1 | // Fill out your copyright notice in the Description page of Project Settings. 2 | 3 | #include "InteractorComponent.h" 4 | #include "Runtime/Engine/Classes/GameFramework/Actor.h" 5 | #include "UnrealNetwork.h" 6 | #include "InteractionComponents/InteractionComponent.h" 7 | #include "InteractionComponents/InteractionComponent_Hold.h" 8 | #include "Interface/InteractionInterface.h" 9 | 10 | #if WITH_EDITORONLY_DATA 11 | #include "DrawDebugHelpers.h" 12 | #endif 13 | 14 | DEFINE_LOG_CATEGORY(LogInteractor); 15 | 16 | UInteractorComponent::UInteractorComponent() 17 | :bInteracting(false), 18 | InteractorStateNetMode(EInteractionNetMode::INM_OwnerOnly), 19 | InteractorReachLength(120.0f) 20 | { 21 | PrimaryComponentTick.bCanEverTick = true; 22 | this->SetIsReplicated(true); 23 | } 24 | 25 | void UInteractorComponent::GetLifetimeReplicatedProps(TArray< FLifetimeProperty > & OutLifetimeProps) const 26 | { 27 | Super::GetLifetimeReplicatedProps(OutLifetimeProps); 28 | 29 | DOREPLIFETIME(UInteractorComponent, bInteracting); 30 | } 31 | 32 | void UInteractorComponent::EndPlay(const EEndPlayReason::Type EndPlayReason) 33 | { 34 | Super::EndPlay(EndPlayReason); 35 | 36 | if ((EndPlayReason == EEndPlayReason::Destroyed || 37 | EndPlayReason == EEndPlayReason::RemovedFromWorld) && bInteracting) 38 | { 39 | TryStopInteraction(); 40 | } 41 | } 42 | 43 | // Called when the game starts 44 | void UInteractorComponent::BeginPlay() 45 | { 46 | Super::BeginPlay(); 47 | 48 | /* Only Tick On Server and Owning Client */ 49 | SetComponentTickEnabled( 50 | ShouldTickInstance() 51 | ); 52 | } 53 | 54 | UInteractionComponent* UInteractorComponent::GetInteractionTrace() 55 | { 56 | /* Get World */ 57 | const UWorld* World = GetWorld(); 58 | if (World == nullptr) 59 | { 60 | return nullptr; 61 | } 62 | 63 | /* Get Owner */ 64 | const AActor* Owner = GetOwner(); 65 | if (!IsValid(Owner)) 66 | { 67 | return nullptr; 68 | } 69 | 70 | /* Set Query Params */ 71 | const FName TraceTag("InteractionTrace"); 72 | FCollisionQueryParams QueryParams = FCollisionQueryParams(TraceTag, true, Owner); 73 | 74 | /* Get Start Location and Rotation */ 75 | FVector OutLocation; 76 | FRotator OutRotator; 77 | 78 | Owner->GetActorEyesViewPoint(OutLocation, OutRotator); 79 | 80 | const FVector StartLocation = OutLocation; 81 | const FVector EndLocation = (OutRotator.Vector() * InteractorReachLength) + OutLocation; 82 | 83 | /* Prepare Hit */ 84 | FHitResult OutHit; 85 | 86 | #if WITH_EDITORONLY_DATA 87 | if (bDrawInteractorTrace) 88 | { 89 | DrawDebugLine(World, StartLocation, EndLocation, FColor::Red, false, 0.0f, 1.f); 90 | } 91 | #endif 92 | 93 | /* Single Line Trace */ 94 | const bool bHit = World->LineTraceSingleByChannel(OutHit, StartLocation, EndLocation, ECollisionChannel::ECC_Visibility, QueryParams); 95 | 96 | /* Get Interaction Component */ 97 | if (bHit) 98 | { 99 | UActorComponent* ActorComp = OutHit.Actor->GetComponentByClass(UInteractionComponent::StaticClass()); 100 | 101 | return Cast (ActorComp); 102 | } 103 | 104 | return nullptr; 105 | 106 | } 107 | 108 | bool UInteractorComponent::ValidateDirection(const UInteractionComponent* InteractionComponent) const 109 | { 110 | if (!IsValid(InteractionComponent)) 111 | { 112 | return false; 113 | } 114 | 115 | /* Return True Early If Face Only Interaction is Not Required */ 116 | if (!InteractionComponent->bOnlyFaceInteraction) 117 | { 118 | return true; 119 | } 120 | 121 | /* Get Interactor Direction from Our Location */ 122 | FVector Direction = GetOwner()->GetActorLocation() - InteractionComponent->GetComponentLocation(); 123 | Direction.Normalize(); 124 | 125 | float DirectionPoint = 0.0f; 126 | 127 | return FVector::DotProduct(Direction, InteractionComponent->GetForwardVector()) > 0.5f; 128 | } 129 | 130 | void UInteractorComponent::TryStartInteraction() 131 | { 132 | /* Make Sure Interaction Starts on Authority */ 133 | if (GetInteractorRole() != ROLE_Authority) 134 | { 135 | Server_TryStartInteraction(); 136 | return; 137 | } 138 | 139 | /* Prevent New Interaction If One Already In Progress */ 140 | if (bInteracting) 141 | { 142 | UE_LOG(LogInteractor, Warning, TEXT("Unable to Start Interaction Due to In Progress Interaction")); 143 | return; 144 | } 145 | 146 | /* Get Server Sided Interaction */ 147 | if (!TryGetInteraction(InteractionCandidate)) 148 | { 149 | UE_LOG(LogInteractor, Warning, TEXT("Unable to Start Interaction Due to No Interaction Available On Server Side")); 150 | return; 151 | } 152 | 153 | /* Start the Interaction */ 154 | if (InteractionCandidate->CanInteractWith(this) && 155 | CanInteractWith(InteractionCandidate)) 156 | { 157 | StartInteraction(); 158 | } 159 | else 160 | { 161 | NotifyInteraction(EInteractionResult::IR_Failed, InteractionCandidate->GetInteractionType()); 162 | } 163 | } 164 | 165 | void UInteractorComponent::Server_TryStartInteraction_Implementation() 166 | { 167 | TryStartInteraction(); 168 | } 169 | 170 | void UInteractorComponent::StartInteraction() 171 | { 172 | 173 | SetInteracting(true); 174 | 175 | const bool bStarted = InteractionCandidate->StartInteraction(this); 176 | 177 | /* If Failed to Start , Fail Initiation */ 178 | if (!bStarted) 179 | { 180 | SetInteracting(false); 181 | 182 | /* Notify Interaction Failed Result */ 183 | NotifyInteraction(EInteractionResult::IR_Failed, InteractionCandidate->GetInteractionType()); 184 | 185 | return; 186 | } 187 | 188 | /* Start Interactor Timer If Interaction Type is Hold */ 189 | if (InteractionCandidate->GetInteractionType() == EInteractionType::IT_Hold) 190 | { 191 | UInteractionComponent_Hold* InteractionHold = Cast(InteractionCandidate); 192 | 193 | ToggleInteractorTimer(true,IsValid(InteractionHold) ? InteractionHold->GetInteractionDuration() : 0.1f); 194 | } 195 | 196 | /* Notify Interaction Started Result */ 197 | NotifyInteraction(EInteractionResult::IR_Started, InteractionCandidate->GetInteractionType()); 198 | } 199 | 200 | void UInteractorComponent::TryStopInteraction() 201 | { 202 | if (GetInteractorRole() != ROLE_Authority) 203 | { 204 | Server_TryStopInteraction(); 205 | return; 206 | } 207 | 208 | /* Return/Exit If No Interaction in Progress */ 209 | if (!bInteracting || !IsValid(InteractionCandidate)) 210 | { 211 | return; 212 | } 213 | 214 | /* Cancel Interaction */ 215 | InteractionCandidate->StopInteraction(this); 216 | } 217 | 218 | void UInteractorComponent::Server_TryStopInteraction_Implementation() 219 | { 220 | TryStopInteraction(); 221 | } 222 | 223 | void UInteractorComponent::EndInteraction(EInteractionResult InteractionResult, UInteractionComponent* InteractionComponent) 224 | { 225 | 226 | /* Validate Ending Interaction Is Interactor Target Else Fail*/ 227 | if (InteractionComponent != InteractionCandidate) 228 | { 229 | UE_LOG(LogInteractor, Warning, TEXT("Unable to Complete/End Interaction Due to Not Matching Interaction Targets")); 230 | 231 | InteractionResult = EInteractionResult::IR_Failed; 232 | } 233 | 234 | /* Set Interacting Status */ 235 | SetInteracting(false); 236 | 237 | /* Get Interaction Type */ 238 | const EInteractionType EndingInteractionType = IsValid(InteractionComponent) ? InteractionComponent->GetInteractionType() : EInteractionType::IT_None; 239 | 240 | /* Clear Interaction Timer In Case Of Interruption */ 241 | if(EndingInteractionType == EInteractionType::IT_Hold) 242 | { 243 | ToggleInteractorTimer(false); 244 | } 245 | 246 | /* Notify Interaction Started Result */ 247 | NotifyInteraction( 248 | InteractionResult, 249 | EndingInteractionType 250 | ); 251 | } 252 | 253 | void UInteractorComponent::LocalEndInteractionFocus(UInteractionComponent* InteractionComponent) 254 | { 255 | if (InteractionComponent == InteractionCandidate && OnNewInteraction.IsBound()) 256 | { 257 | OnNewInteraction.Broadcast(nullptr); 258 | } 259 | } 260 | 261 | bool UInteractorComponent::CanInteractWith(UInteractionComponent* InteractionComponent) 262 | { 263 | /* Get Owner */ 264 | AActor* Owner = GetOwner(); 265 | 266 | /* Check for Interaction Interface and Execute If Owner Implements */ 267 | if (IsValid(Owner) && 268 | Owner->GetClass()->ImplementsInterface(UInteractionInterface::StaticClass())) 269 | { 270 | 271 | return IInteractionInterface::Execute_ICanInteractWith(Owner, InteractionCandidate->GetOwner()); 272 | } 273 | 274 | return true; 275 | } 276 | 277 | void UInteractorComponent::ToggleInteractorTimer(bool bStartTImer /*= true*/, float NewInteractionDuration /*= 0.1f*/) 278 | { 279 | /* Get World */ 280 | const UWorld* World = GetWorld(); 281 | 282 | if (!IsValid(World)) 283 | { 284 | UE_LOG(LogInteractor, Warning, TEXT("Unable to Toggle Hold Interaction Due to Null World")); 285 | return; 286 | } 287 | 288 | if (bStartTImer) 289 | { 290 | /* Start The Timer */ 291 | World->GetTimerManager().SetTimer(InteractorTimer, this, &UInteractorComponent::OnInteractorTimerCompleted, NewInteractionDuration); 292 | } 293 | else 294 | { 295 | /* Clear The Timer */ 296 | World->GetTimerManager().ClearTimer(InteractorTimer); 297 | } 298 | 299 | } 300 | 301 | void UInteractorComponent::OnInteractorTimerCompleted() 302 | { 303 | UE_LOG(LogInteractor, Log, TEXT("Interactor Timer Completed")); 304 | 305 | UInteractionComponent_Hold* InteractionHold = Cast(InteractionCandidate); 306 | 307 | /* Validate Interaction Hold Component Is Valid */ 308 | if (!IsValid(InteractionHold)) 309 | { 310 | /* Fail Interaction to Prevent Deadlock */ 311 | EndInteraction(EInteractionResult::IR_Failed, nullptr); 312 | 313 | UE_LOG(LogInteractor, Error, TEXT("Failed to Cast to Interaction Hold On Timer Completed")); 314 | return; 315 | } 316 | 317 | InteractionHold->OnHoldCompleted(this); 318 | } 319 | 320 | void UInteractorComponent::OnRep_bInteracting() 321 | { 322 | if (OnInteractingChanged.IsBound()) 323 | { 324 | OnInteractingChanged.Broadcast(bInteracting); 325 | } 326 | } 327 | 328 | void UInteractorComponent::RegisterNewInteraction(UInteractionComponent* NewInteraction) 329 | { 330 | if (!IsValid(NewInteraction)) 331 | { 332 | UE_LOG(LogInteractor, Warning, TEXT("Unable to Register New Interaction Due to Invalid Interaction Component")); 333 | return; 334 | } 335 | 336 | /* Prevent Duplicate Registration */ 337 | if (InteractionCandidate == NewInteraction) 338 | { 339 | return; 340 | } 341 | 342 | InteractionCandidate = NewInteraction; 343 | 344 | /* Local Interactor */ 345 | if (IsLocalInteractor()) 346 | { 347 | NewInteraction->SetInteractionFocusState(true,this); 348 | 349 | if (OnNewInteraction.IsBound()) 350 | { 351 | OnNewInteraction.Broadcast(NewInteraction); 352 | } 353 | } 354 | } 355 | 356 | void UInteractorComponent::DeRegisterInteraction() 357 | { 358 | /* Cancel Interaction If Already Interacting */ 359 | if (bInteracting) 360 | { 361 | TryStopInteraction(); 362 | } 363 | 364 | /* Local Interactor */ 365 | if (IsLocalInteractor()) 366 | { 367 | if (IsValid(InteractionCandidate)) 368 | { 369 | InteractionCandidate->SetInteractionFocusState(false); 370 | } 371 | 372 | if (OnNewInteraction.IsBound()) 373 | { 374 | OnNewInteraction.Broadcast(nullptr); 375 | } 376 | } 377 | 378 | InteractionCandidate = nullptr; 379 | } 380 | 381 | void UInteractorComponent::TickComponent(float DeltaTime, ELevelTick TickType, FActorComponentTickFunction* ThisTickFunction) 382 | { 383 | Super::TickComponent(DeltaTime, TickType, ThisTickFunction); 384 | 385 | if (bInteracting) 386 | { 387 | /* If Interacting Get the New Interaction Candidate and Compare to the Current Interacting Component*/ 388 | const UInteractionComponent* NewInteraction = GetInteractionTrace(); 389 | 390 | if (!ValidateDirection(NewInteraction) || NewInteraction != InteractionCandidate) 391 | { 392 | /* Cancel Interaction If not Valid Interaction */ 393 | DeRegisterInteraction(); 394 | } 395 | 396 | } 397 | else if(IsLocalInteractor()) 398 | { 399 | /* Locally Get Interaction and Validate the Component */ 400 | UInteractionComponent* NewInteraction = GetInteractionTrace(); 401 | 402 | if (ValidateDirection(NewInteraction)) 403 | { 404 | /* Register If New Interaction is Not Equal to the Current Candidate */ 405 | if (NewInteraction != InteractionCandidate) 406 | { 407 | RegisterNewInteraction(NewInteraction); 408 | } 409 | } 410 | else if(IsValid(InteractionCandidate)) 411 | { 412 | /* DeRegister Interaction If No Valid Interaction Component Exits*/ 413 | DeRegisterInteraction(); 414 | } 415 | 416 | } 417 | } 418 | 419 | void UInteractorComponent::NotifyInteraction(EInteractionResult NewInteractionResult, EInteractionType NewInteractionType) 420 | { 421 | switch (InteractorStateNetMode) 422 | { 423 | case EInteractionNetMode::INM_OwnerOnly: 424 | Client_NotifyInteraction(NewInteractionResult, NewInteractionType); 425 | break; 426 | case EInteractionNetMode::INM_All: 427 | Multi_NotifyInteraction(NewInteractionResult, NewInteractionType); 428 | break; 429 | default: 430 | break; 431 | } 432 | } 433 | 434 | void UInteractorComponent::Client_NotifyInteraction_Implementation(EInteractionResult NewInteractionResult, EInteractionType NewInteractionType) 435 | { 436 | if (OnInteractorStateChanged.IsBound()) 437 | { 438 | 439 | /* Try to Get Interaction Duration If Candidate is a Hold Interaction */ 440 | float NewInteractionDuration = 0.0f; 441 | 442 | UInteractionComponent_Hold* InteractionComp_Hold = Cast(InteractionCandidate); 443 | 444 | if (IsValid(InteractionComp_Hold)) 445 | { 446 | NewInteractionDuration = InteractionComp_Hold->GetInteractionDuration(); 447 | } 448 | 449 | /* Broadcast the State */ 450 | OnInteractorStateChanged.Broadcast( 451 | NewInteractionResult, 452 | NewInteractionType, 453 | NewInteractionDuration, 454 | IsValid(InteractionCandidate) ? InteractionCandidate->GetOwner() : nullptr 455 | ); 456 | } 457 | 458 | /* Notify Interaction Locally If Interaction Net Mode is Owner Only*/ 459 | if (IsValid(InteractionCandidate) && 460 | InteractionCandidate->InteractionStateNetMode == EInteractionNetMode::INM_OwnerOnly) 461 | { 462 | InteractionCandidate->ClientNotifyInteraction(NewInteractionResult, this); 463 | } 464 | } 465 | 466 | void UInteractorComponent::Multi_NotifyInteraction_Implementation(EInteractionResult NewInteractionResult, EInteractionType NewInteractionType) 467 | { 468 | if (OnInteractorStateChanged.IsBound()) 469 | { 470 | 471 | /* Try to Get Interaction Duration If Candidate is a Hold Interaction */ 472 | float NewInteractionDuration = 0.0f; 473 | 474 | UInteractionComponent_Hold* InteractionComp_Hold = Cast(InteractionCandidate); 475 | 476 | if (IsValid(InteractionComp_Hold)) 477 | { 478 | NewInteractionDuration = InteractionComp_Hold->GetInteractionDuration(); 479 | } 480 | 481 | /* Broadcast the State */ 482 | OnInteractorStateChanged.Broadcast( 483 | NewInteractionResult, 484 | NewInteractionType, 485 | NewInteractionDuration, 486 | IsValid(InteractionCandidate) ? InteractionCandidate->GetOwner() : nullptr 487 | ); 488 | } 489 | } 490 | --------------------------------------------------------------------------------