Skip to content

10 — Інтеграція на C++

🇬🇧 English | 🇺🇦 Українська

Усе, що було до цього розділу, працює й з одного лише Blueprint. Цей розділ — для програмістів, які розширюють плагін на C++.

Залежності модулів

Плагін складається з чотирьох модулів, усі під Plugins/QuestSystem/Source/; додайте до PublicDependencyModuleNames/PrivateDependencyModuleNames свого .Build.cs ті, що реально використовуєте:

Модуль Що всередині
QuestSystem Core: UQuestData/UQuestObjectiveData, UQuestManagerComponent, UQuestWorldSubsystem, UQuestBlueprintLibrary, налаштування, валідація. Підключіть QuestSystemTypes.h, щоб отримати все одразу.
QuestSystemWorld Компоненти та актори для розміщення у світі: UQuestGiverComponent/UQuestReceiverComponent (від нього успадковується перший приклад у цьому розділі), маркери квестів, UQuestInteractorComponent, UQuestTargetComponent, готові актори (див. 05). Залежить від QuestSystem; також залежить від UMG (через віджет-маркер), тож підключайте його лише там, де ці класи справді потрібні. Підключіть QuestSystemWorldTypes.h, щоб отримати все одразу.
QuestSystemDebug Опційні dev-інструменти: оверлей налагодження та cheat-команди. Залежить від QuestSystem + UMG/Slate. Підключайте його заголовки напряму (спільного master-include немає).
QuestSystemTests Editor-only автотести — не те, від чого залежить проєкт-споживач.

Власний HostQuestSystem.Build.cs демо-хоста — робочий приклад: QuestSystem + QuestSystemWorld + QuestSystemDebug у PrivateDependencyModuleNames.

Кастомна логіка нагород

Повністю розібрана в 02 — Інтеграція без налаштування: успадкуйтеся від UQuestManagerComponent, перевизначте GiveQuestRewards_Implementation і вкажіть QuestManagerClass на свій сабклас у Project Settings. Це єдине перевизначення викликається рівно один раз на квест (захищено від дублів), на тому менеджері, який завершив квест.

void UMyQuestManagerComponent::GiveQuestRewards_Implementation(UQuestData* QuestData)
{
    const FQuestReward& Rewards = QuestData->Rewards;

    if (IsPersonalQuestManager())
    {
        if (AMyPlayerState* MyPS = Cast<AMyPlayerState>(GetOwningPlayerState()))
        {
            // RewardAmounts - звичайний TMap<FName, int32>: маршрутизуйте потрібні
            // ключі зі своїх даних квесту туди, де вони щось означають у вашій грі.
            if (const int32* XP = Rewards.RewardAmounts.Find(TEXT("ExperiencePoints")))
            {
                MyPS->AddExperience(*XP);
            }
            if (const int32* Gold = Rewards.RewardAmounts.Find(TEXT("Gold")))
            {
                MyPS->AddGold(*Gold);
            }
            for (const TSubclassOf<AActor>& ItemClass : Rewards.ItemRewards)
            {
                MyPS->GiveItem(ItemClass);
            }
        }
    }
    else if (IsPartyQuestManager())
    {
        // Shared-квест завершився на GameState - нагороджуємо кожного учасника.
        if (const FPartyQuestState* State = FindPartyQuestState(QuestData))
        {
            for (APlayerState* Participant : State->Participants)
            {
                // ... нагородити кожного учасника
            }
        }
    }
}

Не розміщуйте логіку видачі нагород в обробнику OnQuestCompleted — цей делегат спрацьовує після нагород і не захищений від дублів; він існує лише для UI/FX/аналітики.

Розширення компонентів Giver / Receiver

І UQuestGiverComponent, і UQuestReceiverComponent виставляють точки кастомізації як BlueprintNativeEvent, тож сабклас на C++ перевизначає версію _Implementation точно так само, як і будь-яку іншу нативну подію:

UCLASS()
class UMyQuestGiverComponent : public UQuestGiverComponent
{
    GENERATED_BODY()
protected:
    virtual bool ShouldOfferQuestToPlayer_Implementation(UQuestData* Quest, APlayerController* Player) const override
    {
        // наприклад, обмеження за рівнем гравця, що зберігається в кастомному PlayerState.
        // Вбудованого поля "рекомендований рівень" немає - використовуємо домовленість через CustomData.
        if (const AMyPlayerState* MyPS = Player ? Player->GetPlayerState<AMyPlayerState>() : nullptr)
        {
            const FString* RequiredLevelStr = Quest->CustomData.Find(TEXT("RequiredLevel"));
            const int32 RequiredLevel = RequiredLevelStr ? FCString::Atoi(**RequiredLevelStr) : 1;
            return MyPS->GetLevel() >= RequiredLevel;
        }
        return Super::ShouldOfferQuestToPlayer_Implementation(Quest, Player);
    }
};

Надсилання подій зі своїх систем

Якщо у вашій грі вже є системи бою, інвентарю чи діалогів, викликайте функції сповіщення напряму з них, а не використовуйте готові актори світу з 05:

// В обробці шкоди/смерті, щойно ворог справді помер:
void AMyEnemy::Die(APlayerController* Killer)
{
    if (Killer && Killer->PlayerState)
    {
        UQuestBlueprintLibrary::NotifyKillEvent(Killer->PlayerState, EnemyTypeTag);
    }
}

// У системі інвентарю, щойно предмет справді доданий:
void UMyInventoryComponent::AddItem(FName ItemID, int32 Count)
{
    // ... ваша логіка інвентарю ...
    if (APlayerState* PS = GetOwningPlayerState())
    {
        UQuestBlueprintLibrary::NotifyCollectEvent(PS, ItemID, Count);
    }
}

Надсилайте подію лише після того, як базовий ігровий стан справді змінився (ворог дійсно мертвий, предмет дійсно в інвентарі) — квест-система довіряє події як факту й ніяк не може перевірити її на відповідність вашому ігровому стану.

Читання прогресу з C++ (свій UI)

UQuestManagerComponent* Manager = UQuestBlueprintLibrary::GetQuestManager(PlayerState);
for (const FActiveQuest& Quest : Manager->GetActiveQuests())
{
    if (Quest.State != EQuestState::Active) { continue; }

    for (UQuestObjectiveData* Objective : Quest.QuestData->Objectives)
    {
        const FQuestObjectiveProgress Progress = Manager->GetObjectiveProgress(Quest.QuestData, Objective);
        // Progress.CurrentProgress / Progress.TargetProgress / Progress.State
    }
}

Оверлей модуля QuestSystemDebug (QuestDebugQuestCard.cpp/QuestDebugObjectiveCard.cpp у Plugins/QuestSystem/Source/QuestSystemDebug/Private/) — повноцінний робочий приклад саме цього патерну в масштабі: читання цілей, прогресу й стану кожного квесту для побудови віджетів UMG цілком на C++, без жодного WBP-ассету — варто прочитати, якщо ви будуєте свій quest UI на C++.

Делегати, а не опитування

Коли треба реагувати на конкретну подію, надавайте перевагу прив'язці до делегатів менеджера (перелічені в 04 — Подієвий прогрес) замість опитування GetActiveQuests() щокадру:

QuestManager->OnObjectiveUpdated.AddDynamic(this, &AMyHUD::HandleObjectiveUpdated);
QuestManager->OnQuestCompleted.AddDynamic(this, &AMyHUD::HandleQuestCompleted);

Дивіться також