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);
Дивіться також¶
- 02 — Інтеграція без налаштування про те, як ваші кастомні класи підключаються без зміни
PlayerState/GameState. - 06 — Мультиплеєр про API party-квестів, який використовується під час нагородження Shared/Individual квестів.