You are viewing documentation for v1.1. Switch to current version โ†’

๐Ÿ“š Examples

This section provides practical examples of how to implement and use Conditional Object Choosers in various scenarios. Each example demonstrates a specific use case and includes complete header and implementation files for every class used.

The snippets assume you have the Gorgeous Core runtime module in your build dependencies and that you are familiar with Unreal Engine UObject and UActorComponent workflows.

Quest Objective Selection

Use a chooser to select the next quest objective based on a player-facing quest state. The condition reads a world subsystem and returns a stable index that maps to the chooser's Conditions array.

C++ Setup

// QuestObjectiveData.h

USTRUCT(BlueprintType)
struct FQuestObjectiveData
{
    GENERATED_BODY()

    UPROPERTY(EditAnywhere, BlueprintReadWrite)
    FName ObjectiveId;

    UPROPERTY(EditAnywhere, BlueprintReadWrite)
    FText ObjectiveTitle;

    UPROPERTY(EditAnywhere, BlueprintReadWrite)
    FText ObjectiveDescription;

    FQuestObjectiveData()
        : ObjectiveId(NAME_None)
        , ObjectiveTitle(FText::GetEmpty())
        , ObjectiveDescription(FText::GetEmpty())
    {}

    FQuestObjectiveData(FName InId, FText InTitle, FText InDescription)
        : ObjectiveId(InId)
        , ObjectiveTitle(InTitle)
        , ObjectiveDescription(InDescription)
    {}
};
// QuestObjective_OV.h

UCLASS(BlueprintType)
class UQuestObjective_OV : public UGorgeousObjectVariable
{
    GENERATED_BODY()

public:
    UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Quest")
    FQuestObjectiveData ObjectiveData;
};
// QuestStateSubsystem.h

UCLASS()
class UQuestStateSubsystem : public UWorldSubsystem
{
    GENERATED_BODY()

public:
    UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Quest")
    int32 ActiveStageIndex = 0;

    UFUNCTION(BlueprintCallable, Category = "Quest")
    int32 GetActiveStageIndex() const;
};
// QuestStateSubsystem.cpp

int32 UQuestStateSubsystem::GetActiveStageIndex() const
{
    return ActiveStageIndex;
}
// QuestStageCondition.h

UCLASS(BlueprintType, EditInlineNew)
class UQuestStageCondition : public UGorgeousCondition
{
    GENERATED_BODY()

public:
    UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Quest")
    int32 MaxStageIndex = 4;

    UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Quest")
    int32 DefaultIndex = 0;

    UFUNCTION(BlueprintCallable, Category = "Quest")
    int32 GetClampedStageIndex(const UObject* WorldContextObject) const;

    virtual uint8 CheckCondition_Implementation() override;
};
// QuestStageCondition.cpp

int32 UQuestStageCondition::GetClampedStageIndex(const UObject* WorldContextObject) const
{
    if (!WorldContextObject)
    {
        return DefaultIndex;
    }

    const UWorld* World = WorldContextObject->GetWorld();
    if (!World)
    {
        return DefaultIndex;
    }

    const UQuestStateSubsystem* Subsystem = World->GetSubsystem<UQuestStateSubsystem>();
    if (!Subsystem)
    {
        return DefaultIndex;
    }

    return FMath::Clamp(Subsystem->GetActiveStageIndex(), 0, MaxStageIndex);
}

uint8 UQuestStageCondition::CheckCondition_Implementation()
{
    const int32 Index = GetClampedStageIndex(GetWorld());
    return static_cast<uint8>(Index);
}
// QuestObjectiveChooser.h

UCLASS(BlueprintType)
class UQuestObjectiveChooser : public UGorgeousConditionalObjectChooser
{
    GENERATED_BODY()

    UGorgeousConditionalObjectChooser()
    {
        ConditionCheck = NewObject<UQuestStageCondition>(this, TEXT("QuestStageCondition"));

        UQuestObjective_OV* Objective0 = NewObject<UQuestObjective_OV>(this, TEXT("Objective0"));
        Objective0->ObjectiveData = { TEXT("Objective_0"), FText::FromString("Find the Lost Sword"), FText::FromString("The village elder has tasked you with finding the lost sword in the nearby cave.") };

        ... // Create additional objectives for each stage index or pull from data assets, data tabbles or any other source of stable data references.

        Conditions = {
            Objective0,
            ... // Add additional objectives to the Conditions array in the same order as their corresponding stage indices.
        }
    }
};
// QuestDirector.h

UCLASS()
class AQuestDirector : public AActor
{
    GENERATED_BODY()

public:
    UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Quest")
    TObjectPtr<UQuestObjectiveChooser> ObjectiveChooser;

    UFUNCTION(BlueprintCallable, Category = "Quest")
    UQuestObjective_OV* ResolveActiveObjective() const;
};
// QuestDirector.cpp

UQuestObjective_OV* AQuestDirector::ResolveActiveObjective() const
{
    if (!ObjectiveChooser)
    {
        return nullptr;
    }

    return Cast<UQuestObjective_OV>(ObjectiveChooser->DecideCondition());
}

Dynamic Music Selection

Use a gameplay-tag driven condition to select a music track based on the current music state. The condition reads a tag container from a component, then the chooser maps the index to a track variable.

C++ Setup

// MusicTrackData.h

USTRUCT(BlueprintType)
struct FMusicTrackData
{
    GENERATED_BODY()

    UPROPERTY(EditAnywhere, BlueprintReadWrite)
    TSoftObjectPtr<USoundBase> Sound;

    UPROPERTY(EditAnywhere, BlueprintReadWrite)
    float FadeInSeconds = 0.5f;

    UPROPERTY(EditAnywhere, BlueprintReadWrite)
    float FadeOutSeconds = 0.5f;

    FMusicTrackData()
        : Sound(nullptr)
        , FadeInSeconds(0.5f)
        , FadeOutSeconds(0.5f)
    {}

    FMusicTrackData(TSoftObjectPtr<USoundBase> InSound, float InFadeIn, float InFadeOut)
        : Sound(InSound)
        , FadeInSeconds(InFadeIn)
        , FadeOutSeconds(InFadeOut)
    {}
};
// MusicTrack_OV.h

UCLASS(BlueprintType)
class UMusicTrack_OV : public UGorgeousObjectVariable
{
    GENERATED_BODY()

public:
    UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Music")
    FMusicTrackData Track;
};
// MusicStateComponent.h

UCLASS(ClassGroup=(Audio), meta=(BlueprintSpawnableComponent))
class UMusicStateComponent : public UActorComponent
{
    GENERATED_BODY()

public:
    UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Music")
    FGameplayTagContainer MusicStateTags;

    UFUNCTION(BlueprintCallable, Category = "Music")
    const FGameplayTagContainer& GetMusicStateTags() const;
};
// MusicStateComponent.cpp

const FGameplayTagContainer& UMusicStateComponent::GetMusicStateTags() const
{
    return MusicStateTags;
}
// DynamicMusicChooser.h

UCLASS(BlueprintType)
class UDynamicMusicChooser : public UGorgeousConditionalObjectChooser
{
    GENERATED_BODY()

    UDynamicMusicChooser()
    {
        ConditionCheck = NewObject<UGorgeousGameplayTagCondition>(this, TEXT("MusicTagCondition"));
        Cast<UGorgeousGameplayTagCondition>(ConditionCheck)->GameplayTagContainerClassReference = UMusicStateComponent::StaticClass();
        Cast<UGorgeousGameplayTagCondition>(ConditionCheck)->GameplayTagContainerUPropertyName = TEXT("MusicStateTags");

        UMusicTrack_OV* CalmTrack = NewObject<UMusicTrack_OV>(this, TEXT("CalmTrack"));
        CalmTrack->Track = FMusicTrackData(TSoftObjectPtr<USoundBase>(FSoftObjectPath(TEXT("/Game/Audio/CalmTrack.CalmTrack"))), 1.0f, 1.0f);

        UMusicTrack_OV* IntenseTrack = NewObject<UMusicTrack_OV>(this, TEXT("IntenseTrack"));
        IntenseTrack->Track = FMusicTrackData(TSoftObjectPtr<USoundBase>(FSoftObjectPath(TEXT("/Game/Audio/IntenseTrack.IntenseTrack"))), 1.0f, 1.0f);

        Conditions = {
            CalmTrack,    // Default track when no tags are present
            IntenseTrack  // Track to play when the relevant tag is present
        };
    }
};
// MusicDirector.h

UCLASS()
class AMusicDirector : public AActor
{
    GENERATED_BODY()

public:
    UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Music")
    TObjectPtr<UDynamicMusicChooser> MusicChooser;

    UFUNCTION(BlueprintCallable, Category = "Music")
    UMusicTrack_OV* ResolveMusicTrack() const;
};
// MusicDirector.cpp

UMusicTrack_OV* AMusicDirector::ResolveMusicTrack() const
{
    if (!MusicChooser)
    {
        return nullptr;
    }

    return Cast<UMusicTrack_OV>(MusicChooser->DecideCondition());
}

Difficulty Adjustment

Use a custom condition that inspects a difficulty metrics subsystem and maps the score into a tuning index. The chooser then returns the correct tuning object variable.

C++ Setup

// DifficultyTuningData.h

USTRUCT(BlueprintType)
struct FDifficultyTuningData
{
    GENERATED_BODY()

    UPROPERTY(EditAnywhere, BlueprintReadWrite)
    float EnemyHealthMultiplier = 1.0f;

    UPROPERTY(EditAnywhere, BlueprintReadWrite)
    float EnemyDamageMultiplier = 1.0f;

    UPROPERTY(EditAnywhere, BlueprintReadWrite)
    float LootMultiplier = 1.0f;

    FDifficultyTuningData()
        : EnemyHealthMultiplier(1.0f)
        , EnemyDamageMultiplier(1.0f)
        , LootMultiplier(1.0f)
    {}

    FDifficultyTuningData(float InHealthMult, float InDamageMult, float InLootMult)
        : EnemyHealthMultiplier(InHealthMult)
        , EnemyDamageMultiplier(InDamageMult)
        , LootMultiplier(InLootMult)
    {}
};
// DifficultyTuning_OV.h

UCLASS(BlueprintType)
class UDifficultyTuning_OV : public UGorgeousObjectVariable
{
    GENERATED_BODY()

public:
    UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Difficulty")
    FDifficultyTuningData Tuning;
};
// DifficultyMetricsSubsystem.h

UCLASS()
class UDifficultyMetricsSubsystem : public UWorldSubsystem
{
    GENERATED_BODY()

public:
    UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Difficulty")
    float PerformanceScore = 0.0f;

    UFUNCTION(BlueprintCallable, Category = "Difficulty")
    float GetPerformanceScore() const;
};
// DifficultyMetricsSubsystem.cpp

float UDifficultyMetricsSubsystem::GetPerformanceScore() const
{
    return PerformanceScore;
}
// DifficultyIndexCondition.h

UCLASS(BlueprintType, EditInlineNew)
class UDifficultyIndexCondition : public UGorgeousCondition
{
    GENERATED_BODY()

public:
    UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Difficulty")
    float EasyThreshold = 0.25f;

    UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Difficulty")
    float NormalThreshold = 0.6f;

    UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Difficulty")
    float HardThreshold = 0.85f;

    UFUNCTION(BlueprintCallable, Category = "Difficulty")
    int32 ResolveDifficultyIndex(const UObject* WorldContextObject) const;

    virtual uint8 CheckCondition_Implementation() override;
};
// DifficultyIndexCondition.cpp

int32 UDifficultyIndexCondition::ResolveDifficultyIndex(const UObject* WorldContextObject) const
{
    if (!WorldContextObject)
    {
        return 0;
    }

    const UWorld* World = WorldContextObject->GetWorld();
    if (!World)
    {
        return 0;
    }

    const UDifficultyMetricsSubsystem* Subsystem = World->GetSubsystem<UDifficultyMetricsSubsystem>();
    if (!Subsystem)
    {
        return 0;
    }

    const float Score = FMath::Clamp(Subsystem->GetPerformanceScore(), 0.0f, 1.0f);
    if (Score < EasyThreshold)
    {
        return 0;
    }
    if (Score < NormalThreshold)
    {
        return 1;
    }
    if (Score < HardThreshold)
    {
        return 2;
    }
    return 3;
}

uint8 UDifficultyIndexCondition::CheckCondition_Implementation()
{
    const int32 Index = ResolveDifficultyIndex(GetWorld());
    return static_cast<uint8>(Index);
}
// DifficultyTuningChooser.h

UCLASS(BlueprintType)
class UDifficultyTuningChooser : public UGorgeousConditionalObjectChooser
{
    GENERATED_BODY()

    UDifficultyTuningChooser()
    {
        ConditionCheck = NewObject<UDifficultyIndexCondition>(this, TEXT("DifficultyIndexCondition"));

        UDifficultyTuning_OV* EasyTuning = NewObject<UDifficultyTuning_OV>(this, TEXT("EasyTuning"));
        EasyTuning->Tuning = FDifficultyTuningData(0.75f, 0.75f, 1.25f);

        UDifficultyTuning_OV* NormalTuning = NewObject<UDifficultyTuning_OV>(this, TEXT("NormalTuning"));
        NormalTuning->Tuning = FDifficultyTuningData(1.0f, 1.0f, 1.0f);

        UDifficultyTuning_OV* HardTuning = NewObject<UDifficultyTuning_OV>(this, TEXT("HardTuning"));
        HardTuning->Tuning = FDifficultyTuningData(1.25f, 1.25f, 0.75f);

        UDifficultyTuning_OV* ExtremeTuning = NewObject<UDifficultyTuning_OV>(this, TEXT("ExtremeTuning"));
        ExtremeTuning->Tuning = FDifficultyTuningData(1.5f, 1.5f, 0.5f);

        Conditions = {
            EasyTuning,
            NormalTuning,
            HardTuning,
            ExtremeTuning
        };
    }
};
// DifficultyDirector.h

UCLASS()
class ADifficultyDirector : public AActor
{
    GENERATED_BODY()

public:
    UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Difficulty")
    TObjectPtr<UDifficultyTuningChooser> TuningChooser;

    UFUNCTION(BlueprintCallable, Category = "Difficulty")
    UDifficultyTuning_OV* ResolveTuning() const;
};
// DifficultyDirector.cpp

UDifficultyTuning_OV* ADifficultyDirector::ResolveTuning() const
{
    if (!TuningChooser)
    {
        return nullptr;
    }

    return Cast<UDifficultyTuning_OV>(TuningChooser->DecideCondition());
}

Dialogue Options

Use a custom condition to select which dialogue options are available for the current speaker and conversation state. The chooser returns a variable describing the available options.

Since the Chooser only support uin8 values (0-255), using the Chooser system may be too limiting for dialogue systems with a large number of potential states, but it can work well for simple branching dialogues or as a fallback system when more complex data-driven approaches are not feasible.

C++ Setup

// DialogueOptionData.h

USTRUCT(BlueprintType)
struct FDialogueOptionData
{
    GENERATED_BODY()

    UPROPERTY(EditAnywhere, BlueprintReadWrite)
    FText LineText;

    UPROPERTY(EditAnywhere, BlueprintReadWrite)
    FName OutcomeId;

    FDialogueOptionData()
        : LineText(FText::GetEmpty())
        , OutcomeId(NAME_None)
    {}

    FDialogueOptionData(FText InLineText, FName InOutcomeId)
        : LineText(InLineText)
        , OutcomeId(InOutcomeId)
    {}
};
// DialogueOptions_OV.h

UCLASS(BlueprintType)
class UDialogueOptions_OV : public UGorgeousObjectVariable
{
    GENERATED_BODY()

public:
    UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Dialogue")
    TArray<FDialogueOptionData> Options;
};
// DialogueStateSubsystem.h

UCLASS()
class UDialogueStateSubsystem : public UWorldSubsystem
{
    GENERATED_BODY()

public:
    UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Dialogue")
    FName ActiveSpeakerId;

    UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Dialogue")
    int32 ConversationPhaseIndex = 0;

    UFUNCTION(BlueprintCallable, Category = "Dialogue")
    int32 GetConversationPhase() const;
};
// DialogueStateSubsystem.cpp

int32 UDialogueStateSubsystem::GetConversationPhase() const
{
    return ConversationPhaseIndex;
}
// DialogueOptionCondition.h

UCLASS(BlueprintType, EditInlineNew)
class UDialogueOptionCondition : public UGorgeousCondition
{
    GENERATED_BODY()

public:
    UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Dialogue")
    FName SpeakerId;

    UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Dialogue")
    int32 RequiredPhase = 0;

    UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Dialogue")
    int32 MatchingIndex = 0;

    UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Dialogue")
    int32 FallbackIndex = 0;

    virtual uint8 CheckCondition_Implementation() override;
};
// DialogueOptionCondition.cpp

uint8 UDialogueOptionCondition::CheckCondition_Implementation()
{
    const UWorld* World = GetWorld();
    if (!World)
    {
        return static_cast<uint8>(FallbackIndex);
    }

    const UDialogueStateSubsystem* Subsystem = World->GetSubsystem<UDialogueStateSubsystem>();
    if (!Subsystem)
    {
        return static_cast<uint8>(FallbackIndex);
    }

    if (Subsystem->ActiveSpeakerId <mark> SpeakerId && Subsystem->GetConversationPhase() </mark> RequiredPhase)
    {
        return static_cast<uint8>(MatchingIndex);
    }

    return static_cast<uint8>(FallbackIndex);
}
// DialogueOptionsChooser.h

UCLASS(BlueprintType)
class UDialogueOptionsChooser : public UGorgeousConditionalObjectChooser
{
    GENERATED_BODY()

    UDialogueOptionsChooser()
    {
        ConditionCheck = NewObject<UDialogueOptionCondition>(this, TEXT("DialogueOptionCondition"));

        UDialogueOptions_OV* OptionSet1 = NewObject<UDialogueOptions_OV>(this, TEXT("OptionSet1"));
        OptionSet1->Options = {
            FDialogueOptionData(FText::FromString("Hello!"), TEXT("Greet")),
            FDialogueOptionData(FText::FromString("Goodbye!"), TEXT("Farewell"))
        };

        UDialogueOptions_OV* OptionSet2 = NewObject<UDialogueOptions_OV>(this, TEXT("OptionSet2"));
        OptionSet2->Options = {
            FDialogueOptionData(FText::FromString("How are you?"), TEXT("AskWellBeing")),
            FDialogueOptionData(FText::FromString("What's your name?"), TEXT("AskName"))
        };

        Conditions = {
            OptionSet1,
            OptionSet2
        };
    }
};
// DialoguePresenterComponent.h

UCLASS(ClassGroup=(Dialogue), meta=(BlueprintSpawnableComponent))
class UDialoguePresenterComponent : public UActorComponent
{
    GENERATED_BODY()

public:
    UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Dialogue")
    TObjectPtr<UDialogueOptionsChooser> OptionsChooser;

    UFUNCTION(BlueprintCallable, Category = "Dialogue")
    UDialogueOptions_OV* ResolveOptions() const;
};
// DialoguePresenterComponent.cpp

UDialogueOptions_OV* UDialoguePresenterComponent::ResolveOptions() const
{
    if (!OptionsChooser)
    {
        return nullptr;
    }

    return Cast<UDialogueOptions_OV>(OptionsChooser->DecideCondition());
}

Branching Narrative Outcomes

Use a chooser to select a narrative outcome payload based on game state. The condition can map a narrative score to a specific outcome and return a variable that the story system can consume.

C++ Setup

// NarrativeOutcomeData.h

USTRUCT(BlueprintType)
struct FNarrativeOutcomeData
{
    GENERATED_BODY()

    UPROPERTY(EditAnywhere, BlueprintReadWrite)
    FName OutcomeId;

    UPROPERTY(EditAnywhere, BlueprintReadWrite)
    FText OutcomeDescription;

    UPROPERTY(EditAnywhere, BlueprintReadWrite)
    int32 ReputationDelta = 0;

    FNarrativeOutcomeData()
        : OutcomeId(NAME_None)
        , OutcomeDescription(FText::GetEmpty())
        , ReputationDelta(0)
    {}

    FNarrativeOutcomeData(FName InOutcomeId, FText InDescription, int32 InReputationDelta)
        : OutcomeId(InOutcomeId)
        , OutcomeDescription(InDescription)
        , ReputationDelta(InReputationDelta)
    {}
};
// NarrativeOutcome_OV.h

UCLASS(BlueprintType)
class UNarrativeOutcome_OV : public UGorgeousObjectVariable
{
    GENERATED_BODY()

public:
    UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Narrative")
    FNarrativeOutcomeData Outcome;
};
// NarrativeStateSubsystem.h

UCLASS()
class UNarrativeStateSubsystem : public UWorldSubsystem
{
    GENERATED_BODY()

public:
    UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Narrative")
    float NarrativeScore = 0.0f;

    UFUNCTION(BlueprintCallable, Category = "Narrative")
    float GetNarrativeScore() const;
};
// NarrativeStateSubsystem.cpp

float UNarrativeStateSubsystem::GetNarrativeScore() const
{
    return NarrativeScore;
}
// NarrativeOutcomeCondition.h

UCLASS(BlueprintType, EditInlineNew)
class UNarrativeOutcomeCondition : public UGorgeousCondition
{
    GENERATED_BODY()

public:
    UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Narrative")
    float NegativeThreshold = -0.3f;

    UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Narrative")
    float PositiveThreshold = 0.4f;

    UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Narrative")
    int32 NegativeIndex = 0;

    UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Narrative")
    int32 NeutralIndex = 1;

    UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Narrative")
    int32 PositiveIndex = 2;

    virtual uint8 CheckCondition_Implementation() override;
};
// NarrativeOutcomeCondition.cpp

uint8 UNarrativeOutcomeCondition::CheckCondition_Implementation()
{
    const UWorld* World = GetWorld();
    if (!World)
    {
        return static_cast<uint8>(NeutralIndex);
    }

    const UNarrativeStateSubsystem* Subsystem = World->GetSubsystem<UNarrativeStateSubsystem>();
    if (!Subsystem)
    {
        return static_cast<uint8>(NeutralIndex);
    }

    const float Score = Subsystem->GetNarrativeScore();
    if (Score <= NegativeThreshold)
    {
        return static_cast<uint8>(NegativeIndex);
    }

    if (Score >= PositiveThreshold)
    {
        return static_cast<uint8>(PositiveIndex);
    }

    return static_cast<uint8>(NeutralIndex);
}
// NarrativeOutcomeChooser.h

UCLASS(BlueprintType)
class UNarrativeOutcomeChooser : public UGorgeousConditionalObjectChooser
{
    GENERATED_BODY()

    UNarrativeOutcomeChooser()
    {
        ConditionCheck = NewObject<UNarrativeOutcomeCondition>(this, TEXT("NarrativeOutcomeCondition"));

        UNarrativeOutcome_OV* NegativeOutcome = NewObject<UNarrativeOutcome_OV>(this, TEXT("NegativeOutcome"));
        NegativeOutcome->Outcome = FNarrativeOutcomeData(TEXT("BadEnding"), FText::FromString("The hero's journey ends in tragedy."), -50);

        UNarrativeOutcome_OV* NeutralOutcome = NewObject<UNarrativeOutcome_OV>(this, TEXT("NeutralOutcome"));
        NeutralOutcome->Outcome = FNarrativeOutcomeData(TEXT("NeutralEnding"), FText::FromString("The hero's journey ends with mixed results."), 0);

        UNarrativeOutcome_OV* PositiveOutcome = NewObject<UNarrativeOutcome_OV>(this, TEXT("PositiveOutcome"));
        PositiveOutcome->Outcome = FNarrativeOutcomeData(TEXT("GoodEnding"), FText::FromString("The hero's journey ends in triumph."), 50);

        Conditions = {
            NegativeOutcome,
            NeutralOutcome,
            PositiveOutcome
        };
    }
};
// NarrativeController.h

UCLASS()
class ANarrativeController : public AActor
{
    GENERATED_BODY()

public:
    UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Narrative")
    TObjectPtr<UNarrativeOutcomeChooser> OutcomeChooser;

    UFUNCTION(BlueprintCallable, Category = "Narrative")
    UNarrativeOutcome_OV* ResolveOutcome() const;
};
// NarrativeController.cpp

UNarrativeOutcome_OV* ANarrativeController::ResolveOutcome() const
{
    if (!OutcomeChooser)
    {
        return nullptr;
    }

    return Cast<UNarrativeOutcome_OV>(OutcomeChooser->DecideCondition());
}