├── .gitignore ├── Config ├── DefaultEditor.ini ├── DefaultEngine.ini ├── DefaultGame.ini └── DefaultInput.ini ├── Content ├── Examples │ ├── Door │ │ └── BP_LockedDoor.uasset │ ├── LightSwitch │ │ └── BP_LightSwitchComponent.uasset │ └── Mover │ │ ├── BP_Mover.uasset │ │ └── BP_MoverButtonComponent.uasset ├── GameModes │ └── GameMode_InteractionSystem.uasset ├── Maps │ ├── NewMap.umap │ └── NewMap_BuiltData.uasset └── Player │ ├── BP_PlayerController.uasset │ ├── BP_PlayerPawn.uasset │ └── UMG_PlayerHUD.uasset ├── InteractionSystem.uproject ├── LICENSE ├── README.md └── Source ├── InteractionSystem.Target.cs ├── InteractionSystem ├── InteractionSystem.Build.cs ├── InteractionSystem.cpp ├── InteractionSystem.h ├── InteractionSystemGameModeBase.cpp ├── InteractionSystemGameModeBase.h ├── Private │ ├── Interactive.cpp │ ├── InteractiveActor.cpp │ ├── InteractiveBoxComponent.cpp │ └── PlayerPawn.cpp └── Public │ ├── Interactive.h │ ├── InteractiveActor.h │ ├── InteractiveBoxComponent.h │ └── PlayerPawn.h └── InteractionSystemEditor.Target.cs /.gitignore: -------------------------------------------------------------------------------- 1 | /.vs 2 | /Binaries 3 | /Intermediate 4 | /Saved 5 | /InteractionSystem.sln 6 | /WindowsNoEditor 7 | /DerivedDataCache 8 | /Build 9 | -------------------------------------------------------------------------------- /Config/DefaultEditor.ini: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/doomtrinity/InteractionSystem/00b10c98e88d5967c2eb56c99c6d95a3c7a251be/Config/DefaultEditor.ini -------------------------------------------------------------------------------- /Config/DefaultEngine.ini: -------------------------------------------------------------------------------- 1 | [URL] 2 | [/Script/Engine.RendererSettings] 3 | r.DefaultFeature.AutoExposure.ExtendDefaultLuminanceRange=True 4 | 5 | [/Script/HardwareTargeting.HardwareTargetingSettings] 6 | TargetedHardwareClass=Desktop 7 | AppliedTargetedHardwareClass=Desktop 8 | DefaultGraphicsPerformance=Maximum 9 | AppliedDefaultGraphicsPerformance=Maximum 10 | 11 | [/Script/Engine.CollisionProfile] 12 | -Profiles=(Name="NoCollision",CollisionEnabled=NoCollision,ObjectTypeName="WorldStatic",CustomResponses=((Channel="Visibility",Response=ECR_Ignore),(Channel="Camera",Response=ECR_Ignore)),HelpMessage="No collision",bCanModify=False) 13 | -Profiles=(Name="BlockAll",CollisionEnabled=QueryAndPhysics,ObjectTypeName="WorldStatic",CustomResponses=,HelpMessage="WorldStatic object that blocks all actors by default. All new custom channels will use its own default response. ",bCanModify=False) 14 | -Profiles=(Name="OverlapAll",CollisionEnabled=QueryOnly,ObjectTypeName="WorldStatic",CustomResponses=((Channel="WorldStatic",Response=ECR_Overlap),(Channel="Pawn",Response=ECR_Overlap),(Channel="Visibility",Response=ECR_Overlap),(Channel="WorldDynamic",Response=ECR_Overlap),(Channel="Camera",Response=ECR_Overlap),(Channel="PhysicsBody",Response=ECR_Overlap),(Channel="Vehicle",Response=ECR_Overlap),(Channel="Destructible",Response=ECR_Overlap)),HelpMessage="WorldStatic object that overlaps all actors by default. All new custom channels will use its own default response. ",bCanModify=False) 15 | -Profiles=(Name="BlockAllDynamic",CollisionEnabled=QueryAndPhysics,ObjectTypeName="WorldDynamic",CustomResponses=,HelpMessage="WorldDynamic object that blocks all actors by default. All new custom channels will use its own default response. ",bCanModify=False) 16 | -Profiles=(Name="OverlapAllDynamic",CollisionEnabled=QueryOnly,ObjectTypeName="WorldDynamic",CustomResponses=((Channel="WorldStatic",Response=ECR_Overlap),(Channel="Pawn",Response=ECR_Overlap),(Channel="Visibility",Response=ECR_Overlap),(Channel="WorldDynamic",Response=ECR_Overlap),(Channel="Camera",Response=ECR_Overlap),(Channel="PhysicsBody",Response=ECR_Overlap),(Channel="Vehicle",Response=ECR_Overlap),(Channel="Destructible",Response=ECR_Overlap)),HelpMessage="WorldDynamic object that overlaps all actors by default. All new custom channels will use its own default response. ",bCanModify=False) 17 | -Profiles=(Name="IgnoreOnlyPawn",CollisionEnabled=QueryOnly,ObjectTypeName="WorldDynamic",CustomResponses=((Channel="Pawn",Response=ECR_Ignore),(Channel="Vehicle",Response=ECR_Ignore)),HelpMessage="WorldDynamic object that ignores Pawn and Vehicle. All other channels will be set to default.",bCanModify=False) 18 | -Profiles=(Name="OverlapOnlyPawn",CollisionEnabled=QueryOnly,ObjectTypeName="WorldDynamic",CustomResponses=((Channel="Pawn",Response=ECR_Overlap),(Channel="Vehicle",Response=ECR_Overlap),(Channel="Camera",Response=ECR_Ignore)),HelpMessage="WorldDynamic object that overlaps Pawn, Camera, and Vehicle. All other channels will be set to default. ",bCanModify=False) 19 | -Profiles=(Name="Pawn",CollisionEnabled=QueryAndPhysics,ObjectTypeName="Pawn",CustomResponses=((Channel="Visibility",Response=ECR_Ignore)),HelpMessage="Pawn object. Can be used for capsule of any playerable character or AI. ",bCanModify=False) 20 | -Profiles=(Name="Spectator",CollisionEnabled=QueryOnly,ObjectTypeName="Pawn",CustomResponses=((Channel="WorldStatic",Response=ECR_Block),(Channel="Pawn",Response=ECR_Ignore),(Channel="Visibility",Response=ECR_Ignore),(Channel="WorldDynamic",Response=ECR_Ignore),(Channel="Camera",Response=ECR_Ignore),(Channel="PhysicsBody",Response=ECR_Ignore),(Channel="Vehicle",Response=ECR_Ignore),(Channel="Destructible",Response=ECR_Ignore)),HelpMessage="Pawn object that ignores all other actors except WorldStatic.",bCanModify=False) 21 | -Profiles=(Name="CharacterMesh",CollisionEnabled=QueryOnly,ObjectTypeName="Pawn",CustomResponses=((Channel="Pawn",Response=ECR_Ignore),(Channel="Vehicle",Response=ECR_Ignore),(Channel="Visibility",Response=ECR_Ignore)),HelpMessage="Pawn object that is used for Character Mesh. All other channels will be set to default.",bCanModify=False) 22 | -Profiles=(Name="PhysicsActor",CollisionEnabled=QueryAndPhysics,ObjectTypeName="PhysicsBody",CustomResponses=,HelpMessage="Simulating actors",bCanModify=False) 23 | -Profiles=(Name="Destructible",CollisionEnabled=QueryAndPhysics,ObjectTypeName="Destructible",CustomResponses=,HelpMessage="Destructible actors",bCanModify=False) 24 | -Profiles=(Name="InvisibleWall",CollisionEnabled=QueryAndPhysics,ObjectTypeName="WorldStatic",CustomResponses=((Channel="Visibility",Response=ECR_Ignore)),HelpMessage="WorldStatic object that is invisible.",bCanModify=False) 25 | -Profiles=(Name="InvisibleWallDynamic",CollisionEnabled=QueryAndPhysics,ObjectTypeName="WorldDynamic",CustomResponses=((Channel="Visibility",Response=ECR_Ignore)),HelpMessage="WorldDynamic object that is invisible.",bCanModify=False) 26 | -Profiles=(Name="Trigger",CollisionEnabled=QueryOnly,ObjectTypeName="WorldDynamic",CustomResponses=((Channel="WorldStatic",Response=ECR_Overlap),(Channel="Pawn",Response=ECR_Overlap),(Channel="Visibility",Response=ECR_Ignore),(Channel="WorldDynamic",Response=ECR_Overlap),(Channel="Camera",Response=ECR_Overlap),(Channel="PhysicsBody",Response=ECR_Overlap),(Channel="Vehicle",Response=ECR_Overlap),(Channel="Destructible",Response=ECR_Overlap)),HelpMessage="WorldDynamic object that is used for trigger. All other channels will be set to default.",bCanModify=False) 27 | -Profiles=(Name="Ragdoll",CollisionEnabled=QueryAndPhysics,ObjectTypeName="PhysicsBody",CustomResponses=((Channel="Pawn",Response=ECR_Ignore),(Channel="Visibility",Response=ECR_Ignore)),HelpMessage="Simulating Skeletal Mesh Component. All other channels will be set to default.",bCanModify=False) 28 | -Profiles=(Name="Vehicle",CollisionEnabled=QueryAndPhysics,ObjectTypeName="Vehicle",CustomResponses=,HelpMessage="Vehicle object that blocks Vehicle, WorldStatic, and WorldDynamic. All other channels will be set to default.",bCanModify=False) 29 | -Profiles=(Name="UI",CollisionEnabled=QueryOnly,ObjectTypeName="WorldDynamic",CustomResponses=((Channel="WorldStatic",Response=ECR_Overlap),(Channel="Pawn",Response=ECR_Overlap),(Channel="Visibility",Response=ECR_Block),(Channel="WorldDynamic",Response=ECR_Overlap),(Channel="Camera",Response=ECR_Overlap),(Channel="PhysicsBody",Response=ECR_Overlap),(Channel="Vehicle",Response=ECR_Overlap),(Channel="Destructible",Response=ECR_Overlap)),HelpMessage="WorldStatic object that overlaps all actors by default. All new custom channels will use its own default response. ",bCanModify=False) 30 | +Profiles=(Name="NoCollision",CollisionEnabled=NoCollision,ObjectTypeName="WorldStatic",CustomResponses=((Channel="Visibility",Response=ECR_Ignore),(Channel="Camera",Response=ECR_Ignore)),HelpMessage="No collision",bCanModify=False) 31 | +Profiles=(Name="BlockAll",CollisionEnabled=QueryAndPhysics,ObjectTypeName="WorldStatic",CustomResponses=,HelpMessage="WorldStatic object that blocks all actors by default. All new custom channels will use its own default response. ",bCanModify=False) 32 | +Profiles=(Name="OverlapAll",CollisionEnabled=QueryOnly,ObjectTypeName="WorldStatic",CustomResponses=((Channel="WorldStatic",Response=ECR_Overlap),(Channel="Pawn",Response=ECR_Overlap),(Channel="Visibility",Response=ECR_Overlap),(Channel="WorldDynamic",Response=ECR_Overlap),(Channel="Camera",Response=ECR_Overlap),(Channel="PhysicsBody",Response=ECR_Overlap),(Channel="Vehicle",Response=ECR_Overlap),(Channel="Destructible",Response=ECR_Overlap)),HelpMessage="WorldStatic object that overlaps all actors by default. All new custom channels will use its own default response. ",bCanModify=False) 33 | +Profiles=(Name="BlockAllDynamic",CollisionEnabled=QueryAndPhysics,ObjectTypeName="WorldDynamic",CustomResponses=,HelpMessage="WorldDynamic object that blocks all actors by default. All new custom channels will use its own default response. ",bCanModify=False) 34 | +Profiles=(Name="OverlapAllDynamic",CollisionEnabled=QueryOnly,ObjectTypeName="WorldDynamic",CustomResponses=((Channel="WorldStatic",Response=ECR_Overlap),(Channel="Pawn",Response=ECR_Overlap),(Channel="Visibility",Response=ECR_Overlap),(Channel="WorldDynamic",Response=ECR_Overlap),(Channel="Camera",Response=ECR_Overlap),(Channel="PhysicsBody",Response=ECR_Overlap),(Channel="Vehicle",Response=ECR_Overlap),(Channel="Destructible",Response=ECR_Overlap)),HelpMessage="WorldDynamic object that overlaps all actors by default. All new custom channels will use its own default response. ",bCanModify=False) 35 | +Profiles=(Name="IgnoreOnlyPawn",CollisionEnabled=QueryOnly,ObjectTypeName="WorldDynamic",CustomResponses=((Channel="Pawn",Response=ECR_Ignore),(Channel="Vehicle",Response=ECR_Ignore)),HelpMessage="WorldDynamic object that ignores Pawn and Vehicle. All other channels will be set to default.",bCanModify=False) 36 | +Profiles=(Name="OverlapOnlyPawn",CollisionEnabled=QueryOnly,ObjectTypeName="WorldDynamic",CustomResponses=((Channel="Pawn",Response=ECR_Overlap),(Channel="Vehicle",Response=ECR_Overlap),(Channel="Camera",Response=ECR_Ignore)),HelpMessage="WorldDynamic object that overlaps Pawn, Camera, and Vehicle. All other channels will be set to default. ",bCanModify=False) 37 | +Profiles=(Name="Pawn",CollisionEnabled=QueryAndPhysics,ObjectTypeName="Pawn",CustomResponses=((Channel="Visibility",Response=ECR_Ignore)),HelpMessage="Pawn object. Can be used for capsule of any playerable character or AI. ",bCanModify=False) 38 | +Profiles=(Name="Spectator",CollisionEnabled=QueryOnly,ObjectTypeName="Pawn",CustomResponses=((Channel="WorldStatic"),(Channel="Pawn",Response=ECR_Ignore),(Channel="Visibility",Response=ECR_Ignore),(Channel="WorldDynamic",Response=ECR_Ignore),(Channel="Camera",Response=ECR_Ignore),(Channel="PhysicsBody",Response=ECR_Ignore),(Channel="Vehicle",Response=ECR_Ignore),(Channel="Destructible",Response=ECR_Ignore)),HelpMessage="Pawn object that ignores all other actors except WorldStatic.",bCanModify=False) 39 | +Profiles=(Name="CharacterMesh",CollisionEnabled=QueryOnly,ObjectTypeName="Pawn",CustomResponses=((Channel="Pawn",Response=ECR_Ignore),(Channel="Vehicle",Response=ECR_Ignore),(Channel="Visibility",Response=ECR_Ignore)),HelpMessage="Pawn object that is used for Character Mesh. All other channels will be set to default.",bCanModify=False) 40 | +Profiles=(Name="PhysicsActor",CollisionEnabled=QueryAndPhysics,ObjectTypeName="PhysicsBody",CustomResponses=,HelpMessage="Simulating actors",bCanModify=False) 41 | +Profiles=(Name="Destructible",CollisionEnabled=QueryAndPhysics,ObjectTypeName="Destructible",CustomResponses=,HelpMessage="Destructible actors",bCanModify=False) 42 | +Profiles=(Name="InvisibleWall",CollisionEnabled=QueryAndPhysics,ObjectTypeName="WorldStatic",CustomResponses=((Channel="Visibility",Response=ECR_Ignore)),HelpMessage="WorldStatic object that is invisible.",bCanModify=False) 43 | +Profiles=(Name="InvisibleWallDynamic",CollisionEnabled=QueryAndPhysics,ObjectTypeName="WorldDynamic",CustomResponses=((Channel="Visibility",Response=ECR_Ignore)),HelpMessage="WorldDynamic object that is invisible.",bCanModify=False) 44 | +Profiles=(Name="Trigger",CollisionEnabled=QueryOnly,ObjectTypeName="WorldDynamic",CustomResponses=((Channel="WorldStatic",Response=ECR_Overlap),(Channel="Pawn",Response=ECR_Overlap),(Channel="Visibility",Response=ECR_Ignore),(Channel="WorldDynamic",Response=ECR_Overlap),(Channel="Camera",Response=ECR_Overlap),(Channel="PhysicsBody",Response=ECR_Overlap),(Channel="Vehicle",Response=ECR_Overlap),(Channel="Destructible",Response=ECR_Overlap)),HelpMessage="WorldDynamic object that is used for trigger. All other channels will be set to default.",bCanModify=False) 45 | +Profiles=(Name="Ragdoll",CollisionEnabled=QueryAndPhysics,ObjectTypeName="PhysicsBody",CustomResponses=((Channel="Pawn",Response=ECR_Ignore),(Channel="Visibility",Response=ECR_Ignore)),HelpMessage="Simulating Skeletal Mesh Component. All other channels will be set to default.",bCanModify=False) 46 | +Profiles=(Name="Vehicle",CollisionEnabled=QueryAndPhysics,ObjectTypeName="Vehicle",CustomResponses=,HelpMessage="Vehicle object that blocks Vehicle, WorldStatic, and WorldDynamic. All other channels will be set to default.",bCanModify=False) 47 | +Profiles=(Name="UI",CollisionEnabled=QueryOnly,ObjectTypeName="WorldDynamic",CustomResponses=((Channel="WorldStatic",Response=ECR_Overlap),(Channel="Pawn",Response=ECR_Overlap),(Channel="Visibility"),(Channel="WorldDynamic",Response=ECR_Overlap),(Channel="Camera",Response=ECR_Overlap),(Channel="PhysicsBody",Response=ECR_Overlap),(Channel="Vehicle",Response=ECR_Overlap),(Channel="Destructible",Response=ECR_Overlap)),HelpMessage="WorldStatic object that overlaps all actors by default. All new custom channels will use its own default response. ",bCanModify=False) 48 | +DefaultChannelResponses=(Channel=ECC_GameTraceChannel1,Name="Interaction",DefaultResponse=ECR_Ignore,bTraceType=True,bStaticObject=False) 49 | -ProfileRedirects=(OldName="BlockingVolume",NewName="InvisibleWall") 50 | -ProfileRedirects=(OldName="InterpActor",NewName="IgnoreOnlyPawn") 51 | -ProfileRedirects=(OldName="StaticMeshComponent",NewName="BlockAllDynamic") 52 | -ProfileRedirects=(OldName="SkeletalMeshActor",NewName="PhysicsActor") 53 | -ProfileRedirects=(OldName="InvisibleActor",NewName="InvisibleWallDynamic") 54 | +ProfileRedirects=(OldName="BlockingVolume",NewName="InvisibleWall") 55 | +ProfileRedirects=(OldName="InterpActor",NewName="IgnoreOnlyPawn") 56 | +ProfileRedirects=(OldName="StaticMeshComponent",NewName="BlockAllDynamic") 57 | +ProfileRedirects=(OldName="SkeletalMeshActor",NewName="PhysicsActor") 58 | +ProfileRedirects=(OldName="InvisibleActor",NewName="InvisibleWallDynamic") 59 | -CollisionChannelRedirects=(OldName="Static",NewName="WorldStatic") 60 | -CollisionChannelRedirects=(OldName="Dynamic",NewName="WorldDynamic") 61 | -CollisionChannelRedirects=(OldName="VehicleMovement",NewName="Vehicle") 62 | -CollisionChannelRedirects=(OldName="PawnMovement",NewName="Pawn") 63 | +CollisionChannelRedirects=(OldName="Static",NewName="WorldStatic") 64 | +CollisionChannelRedirects=(OldName="Dynamic",NewName="WorldDynamic") 65 | +CollisionChannelRedirects=(OldName="VehicleMovement",NewName="Vehicle") 66 | +CollisionChannelRedirects=(OldName="PawnMovement",NewName="Pawn") 67 | 68 | [/Script/EngineSettings.GameMapsSettings] 69 | GlobalDefaultGameMode=/Game/GameModes/GameMode_InteractionSystem.GameMode_InteractionSystem_C 70 | EditorStartupMap=/Game/Maps/NewMap.NewMap 71 | 72 | [/Script/Engine.PhysicsSettings] 73 | DefaultGravityZ=-980.000000 74 | DefaultTerminalVelocity=4000.000000 75 | DefaultFluidFriction=0.300000 76 | SimulateScratchMemorySize=262144 77 | RagdollAggregateThreshold=4 78 | TriangleMeshTriangleMinAreaThreshold=5.000000 79 | bEnableShapeSharing=False 80 | bEnablePCM=True 81 | bEnableStabilization=False 82 | bWarnMissingLocks=True 83 | bEnable2DPhysics=False 84 | PhysicErrorCorrection=(PingExtrapolation=0.100000,PingLimit=100.000000,ErrorPerLinearDifference=1.000000,ErrorPerAngularDifference=1.000000,MaxRestoredStateError=1.000000,MaxLinearHardSnapDistance=400.000000,PositionLerp=0.000000,AngleLerp=0.400000,LinearVelocityCoefficient=100.000000,AngularVelocityCoefficient=10.000000,ErrorAccumulationSeconds=0.500000,ErrorAccumulationDistanceSq=15.000000,ErrorAccumulationSimilarity=100.000000) 85 | LockedAxis=Invalid 86 | DefaultDegreesOfFreedom=Full3D 87 | BounceThresholdVelocity=200.000000 88 | FrictionCombineMode=Average 89 | RestitutionCombineMode=Average 90 | MaxAngularVelocity=3600.000000 91 | MaxDepenetrationVelocity=0.000000 92 | ContactOffsetMultiplier=0.020000 93 | MinContactOffset=2.000000 94 | MaxContactOffset=8.000000 95 | bSimulateSkeletalMeshOnDedicatedServer=True 96 | DefaultShapeComplexity=CTF_UseSimpleAndComplex 97 | bDefaultHasComplexCollision=True 98 | bSuppressFaceRemapTable=False 99 | bSupportUVFromHitResults=False 100 | bDisableActiveActors=False 101 | bDisableKinematicStaticPairs=False 102 | bDisableKinematicKinematicPairs=False 103 | bDisableCCD=False 104 | bEnableEnhancedDeterminism=False 105 | MaxPhysicsDeltaTime=0.033333 106 | bSubstepping=False 107 | bSubsteppingAsync=False 108 | MaxSubstepDeltaTime=0.016667 109 | MaxSubsteps=6 110 | SyncSceneSmoothingFactor=0.000000 111 | InitialAverageFrameRate=0.016667 112 | PhysXTreeRebuildRate=10 113 | DefaultBroadphaseSettings=(bUseMBPOnClient=False,bUseMBPOnServer=False,MBPBounds=(Min=(X=0.000000,Y=0.000000,Z=0.000000),Max=(X=0.000000,Y=0.000000,Z=0.000000),IsValid=0),MBPNumSubdivs=2) 114 | 115 | 116 | -------------------------------------------------------------------------------- /Config/DefaultGame.ini: -------------------------------------------------------------------------------- 1 | [/Script/EngineSettings.GeneralProjectSettings] 2 | ProjectID=0192B9D245692E3EC6D392A1F93E102D 3 | -------------------------------------------------------------------------------- /Config/DefaultInput.ini: -------------------------------------------------------------------------------- 1 | 2 | [/Script/Engine.InputSettings] 3 | -AxisConfig=(AxisKeyName="Gamepad_LeftX",AxisProperties=(DeadZone=0.25,Exponent=1.f,Sensitivity=1.f)) 4 | -AxisConfig=(AxisKeyName="Gamepad_LeftY",AxisProperties=(DeadZone=0.25,Exponent=1.f,Sensitivity=1.f)) 5 | -AxisConfig=(AxisKeyName="Gamepad_RightX",AxisProperties=(DeadZone=0.25,Exponent=1.f,Sensitivity=1.f)) 6 | -AxisConfig=(AxisKeyName="Gamepad_RightY",AxisProperties=(DeadZone=0.25,Exponent=1.f,Sensitivity=1.f)) 7 | -AxisConfig=(AxisKeyName="MouseX",AxisProperties=(DeadZone=0.f,Exponent=1.f,Sensitivity=0.07f)) 8 | -AxisConfig=(AxisKeyName="MouseY",AxisProperties=(DeadZone=0.f,Exponent=1.f,Sensitivity=0.07f)) 9 | +AxisConfig=(AxisKeyName="Gamepad_LeftX",AxisProperties=(DeadZone=0.250000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) 10 | +AxisConfig=(AxisKeyName="Gamepad_LeftY",AxisProperties=(DeadZone=0.250000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) 11 | +AxisConfig=(AxisKeyName="Gamepad_RightX",AxisProperties=(DeadZone=0.250000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) 12 | +AxisConfig=(AxisKeyName="Gamepad_RightY",AxisProperties=(DeadZone=0.250000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) 13 | +AxisConfig=(AxisKeyName="MouseX",AxisProperties=(DeadZone=0.000000,Sensitivity=0.070000,Exponent=1.000000,bInvert=False)) 14 | +AxisConfig=(AxisKeyName="MouseY",AxisProperties=(DeadZone=0.000000,Sensitivity=0.070000,Exponent=1.000000,bInvert=False)) 15 | +AxisConfig=(AxisKeyName="MouseWheelAxis",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) 16 | +AxisConfig=(AxisKeyName="Gamepad_LeftTriggerAxis",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) 17 | +AxisConfig=(AxisKeyName="Gamepad_RightTriggerAxis",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) 18 | +AxisConfig=(AxisKeyName="MotionController_Left_Thumbstick_X",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) 19 | +AxisConfig=(AxisKeyName="MotionController_Left_Thumbstick_Y",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) 20 | +AxisConfig=(AxisKeyName="MotionController_Left_TriggerAxis",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) 21 | +AxisConfig=(AxisKeyName="MotionController_Left_Grip1Axis",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) 22 | +AxisConfig=(AxisKeyName="MotionController_Left_Grip2Axis",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) 23 | +AxisConfig=(AxisKeyName="MotionController_Right_Thumbstick_X",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) 24 | +AxisConfig=(AxisKeyName="MotionController_Right_Thumbstick_Y",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) 25 | +AxisConfig=(AxisKeyName="MotionController_Right_TriggerAxis",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) 26 | +AxisConfig=(AxisKeyName="MotionController_Right_Grip1Axis",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) 27 | +AxisConfig=(AxisKeyName="MotionController_Right_Grip2Axis",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) 28 | +AxisConfig=(AxisKeyName="Gamepad_Special_Left_X",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) 29 | +AxisConfig=(AxisKeyName="Gamepad_Special_Left_Y",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) 30 | bAltEnterTogglesFullscreen=True 31 | bF11TogglesFullscreen=True 32 | bUseMouseForTouch=False 33 | bEnableMouseSmoothing=True 34 | bEnableFOVScaling=True 35 | bCaptureMouseOnLaunch=True 36 | bDefaultViewportMouseLock=False 37 | bAlwaysShowTouchInterface=False 38 | bShowConsoleOnFourFingerTap=True 39 | bEnableGestureRecognizer=False 40 | bUseAutocorrect=False 41 | DefaultViewportMouseCaptureMode=CapturePermanently_IncludingInitialMouseDown 42 | DefaultViewportMouseLockMode=LockOnCapture 43 | FOVScale=0.011110 44 | DoubleClickTime=0.200000 45 | +ActionMappings=(ActionName="Interact",bShift=False,bCtrl=False,bAlt=False,bCmd=False,Key=F) 46 | +AxisMappings=(AxisName="MoveForward",Scale=1.000000,Key=W) 47 | +AxisMappings=(AxisName="MoveForward",Scale=-1.000000,Key=S) 48 | +AxisMappings=(AxisName="MoveRight",Scale=1.000000,Key=D) 49 | +AxisMappings=(AxisName="MoveRight",Scale=-1.000000,Key=A) 50 | +AxisMappings=(AxisName="Turn",Scale=1.000000,Key=MouseX) 51 | +AxisMappings=(AxisName="LookUp",Scale=-1.000000,Key=MouseY) 52 | DefaultTouchInterface=/Engine/MobileResources/HUD/DefaultVirtualJoysticks.DefaultVirtualJoysticks 53 | ConsoleKey=None 54 | -ConsoleKeys=Tilde 55 | +ConsoleKeys=Tilde 56 | +ConsoleKeys=Backslash 57 | 58 | 59 | -------------------------------------------------------------------------------- /Content/Examples/Door/BP_LockedDoor.uasset: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/doomtrinity/InteractionSystem/00b10c98e88d5967c2eb56c99c6d95a3c7a251be/Content/Examples/Door/BP_LockedDoor.uasset -------------------------------------------------------------------------------- /Content/Examples/LightSwitch/BP_LightSwitchComponent.uasset: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/doomtrinity/InteractionSystem/00b10c98e88d5967c2eb56c99c6d95a3c7a251be/Content/Examples/LightSwitch/BP_LightSwitchComponent.uasset -------------------------------------------------------------------------------- /Content/Examples/Mover/BP_Mover.uasset: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/doomtrinity/InteractionSystem/00b10c98e88d5967c2eb56c99c6d95a3c7a251be/Content/Examples/Mover/BP_Mover.uasset -------------------------------------------------------------------------------- /Content/Examples/Mover/BP_MoverButtonComponent.uasset: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/doomtrinity/InteractionSystem/00b10c98e88d5967c2eb56c99c6d95a3c7a251be/Content/Examples/Mover/BP_MoverButtonComponent.uasset -------------------------------------------------------------------------------- /Content/GameModes/GameMode_InteractionSystem.uasset: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/doomtrinity/InteractionSystem/00b10c98e88d5967c2eb56c99c6d95a3c7a251be/Content/GameModes/GameMode_InteractionSystem.uasset -------------------------------------------------------------------------------- /Content/Maps/NewMap.umap: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/doomtrinity/InteractionSystem/00b10c98e88d5967c2eb56c99c6d95a3c7a251be/Content/Maps/NewMap.umap -------------------------------------------------------------------------------- /Content/Maps/NewMap_BuiltData.uasset: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/doomtrinity/InteractionSystem/00b10c98e88d5967c2eb56c99c6d95a3c7a251be/Content/Maps/NewMap_BuiltData.uasset -------------------------------------------------------------------------------- /Content/Player/BP_PlayerController.uasset: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/doomtrinity/InteractionSystem/00b10c98e88d5967c2eb56c99c6d95a3c7a251be/Content/Player/BP_PlayerController.uasset -------------------------------------------------------------------------------- /Content/Player/BP_PlayerPawn.uasset: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/doomtrinity/InteractionSystem/00b10c98e88d5967c2eb56c99c6d95a3c7a251be/Content/Player/BP_PlayerPawn.uasset -------------------------------------------------------------------------------- /Content/Player/UMG_PlayerHUD.uasset: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/doomtrinity/InteractionSystem/00b10c98e88d5967c2eb56c99c6d95a3c7a251be/Content/Player/UMG_PlayerHUD.uasset -------------------------------------------------------------------------------- /InteractionSystem.uproject: -------------------------------------------------------------------------------- 1 | { 2 | "FileVersion": 3, 3 | "EngineAssociation": "4.22", 4 | "Category": "", 5 | "Description": "", 6 | "Modules": [ 7 | { 8 | "Name": "InteractionSystem", 9 | "Type": "Runtime", 10 | "LoadingPhase": "Default", 11 | "AdditionalDependencies": [ 12 | "Engine" 13 | ] 14 | } 15 | ] 16 | } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Lorenzo Citron 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # UE4 component-based interaction system with built-in replication 2 | by doomtrinity 3 | 4 | In this UE4 sample project you'll find a stripped-down version of the interaction system that we're using in the game Retchid. 5 | 6 | It's a rather simple system, based on a custom interaction component with built-in replication, that can be used to handle the interaction with objects like buttons, levers, doors, etc. 7 | 8 | You can dissect the project, you'll find the documentation in the source files. 9 | 10 | Tested on UE 4.22.3 and 4.26.1 (make sure to checkout the matching branch) 11 | 12 | ![interaction_system_door](https://user-images.githubusercontent.com/16953856/123490853-77f2f300-d615-11eb-9173-a8c493093dcf.PNG) 13 | 14 | ## Links 15 | - [UE4 Forums](https://forums.unrealengine.com/t/component-based-interaction-system-with-built-in-replication/232091) 16 | - [YouTube](https://www.youtube.com/watch?v=Dd5ZCetPw3w) 17 | -------------------------------------------------------------------------------- /Source/InteractionSystem.Target.cs: -------------------------------------------------------------------------------- 1 | // Fill out your copyright notice in the Description page of Project Settings. 2 | 3 | using UnrealBuildTool; 4 | using System.Collections.Generic; 5 | 6 | public class InteractionSystemTarget : TargetRules 7 | { 8 | public InteractionSystemTarget(TargetInfo Target) : base(Target) 9 | { 10 | Type = TargetType.Game; 11 | 12 | ExtraModuleNames.AddRange( new string[] { "InteractionSystem" } ); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /Source/InteractionSystem/InteractionSystem.Build.cs: -------------------------------------------------------------------------------- 1 | // Fill out your copyright notice in the Description page of Project Settings. 2 | 3 | using UnrealBuildTool; 4 | 5 | public class InteractionSystem : ModuleRules 6 | { 7 | public InteractionSystem(ReadOnlyTargetRules Target) : base(Target) 8 | { 9 | PCHUsage = PCHUsageMode.UseExplicitOrSharedPCHs; 10 | 11 | PublicDependencyModuleNames.AddRange(new string[] { "Core", "CoreUObject", "Engine", "InputCore" }); 12 | 13 | PrivateDependencyModuleNames.AddRange(new string[] { }); 14 | 15 | // Uncomment if you are using Slate UI 16 | // PrivateDependencyModuleNames.AddRange(new string[] { "Slate", "SlateCore" }); 17 | 18 | // Uncomment if you are using online features 19 | // PrivateDependencyModuleNames.Add("OnlineSubsystem"); 20 | 21 | // To include OnlineSubsystemSteam, add it to the plugins section in your uproject file with the Enabled attribute set to true 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /Source/InteractionSystem/InteractionSystem.cpp: -------------------------------------------------------------------------------- 1 | // Fill out your copyright notice in the Description page of Project Settings. 2 | 3 | #include "InteractionSystem.h" 4 | #include "Modules/ModuleManager.h" 5 | 6 | IMPLEMENT_PRIMARY_GAME_MODULE( FDefaultGameModuleImpl, InteractionSystem, "InteractionSystem" ); 7 | 8 | DEFINE_LOG_CATEGORY(LogInteraction) -------------------------------------------------------------------------------- /Source/InteractionSystem/InteractionSystem.h: -------------------------------------------------------------------------------- 1 | // Fill out your copyright notice in the Description page of Project Settings. 2 | 3 | #pragma once 4 | 5 | #include "CoreMinimal.h" 6 | 7 | DECLARE_LOG_CATEGORY_EXTERN(LogInteraction, Log, All); 8 | 9 | #define COLLISION_INTERACTIVE ECC_GameTraceChannel11 -------------------------------------------------------------------------------- /Source/InteractionSystem/InteractionSystemGameModeBase.cpp: -------------------------------------------------------------------------------- 1 | // Fill out your copyright notice in the Description page of Project Settings. 2 | 3 | 4 | #include "InteractionSystemGameModeBase.h" 5 | #include "PlayerPawn.h" 6 | 7 | AInteractionSystemGameModeBase::AInteractionSystemGameModeBase(const FObjectInitializer& ObjectInitializer) 8 | : Super(ObjectInitializer) 9 | { 10 | DefaultPawnClass = APlayerPawn::StaticClass(); 11 | } -------------------------------------------------------------------------------- /Source/InteractionSystem/InteractionSystemGameModeBase.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 "GameFramework/GameModeBase.h" 7 | #include "InteractionSystemGameModeBase.generated.h" 8 | 9 | /** 10 | * 11 | */ 12 | UCLASS() 13 | class INTERACTIONSYSTEM_API AInteractionSystemGameModeBase : public AGameModeBase 14 | { 15 | GENERATED_BODY() 16 | 17 | AInteractionSystemGameModeBase(const FObjectInitializer& ObjectInitializer); 18 | 19 | }; 20 | -------------------------------------------------------------------------------- /Source/InteractionSystem/Private/Interactive.cpp: -------------------------------------------------------------------------------- 1 | // Fill out your copyright notice in the Description page of Project Settings. 2 | 3 | 4 | #include "Interactive.h" 5 | 6 | void IInteractive::Interact(UObject* Target, APawn* Interactor) 7 | { 8 | IInteractive* Interactive = Cast(Target); 9 | if (Interactive) 10 | { 11 | Interactive->TryInteract(Interactor); 12 | } 13 | } 14 | 15 | void IInteractive::StopInteraction(UObject* Target, APawn* Interactor) 16 | { 17 | if (Target == nullptr || false == Target->GetClass()->ImplementsInterface(UInteractive::StaticClass())) 18 | { 19 | // log error and return 20 | return; 21 | } 22 | 23 | IInteractive::Execute_OnStopInteraction(Target, Interactor); 24 | } 25 | -------------------------------------------------------------------------------- /Source/InteractionSystem/Private/InteractiveActor.cpp: -------------------------------------------------------------------------------- 1 | // Fill out your copyright notice in the Description page of Project Settings. 2 | 3 | 4 | #include "InteractiveActor.h" 5 | 6 | // Add default functionality here for any IInteractiveActor functions that are not pure virtual. 7 | -------------------------------------------------------------------------------- /Source/InteractionSystem/Private/InteractiveBoxComponent.cpp: -------------------------------------------------------------------------------- 1 | // Fill out your copyright notice in the Description page of Project Settings. 2 | 3 | 4 | #include "InteractiveBoxComponent.h" 5 | #include "GameFramework/Pawn.h" 6 | #include "InteractionSystem.h" 7 | #include "InteractiveActor.h" 8 | #include "Net/UnrealNetwork.h" 9 | 10 | #define LOCTEXT_NAMESPACE "InteractionSystem" 11 | 12 | UInteractiveBoxComponent::UInteractiveBoxComponent(const FObjectInitializer& ObjectInitializer) 13 | : Super(ObjectInitializer) 14 | { 15 | 16 | PrimaryComponentTick.bCanEverTick = true; 17 | 18 | // actor (owner) must replicate too, and should always be relevant 19 | bReplicates = true; // 4.22 20 | // SetIsReplicatedByDefault(true); // 4.26 21 | 22 | SetCollisionEnabled(ECollisionEnabled::QueryOnly); 23 | SetCollisionResponseToAllChannels(ECR_Ignore); 24 | SetCollisionResponseToChannel(COLLISION_INTERACTIVE, ECR_Block); 25 | BoxExtent = FVector(16.0f, 16.0f, 16.0f); 26 | 27 | bInteractionDisabled = false; 28 | } 29 | 30 | void UInteractiveBoxComponent::TryInteract(APawn* Interactor) 31 | { 32 | const bool bInteractionDisabled_ = IInteractive::Execute_IsInteractionDisabled(this); 33 | if (false == bInteractionDisabled_) 34 | { 35 | const bool bCanInteract = IInteractive::Execute_CanInteract(this, Interactor); 36 | IInteractive::Execute_OnInteract(this, Interactor, bCanInteract); 37 | } 38 | } 39 | 40 | void UInteractiveBoxComponent::OnInteract_Implementation(APawn* Interactor, bool bCanInteract) 41 | { 42 | const bool bFromReplication = LastInteraction.bFromRep; 43 | if (bFromReplication || (false == CurrentInteractor.IsValid() && Interactor && Interactor->HasAuthority())) 44 | { 45 | CurrentInteractor = Interactor; 46 | 47 | if (IsOwnerInteractive()) 48 | { 49 | if (bCanInteract) 50 | { 51 | IInteractiveActor::Execute_OnInteractionSucceeded(GetOwner(), this, Interactor); 52 | } 53 | else 54 | { 55 | IInteractiveActor::Execute_OnInteractionDenied(GetOwner(), this, Interactor); 56 | } 57 | } 58 | 59 | LastInteraction.bCanInteract = bCanInteract; 60 | LastInteraction.Interactor = Interactor; 61 | LastInteraction.bStopInteraction = false; 62 | LastInteraction.EnsureReplication(); 63 | } 64 | 65 | } 66 | 67 | void UInteractiveBoxComponent::OnStopInteraction_Implementation(APawn* Interactor) 68 | { 69 | const bool bFromReplication = LastInteraction.bFromRep; 70 | if (bFromReplication || CurrentInteractor.IsValid() && CurrentInteractor == Interactor) 71 | { 72 | CurrentInteractor = nullptr; 73 | 74 | if (IsOwnerInteractive()) 75 | { 76 | IInteractiveActor::Execute_OnStopInteraction(GetOwner(), this, Interactor); 77 | } 78 | 79 | LastInteraction.Interactor = Interactor; 80 | LastInteraction.bStopInteraction = true; 81 | LastInteraction.EnsureReplication(); 82 | } 83 | 84 | } 85 | 86 | void UInteractiveBoxComponent::OnFocusReceived_Implementation(APawn* Interactor) 87 | { 88 | if (IsOwnerInteractive()) 89 | { 90 | IInteractiveActor::Execute_OnFocusReceived(GetOwner(), this, Interactor); 91 | } 92 | } 93 | 94 | void UInteractiveBoxComponent::OnFocusLost_Implementation(APawn* Interactor) 95 | { 96 | if (IsOwnerInteractive()) 97 | { 98 | IInteractiveActor::Execute_OnFocusLost(GetOwner(), this, Interactor); 99 | } 100 | } 101 | 102 | bool UInteractiveBoxComponent::CanInteract_Implementation(const APawn* Interactor) const 103 | { 104 | if (ShouldUseActorImplementation()) 105 | { 106 | return IInteractiveActor::Execute_CanInteract(GetOwner(), this, Interactor); 107 | } 108 | return false == IInteractive::Execute_IsInteractionDisabled(this); 109 | } 110 | 111 | FText UInteractiveBoxComponent::GetMessage_Implementation(const APawn* Interactor) const 112 | { 113 | if (ShouldUseActorImplementation()) 114 | { 115 | return IInteractiveActor::Execute_GetMessage(GetOwner(), this, Interactor); 116 | } 117 | 118 | const bool bInteractionDisabled_ = IInteractive::Execute_IsInteractionDisabled(this); 119 | 120 | return bInteractionDisabled_ ? LOCTEXT("InteractionDisabled", "INTERACTION DISABLED") : LOCTEXT("Interact", "INTERACT"); 121 | } 122 | 123 | bool UInteractiveBoxComponent::IsInteractionDisabled_Implementation() const 124 | { 125 | if (ShouldUseActorImplementation()) 126 | { 127 | return IInteractiveActor::Execute_IsInteractionDisabled(GetOwner(), this); 128 | } 129 | 130 | return bInteractionDisabled; 131 | } 132 | 133 | bool UInteractiveBoxComponent::ShouldUseActorImplementation() const 134 | { 135 | if (IsOwnerInteractive()) 136 | { 137 | return IInteractiveActor::Execute_ShouldUseActorImplementation(GetOwner()); 138 | } 139 | 140 | return false; 141 | } 142 | 143 | bool UInteractiveBoxComponent::IsOwnerInteractive() const 144 | { 145 | return GetOwner() && GetOwner()->GetClass()->ImplementsInterface(UInteractiveActor::StaticClass()); 146 | } 147 | 148 | void UInteractiveBoxComponent::SetInteractionDisabled(bool bDisabled) 149 | { 150 | bInteractionDisabled = bDisabled; 151 | } 152 | 153 | void UInteractiveBoxComponent::OnRep_LastInteraction() 154 | { 155 | // we shouldn't call this fucntion on server (could check if owner is simulated proxy to be sure...) 156 | LastInteraction.bFromRep = true; 157 | if (LastInteraction.bStopInteraction) 158 | { 159 | IInteractive::Execute_OnStopInteraction(this, LastInteraction.Interactor.Get()); 160 | } 161 | else 162 | { 163 | IInteractive::Execute_OnInteract(this, LastInteraction.Interactor.Get(), LastInteraction.bCanInteract); 164 | } 165 | // reset flag 166 | LastInteraction.bFromRep = false; 167 | } 168 | 169 | void UInteractiveBoxComponent::GetLifetimeReplicatedProps(TArray< FLifetimeProperty > & OutLifetimeProps) const 170 | { 171 | Super::GetLifetimeReplicatedProps(OutLifetimeProps); 172 | 173 | DOREPLIFETIME(UInteractiveBoxComponent, bInteractionDisabled); 174 | DOREPLIFETIME(UInteractiveBoxComponent, LastInteraction); 175 | 176 | } 177 | 178 | FInteractionData::FInteractionData() 179 | : Interactor(NULL) 180 | , bCanInteract(false) 181 | , bStopInteraction(false) 182 | , bFromRep(false) 183 | , EnsureReplicationByte(0) 184 | {} 185 | 186 | 187 | void FInteractionData::EnsureReplication() 188 | { 189 | EnsureReplicationByte++; 190 | } 191 | 192 | #undef LOCTEXT_NAMESPACE -------------------------------------------------------------------------------- /Source/InteractionSystem/Private/PlayerPawn.cpp: -------------------------------------------------------------------------------- 1 | // Fill out your copyright notice in the Description page of Project Settings. 2 | 3 | 4 | #include "PlayerPawn.h" 5 | #include "Interactive.h" 6 | #include "InteractionSystem.h" 7 | #include "TimerManager.h" 8 | #include "Components/InputComponent.h" 9 | #include "Components/PrimitiveComponent.h" 10 | #include "GameFramework/PlayerController.h" 11 | #include "Camera/PlayerCameraManager.h" 12 | #include "CollisionQueryParams.h" 13 | #include "Engine/World.h" 14 | #include "Engine/EngineTypes.h" 15 | 16 | APlayerPawn::APlayerPawn() 17 | { 18 | CurrentInteractive = nullptr; 19 | MaxInteractionDistance = 100.f; 20 | 21 | } 22 | 23 | void APlayerPawn::BeginPlay() 24 | { 25 | Super::BeginPlay(); 26 | 27 | GetWorldTimerManager().SetTimer(TimerHandle_FindInteractive, 28 | this, 29 | &APlayerPawn::FindInteractive, 30 | 0.128f, // rate 31 | true, // loop 32 | 1.f // first delay 33 | ); 34 | 35 | } 36 | 37 | void APlayerPawn::Tick(float DeltaTime) 38 | { 39 | Super::Tick(DeltaTime); 40 | 41 | TryStopInteraction(); 42 | } 43 | 44 | void APlayerPawn::SetupPlayerInputComponent(UInputComponent* PlayerInputComponent) 45 | { 46 | Super::SetupPlayerInputComponent(PlayerInputComponent); 47 | 48 | // interaction 49 | PlayerInputComponent->BindAction("Interact", IE_Pressed, this, &APlayerPawn::InteractPressed); 50 | PlayerInputComponent->BindAction("Interact", IE_Released, this, &APlayerPawn::InteractReleased); 51 | 52 | // movement 53 | PlayerInputComponent->BindAxis("MoveForward", this, &APlayerPawn::MoveForward); 54 | PlayerInputComponent->BindAxis("MoveRight", this, &APlayerPawn::MoveRight); 55 | PlayerInputComponent->BindAxis("Turn", this, &APawn::AddControllerYawInput); 56 | PlayerInputComponent->BindAxis("LookUp", this, &APawn::AddControllerPitchInput); 57 | 58 | } 59 | 60 | void APlayerPawn::InteractPressed() 61 | { 62 | Interact(GetCurrentInteractive()); 63 | } 64 | 65 | void APlayerPawn::InteractReleased() 66 | { 67 | StopInteraction(GetCurrentInteractive()); 68 | } 69 | 70 | void APlayerPawn::FindInteractive() 71 | { 72 | UObject* Interactive = nullptr; 73 | const APlayerController* PC = Cast(GetController()); 74 | if (PC && PC->IsLocalController()) 75 | { 76 | const APlayerCameraManager* Camera = PC->PlayerCameraManager; 77 | if (Camera) 78 | { 79 | FCollisionQueryParams LineParams(SCENE_QUERY_STAT(FindInteractive), true); 80 | LineParams.AddIgnoredActor(this); 81 | 82 | // line trace 83 | FHitResult OutHit; 84 | const FVector TargetPoint = Camera->GetCameraLocation() + Camera->GetActorForwardVector() * MaxInteractionDistance; 85 | const bool bHit = GetWorld()->LineTraceSingleByChannel(OutHit, Camera->GetCameraLocation(), TargetPoint, COLLISION_INTERACTIVE, LineParams); 86 | if (bHit && OutHit.Component.IsValid() && OutHit.Component->GetClass()->ImplementsInterface(UInteractive::StaticClass())) 87 | { 88 | Interactive = Cast(OutHit.Component); 89 | if (Interactive) 90 | { 91 | const bool bInteractionDisabled = IInteractive::Execute_IsInteractionDisabled(Interactive); 92 | if (bInteractionDisabled) 93 | { 94 | Interactive = nullptr; 95 | } 96 | } 97 | } 98 | } 99 | } 100 | 101 | if (Interactive != CurrentInteractive) 102 | { 103 | // update cached value, call focus events 104 | if (Interactive != nullptr) 105 | { 106 | IInteractive::Execute_OnFocusReceived(Interactive, this); 107 | } 108 | if (CurrentInteractive.IsValid()) 109 | { 110 | // force stop interaction on focus lost, should refactor this if we want to keep the interaction active (maybe by adding a new method "ShouldStopInteraction" in IInteractive interface) 111 | StopInteraction(CurrentInteractive.Get()); 112 | IInteractive::Execute_OnFocusLost(CurrentInteractive.Get(), this); 113 | } 114 | CurrentInteractive = Interactive; 115 | 116 | } 117 | } 118 | 119 | void APlayerPawn::TryStopInteraction() 120 | { 121 | if (CurrentInteractive.IsValid()) 122 | { 123 | const bool bInteractionDisabled = IInteractive::Execute_IsInteractionDisabled(CurrentInteractive.Get()); 124 | if (bInteractionDisabled) 125 | { 126 | StopInteraction(CurrentInteractive.Get()); 127 | IInteractive::Execute_OnFocusLost(CurrentInteractive.Get(), this); 128 | CurrentInteractive = nullptr; 129 | } 130 | } 131 | 132 | } 133 | 134 | UObject* APlayerPawn::GetCurrentInteractive() const 135 | { 136 | return CurrentInteractive.IsValid() ? CurrentInteractive.Get() : nullptr; 137 | } 138 | 139 | void APlayerPawn::Interact(UObject* Target) 140 | { 141 | if (Target == nullptr) 142 | { 143 | return; 144 | } 145 | if (false == HasAuthority()) 146 | { 147 | ServerInteract(Target); 148 | return; 149 | } 150 | 151 | IInteractive::Interact(Target, this); 152 | } 153 | 154 | void APlayerPawn::StopInteraction(UObject* Target) 155 | { 156 | if (Target == nullptr) 157 | { 158 | return; 159 | } 160 | if (false == HasAuthority()) 161 | { 162 | ServerStopInteraction(Target); 163 | return; 164 | } 165 | 166 | IInteractive::StopInteraction(Target, this); 167 | } 168 | 169 | bool APlayerPawn::ServerInteract_Validate(UObject* Target) 170 | { 171 | return true; 172 | } 173 | 174 | void APlayerPawn::ServerInteract_Implementation(UObject* Target) 175 | { 176 | Interact(Target); 177 | } 178 | 179 | bool APlayerPawn::ServerStopInteraction_Validate(UObject* Target) 180 | { 181 | return true; 182 | } 183 | 184 | void APlayerPawn::ServerStopInteraction_Implementation(UObject* Target) 185 | { 186 | StopInteraction(Target); 187 | } 188 | 189 | void APlayerPawn::MoveForward(float Value) 190 | { 191 | if (Value != 0.0f) 192 | { 193 | AddMovementInput(GetActorForwardVector(), Value); 194 | } 195 | } 196 | 197 | 198 | void APlayerPawn::MoveRight(float Value) 199 | { 200 | if (Value != 0.0f) 201 | { 202 | AddMovementInput(GetActorRightVector(), Value); 203 | } 204 | } -------------------------------------------------------------------------------- /Source/InteractionSystem/Public/Interactive.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 "Interactive.generated.h" 8 | 9 | class APawn; 10 | 11 | /** 12 | * Implement this interface in a component of an actor that should handle player interaction (e.g. light switches, doors, levers, etc.). 13 | * In order for the player to detect an interactive component, the component must have a collision setup that blocks "Interactive" trace channel. 14 | * 15 | * Please note that this interface can also be implemented by actors, but the system does not actually handle that case. 16 | * Adapting the code to support that should be pretty straightforward though, 17 | * I just didn't need it - and there's a specific interface for actors, see IInteractiveActor. 18 | * 19 | * You can find an implementation of this interface in UInteractiveBoxComponent, which you can subclass or use directly as a component of your actor. 20 | * 21 | * See UInteractiveBoxComponent 22 | * See IInteractiveActor 23 | * See PlayerPawn (for interactive component detection) 24 | */ 25 | UINTERFACE(MinimalAPI, BlueprintType) 26 | class UInteractive : public UInterface 27 | { 28 | GENERATED_BODY() 29 | }; 30 | 31 | class INTERACTIONSYSTEM_API IInteractive 32 | { 33 | GENERATED_BODY() 34 | 35 | protected: 36 | 37 | /** 38 | * [server] player wants to interact (i.e. pressed the button on keyboard) 39 | */ 40 | virtual void TryInteract(APawn* Interactor) = 0; 41 | 42 | /** 43 | * [server] process interaction, depending on whether the player can interact or not (bCanInteract true -> succeed, false -> deny). 44 | * The implementation calls this event from TryInteract, and bCanInteract holds the return value of CanInteract() function 45 | */ 46 | UFUNCTION(BlueprintNativeEvent) 47 | void OnInteract(APawn* Interactor, bool bCanInteract); 48 | 49 | /** 50 | * [server] Fire in the following cases: 51 | * on focus lost; 52 | * interaction is disabled (IsInteractionDisabled returns true); 53 | * when locally controlled player aborts interaction (i.e. when button on keyboard is released). 54 | * 55 | * You'll likely need this for press and hold functionality. 56 | */ 57 | UFUNCTION(BlueprintNativeEvent) 58 | void OnStopInteraction(APawn* Interactor); 59 | 60 | /** 61 | * [local] locally controlled player started looking at interactive component 62 | */ 63 | UFUNCTION(BlueprintNativeEvent) 64 | void OnFocusReceived(APawn* Interactor); 65 | 66 | /** 67 | * [local] locally controlled player is not looking at interactive component anymore, or interaction has stopped 68 | */ 69 | UFUNCTION(BlueprintNativeEvent) 70 | void OnFocusLost(APawn* Interactor); 71 | 72 | public: 73 | 74 | /** 75 | * [server] helper function that calls TryInteract 76 | */ 77 | static void Interact(UObject* Target, APawn* Interactor); 78 | 79 | /** 80 | * [server] helper function that calls OnStopInteraction 81 | */ 82 | static void StopInteraction(UObject* Target, APawn* Interactor); 83 | 84 | /** 85 | * [server] check whether player can interact with component. 86 | * Can be called on client too, just make sure to replicate the actor state. 87 | */ 88 | UFUNCTION(BlueprintNativeEvent, BlueprintCallable) 89 | bool CanInteract(const APawn* Interactor) const; 90 | 91 | /** 92 | * [local] Get the interaction message to show on HUD. Make sure to replicate the actor state if locally controlled player is client. 93 | */ 94 | UFUNCTION(BlueprintNativeEvent, BlueprintCallable) 95 | FText GetMessage(const APawn* Interactor) const; 96 | 97 | /** 98 | * [local + server] check whether interaction is disabled, which means no focus and no interaction events, if true. 99 | */ 100 | UFUNCTION(BlueprintNativeEvent, BlueprintCallable) 101 | bool IsInteractionDisabled() const; 102 | }; 103 | -------------------------------------------------------------------------------- /Source/InteractionSystem/Public/InteractiveActor.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 "InteractiveActor.generated.h" 8 | 9 | class UInteractiveBoxComponent; 10 | class APawn; 11 | 12 | /** 13 | * This interface is intended to be implemented by actors with interactive components of type UInteractiveBoxComponent. 14 | * It's similar to the IInteractive interface, and it will be used by UInteractiveBoxComponent (the component implementation of IInteractive). 15 | * The reason for using this interface is to provide a way to handle any interaction validation directly in the actor, instead of component, 16 | * since an actor with interactive components is supposed to be the "master" entity that knows how to deal with interaction, depending on its internal state, 17 | * especially when such an actor has multiple interactive components (see BP_Mover blueprint). 18 | * In order for actors to handle interaction validation, you must override ShouldUseActorImplementation and set its return value to true. 19 | * The validation functions are CanInteract and IsInteractionDisabled. Please note that you must not call the IInteractive "counterpart" from the 20 | * actor function, e.g. calling component's CanInteract from actor's CanInteract, will result in an infinite loop. 21 | * That happens because the functions in InteractiveBoxComponent call the matching functions in the actor, if the actor implementation is used (see UInteractiveBoxComponent.cpp). 22 | */ 23 | UINTERFACE(MinimalAPI) 24 | class UInteractiveActor : public UInterface 25 | { 26 | GENERATED_BODY() 27 | }; 28 | 29 | class INTERACTIONSYSTEM_API IInteractiveActor 30 | { 31 | GENERATED_BODY() 32 | 33 | 34 | public: 35 | 36 | /** 37 | * [all] Fires when locally controlled player attempts to interact with the interactive component (i.e. presses the button on keyboard), 38 | * and interaction is allowed (CanInteract returns true). 39 | */ 40 | UFUNCTION(BlueprintNativeEvent) 41 | void OnInteractionSucceeded(UInteractiveBoxComponent* InteractiveComponent, APawn* Interactor); 42 | 43 | /** 44 | * [all] Fires when locally controlled player attempts to interact with the interactive component (i.e. presses the button on keyboard), 45 | * but interaction is denied (CanInteract returns false). 46 | */ 47 | UFUNCTION(BlueprintNativeEvent) 48 | void OnInteractionDenied(UInteractiveBoxComponent* InteractiveComponent, APawn* Interactor); 49 | 50 | /** 51 | * [all] See IInteractive::OnStopInteraction 52 | */ 53 | UFUNCTION(BlueprintNativeEvent) 54 | void OnStopInteraction(UInteractiveBoxComponent* InteractiveComponent, APawn* Interactor); 55 | 56 | /** 57 | * [local] See IInteractive::OnFocusReceived 58 | */ 59 | UFUNCTION(BlueprintNativeEvent) 60 | void OnFocusReceived(UInteractiveBoxComponent* InteractiveComponent, APawn* Interactor); 61 | 62 | /** 63 | * [local] See IInteractive::OnFocusLost 64 | */ 65 | UFUNCTION(BlueprintNativeEvent) 66 | void OnFocusLost(UInteractiveBoxComponent* InteractiveComponent, APawn* Interactor); 67 | 68 | /** 69 | * [server] See IInteractive::CanInteract 70 | */ 71 | UFUNCTION(BlueprintNativeEvent, BlueprintCallable) 72 | bool CanInteract(const UInteractiveBoxComponent* InteractiveComponent, const APawn* Interactor) const; 73 | 74 | /** 75 | * [local] See IInteractive::GetMessage 76 | */ 77 | UFUNCTION(BlueprintNativeEvent, BlueprintCallable) 78 | FText GetMessage(const UInteractiveBoxComponent* InteractiveComponent, const APawn* Interactor) const; 79 | 80 | /** 81 | * [local + server] See IInteractive::IsInteractionDisabled 82 | */ 83 | UFUNCTION(BlueprintNativeEvent, BlueprintCallable) 84 | bool IsInteractionDisabled(const UInteractiveBoxComponent* InteractiveComponent) const; 85 | 86 | /** 87 | * [server] You need to override this function, which must return true, in order for interactive component 88 | * to use actor's CanInteract, IsInteractionDisabled, GetMessage functions. 89 | * These functions will then "override" component functions. 90 | * You shouldn't implement any custom logic in here, you just need to flag the return value to true, 91 | * and you shouldn't change the return value at runtime, this is why I marked it as server, but clients use it too. 92 | * When actor implementation is used you must not call component functions from actor functions, 93 | * e.g. actor GetMessage must not call component GetMessage, or you'll get an infinite loop. 94 | */ 95 | UFUNCTION(BlueprintNativeEvent, BlueprintCallable) 96 | bool ShouldUseActorImplementation() const; 97 | 98 | }; 99 | -------------------------------------------------------------------------------- /Source/InteractionSystem/Public/InteractiveBoxComponent.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/BoxComponent.h" 7 | #include "Interactive.h" 8 | #include "InteractiveBoxComponent.generated.h" 9 | 10 | class APawn; 11 | 12 | USTRUCT() 13 | struct FInteractionData 14 | { 15 | GENERATED_USTRUCT_BODY() 16 | 17 | public: 18 | 19 | FInteractionData(); 20 | 21 | void EnsureReplication(); 22 | 23 | UPROPERTY() 24 | TWeakObjectPtr Interactor; 25 | 26 | UPROPERTY() 27 | uint32 bCanInteract : 1; 28 | 29 | UPROPERTY() 30 | uint32 bStopInteraction : 1; 31 | 32 | UPROPERTY(NotReplicated) 33 | uint32 bFromRep : 1; 34 | 35 | private: 36 | 37 | UPROPERTY() 38 | uint8 EnsureReplicationByte; 39 | 40 | }; 41 | 42 | /** 43 | * BoxComponent-based implementation of IInteractive interface, with built-in replication. 44 | * If you're going to add this component or a subclass of this component in your custom actor, you'll likely want to implement the IInteractiveActor interface in that actor. 45 | * You must set the actor to replicate, and it should always be relevant (optional steps, if you need to handle replication). 46 | * 47 | * A general tip about replication. You shouldn't handle things that require some control on synchronization over time, like a moving mesh, 48 | * through the replicated events of this component, but you should replicate such things in a different way (see mover timeline in BP_Mover), 49 | * and take advantage of the built-in event replication only to handle fire and forget things or cosmetic stuff. 50 | * 51 | * See IInteractive 52 | * See IInteractiveActor 53 | */ 54 | UCLASS(Blueprintable, meta = (BlueprintSpawnableComponent)) 55 | class INTERACTIONSYSTEM_API UInteractiveBoxComponent : public UBoxComponent, public IInteractive 56 | { 57 | GENERATED_BODY() 58 | 59 | public: 60 | UInteractiveBoxComponent(const FObjectInitializer& ObjectInitializer); 61 | 62 | //~ Begin UObject Interface 63 | virtual void GetLifetimeReplicatedProps(TArray& OutLifetimeProps) const override; 64 | //~ End UObject Interface 65 | 66 | private: 67 | // let the interactive component to be used by one pawn only at a time, property used on server only 68 | TWeakObjectPtr CurrentInteractor; 69 | 70 | /** 71 | * See SetInteractionDisabled 72 | */ 73 | UPROPERTY(EditAnywhere, Replicated) 74 | bool bInteractionDisabled; 75 | 76 | UPROPERTY(ReplicatedUsing=OnRep_LastInteraction) 77 | FInteractionData LastInteraction; 78 | 79 | /** 80 | * does owner implement IInteractiveActor interface? 81 | */ 82 | bool IsOwnerInteractive() const; 83 | 84 | /** 85 | * does owner implement IInteractiveActor interface, and IInteractiveActor::ShouldUseActorImplementation return true? 86 | */ 87 | bool ShouldUseActorImplementation() const; 88 | 89 | UFUNCTION() 90 | void OnRep_LastInteraction(); 91 | 92 | // ~Begin IInteractive Interface 93 | 94 | protected: 95 | 96 | /** 97 | * [server] 98 | */ 99 | virtual void TryInteract(APawn* Interactor) override; 100 | 101 | /** 102 | * [all] 103 | */ 104 | virtual void OnInteract_Implementation(APawn* Interactor, bool bCanInteract) override; 105 | 106 | /** 107 | * [all] 108 | */ 109 | virtual void OnStopInteraction_Implementation(APawn* Interactor) override; 110 | 111 | /** 112 | * [local] 113 | */ 114 | virtual void OnFocusReceived_Implementation(APawn* Interactor) override; 115 | 116 | /** 117 | * [local] 118 | */ 119 | virtual void OnFocusLost_Implementation(APawn* Interactor) override; 120 | 121 | public: 122 | 123 | /** 124 | * [server] 125 | */ 126 | virtual bool CanInteract_Implementation(const APawn* Interactor) const override; 127 | 128 | /** 129 | * [local] 130 | */ 131 | virtual FText GetMessage_Implementation(const APawn* Interactor) const override; 132 | 133 | /** 134 | * [local + server] 135 | */ 136 | virtual bool IsInteractionDisabled_Implementation() const override; 137 | 138 | // ~End IInteractive Interface 139 | 140 | public: 141 | 142 | /** 143 | * See bInteractionDisabled 144 | */ 145 | UFUNCTION(BlueprintCallable) 146 | void SetInteractionDisabled(bool bDisabled); 147 | 148 | 149 | 150 | }; 151 | -------------------------------------------------------------------------------- /Source/InteractionSystem/Public/PlayerPawn.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 "GameFramework/Character.h" 7 | #include "PlayerPawn.generated.h" 8 | 9 | UCLASS() 10 | class INTERACTIONSYSTEM_API APlayerPawn : public ACharacter 11 | { 12 | GENERATED_BODY() 13 | 14 | public: 15 | 16 | APlayerPawn(); 17 | 18 | protected: 19 | 20 | virtual void BeginPlay() override; 21 | 22 | UPROPERTY(EditDefaultsOnly, Category = InteractionSystem) 23 | float MaxInteractionDistance; 24 | 25 | public: 26 | 27 | virtual void Tick(float DeltaTime) override; 28 | 29 | // TODO make sure to forcibly stop interaction on unpossess, so interactive component has a chance to immediately clear its reference to the current interactor 30 | // not 100% sure if UnPossessed is the right place for this 31 | //virtual void UnPossessed() override; 32 | 33 | virtual void SetupPlayerInputComponent(class UInputComponent* PlayerInputComponent) override; 34 | 35 | /** 36 | Get the interactive component the player is looking at if any, or nullptr otherwise. 37 | This is intended for locally controlled players only to handle HUD stuff, non-locally controlled players will always return nullptr. 38 | */ 39 | UFUNCTION(BlueprintCallable) 40 | UObject* GetCurrentInteractive() const; 41 | 42 | private: 43 | 44 | TWeakObjectPtr CurrentInteractive; 45 | 46 | FTimerHandle TimerHandle_FindInteractive; 47 | 48 | void FindInteractive(); 49 | 50 | void TryStopInteraction(); 51 | 52 | void InteractPressed(); 53 | 54 | void InteractReleased(); 55 | 56 | void Interact(UObject* Target); 57 | void StopInteraction(UObject* Target); 58 | 59 | UFUNCTION(Reliable, Server, WithValidation) 60 | void ServerInteract(UObject* Target); 61 | 62 | UFUNCTION(Reliable, Server, WithValidation) 63 | void ServerStopInteraction(UObject* Target); 64 | 65 | protected: 66 | 67 | // movement stuff 68 | void MoveForward(float Value); 69 | void MoveRight(float Value); 70 | 71 | }; 72 | -------------------------------------------------------------------------------- /Source/InteractionSystemEditor.Target.cs: -------------------------------------------------------------------------------- 1 | // Fill out your copyright notice in the Description page of Project Settings. 2 | 3 | using UnrealBuildTool; 4 | using System.Collections.Generic; 5 | 6 | public class InteractionSystemEditorTarget : TargetRules 7 | { 8 | public InteractionSystemEditorTarget(TargetInfo Target) : base(Target) 9 | { 10 | Type = TargetType.Editor; 11 | 12 | ExtraModuleNames.AddRange( new string[] { "InteractionSystem" } ); 13 | } 14 | } 15 | --------------------------------------------------------------------------------