How did I deal with…?

Programming Gameplay Using C++ in Unreal Engine

The goal of this project is to write a reusable gameplay system in C++. I'm achieving this by setting the same C++ logic in every game's level, solving local tasks with Blueprints (BP), and avoiding reprogramming.

Starting Gameplay Starting Gameplay Starting Gameplay
Gameplay starting UI, opening gates, and updating crowd.

These are the steps I'm following to bring together all the gameplay parts in this project:

Interaction in real time

First of all, you have a start screen with three options, one of them is taking you into the game and starting Gameplay. Once there, you have a starting point and gates/doors that open/close from in and outside. The user interface (UI) is always giving you messages and hints:

  • The minimap is telling you where you are and where to find the pickups
  • Also there is a counter, telling you how many pickups you have collected so far
  • The Interface is changing the color of your pickup when you get close, and
  • Telling you that you are able to pickup.

At the same time, the UI tells you that you have to press “E” to collect, and, when you do so, that you are actually collecting an important piece of information:

Collecting item Collecting item Collecting item
Getting close and collecting pickup.

If you try to leave the level without all pickups collected, you get messages and hints on the UI, telling you that you're not ready yet:

You are not ready yet You are not ready yet You are not ready yet
The Interface triggers several delegates if you try open the exit gates before collecting all pickups.

There are also some invisible barriers:

Invisible barriers Invisible barriers
Using Event Dispatchers for local Events.

You can enter the buildings, go up stairs and get on balconies:

Showcasing architecture Showcasing architecture
Every corner in the level is accessible.

When you reach the penultimate pickup, the UI tells you that there is only one item left to unlock the exit gates.

Penultimate item collected Penultimate item collected
The interface warns you when you have only one item left.

Wen you collect the last item, you get hints and messages from the UI, telling you that you accomplished the mission.

Collecting last item Collecting last item Collecting last item
The Interface unlocks the exit gates when you collect the last item.

Once at the exit, the gates open and you can go out to the next level, most commonly interviews with counselors from the city, sharing information and prepping for the next mission.

I'm defining gameplay during pre‑production. This schematics are showing the whole gameplay cycle, start to finish. All contents vary, only the logic of gameplay stay constant, working inside each level:

One Page Design

One Page Design One Page Design One Page Design
One Page Design, one big diagram showing the logic of all levels at a glance.

Storyboard

Also, the storyboard is telling me what actions I am handling for Gameplay. At this stage of the development – block out – I am programming:

  • Start and End Screens
  • Pickups counter
  • Pickups logic
  • opening and closing gates/doors
  • lock/unlock exit gates, and
  • Provisional User Interface
Carousel showing interaction. Click on the arrows to progress through the vignettes.

Blueprints before C++

I am visual‑programming using Blueprint Widgets first, before doing a translation to C++:

  • Start and End Screens
  • The whole UI
  • Door animations
  • Door overlapping, and
  • Initial count of total items present in the level.
All Blueprints interaction methods All Blueprints interaction methods All Blueprints interaction methods
Testing all functionalities in Blueprints before translating to C++.

This image provides only an overview of the different methods I'm including in the Interaction function. For a closer look at the translations, please click on the Interaction case study card:

Programming video game interaction with C++
Programming interaction

The C++ Interface is able to identify when the player picks up an item, adds to the counter, loops remaining and sends delegates when all collected.

Once my prototype is functional, I'm in the position to draw a schematics for the C++ logic:

C++ logic schematic

C++ logic schematic C++ logic schematic C++ logic schematic
This schematic shows the logic behind the functionalities I'm developing in Blueprints before translating to C++.

I'm distributing the the specific C++ logic implementation for this gameplay in smaller nodes:

Interface

I am calling my Interface “InteractInterface” and this is what its header looks like (note the IInterface instead of UInterface in the class name):

    
class SCIFIPROJ_API IInteractInterface
{
	GENERATED_BODY()

public:
	UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category = "Interaction")
	void OnInteract(AActor* Caller);
};

    

And that's all it takes to declare the Interface. I am dedicating a whole article to Interface in C++ for Unreal Engine API in this site. Click on this workflow break down card to take a look:

Implementing Interface for video game with C++
Implementing C++ Interface

The C++ interface becomes active when overlapping items, sends delegates when the player clicks the “E” key modifying the environment and other actors.

Player character

I am calling my player character “SciFiCharacter”. This Actor is holding most of the programming, namely:

  • All the delegates sending messages to the UI,
  • An additional Input Action for handling interaction with objects in the level,
  • An additional capsule to trigger specific collision objects from InteractableBase, and
  • All the variables needed to interact with pickups and gates/doors.

Here you can see the additional declarations in header, including all variables and Delegates:


DECLARE_DYNAMIC_MULTICAST_DELEGATE(FMission);
DECLARE_DYNAMIC_MULTICAST_DELEGATE(FCounter);
DECLARE_DYNAMIC_MULTICAST_DELEGATE(FHint);
DECLARE_DYNAMIC_MULTICAST_DELEGATE(FMessage);

	/** Interact Input Action */
	UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = Input, meta = (AllowPrivateAccess = "true"))
	class UInputAction* InteractAction;

	UPROPERTY(VisibleAnywhere, Category = "Trigger Capsule")
	class UCapsuleComponent* TriggerCapsule;

public:
	//************ Delegates
	UPROPERTY(BlueprintAssignable)
	FMission StartMission;

	UPROPERTY(BlueprintAssignable)
	FMission Mission_Accomplished;

	UPROPERTY(BlueprintAssignable)
	FHint Interact_Empty;

	UPROPERTY(BlueprintAssignable)
	FHint Interact_OneToGo;

	UPROPERTY(BlueprintAssignable)
	FHint Interact_Finished;

	UPROPERTY(BlueprintAssignable)
	FCounter Add_Item;

	UPROPERTY(BlueprintAssignable)
	FMessage Overlapping_Pickup;

	UPROPERTY(BlueprintAssignable)
	FMessage All_Pickups_Collected;


protected:
	/** Called for interacting input */
	void Interact(const FInputActionValue& Value);

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);

private:

	AActor* OverlappedActor;

	UPROPERTY(EditAnywhere, Category = "Pickup", BlueprintReadWrite, meta = (AllowPrivateAccess = "true"))
	int32 TotalItems;

	UPROPERTY(VisibleAnywhere, Category = "Pickup", BlueprintReadOnly, meta = (AllowPrivateAccess = "true"))
	int32 CollectedItems;

	UPROPERTY(VisibleAnywhere, Category = "Pickup", BlueprintReadWrite, meta = (AllowPrivateAccess = "true"))
	bool CanPickup;

	UPROPERTY(VisibleAnywhere, Category = "Pickup", BlueprintReadWrite, meta = (AllowPrivateAccess = "true"))
	bool MissionAccomplished;

This is Constructor in Source File, with additional capsule and overlapping Events:


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);

This is BeginPlay with Delegates and variables definitions:


StartMission.Broadcast();
CollectedItems = 0;
CanPickup = false;
MissionAccomplished = false;

Overlap begins, checking out whether pickups are implementing the Interface or not:


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();
	}
}

Overlap ends, updating variables:


void ASciFiProjCharacter::OnOverlapEnd(UPrimitiveComponent* OverlappedCpom, AActor* OtherActor, UPrimitiveComponent* OtherComp, int32 OtherBodyIndex)
{
	OverlappedActor = nullptr;
	CanPickup = false;
}

Implementing Interface and Delegates Events when pressing the “E” key in source file:

	
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();
		}
	}
}

From C++ to Blueprints to UI

This is the Blueprint to update the counter, reading the integer variables from Player Character, counting the total pickups present in the level, and replacing the text in UI:

Blueprints counting all pickups at start Blueprints counting all pickups at start Blueprints counting all pickups at start
Blueprints counting all pickups at game start.

This is the Macro replacing text in the Messages Panel:

Macro replacing text in Messages Panel Macro replacing text in Messages Panel Macro replacing text in Messages Panel
Macro replacing text in Messages Panel.

My goal here is to avoid making changes in C++ every time I want to make text changes in the UI. That's why I'm updating them by hand in the Blueprint. Here you can see the Listeners to the Event Dispatchers:

Listeners to Event Dispatchers Listeners to Event Dispatchers Listeners to Event Dispatchers
List of listeners to Event Dispatchers (BP version of Delegates).

These are the manual text changes, calling the same macro every time an Delegate triggers an Event:

Delegates triggering Events updating text in UI Delegates triggering Events updating text in UI
Delegates triggering Events updating text in UI.

Another Macro doing the same for the Hints Panel:

Macro changing text in Hints panel Macro changing text in Hints panel Macro changing text in Hints panel
Macro prepping text changing in Hints Panel.

And the Event Dispatcher Listeners in the Mission panel:

Event Dispatcher Listeners in Mission Panel Event Dispatcher Listeners in Mission Panel Event Dispatcher Listeners in Mission Panel
Event Dispatcher Listeners in Mission Panel.

Last but not least, here is the additional input component for the “E” key:

    
void ASciFiProjCharacter::SetupPlayerInputComponent(class UInputComponent* PlayerInputComponent)
{
	// Set up action bindings
	if (UEnhancedInputComponent* EnhancedInputComponent = CastChecked(PlayerInputComponent)) {
		//Interacting
		EnhancedInputComponent->BindAction(InteractAction, ETriggerEvent::Triggered, this, &ASciFiProjCharacter::Interact);
	}
}

InteractableBase

I am calling the Actor for my pickups “InteractableBase” because other levels could require different types of meshes or collisions. When the Interface calls an Event, it destroys the pickup and a Delegate sends a message for the UI. Here is the header with Delegate, Interface and UProperties:

    
DECLARE_DYNAMIC_MULTICAST_DELEGATE(FInteract);

public:
	UPROPERTY(BlueprintAssignable)
	FInteract Interacting;

	UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category = "Interaction")
	void OnInteract(AActor* Caller);
	virtual void OnInteract_Implementation(AActor* Caller);

private:
	UPROPERTY(VisibleAnywhere, Category = "Component", BlueprintReadOnly, meta = (AllowPrivateAccess = "true"))
	class USphereComponent* CollisionComp;

	UPROPERTY(VisibleAnywhere, Category = "Component", BlueprintReadOnly, meta = (AllowPrivateAccess = "true"))
	UStaticMeshComponent* InteractableMesh;

And here is the Actor's source file, with Constructor and Interface implementation:

    
AInteractable_Base::AInteractable_Base()
{
	PrimaryActorTick.bCanEverTick = true;

	CollisionComp = CreateDefaultSubobject(TEXT("Collision Component"));
	CollisionComp->SetupAttachment(RootComponent);
	CollisionComp->SetSphereRadius(50.0f);

	InteractableMesh = CreateDefaultSubobject(TEXT("Pickup Mesh"));
	InteractableMesh->SetupAttachment(CollisionComp);
}

void AInteractable_Base::OnInteract_Implementation(AActor* Caller)
{
	if (GEngine)
	{
		Interacting.Broadcast();
		Destroy();
	}
}

The only operation on the pickup at this point is destroying the actor via Interface and sending the message through a Delegate. This is the Pickup Blueprint to change color when overlapping:

Blueprint changing pickup color on overlapping Blueprint changing pickup color on overlapping Blueprint changing pickup color on overlapping
This Blueprint changes the pickup color when overlapping (placeholder).

WalkThroughBase

I'm naming my base Actor for gates and doors “WalkThroughBase” because there might be other levels with other kind of behavior than rotating doors. It consists of a constructor with a mesh and a collider. Here you can see the header containing the UProperties:

    
private:
	UPROPERTY(VisibleAnywhere, Category = "Component",BlueprintReadOnly, meta = (AllowPrivateAccess = "true"))
	class UBoxComponent* WalkThroughCollision;

	UPROPERTY(VisibleAnywhere, Category = "Component", BlueprintReadOnly, meta = (AllowPrivateAccess = "true"))
	class UStaticMeshComponent* WalkThroughMesh;

And here is the Constructor in Source file:

    
AWalkThroughBase::AWalkThroughBase()
{
	PrimaryActorTick.bCanEverTick = true;

	WalkThroughCollision = CreateDefaultSubobject(TEXT("WalkThrough Collision"));
	WalkThroughCollision->SetBoxExtent(FVector(100, 75, 150));

	WalkThroughMesh = CreateDefaultSubobject(TEXT("WalkThrough Mesh"));
	WalkThroughMesh->SetupAttachment(WalkThroughCollision);
}

Once my doors and gates have a mesh and a collider, I can vary their behavior in Blueprints any way I want. Again, enabling stake‑holders with no programming background to make those changes at a higher level, allowing programmers do their thing at a lower level:

Blueprint rotating doors Blueprint rotating doors Blueprint rotating doors
Blueprint rotating doors when overlapping with NPC and Player capsules.

There are many other subtle programming details in this project, but these 4 nodes represent the nucleus of the whole functionality, shared by all levels.
With this simple functionalities, we can confront any situation in every imaginable level and even escalate it to a first person shooter mode.
For a closer look at other dimensions of this project, from the point of view of a Technical Artist, click on the following case studies.

Programming video game interaction with C++
Programming interaction

The C++ Interface is able to identify when the player picks up an item, adds to the counter, loops remaining and sends delegates when all collected.

Implementing Interface for video game with C++
Implementing C++ Interface

The C++ interface becomes active when overlapping items, sends delegates when the player clicks the “E” key, modifying the environment and other actors.

Delegates communicating UI and Gameplay
Delegates for UI & Gameplay

Using Delegates to establish communication between UI and Gameplay. Declaring Delegates, broadcasting, writing listeners in BP, updating UI, using Event Dispatchers for local events..

Adding Input Action from C++ into Unreal Engine
Adding Input Action in C++

Using the existing C++ third person character syntax to create an additional input action binding on UProperties, protected functions, and source.

Discover Technical Skills