2026

Custom Objectives

A framework that allows designers to create bespoke mission objectives in a simple and reusable way.

  • Role: Programmer
  • Discipline: Code (Systems)
  • Engine: Unreal

As Rogue Point developed, it became clear that our core objective types (Hostages, Bombs, Laptops, etc.) became repetitive across multiple playthroughs. We wanted a way for designers to introduce more bespoke, level-specific scenarios, closer to the kind of “unlocking the level” moments seen in Half-Life, while still fitting within a modular framework.

To solve this, I built the Custom Objective system, which consists of two parts:

  1. A Custom Objective actor, which defines the objective itself
  2. A Custom Objective component, which allows other actors to participate in that objective

The Objective Manager interacts with the Custom Objective actor, while individual actors communicate through their components.

At its core, the system is intentionally simple. The Custom Objective tracks an array of Objective Actors. When activated, it listens for completion signals from those actors’ Custom Objective components. Once all required actors are complete, the objective itself is finished.

This lightweight structure makes it easy to build a wide variety of scenarios with minimal setup.

// The Custom Objective actor calls this function on all its "ObjectiveActor" components when it is setup
void UTangoCustomObjectiveComponent::SetupFromCustomObjective(ATangoCustomObjective* InObjective)
{
  // Setup vars
  CustomObjective = InObjective;

  // Handle state
  ActorState = EObjectiveState::Inactive;
  OnObjectiveSetup.Broadcast();
}

// The actor owning this component can call this function at its discretion (such as when a button is pressed)
void UTangoCustomObjectiveComponent::SetCompleted()
{
  // Early exit if we weren't setup
  if(!IsValid(CustomObjective))
  {
    UE_LOG(LogTangoObjectives, Warning, 
    TEXT("%s Custom Objective Component did not have a valid Custom Objective Actor while trying to SetCompleted()!"),
	*GetOwner()->GetName());
    return;
  }
  ActorState = EObjectiveState::Completed;
  CustomObjective->SetActorCompleted(GetOwner());
}

// This is what the Custom Objective actor does in response to any ObjectiveActor being completed
void ATangoCustomObjective::SetActorCompleted(AActor* InActor)
{
  if(!IsValid(InActor) ||!ObjectiveActors.Contains(InActor) || CompletedActors.Contains(InActor))
  {
    return;
  }
  UE_LOG(LogTangoObjectives, Display, 
    TEXT("%s Actor was added to %s Custom Objective's CompletedActors!"), *InActor->GetName(), *GetName())
  CompletedActors.AddUnique(InActor);
  OnRep_CompletedActors();
  
  // Complete if necessary
  if(CompletedActors.Num() == ObjectiveActors.Num())
  {
    UE_LOG(LogTangoObjectives, Display, 
      TEXT("%s Custom Objective was completed!"), 
	    *GetName())
    ObjectiveState = EObjectiveState::Completed;
    OnRep_ObjectiveState();
    if(IsValid(ObjectiveManager))
    {
      ObjectiveManager->SetCustomObjectiveCompleted(this);
      // For Raid layouts, the next objective activation (if there is one) is handled via the RaidMissionComponent
      if(ObjectiveManager->GetRandomizationParams().LayoutType != ELevelLayoutType::Raid)
      {
        ObjectiveManager->SetActiveCustomObjective(NextObjective);
      }
    }
  }
  else // If we haven't completed, attempt to announce the remaining count
  {
    const FName CountAnnouncementName = ProgressCountAnnouncements.FindRef(CompletedActors.Num());
    if(CountAnnouncementName != NAME_None && IsValid(PlayerManager))
    {
      PlayerManager->QueueAnnouncementFromTable(CountAnnouncementName);
    }
  }

  FlushNetDormancy();
}

Example Objectives

Using this system, designers were able to quickly create a range of varied interactions, such as:

  • Activating circuit breakers to power an elevator on the Oilrig
  • Disabling a security gate in the Mall
  • Destroying cryptocurrency servers in Office
  • Searching computers for a file hidden in one at random

These added meaningful variation to missions without requiring bespoke systems for each case.

Networking

Because the system centralises its data, it also simplifies replication. Only a small amount of information needs to be synchronised: the active objective and its completion state, allowing the UI to stay fully up to date across clients.

Ease of Implementation

Here’s an example of how little work was required to integrate this into existing actors:

Implementation required just two nodes!