As in previous recipes, we mark UFUNCTION as BlueprintNativeEvent and BlueprintCallable to allow the UInterface to be implemented in either native code or Blueprint, and allow the functions to be called with either method.
We create DoorBell based on StaticMeshActor for convenience, and have DoorBell implement the Interactable interface. Inside the constructor for DoorBell, we initialize HasBeenPushed and DoorToOpen to the default safe values.
Within the implementation for CanInteract, we return the inverse of HasBeenPushed so that once the button has been pushed, it can't be interacted with.
Inside PerformInteract, we check if we have a reference to a door object to open. If we have a valid reference, we verify that the door actor implements Openable, and then we invoke the Open function on our door. Within Door, we implement both Interactable and Openable, and override the functions from each.
We define the Door implementation of CanInteract to be the same as the default. Within PerformInteract, we display a message to the user. Inside Open, we use AddActorLocalOffset to move the door a certain distance away. With Timeline in Blueprint or a linear interpolation, we could make this transition smooth rather than a teleport.
Lastly, we create a new Pawn so that the player can actually interact with objects. We create a TryInteract function, which we bind to the Interact input action in the overridden SetupPlayerInputComponent function.
This means that when the player performs the input that is bound to Interact, our TryInteract function will run. TryInteract gets a reference to PlayerController, casting the generic controller reference that all Pawns have.
PlayerCameraManager is retrieved through PlayerController, so we can access the current location and rotation of the player camera. We create start and end points using the camera's location, then 100 units in the forward direction away from the camera's location, and pass those into GetWorld::SweepSingleByObjectType. This function takes in a number of parameters. HitResult is a variable that allows the function to return information about any object hit by the trace. CollisionObjectQueryParams allows us to specify whether we are interested in dynamic, static items, or both.
We accomplish a sphere trace by passing the shape in using the MakeSphere function. Sphere traces allow for slightly more human error by defining a cylinder to check for objects rather than a straight line. Given that the players might not look directly at your object, you can tweak the sphere's radius as appropriate.
The final parameter, SweepSingleByObjectType, is a struct that gives the trace a name, lets us specify whether we are colliding against a complex collision geometry, and most importantly, allows us to specify that we want to ignore the object that is initiating the trace.
If HitResult contains an actor after the trace is done, we check whether the actor implements our interface, then attempt to call CanInteract on it. If the actor indicates yes, it can be interacted with, so we then tell it to actually perform the interaction.