I created a Workbench Combination System that allows the player to combine collected fragments
and unlock new collectible items.
The core logic is written in C++, while feedback animations, presentation events, and UI moments
are exposed to Blueprints so they can be easily adjusted in the editor.
The system checks if the player has enough fragments, selects a valid item category, spends the required fragments,
and then plays a short reward presentation sequence.
Checking if a combination is available
bool AWorkbenchActor::HasCombinationAvailable()
{
RefreshFragmentCounts();
return GetValidCombinationTypes().Num() > 0;
}
This function refreshes the current fragment amount from the game instance and checks if there is at least
one valid item type that can be crafted.
If a valid combination exists, the workbench changes to a ready state and shows feedback to the player.
Getting valid combination types
TArray<EItemType> AWorkbenchActor::GetValidCombinationTypes() const
{
TArray<EItemType> ValidTypes;
for (const auto& Pair : FragmentCounts)
{
const EItemType ItemType = Pair.Key;
const int Count = Pair.Value;
if (Count >= FragmentNeeded && HasRemainingItemsOfType(ItemType))
{
ValidTypes.Add(ItemType);
}
}
return ValidTypes;
}
The system loops through the player's fragment inventory and finds which item categories can be used.
A category is valid only if the player has enough fragments and there are still uncollected rewards available
for that type.
Trying to combine fragments
void AWorkbenchActor::TryToCombineFragments()
{
WorkbenchState = EWorkbenchState::Combining;
RefreshFragmentCounts();
EItemType SelectedItemType;
if (!PickRandomCombinationType(SelectedItemType))
{
CheckForCombinations();
ShowCollectionCategoryCompleteFeedback();
UnlockPlayer();
return;
}
if (!ChesterGameInstance || !ChesterGameInstance->SpendFragments(SelectedItemType, FragmentNeeded))
{
CheckForCombinations();
UnlockPlayer();
return;
}
CommitCombination(SelectedItemType);
}
This is the main crafting flow.
The workbench selects a valid item type, spends the required fragments, and then commits the reward.
If no item can be created, the system safely restores the player state and updates the workbench feedback.
Creating the reward
void AWorkbenchActor::CommitCombination(EItemType ItemType)
{
PendingRewardName = ChesterGameInstance->GetRandomRemainingItem(ItemType);
if (PendingRewardName.IsNone())
{
CheckForCombinations();
ShowCollectionCategoryCompleteFeedback();
UnlockPlayer();
return;
}
ChesterGameInstance->CollectItem(PendingRewardName, ItemType);
SetupRewardUI();
PlayCombinationPresentation();
}
After a valid item type is selected, the system chooses a random remaining reward from that category.
The reward is registered in the player's collection, the reward UI is prepared, and a Blueprint presentation event
is triggered so the animation can be handled visually.
World space widget looking at the camera
void AWorkbenchActor::Tick(float DeltaTime)
{
Super::Tick(DeltaTime);
if (!CombiationAvailableWidget)
{
return;
}
const APlayerCameraManager* CameraManager = UGameplayStatics::GetPlayerCameraManager(GetWorld(), 0);
if (!CameraManager)
{
return;
}
const FVector WidgetLocation = CombiationAvailableWidget->GetComponentLocation();
const FVector CameraLocation = CameraManager->GetCameraLocation();
const FRotator LookAtRotation = UKismetMathLibrary::FindLookAtRotation(WidgetLocation, CameraLocation);
CombiationAvailableWidget->SetWorldRotation(LookAtRotation);
}
The workbench also uses a World Space Widget to show feedback in the level.
Since the widget exists in 3D space, it updates its rotation every frame to face the player camera, making it readable
from the gameplay view.
Blueprint presentation event
UFUNCTION(BlueprintImplementableEvent)
void PlayCombinationPresentation();
I exposed the reward presentation as a BlueprintImplementableEvent.
This lets me keep the gameplay rules in C++, while the visual animation and timing can be created directly in Blueprint.