How did I deal with…?
Implementing an Interface for Video Game Using C++ for Unreal Engine API
The goal of this project is to use one level to prototype the C++ programming for the rest of the video game levels. Gameplay consists of a 3rd person character collecting pickups until all are collected. Once that happens, the exit gates unlock and she can go meet a counselor taking her to the next level in the game. Here you can see the schematics for gameplay in the One Page Design Document:
Pickup up item
Here you can see her picking up one of the collectibles in the level (models are placeholders):
As you can see, there are a lot of tasks accomplished via interface. These schematics show the interface wiring together the many communication channels:
The solution I'm developing here consists of making a prototype of the interface in Blueprints first (I'm working in Unreal Engine) and, when it's functional, translating and reorganizing the tasks in C++ using Visual Studio. This is the functional prototype in Blueprints for the main interaction:
This Blueprint is showing how I'm using the interface to execute the following tasks by pressing the “E” key:
- Updating the counter
- Destroying the collected Actor
- Checking out whether all items in the level are collected and, if that's the case
- Unlocking the exit gates.
These are the tasks I'm executing to implement these methods in C++
Interface header
This is what my Interface's header looks like in C++:
class SCIFIPROJ_API IInteractInterface
{
GENERATED_BODY()
public:
UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category = "Interaction")
void OnInteract(AActor* Caller);
};
Mapping interface to “E” key
I'm calling the Interface by pressing the “E” key, which I'm mapping to an input action. Here is the definition in the player character's header:
/** Interact Input Action */
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = Input, meta = (AllowPrivateAccess = "true"))
class UInputAction* InteractAction;
protected:
/** Called for interacting input */
void Interact(const FInputActionValue& Value);
Additional capsule
Also, I'm creating an additional capsule to overlap with the pickups only:
UPROPERTY(VisibleAnywhere, Category = "Trigger Capsule")
class UCapsuleComponent* TriggerCapsule;
Overlapping methods
And here I'm declaring begin and end overlapping functions:
public:
UFUNCTION()
void OnOverlapBegin(
UPrimitiveComponent* OverlappedComp,
AActor* OtherActor,
UPrimitiveComponent* OtherComp,
int32 OtherBodyIndex,
bool bFromSweep,
const FHitResult& SweepResult);
UFUNCTION()
void OnOverlapEnd(
UPrimitiveComponent* OverlappedCpom,
AActor* OtherActor,
UPrimitiveComponent* OtherComp,
int32 OtherBodyIndex);
Pickup variable
This is the variable I'm using to communicate through the interface with the pickups:
private:
AActor* OverlappedActor;
When everything is declared in the header, I'm implementing them in the C++ file's constructor:
TriggerCapsule = CreateDefaultSubobject(TEXT("Trigger Capsule"));
TriggerCapsule->InitCapsuleSize(42.f, 96.0f);
TriggerCapsule->SetCollisionProfileName(TEXT("Trigger"));
TriggerCapsule->SetupAttachment(RootComponent);
TriggerCapsule->OnComponentBeginOverlap.AddDynamic(this, &ASciFiProjCharacter::OnOverlapBegin);
TriggerCapsule->OnComponentEndOverlap.AddDynamic(this, &ASciFiProjCharacter::OnOverlapEnd);
Implementing in constructor
BeginOverlap function implementation, calling the Interface:
void ASciFiProjCharacter::OnOverlapBegin(UPrimitiveComponent* OverlappedComp, AActor* OtherActor, UPrimitiveComponent* OtherComp, int32 OtherBodyIndex, bool bFromSweep, const FHitResult& SweepResult)
{
IInteractInterface* Interface = Cast(OtherActor);
if (OtherActor && (OtherActor != this) && OtherComp && Interface)
{
OverlappedActor = OtherActor;
CanPickup = true;
Overlapping_Pickup.Broadcast();
}
}
“E” key function
Calling the interface and sending messages through delegates by pressing “E” through Interact Input Action:
void ASciFiProjCharacter::Interact(const FInputActionValue& Value)
{
if (OverlappedActor && CanPickup)
{
IInteractInterface* Interface = Cast(OverlappedActor);
if (Interface && (MissionAccomplished == false))
{
Interface->Execute_OnInteract(OverlappedActor, this);
CollectedItems++;
Add_Item.Broadcast();
}
if (CollectedItems == (TotalItems - 1))
{
Interact_OneToGo.Broadcast();
}
if (CollectedItems == TotalItems)
{
CanPickup = false;
MissionAccomplished = true;
Mission_Accomplished.Broadcast();
All_Pickups_Collected.Broadcast();
Interact_Finished.Broadcast();
}
}
else
{
if (OverlappedActor == nullptr && CanPickup == false && MissionAccomplished == false)
{
Interact_Empty.Broadcast();
}
if (OverlappedActor == nullptr && CanPickup == false && MissionAccomplished == true)
{
Interact_Finished.Broadcast();
}
}
}
Interface on pickup
Here I'm calling the interface from my pickup's definition in the header file:
class SCIFIPROJ_API AInteractable_Base : public AActor, public IinteractInterface
And here I'm implementing the interface, right away in the header:
public:
UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category = "Interaction")
void OnInteract(AActor* Caller);
virtual void OnInteract_Implementation(AActor* Caller);
This is the Destroy Actor Command inside the source file from the pickup, also hiding a debug message and sending a delegate:
void AInteractable_Base::OnInteract_Implementation(AActor* Caller)
{
if (GEngine)
{
//GEngine->AddOnScreenDebugMessage(-1, 1.0f, FColor::Emerald, TEXT("Collecting"));
Interacting.Broadcast();
Destroy();
}
}
Summary
This is how the interface tunnels the functions sequence through the Interact Input Action:
- Updating the counter by implementing Interact function in the player character node, adding #1 to the CollectedItems variable
- Destroying the collected Actor from inside Interact Action's implementation in the pickup node, and
- Unlocking the Exit Gates by updating the MissionAccomplished boolean variable, in case the number of CollectedItems = TotalItems' value.
If you want to take a closer look at the gameplay mechanics, please check out these workflow breakdowns: