As always, we create a new interface, and then add some function definitions to the IInterface class. We use the BlueprintNativeEvent specifier to indicate that we want to declare a default implementation in C++ that can then be overridden in Blueprint. We create a new class (inheriting from StaticMeshActor for convenience) and implement the interface on it.
In the implementation of the new class constructor, we load a static mesh and set our collision as usual. We then add an implementation for our interface function, which simply prints a message to the screen.
If you were using this in a full-blown project, you could play animations, play audio, alter the user interface, and whatever else was necessary to start a conversation with your Talker.
At this point, though, we don't have anything to actually call StartTalking on our Talker. The simplest way to implement this is to create a new Pawn subclass (again, inheriting from DefaultPawn for convenience) that can start talking to any Talker actors that it collides with.
For this to work, we create a new BoxComponent to establish the radius at which we will trigger a conversation. As always, it is a UPROPERTY, so it won't get garbage collected. We also create the definition for a function that will get triggered when the new BoxComponent overlaps another Actor in the scene.
The constructor for our TalkingPawn initializes the new BoxComponent, and sets its extents appropriately. The constructor also binds the OnTalkOverlap function as an event handler to handle collisions with our BoxComponent. It also attaches the box component to our RootComponent so that it moves with the rest of the player character as the player moves around the level.
Inside OnTalkOverlap, we need to check if the other actor, which is overlapping our box, implements the Talker interface. The most reliable way to do this is with the ImplementsInterface function in UClass. This function uses the class information that's generated by the Unreal Header Tool during compilation, and correctly handles both C++ and Blueprint-implemented interfaces.
If the function returns true, we can use a special autogenerated function contained in our IInterface to invoke the interface method of our choice on our instance. This is a static method of the form <IInterface>::Execute_<FunctionName>. In our instance, our IInterface is ITalker, and the function is StartTalking, so the function we want to invoke is ITalker::Execute_StartTalking().
The reason we need this function is that, when an interface is implemented in Blueprint, the relationship isn't actually established at compile time. C++ is, therefore, not aware of the fact that the interface is implemented, and so we can't cast the Blueprint class to IInterface to call functions directly.
The Execute_ functions take a pointer to the object that implements the interface and call a number of internal methods to invoke the desired function's Blueprint implementation.
When you play the level and walk around, the custom Pawn is constantly receiving notifications when its BoxComponent overlaps other objects. If they implement the UTalker/ITalker interface, the pawn then tries to invoke StartTalking on the Actor instance in question, which then prints the appropriate message on screen.