Skip to content

Crow Mission System

Module: CrowMissionSystem | Engine: Unreal Engine 5.6 | © 2025 SuspectCrow

1. Overview

The Crow Mission System is a data-driven, multiplayer-ready quest framework for Unreal Engine 5. It lets you define missions as data assets with zero per-mission C++ code, track objective progress via Gameplay Tags, grant rewards on completion, and react to every lifecycle event through Blueprint-assignable delegates.

Design Goals

GoalHow the System Achieves It
No per-mission codeAll static data (objectives, rewards, flags) lives in USCMissionDefinition data assets. Runtime state is owned by USCMissionsComponent
Gameplay Tag–driven objectivesProgress is reported by broadcasting a tag + value. The component’s internal lookup routes the update to every matching active objective automatically
Multiplayer-safeMissionInstances replicates to the owning client only. All mutations are authority-guarded. Client-initiated actions (add, pin) go through server RPCs
Sequential & parallel objectivesThe bIsSequential flag makes the component enforce a strict order; without it all objectives accept updates simultaneously
Extensible rewardsUSCMissionReward is an abstract, instanced UObject — subclass it in C++ or Blueprint to add any reward type without touching the system
Director patternComplex, stateful mission logic lives in a ASCMissionDirector subclass, keeping the component lean and the logic scene-contextual
Save-readySaveGame is set on all persistent properties. Two functions handle full round-trip serialization

2. Architecture

The system separates static data (what a mission is) from runtime state (how far along it is) and routes all mutations through an authority-guarded component.

┌─────────────────────────────────────────────────────────┐
│                  PlayerController / Pawn                │
│                                                         │
│  ┌───────────────────────────────────────────────────┐  │
│  │              USCMissionsComponent                 │  │
│  │                                                   │  │
│  │  MissionInstances[]  ◄──── replicated (OwnerOnly) │  │
│  │  ActiveObjectiveLookup  (TMap tag → indices)      │  │
│  │                                                   │  │
│  │  AddMission(Definition) ─────────────────────────►│──┼──► FSCMissionInstance created
│  │  ReportObjectiveUpdate(Tag, Value) ───────────────►│──┼──► lookup → increment progress
│  │  CancelMission(Index) ────────────────────────────►│──┼──► status = Cancelled
│  └───────────────────────────────────────────────────┘  │
│                                                         │
└─────────────────────────────────────────────────────────┘
          │ reads static data                  │ spawns / initializes
          ▼                                    ▼
  USCMissionDefinition              ASCMissionDirector
  (PrimaryDataAsset)                (AInfo subclass)
  ├─ MissionID (tag)                ├─ InitializeDirector()
  ├─ Objectives[]                   ├─ OnDirectorInitialized (BP event)
  ├─ Rewards[] (instanced)          └─ CleanupTrackedActors()
  └─ QuestsToAddAfterComplete[]
          │
          │ on completion
          ▼
  USCMissionReward (abstract)
  └─ GiveReward(Instigator)

Interaction with World Actors

World Actor (NPC, trigger, etc.)
  │  implements
  ▼
ISCMissionInterface
  └─ AssignObjective(Definition, Tag, Director)
  └─ RevokeObjective()
  └─ CanAssignObjective()

The ASCMissionDirector is the primary consumer of ISCMissionInterface. It decides which world actors receive which objective assignment and calls AssignObjective on them, giving them everything they need to fire ReportObjectiveUpdate back on the component.

3. Quick Start

Step 1 — Add USCMissionsComponent to Your Actor

Recommendation: Add the component to your PlayerState or APlayerController so it persists across pawn respawns. The component can live on any AActor that has authority over the player’s progression.

// MyPlayerState.h
#include "Components/SCMissionsComponent.h"

UPROPERTY(VisibleAnywhere, BlueprintReadOnly)
TObjectPtr<USCMissionsComponent> MissionsComponent;
// MyPlayerState.cpp — constructor
MissionsComponent = CreateDefaultSubobject<USCMissionsComponent>(TEXT("MissionsComponent"));

Step 2 — Create a USCMissionDefinition Data Asset

In the Content Browser: Right-click → Miscellaneous → Data Asset → USCMissionDefinition

FieldExample Value
MissionIDSC.Mission.DeliverPackage
MissionTitle"Special Delivery"
bIsSequentialtrue
MissionDirectorClassBP_DeliverPackageDirector (optional)

Add at least one entry to Objectives:

FieldExample
ObjectiveTagSC.Objective.Deliver.Package
RequiredValue1
IntroductionText"Bring the package to the drop-off point."

Data Validation: The editor will refuse to save a USCMissionDefinition with no MissionID, no objectives, or any objective with a zero/negative RequiredValue that is not marked bIsInfinite.

Step 3 — Add a Mission at Runtime

// From an interaction system, quest giver NPC, etc.
USCMissionsComponent* Missions = USCMissionSystemHelper::GetMissionsComponent(PlayerActor);
if (Missions)
{
    int32 NewIndex = Missions->AddMission(DeliverPackageDefinition);
}

Blueprint: Call Add Mission on the component and cache the returned index.

Step 4 — Report Objective Progress

Anywhere that a relevant game event occurs, call ReportObjectiveUpdate with the matching tag and the amount of progress made:

// Called when the player interacts with the drop-off point:
MissionsComponent->ReportObjectiveUpdate(
    FGameplayTag::RequestGameplayTag(TEXT("SC.Objective.Deliver.Package")),
    1
);

The system automatically:

  • Looks up every active mission that has this objective tag
  • Increments the progress counter
  • Marks the objective complete when CurrentValue >= RequiredValue
  • Advances the sequence index (if the mission is sequential)
  • Grants rewards and chains follow-up missions when all objectives are done

Step 5 — React to Events in Blueprint

BeginPlay
  └── GetPlayerState → MissionsComponent
        ├── Bind OnMissionAdded     → ShowNewMissionUI(MissionIndex)
        ├── Bind OnObjectiveUpdated → UpdateObjectiveTracker(Tag, Handle)
        └── Bind OnMissionCompleted → PlayCompletionCinematic(MissionIndex)

4. Core Classes & Types

4.1 USCMissionDefinition

File: DataAssets/SCMissionDefinition.h | Base: UPrimaryDataAsset

The static blueprint of a mission. This asset never holds runtime state — one instance is shared by every player who has that mission active simultaneously. All mutable progress lives in FSCMissionInstance.

Core Properties
PropertyTypeDescription
MissionIDFGameplayTagUnique identifier. Used for asset registry search and deduplication. Required.
MissionTitleFTextLocalizable display name shown in the quest log UI.
DescriptionFTextLocalizable body text describing the mission’s context and goals.
CategoryFGameplayTagGroups missions by type (e.g., SC.Mission.Category.Main). Defaults to DefaultMissionCategory from settings.
MissionRarityint32Used for loot tables or mission filtering. Defaults to DefaultMissionRarity from settings.
MinRecommendedLevelint32Informational — not enforced by the system. Use in your own eligibility checks.
MissionDirectorClassTSubclassOf<ASCMissionDirector>The director spawned when this mission starts. Leave unset for simple missions that need no scene logic.
AssignableActorClassesTArray<TSubclassOf<AActor>>Classes the director knows it may call AssignObjective on. Informational — used by your director implementation.
Behavior Flags
PropertyDefaultDescription
bIsSequentialtrueIf set, objectives must be completed in array order. Progress reports for out-of-order objectives are silently dropped.
bIsCancellabletrueControls whether CancelMission will succeed. Inherits from settings default.
bIsHiddenfalseHides the mission from the active quest log. The mission still tracks progress normally.
bPinnedByDefaultfalseNew instances start with bIsPinned = true.
bFailOnInstigatorDeathfalseInformational flag. Wire your own death detection to CancelMission when this is set.
Chain & Reward Properties
PropertyTypeDescription
RewardsTArray<USCMissionReward*> (Instanced)Rewards granted to the instigator when all objectives complete. Add any number of USCMissionReward subclass instances.
QuestsToAddAfterCompleteTArray<USCMissionDefinition*>Missions automatically added when this one completes. If bAutoAcceptSequentialQuests is enabled in settings, they are immediately set to Active.
ObjectivesTArray<FSCObjectiveDefinition>The ordered list of objective definitions. At least one is required.

4.2 FSCObjectiveDefinition

File: DataAssets/SCMissionDefinition.h | Container: USCMissionDefinition::Objectives

A single entry in a mission’s objective list. This is pure static data — it never changes after the asset is saved.

PropertyTypeDescription
ObjectiveTagFGameplayTagThe tag this objective listens for. ReportObjectiveUpdate matches against this tag. Must be unique within the mission.
IntroductionTextFTextText displayed when this objective becomes active (sequential unlock or mission start).
RequiredValueint32Progress units needed to complete the objective. Default: 1. Must be >= 1 unless bIsInfinite is set.
RelevantLocationsFGameplayTagContainerLocation tags broadcast via OnRelevantLocationsUpdated while this objective is active and incomplete. Use for map markers, waypoints, or minimap indicators.
bIsInfiniteboolIf true, the objective never formally completes and stays active for the mission’s entire duration. RequiredValue is ignored. Useful for “escort” or “survive” objectives.

4.3 USCMissionsComponent

File: Components/SCMissionsComponent.h | Base: UActorComponent

The runtime engine of the system. It owns the MissionInstances array, enforces authority, manages the objective lookup cache, fires all delegates, and drives replication.

Mission Management API
MethodAuthorityBlueprintDescription
AddMission(Definition)Server✅ CallableCreates a new FSCMissionInstance, populates objective progresses, fires OnMissionAdded, optionally starts the director. Returns the new mission index or INDEX_NONE on failure.
ActivateMission(Index)Server✅ CallableTransitions a NotStarted mission to Active. Use when MarkAsNewMissionActive is false in settings.
CancelMission(Index)Server✅ CallableSets status to Cancelled if bIsCancellable is true. Returns success.
ToggleMissionPin(Index)Either✅ CallableFlips bIsPinned. Routes through Server_ToggleMissionPin when called without authority.
ReportObjectiveUpdate(Tag, Value)Server✅ CallableThe primary progress input. Looks up Tag in ActiveObjectiveLookup and increments all matching active objectives.
StartMissionDirector(Index, Def)Server✅ CallableSpawns (or reuses) a director of Def->MissionDirectorClass and calls InitializeDirector. Called automatically by AddMission when StartDirectorWhenMissionAdded is enabled.
Query API
MethodBlueprintDescription
HasMission(Definition)✅ PureReturns true if any instance (any status) references this definition.
FindMissionIndexByDefinition(Definition)✅ PureReturns the first index matching this definition, or INDEX_NONE.
GetMissionInstance(Index)✅ PureReturns a copy of the FSCMissionInstance at the given index. Returns an empty default on invalid index.
GetActiveMissions()✅ PureReturns all instances with Status == Active.
IsMissionValid(Index)✅ Puretrue if the index is in range and the instance has a non-null definition.
IsMissionStatusEqual(Index, Status)✅ PureCompact status check. Compact node title: Status ==.
AreAllObjectivesCompleted(Index)✅ Puretrue if every FSCObjectiveProgress in the instance has bCompleted == true.
FindObjectiveIndexByTag(...)✅ PureSearches the instance’s ObjectiveProgresses array by tag.
GetObjectiveProgress(Handle, ...)✅ PureResolves a handle to the full FSCObjectiveProgress. Returns false on invalid handle.
GetObjectiveDefinition(Handle, ...)✅ PureResolves a handle to the static FSCObjectiveDefinition. Returns false on invalid handle.
BroadcastActiveLocations()✅ CallableManually triggers OnRelevantLocationsUpdated with all currently active, incomplete objective locations.

4.4 FSCMissionInstance

File: Structs/SCMissionInstance.h

The runtime container for a single mission. One instance is created per AddMission call. All fields marked SaveGame are included in the save data returned by GetMissionSaveData.

PropertyTypeSaveGameDescription
Definitionconst USCMissionDefinition*Pointer to the static data asset. The anchor for all config lookups.
StatusESCMissionStatusCurrent lifecycle state. See table below.
InstigatorAActor*The actor that owns this mission (set to GetOwner() on add and on load). Used as the GiveReward target.
bIsPinnedboolToggled by ToggleMissionPin. Initialized from Definition->bPinnedByDefault.
CurrentSequenceIndexint32For sequential missions, the index of the currently active objective. Advances on each objective completion.
ObjectiveProgressesTArray<FSCObjectiveProgress>One entry per objective, parallel to Definition->Objectives.

4.5 FSCObjectiveProgress

File: Structs/SCMissionInstance.h | Container: FSCMissionInstance::ObjectiveProgresses

PropertyTypeSaveGameDescription
ObjectiveTagFGameplayTagMirrors FSCObjectiveDefinition::ObjectiveTag. Cached here for fast lookup without going through the definition.
CurrentValueint32Accumulated progress. Clamped upward naturally; never decremented by the system.
bCompletedboolSet to true when CurrentValue >= RequiredValue. Infinite objectives never reach this state.

4.6 ASCMissionDirector

File: Actors/SCMissionDirector.h | Base: AInfo

A scene actor whose lifetime matches a single mission. It is the right place for mission-specific logic that needs world context: spawning enemies, playing cinematics, tracking escort targets, enabling triggers. Because it inherits from AInfo, it has no visible mesh or collision — it is invisible bookkeeping infrastructure.

Public API
MethodBlueprintDescription
InitializeDirector(Component, Index)✅ CallableBinds the director to its owning component and mission. Called automatically by the component.
GetMissionInstance()✅ PureReturns the current FSCMissionInstance by querying OwningMissionComponent. Always up to date.
GetMissionDefinition()✅ PureReturns the cached definition pointer.
RegisterActorForCleanup(Actor)✅ CallableAdds an actor to TrackedActors. Call from Blueprint for NPCs, props, or triggers the director spawns.
CleanupTrackedActors()✅ CallableIterates TrackedActors and calls Destroy() on each valid one. Call when the mission ends or is cancelled.
// Override this in Blueprint to run your mission setup:
UFUNCTION(BlueprintImplementableEvent)
void OnDirectorInitialized();

4.7 USCMissionReward

File: Other/SCMissionReward.h | Base: UObject

Abstract base class for all mission rewards. Subclass it to implement any delivery logic — experience points, inventory items, currency, unlocks, etc.

UCLASS()
class UMyXPReward : public USCMissionReward
{
    GENERATED_BODY()
public:
    UPROPERTY(EditDefaultsOnly)
    int32 XPAmount = 100;

    virtual void GiveReward_Implementation(AActor* Instigator) const override
    {
        if (UMyXPComponent* XP = Instigator->FindComponentByClass<UMyXPComponent>())
        {
            XP->AddXP(XPAmount);
        }
    }
};

4.8 ISCMissionInterface

File: Interfaces/SCMissionInterface.h

MethodDescription
AssignObjective(Definition, Tag, Director)Called by the director to give this actor a specific objective. The actor should store the director reference and call ReportObjectiveUpdate on its component when relevant events occur.
RevokeObjective()Called to clear any active objective from this actor (e.g., when the mission is cancelled or the objective changes).
CanAssignObjective()Returns true if this actor is currently in a state where it can accept a new assignment.

4.9 FSCObjectiveHandle

File: Structs/SCObjectiveHandle.h

A lightweight, two-integer pair used to reference a specific objective within a specific mission instance without holding pointers.

// Returns true if neither index is INDEX_NONE
bool IsValid() const;

// Operator== — used by USCMissionSystemHelper::EqualEqual_ObjectiveHandle for Blueprint comparison
bool operator==(const FSCObjectiveHandle& Other) const;

4.10 USCMissionSystemSettings

File: DevSettings/SCMissionSystemSettings.h | Base: UDeveloperSettings

Project-wide defaults accessible via Project Settings → Game → SC Mission System.

PropertyDefaultDescription
DefaultMissionCategory(empty)FGameplayTag pre-filled into new USCMissionDefinition assets.
DefaultMissionRarity10Integer pre-filled into new definitions.
bAllowMissionCancellationtrueDefault value for bIsCancellable on new definitions.
bAutoAcceptSequentialQueststrueWhen true, missions listed in QuestsToAddAfterComplete are immediately set to Active after being added.
MarkAsNewMissionActivetrueWhen true, AddMission immediately sets status to Active.
StartDirectorWhenMissionAddedtrueWhen true, AddMission automatically calls StartMissionDirector.

5. Delegates Reference

OnMissionAdded         (int32 MissionIndex)
  └── Fired after a new instance is successfully added.
      Use to: show a "New Quest" notification, update the quest log.

OnMissionUpdated       (int32 MissionIndex)
  └── Fired after any state change that is not a terminal transition
      (pin toggle, objective progress, sequence advance, status to Active).
      Use to: refresh the objective tracker UI, update map markers.

OnMissionCompleted     (int32 MissionIndex)
  └── Fired when all objectives are met and rewards have been granted.
      Use to: play a completion fanfare, unlock the next area, save game.

OnMissionCancelled     (int32 MissionIndex)
  └── Fired when CancelMission succeeds.
      Use to: remove the mission from the active quest log, despawn director actors.

OnObjectiveUpdated     (FGameplayTag ObjectiveTag, FSCObjectiveHandle Handle)
  └── Fired for each objective that receives progress in a ReportObjectiveUpdate call.
      Use to: update a specific objective's progress bar, show a "Objective Complete" flash.

OnMissionInstancesUpdated  ()
  └── Fired as a catch-all after any mutation to MissionInstances, and on replication.
      Use to: do a full UI refresh when granular event tracking is not needed.

OnRelevantLocationsUpdated (const FGameplayTagContainer& ActiveLocations)
  └── Fired after any change that affects which objectives are active/incomplete.
      Delivers a fresh container of all location tags from all active, visible objectives.
      Use to: drive map marker visibility, waypoint arrows, minimap icons.

6. Mission Lifecycle

Full Session Flow

AddMission(Definition)   [Server]
     │
     ├─ Create FSCMissionInstance
     ├─ Set Status = NotStarted (or Active if MarkAsNewMissionActive)
     ├─ Populate ObjectiveProgresses[]
     ├─ Set Instigator = GetOwner()
     ├─ Apply bPinnedByDefault
     ├─ UpdateObjectiveLookup()
     ├─ OnMissionAdded.Broadcast(Index)
     ├─ BroadcastActiveLocations()
     └─ StartMissionDirector()  (if StartDirectorWhenMissionAdded)

          [Player plays — objective events fire in the world]

ReportObjectiveUpdate(Tag, Value)   [Server]
     │
     ├─ Look up Tag in ActiveObjectiveLookup
     ├─ For each matching (MissionIndex, ObjectiveIndex):
     │     ├─ Skip if mission not Active
     │     ├─ Skip if sequential and ObjectiveIndex != CurrentSequenceIndex
     │     ├─ Skip if already complete (and not Infinite)
     │     ├─ Increment CurrentValue
     │     ├─ OnObjectiveUpdated.Broadcast(Tag, Handle)
     │     ├─ BroadcastActiveLocations()
     │     └─ If CurrentValue >= RequiredValue:
     │           ├─ bCompleted = true
     │           └─ Advance CurrentSequenceIndex (sequential missions)
     │
     └─ For each updated mission:
           ├─ OnMissionUpdated.Broadcast(MissionIndex)
           └─ If AllObjectivesCompleted():
                 ├─ Status = Completed
                 ├─ GiveReward(Instigator)  for each Reward
                 ├─ AddMission(NextQuest)   for each QuestsToAddAfterComplete
                 └─ OnMissionCompleted.Broadcast(MissionIndex)

CancelMission(Index)   [Server]
     ├─ Check bIsCancellable
     ├─ Status = Cancelled
     ├─ UpdateObjectiveLookup()
     ├─ OnMissionCancelled.Broadcast(Index)
     └─ BroadcastActiveLocations()

Sequential vs. Parallel Objectives

bIsSequential = false  (parallel)
  All objectives in ActiveObjectiveLookup simultaneously.
  Progress reports for any objective tag are accepted in any order.

bIsSequential = true
  Only the objective at CurrentSequenceIndex is in the lookup.
  ReportObjectiveUpdate silently drops reports for other indices.
  On completion, CurrentSequenceIndex increments, the next objective
  enters the lookup, and OnMissionUpdated fires to refresh the UI.

7. Objective Tracking Internals

ActiveObjectiveLookup

TMap<FGameplayTag, TArray<TPair<int32, int32>>> ActiveObjectiveLookup;
//                                   ^         ^
//                              MissionIndex  ObjectiveIndex

This private map is the performance backbone of ReportObjectiveUpdate. Instead of scanning all missions on every update, the component pre-builds a direct path from tag to index pairs.

BroadcastActiveLocations

void BroadcastActiveLocations()

Aggregates FSCObjectiveDefinition::RelevantLocations from every objective that is currently active and incomplete, taking sequential ordering into account. Fires OnRelevantLocationsUpdated with the merged FGameplayTagContainer.

8. Replication & Authority

OperationRequirementNotes
AddMissionServerClient calls auto-forward to Server_AddMission. Returns INDEX_NONE on client.
ActivateMissionServerNo client RPC — call from authoritative code only.
CancelMissionServerNo client RPC — call from authoritative code only.
ToggleMissionPinEitherClient call forwards to Server_ToggleMissionPin.
ReportObjectiveUpdateServerNo forwarding — must be called from authoritative context.
LoadMissionSaveDataServerNo forwarding — called during game load, which is always authoritative.

Replication Flow

Server mutates MissionInstances
        │
        ▼
DOREPLIFETIME_CONDITION(COND_OwnerOnly) → replicated to owning client
        │
        ▼
OnRep_MissionInstances() fires on client
        │
        ├─ UpdateObjectiveLookup()          (client-side cache refresh)
        ├─ Diff vs. MissionInstances_Previous
        │     ├─ New indices → OnMissionAdded.Broadcast
        │     ├─ Status changed to Completed → OnMissionCompleted.Broadcast
        │     ├─ Status changed to Cancelled → OnMissionCancelled.Broadcast
        │     ├─ Other status / pin / sequence change → OnMissionUpdated.Broadcast
        │     └─ Objective value / completion change → OnObjectiveUpdated.Broadcast
        ├─ MissionInstances_Previous = MissionInstances  (snapshot for next rep)
        ├─ BroadcastActiveLocations()
        └─ OnMissionInstancesUpdated.Broadcast()

9. Save & Load

Saving

// Returns the full MissionInstances array. All SaveGame-tagged properties are included.
TArray<FSCMissionInstance> USCMissionsComponent::GetMissionSaveData() const;

Loading

// Restores the MissionInstances array and reconnects the Instigator pointer.
void USCMissionsComponent::LoadMissionSaveData(const TArray<FSCMissionInstance>& SavedData);

Call this on the server during game load. After loading: Instigator is set to GetOwner() for every instance, UpdateObjectiveLookup is called, and delegates fire to let the UI rebuild.

10. Common Patterns & Recipes

Pattern 1 — Show a “New Quest” Toast and Update the Quest Log

BeginPlay
  └── MissionsComponent → Bind OnMissionAdded → OnMissionUpdated → OnMissionCompleted

OnMissionAdded(MissionIndex)
  └── GetMissionInstance(MissionIndex) → Definition → MissionTitle
      Show toast widget with title text for 3 seconds
      Add entry to quest log list

OnMissionUpdated(MissionIndex)
  └── Find matching quest log entry by index
      For each objective: GetObjectiveProgress(Handle) → CurrentValue / RequiredValue
      Refresh progress bars

Pattern 2 — Drive Map Markers from Active Locations

OnRelevantLocationsUpdated(ActiveLocations)
  ├── For each MapMarker in all map markers:
  │     If ActiveLocations.HasTag(Marker.LocationTag) → Show marker
  │     Else → Hide marker
  └── Update minimap icons accordingly

Pattern 3 — Enemy Defeated Anywhere Reports Objective Progress

// In AMyEnemy::Die():
for (APlayerController* PC : /* all relevant players */)
{
    USCMissionsComponent* Missions = USCMissionSystemHelper::GetMissionsComponent(PC);
    if (Missions)
    {
        Missions->ReportObjectiveUpdate(
            FGameplayTag::RequestGameplayTag(TEXT("SC.Objective.Kill.Enemy")),
            1
        );
    }
}

Pattern 4 — Reading Objective Progress in the HUD

FSCObjectiveProgress Progress;
FSCObjectiveDefinition Definition;

if (MissionsComponent->GetObjectiveProgress(Handle, Progress) &&
    MissionsComponent->GetObjectiveDefinition(Handle, Definition))
{
    float Ratio = (float)Progress.CurrentValue / (float)Definition.RequiredValue;
    ProgressBar->SetPercent(Ratio);
    LabelText->SetText(Definition.IntroductionText);
}