feat(collection): P-013收集系统三层架构解耦实现
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -26,6 +26,7 @@ public class ChildHood : ModuleRules
|
||||
PublicIncludePaths.AddRange(new string[] {
|
||||
"ChildHood",
|
||||
"ChildHood/Interaction",
|
||||
"ChildHood/Collection",
|
||||
"ChildHood/Variant_Platforming",
|
||||
"ChildHood/Variant_Platforming/Animation",
|
||||
"ChildHood/Variant_Combat",
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
#include "InputActionValue.h"
|
||||
#include "ChildHood.h"
|
||||
#include "Interaction/InteractionPromptComponent.h"
|
||||
#include "Collection/CollectionHUDComponent.h"
|
||||
#include "SystemDriver.h"
|
||||
#include "Interaction/InteractionManager.h"
|
||||
#include "Interaction/EffectManager.h"
|
||||
@@ -67,6 +68,9 @@ AChildHoodCharacter::AChildHoodCharacter()
|
||||
|
||||
// 创建交互提示组件(替代旧 InteractionManager)
|
||||
PromptComponent = CreateDefaultSubobject<UInteractionPromptComponent>(TEXT("PromptComponent"));
|
||||
|
||||
// 创建收集系统 HUD 组件(表现层,挂在角色上;Tick 兜底查找 Driver)
|
||||
CollectionHUDComponent = CreateDefaultSubobject<UCollectionHUDComponent>(TEXT("CollectionHUDComponent"));
|
||||
}
|
||||
|
||||
void AChildHoodCharacter::BeginPlay()
|
||||
@@ -81,6 +85,15 @@ void AChildHoodCharacter::BeginPlay()
|
||||
PromptComponent->Initialize(Driver);
|
||||
}
|
||||
}
|
||||
|
||||
// 初始化收集系统 HUD 组件(Driver 弱引用;Tick 会兜底查找,规避 BeginPlay 时序竞争)
|
||||
if (CollectionHUDComponent)
|
||||
{
|
||||
if (ASystemDriver* Driver = ASystemDriver::Get(this))
|
||||
{
|
||||
CollectionHUDComponent->Initialize(Driver);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void AChildHoodCharacter::SetupPlayerInputComponent(UInputComponent* PlayerInputComponent)
|
||||
@@ -235,6 +248,11 @@ UInteractionPromptComponent* AChildHoodCharacter::GetPromptComponent() const
|
||||
return PromptComponent;
|
||||
}
|
||||
|
||||
UCollectionHUDComponent* AChildHoodCharacter::GetCollectionHUDComponent() const
|
||||
{
|
||||
return CollectionHUDComponent;
|
||||
}
|
||||
|
||||
void AChildHoodCharacter::SetInteracting(bool bValue)
|
||||
{
|
||||
bIsInteracting = bValue;
|
||||
|
||||
@@ -15,6 +15,7 @@ class USpringArmComponent;
|
||||
class UCameraComponent;
|
||||
class UInputAction;
|
||||
class UInteractionPromptComponent;
|
||||
class UCollectionHUDComponent;
|
||||
class ASystemDriver;
|
||||
struct FInputActionValue;
|
||||
|
||||
@@ -71,6 +72,10 @@ protected:
|
||||
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Interaction", meta = (AllowPrivateAccess = "true"))
|
||||
UInteractionPromptComponent* PromptComponent;
|
||||
|
||||
/** 收集系统 HUD 组件 — 表现层,只读 CollectionManager 缓存驱动收集 UI */
|
||||
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Collection", meta = (AllowPrivateAccess = "true"))
|
||||
UCollectionHUDComponent* CollectionHUDComponent;
|
||||
|
||||
/** Whether the character is currently in an interaction */
|
||||
bool bIsInteracting = false;
|
||||
|
||||
@@ -85,6 +90,10 @@ public:
|
||||
UFUNCTION(BlueprintCallable, BlueprintPure, Category = "Interaction")
|
||||
UInteractionPromptComponent* GetPromptComponent() const;
|
||||
|
||||
/** 获取收集系统 HUD 组件 */
|
||||
UFUNCTION(BlueprintCallable, BlueprintPure, Category = "Collection")
|
||||
UCollectionHUDComponent* GetCollectionHUDComponent() const;
|
||||
|
||||
/** Set interaction state — called by interaction system */
|
||||
UFUNCTION(BlueprintCallable, Category = "Interaction")
|
||||
void SetInteracting(bool bValue);
|
||||
|
||||
@@ -0,0 +1,58 @@
|
||||
// ChildHood Project - 收集完成提示 UI 实现
|
||||
// 三段时序:淡入 → 停留 → 淡出 → 移除(自管理,HUDComponent 按需创建后无需追踪)
|
||||
|
||||
#include "Collection/CollectionCompleteWidget.h"
|
||||
#include "Components/TextBlock.h"
|
||||
#include "Engine/World.h"
|
||||
#include "TimerManager.h"
|
||||
|
||||
void UCollectionCompleteWidget::NativeOnInitialized()
|
||||
{
|
||||
Super::NativeOnInitialized();
|
||||
}
|
||||
|
||||
void UCollectionCompleteWidget::ShowComplete(const FString& Text, float DisplayDuration, float InFadeOutDuration)
|
||||
{
|
||||
CachedFadeOutDuration = InFadeOutDuration;
|
||||
|
||||
if (CompleteTextBlock)
|
||||
{
|
||||
CompleteTextBlock->SetText(FText::FromString(Text));
|
||||
}
|
||||
|
||||
// 淡入
|
||||
if (FadeInAnim)
|
||||
{
|
||||
PlayAnimation(FadeInAnim);
|
||||
}
|
||||
|
||||
// 停留时长后触发淡出
|
||||
if (UWorld* World = GetWorld())
|
||||
{
|
||||
World->GetTimerManager().SetTimer(StayTimerHandle, [this]()
|
||||
{
|
||||
StartFadeOut();
|
||||
}, DisplayDuration, false);
|
||||
}
|
||||
}
|
||||
|
||||
void UCollectionCompleteWidget::StartFadeOut()
|
||||
{
|
||||
if (FadeOutAnim)
|
||||
{
|
||||
PlayAnimation(FadeOutAnim);
|
||||
}
|
||||
|
||||
if (UWorld* World = GetWorld())
|
||||
{
|
||||
World->GetTimerManager().SetTimer(FadeOutTimerHandle, [this]()
|
||||
{
|
||||
OnFadeOutComplete();
|
||||
}, CachedFadeOutDuration, false);
|
||||
}
|
||||
}
|
||||
|
||||
void UCollectionCompleteWidget::OnFadeOutComplete()
|
||||
{
|
||||
RemoveFromParent();
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
// ChildHood Project - 收集完成提示 UI 基类
|
||||
// 对应技术方案: docs/technical/收集系统-技术方案.md 第3节(UCollectionCompleteWidget)+ 第7节
|
||||
// 职责:全部收集完成时的屏幕中央提示(淡入→停留→淡出→移除)
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
#include "Blueprint/UserWidget.h"
|
||||
#include "CollectionCompleteWidget.generated.h"
|
||||
|
||||
class UTextBlock;
|
||||
|
||||
/**
|
||||
* UCollectionCompleteWidget — 全部收集完成中心提示(执行案第四节「全部收集完成表现」)。
|
||||
*
|
||||
* 屏幕正中央弹出「杂物全部收集完成!」白色大字,显示 2.0s 后淡出移除。
|
||||
* 三段时序:淡入 → 停留(DisplayDuration) → 淡出 → RemoveFromParent。
|
||||
*
|
||||
* 绑定要求(蓝图 WBP_CollectionComplete 继承本类):
|
||||
* - TextBlock 名 "CompleteTextBlock" — 完成文字
|
||||
* - WidgetAnimation 名 "FadeInAnim" — 淡入动画
|
||||
* - WidgetAnimation 名 "FadeOutAnim" — 淡出动画
|
||||
*
|
||||
* 布局要求:Anchors Center,白色大字,屏幕正中。
|
||||
*/
|
||||
UCLASS(Abstract, Blueprintable)
|
||||
class UCollectionCompleteWidget : public UUserWidget
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
public:
|
||||
|
||||
/** 显示完成提示:设置文字,启动淡入→停留→淡出时序 */
|
||||
UFUNCTION(BlueprintCallable, Category = "Collection|Complete")
|
||||
void ShowComplete(const FString& Text, float DisplayDuration, float InFadeOutDuration);
|
||||
|
||||
protected:
|
||||
|
||||
/** 完成文字 TextBlock,蓝图子类必须有同名 TextBlock */
|
||||
UPROPERTY(meta = (BindWidget))
|
||||
UTextBlock* CompleteTextBlock;
|
||||
|
||||
/** 淡入动画,蓝图子类必须有同名 WidgetAnimation */
|
||||
UPROPERTY(meta = (BindWidgetAnim), Transient)
|
||||
UWidgetAnimation* FadeInAnim;
|
||||
|
||||
/** 淡出动画,蓝图子类必须有同名 WidgetAnimation */
|
||||
UPROPERTY(meta = (BindWidgetAnim), Transient)
|
||||
UWidgetAnimation* FadeOutAnim;
|
||||
|
||||
/** 停留计时器(淡入结束后启动,到时触发淡出)*/
|
||||
FTimerHandle StayTimerHandle;
|
||||
|
||||
/** 淡出计时器(淡出开始后启动,到时移除 Widget)*/
|
||||
FTimerHandle FadeOutTimerHandle;
|
||||
|
||||
/** 淡出时长(缓存)*/
|
||||
float CachedFadeOutDuration = 0.5f;
|
||||
|
||||
virtual void NativeOnInitialized() override;
|
||||
|
||||
private:
|
||||
|
||||
/** 停留结束 → 播放淡出动画 */
|
||||
void StartFadeOut();
|
||||
|
||||
/** 淡出时长到 → RemoveFromParent */
|
||||
void OnFadeOutComplete();
|
||||
};
|
||||
@@ -0,0 +1,265 @@
|
||||
// ChildHood Project - 收集系统表现层 实现
|
||||
// 对应技术方案: docs/technical/收集系统-技术方案.md 第5节(数据结构)+ 第6节(行为规则实现)+ 第9节
|
||||
|
||||
#include "Collection/CollectionHUDComponent.h"
|
||||
#include "SystemDriver.h"
|
||||
#include "Collection/CollectionManager.h"
|
||||
#include "Collection/CollectionNotifWidget.h"
|
||||
#include "Collection/CollectionProgressWidget.h"
|
||||
#include "Collection/CollectionCompleteWidget.h"
|
||||
#include "Engine/World.h"
|
||||
#include "TimerManager.h"
|
||||
#include "GameFramework/PlayerController.h"
|
||||
#include "Blueprint/UserWidget.h"
|
||||
#include "ChildHood.h"
|
||||
|
||||
// ============================================================================
|
||||
// 构造 / 生命周期
|
||||
// ============================================================================
|
||||
|
||||
UCollectionHUDComponent::UCollectionHUDComponent()
|
||||
{
|
||||
// 需要每帧 Tick 读取 Manager 缓存(开销极低:几个 int32 比较)
|
||||
PrimaryComponentTick.bCanEveryTick = true;
|
||||
PrimaryComponentTick.bStartWithTickEnabled = true;
|
||||
}
|
||||
|
||||
void UCollectionHUDComponent::Initialize(ASystemDriver* InDriver)
|
||||
{
|
||||
DriverPtr = InDriver;
|
||||
}
|
||||
|
||||
void UCollectionHUDComponent::BeginPlay()
|
||||
{
|
||||
Super::BeginPlay();
|
||||
}
|
||||
|
||||
void UCollectionHUDComponent::EndPlay(const EEndPlayReason::Type EndPlayReason)
|
||||
{
|
||||
// 清理 timer:NotifDelayTimer 的 lambda 与 CompleteTimer 的成员函数绑定均捕获裸 this,
|
||||
// 组件销毁后 fire 会野指针。显式 ClearTimer 防御(UE5 Widget/Timer 卫生写法,与项目其他 Character 一致)。
|
||||
if (UWorld* World = GetWorld())
|
||||
{
|
||||
World->GetTimerManager().ClearTimer(NotifDelayTimerHandle);
|
||||
World->GetTimerManager().ClearTimer(CompleteTimerHandle);
|
||||
}
|
||||
|
||||
// 移除挂载的通知 Widget
|
||||
for (UCollectionNotifWidget* Notif : ActiveNotifications)
|
||||
{
|
||||
if (Notif)
|
||||
{
|
||||
Notif->RemoveFromParent();
|
||||
}
|
||||
}
|
||||
ActiveNotifications.Empty();
|
||||
|
||||
// 移除常驻进度 Widget
|
||||
if (ProgressWidget)
|
||||
{
|
||||
ProgressWidget->RemoveFromParent();
|
||||
ProgressWidget = nullptr;
|
||||
}
|
||||
|
||||
Super::EndPlay(EndPlayReason);
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Tick — 只读 Manager 缓存,检测变化驱动 UI
|
||||
// ============================================================================
|
||||
|
||||
void UCollectionHUDComponent::TickComponent(float DeltaTime, ELevelTick TickType, FActorComponentTickFunction* ThisTickFunction)
|
||||
{
|
||||
Super::TickComponent(DeltaTime, TickType, ThisTickFunction);
|
||||
|
||||
// 获取 Driver(优先缓存,失效则全局查找并更新缓存)
|
||||
ASystemDriver* Driver = DriverPtr.Get();
|
||||
if (!Driver)
|
||||
{
|
||||
Driver = ASystemDriver::Get(GetWorld());
|
||||
if (Driver)
|
||||
{
|
||||
DriverPtr = Driver;
|
||||
}
|
||||
}
|
||||
if (!Driver) return;
|
||||
|
||||
// 懒创建常驻进度 Widget(首次有效 Tick 时,确保 World 就绪 + 规避 Driver BeginPlay 时序竞争)
|
||||
if (!ProgressWidget && ProgressWidgetClass)
|
||||
{
|
||||
CreateProgressWidget(Driver);
|
||||
}
|
||||
if (!ProgressWidget) return;
|
||||
|
||||
const FCollectionManager& CollMgr = Driver->GetCollectionManager();
|
||||
const FCollectionCachedInfo& Cache = CollMgr.GetCachedInfo();
|
||||
|
||||
// === 检测进度变化(步骤4:弹收集通知)===
|
||||
// LastSeenProgress < 0 表示首次 Tick,由 CreateProgressWidget 初始化为当前进度,不触发通知
|
||||
if (Cache.Progress != LastSeenProgress && LastSeenProgress >= 0)
|
||||
{
|
||||
// 更新进度 UI(同步)+ 弹性动画
|
||||
UpdateProgress(Cache.Progress, Cache.TotalCount);
|
||||
|
||||
// 弹收集通知(延迟 NotifDelay 后创建,避免与拾取通知视觉重叠 — 执行案第二节)
|
||||
const float NotifDelay = CollMgr.GetConfig().NotifDelay;
|
||||
const FString NotifText = Cache.PendingNotificationText;
|
||||
if (UWorld* World = GetWorld())
|
||||
{
|
||||
World->GetTimerManager().SetTimer(NotifDelayTimerHandle, [this, NotifText]()
|
||||
{
|
||||
ShowNotification(NotifText);
|
||||
}, NotifDelay, false);
|
||||
}
|
||||
}
|
||||
LastSeenProgress = Cache.Progress;
|
||||
|
||||
// === 检测完成(步骤5:完成判定 → 完成表现)===
|
||||
if (Cache.bJustCompleted && !bCompletionShown)
|
||||
{
|
||||
TriggerCompletionSequence(Driver);
|
||||
bCompletionShown = true;
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// 进度 Widget
|
||||
// ============================================================================
|
||||
|
||||
void UCollectionHUDComponent::CreateProgressWidget(ASystemDriver* Driver)
|
||||
{
|
||||
UWorld* World = GetWorld();
|
||||
if (!World) return;
|
||||
|
||||
APlayerController* PC = World->GetFirstPlayerController();
|
||||
if (!PC) return;
|
||||
|
||||
ProgressWidget = CreateWidget<UCollectionProgressWidget>(PC, ProgressWidgetClass);
|
||||
if (!ProgressWidget) return;
|
||||
|
||||
// 常驻显示(ZOrder 5,低于通知/完成提示)
|
||||
ProgressWidget->AddToViewport(5);
|
||||
|
||||
// 初始显示已恢复的进度(执行案第三节:游戏开始后始终可见)
|
||||
const FCollectionCachedInfo& Cache = Driver->GetCollectionManager().GetCachedInfo();
|
||||
ProgressWidget->UpdateProgress(Cache.Progress, Cache.TotalCount);
|
||||
if (Cache.bIsComplete)
|
||||
{
|
||||
ProgressWidget->SetCompleteStyle();
|
||||
}
|
||||
|
||||
// 记录初始进度,避免首次 Tick 误触发通知
|
||||
LastSeenProgress = Cache.Progress;
|
||||
|
||||
UE_LOG(LogChildHood, Log, TEXT("CollectionHUDComponent: 进度 Widget 创建完成,初始进度 %d/%d"),
|
||||
Cache.Progress, Cache.TotalCount);
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// 收集通知
|
||||
// ============================================================================
|
||||
|
||||
void UCollectionHUDComponent::ShowNotification(const FString& Text)
|
||||
{
|
||||
ASystemDriver* Driver = DriverPtr.Get();
|
||||
if (!Driver) return;
|
||||
|
||||
const FCollectionConfig& Config = Driver->GetCollectionManager().GetConfig();
|
||||
|
||||
// 同屏最大数量限制(执行案第二节)
|
||||
if (ActiveNotifications.Num() >= Config.MaxSimultaneousNotifs)
|
||||
{
|
||||
UE_LOG(LogChildHood, Verbose, TEXT("CollectionHUDComponent: 通知已达上限 %d,跳过"), Config.MaxSimultaneousNotifs);
|
||||
return;
|
||||
}
|
||||
|
||||
UWorld* World = GetWorld();
|
||||
if (!NotifWidgetClass || !World) return;
|
||||
|
||||
APlayerController* PC = World->GetFirstPlayerController();
|
||||
if (!PC) return;
|
||||
|
||||
UCollectionNotifWidget* NotifWidget = CreateWidget<UCollectionNotifWidget>(PC, NotifWidgetClass);
|
||||
if (!NotifWidget) return;
|
||||
|
||||
// 绑定完成回调(淡出移除时清理 ActiveNotifications)
|
||||
NotifWidget->OnFinished.BindUFunction(this, FName("OnNotificationRemoved"));
|
||||
|
||||
NotifWidget->InitializeNotif(Text, Config.NotifTextColor,
|
||||
Config.NotifFadeInDuration, Config.NotifStayDuration, Config.NotifFadeOutDuration);
|
||||
NotifWidget->AddToViewport(10);
|
||||
|
||||
ActiveNotifications.Add(NotifWidget);
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// 进度更新
|
||||
// ============================================================================
|
||||
|
||||
void UCollectionHUDComponent::UpdateProgress(int32 Current, int32 Total)
|
||||
{
|
||||
if (ProgressWidget)
|
||||
{
|
||||
ProgressWidget->UpdateProgress(Current, Total);
|
||||
// 数字弹性动画(执行案第三节)
|
||||
ProgressWidget->PlayBounceAnimation();
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// 完成序列
|
||||
// ============================================================================
|
||||
|
||||
void UCollectionHUDComponent::TriggerCompletionSequence(ASystemDriver* Driver)
|
||||
{
|
||||
if (!ProgressWidget) return;
|
||||
|
||||
const FCollectionConfig& Config = Driver->GetCollectionManager().GetConfig();
|
||||
|
||||
// 1. 进度 UI 变金色 + 完成弹性动画(执行案第四节)
|
||||
ProgressWidget->SetCompleteStyle();
|
||||
ProgressWidget->PlayCompleteAnimation();
|
||||
|
||||
// 2. 延迟弹出完成提示(等最后一条通知消失 + CompletePromptDelay)
|
||||
const float TotalDelay = Config.NotifDelay + Config.NotifStayDuration
|
||||
+ Config.NotifFadeOutDuration + Config.CompletePromptDelay;
|
||||
if (UWorld* World = GetWorld())
|
||||
{
|
||||
World->GetTimerManager().SetTimer(CompleteTimerHandle, this,
|
||||
&UCollectionHUDComponent::ShowCompletionPrompt, TotalDelay, false);
|
||||
}
|
||||
|
||||
UE_LOG(LogChildHood, Log, TEXT("CollectionHUDComponent: 触发完成序列,%.2fs 后弹出完成提示"), TotalDelay);
|
||||
}
|
||||
|
||||
void UCollectionHUDComponent::ShowCompletionPrompt()
|
||||
{
|
||||
UWorld* World = GetWorld();
|
||||
if (!CompleteWidgetClass || !World) return;
|
||||
|
||||
APlayerController* PC = World->GetFirstPlayerController();
|
||||
if (!PC) return;
|
||||
|
||||
UCollectionCompleteWidget* CompleteWidget = CreateWidget<UCollectionCompleteWidget>(PC, CompleteWidgetClass);
|
||||
if (!CompleteWidget) return;
|
||||
|
||||
// 完成提示参数(执行案第四节:白色大字,显示 2.0s 后淡出)
|
||||
ASystemDriver* Driver = DriverPtr.Get();
|
||||
const float CompleteDuration = Driver ? Driver->GetCollectionManager().GetConfig().CompletePromptDuration : 2.0f;
|
||||
const float FadeOutDuration = 0.5f;
|
||||
|
||||
CompleteWidget->ShowComplete(TEXT("杂物全部收集完成!"), CompleteDuration, FadeOutDuration);
|
||||
CompleteWidget->AddToViewport(20);
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// 通知清理回调
|
||||
// ============================================================================
|
||||
|
||||
void UCollectionHUDComponent::OnNotificationRemoved(UCollectionNotifWidget* Widget)
|
||||
{
|
||||
if (Widget)
|
||||
{
|
||||
ActiveNotifications.RemoveSingle(Widget);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,102 @@
|
||||
// ChildHood Project - 收集系统表现层组件
|
||||
// 对应技术方案: docs/technical/收集系统-技术方案.md 第3节(UCollectionHUDComponent)+ 第5节 + 第6节
|
||||
// 职责:只读 FCollectionManager 缓存,驱动收集通知/进度/完成 UI
|
||||
// 架构:UActorComponent(挂在 AChildHoodCharacter 上),自有 TickComponent,
|
||||
// 只读 Manager 缓存,绝不访问 InteractableComponent 或 UPersistenceSubsystem
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
#include "Components/ActorComponent.h"
|
||||
#include "CollectionHUDComponent.generated.h"
|
||||
|
||||
class ASystemDriver;
|
||||
class UCollectionNotifWidget;
|
||||
class UCollectionProgressWidget;
|
||||
class UCollectionCompleteWidget;
|
||||
|
||||
/**
|
||||
* UCollectionHUDComponent — 收集系统表现层(执行案第二/三/四节 UI)。
|
||||
*
|
||||
* 数据流(技术方案第9节 Presentation 流):
|
||||
* TickComponent 每帧
|
||||
* → Driver->GetCollectionManager().GetCachedInfo()
|
||||
* → Cache.Progress != LastSeenProgress? → UpdateProgress + SetTimer(NotifDelay) → ShowNotification
|
||||
* → Cache.bJustCompleted && !bCompletionShown? → TriggerCompletionSequence
|
||||
*
|
||||
* 不持有 Manager 所有权,不写 Manager 状态,不碰 PersistenceSubsystem(技术方案注意事项3)。
|
||||
*/
|
||||
UCLASS(ClassGroup = (Collection), meta = (BlueprintSpawnableComponent))
|
||||
class UCollectionHUDComponent : public UActorComponent
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
public:
|
||||
|
||||
UCollectionHUDComponent();
|
||||
|
||||
/** 初始化(Character BeginPlay 调用,存 Driver 弱引用;可为空,Tick 兜底查找)*/
|
||||
UFUNCTION(BlueprintCallable, Category = "Collection|HUD")
|
||||
void Initialize(ASystemDriver* InDriver);
|
||||
|
||||
protected:
|
||||
|
||||
virtual void BeginPlay() override;
|
||||
virtual void EndPlay(const EEndPlayReason::Type EndPlayReason) override;
|
||||
virtual void TickComponent(float DeltaTime, ELevelTick TickType, FActorComponentTickFunction* ThisTickFunction) override;
|
||||
|
||||
// === UI 类引用(蓝图/实例设置:创建 WBP 后在此指定)===
|
||||
|
||||
/** 收集通知 Widget 蓝图类(WBP_CollectionNotification)*/
|
||||
UPROPERTY(EditDefaultsOnly, BlueprintReadWrite, Category = "Collection|UI")
|
||||
TSubclassOf<UCollectionNotifWidget> NotifWidgetClass;
|
||||
|
||||
/** 进度 HUD Widget 蓝图类(WBP_CollectionProgress)*/
|
||||
UPROPERTY(EditDefaultsOnly, BlueprintReadWrite, Category = "Collection|UI")
|
||||
TSubclassOf<UCollectionProgressWidget> ProgressWidgetClass;
|
||||
|
||||
/** 完成提示 Widget 蓝图类(WBP_CollectionComplete)*/
|
||||
UPROPERTY(EditDefaultsOnly, BlueprintReadWrite, Category = "Collection|UI")
|
||||
TSubclassOf<UCollectionCompleteWidget> CompleteWidgetClass;
|
||||
|
||||
private:
|
||||
|
||||
// === Driver 弱引用(Tick 时优先用,失效则全局查找)===
|
||||
TWeakObjectPtr<ASystemDriver> DriverPtr;
|
||||
|
||||
// === 变化检测(Presentation 层状态)===
|
||||
int32 LastSeenProgress = -1; // 上次看到的进度,-1 = 未初始化
|
||||
bool bCompletionShown = false; // 完成表现是否已显示
|
||||
|
||||
// === UI 实例 ===
|
||||
UPROPERTY()
|
||||
TArray<TObjectPtr<UCollectionNotifWidget>> ActiveNotifications;
|
||||
|
||||
UPROPERTY()
|
||||
TObjectPtr<UCollectionProgressWidget> ProgressWidget = nullptr;
|
||||
|
||||
// === 定时器 ===
|
||||
FTimerHandle NotifDelayTimerHandle; // 通知延迟(0.3s)
|
||||
FTimerHandle CompleteTimerHandle; // 完成提示延迟
|
||||
|
||||
// === 内部方法 ===
|
||||
|
||||
/** 创建常驻进度 Widget(懒创建:首次有效 Tick 时,避免与 Driver BeginPlay 时序竞争)*/
|
||||
void CreateProgressWidget(ASystemDriver* Driver);
|
||||
|
||||
/** 弹出收集通知(检查同屏最大数量限制)*/
|
||||
void ShowNotification(const FString& Text);
|
||||
|
||||
/** 更新进度 UI + 弹性动画 */
|
||||
void UpdateProgress(int32 Current, int32 Total);
|
||||
|
||||
/** 触发完成序列:进度金色 + 延迟完成提示 */
|
||||
void TriggerCompletionSequence(ASystemDriver* Driver);
|
||||
|
||||
/** 显示完成提示(延迟后调用)*/
|
||||
void ShowCompletionPrompt();
|
||||
|
||||
/** 通知 Widget 淡出移除回调 — 从 ActiveNotifications 清理 */
|
||||
UFUNCTION()
|
||||
void OnNotificationRemoved(UCollectionNotifWidget* Widget);
|
||||
};
|
||||
@@ -0,0 +1,140 @@
|
||||
// ChildHood Project - 收集系统逻辑核心 实现
|
||||
// 对应技术方案: docs/technical/收集系统-技术方案.md 第5节(数据结构)+ 第6节(行为规则实现)+ 第9节(数据流)
|
||||
|
||||
#include "Collection/CollectionManager.h"
|
||||
#include "Interaction/PersistenceSubsystem.h"
|
||||
#include "ChildHood.h"
|
||||
|
||||
// ============================================================================
|
||||
// 初始化
|
||||
// ============================================================================
|
||||
|
||||
void FCollectionManager::Initialize()
|
||||
{
|
||||
// 重置本地运行时状态
|
||||
Progress = 0;
|
||||
CollectedIDs.Empty();
|
||||
bIsComplete = false;
|
||||
|
||||
// 初始化缓存(稳定态,无瞬时通知)
|
||||
CachedInfo.Progress = 0;
|
||||
CachedInfo.TotalCount = Config.TotalCount;
|
||||
CachedInfo.bIsComplete = false;
|
||||
CachedInfo.ResetTransient();
|
||||
}
|
||||
|
||||
void FCollectionManager::RestoreFromPersistence(UPersistenceSubsystem* Subsystem)
|
||||
{
|
||||
// 缓存持久层弱引用,供后续 SaveStateToPersistence 使用
|
||||
PersistencePtr = Subsystem;
|
||||
|
||||
if (!Subsystem)
|
||||
{
|
||||
UE_LOG(LogChildHood, Warning, TEXT("CollectionManager: RestoreFromPersistence - 持久层为空,保持初始状态"));
|
||||
return;
|
||||
}
|
||||
|
||||
// 从持久层恢复
|
||||
TSet<FName> SavedIDs;
|
||||
int32 SavedProgress = 0;
|
||||
bool bSavedComplete = false;
|
||||
Subsystem->GetCollectionState(SavedIDs, SavedProgress, bSavedComplete);
|
||||
|
||||
CollectedIDs = MoveTemp(SavedIDs);
|
||||
Progress = SavedProgress;
|
||||
bIsComplete = bSavedComplete;
|
||||
|
||||
// 初始化缓存(恢复后的稳定态,无瞬时通知 — 不触发 UI 动画)
|
||||
CachedInfo.Progress = Progress;
|
||||
CachedInfo.TotalCount = Config.TotalCount;
|
||||
CachedInfo.bIsComplete = bIsComplete;
|
||||
CachedInfo.ResetTransient();
|
||||
|
||||
UE_LOG(LogChildHood, Log, TEXT("CollectionManager: 从持久层恢复 - 进度 %d/%d, 完成 %s"),
|
||||
Progress, Config.TotalCount, bIsComplete ? TEXT("是") : TEXT("否"));
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// 物品事件入口
|
||||
// ============================================================================
|
||||
|
||||
void FCollectionManager::OnItemObtained(FName ItemId, const FString& DisplayName, const FName& Category)
|
||||
{
|
||||
// 步骤1: 查品类 — 非追踪品类忽略(执行案第一节步骤1)
|
||||
if (Category != Config.TrackedCategory)
|
||||
{
|
||||
UE_LOG(LogChildHood, Verbose, TEXT("CollectionManager: 忽略非追踪品类物品 '%s' (Category=%s)"),
|
||||
*DisplayName, *Category.ToString());
|
||||
return;
|
||||
}
|
||||
|
||||
ProcessNewCollection(ItemId, DisplayName);
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// 内部:处理新收集(步骤2-5)
|
||||
// ============================================================================
|
||||
|
||||
void FCollectionManager::ProcessNewCollection(FName ItemId, const FString& DisplayName)
|
||||
{
|
||||
// 步骤2: 查重复 — 已收集则流程结束,不增加 progress,不弹收集通知
|
||||
if (CollectedIDs.Contains(ItemId))
|
||||
{
|
||||
UE_LOG(LogChildHood, Verbose, TEXT("CollectionManager: 重复物品 '%s',忽略"), *DisplayName);
|
||||
return;
|
||||
}
|
||||
|
||||
// 步骤3: 记录 — 加入集合,Progress 取 Set 大小作为权威值
|
||||
CollectedIDs.Add(ItemId);
|
||||
Progress = CollectedIDs.Num();
|
||||
SaveStateToPersistence();
|
||||
|
||||
// 步骤4: 刷新 Presentation 缓存(供 HUDComponent Tick 检测)
|
||||
// LastCollectedItemId / bJustCompleted 由本方法控制;RefreshCachedInfo 只刷新进度+通知文本
|
||||
CachedInfo.LastCollectedItemId = ItemId;
|
||||
CachedInfo.bJustCompleted = false; // 默认非完成,步骤5判定时置 true
|
||||
RefreshCachedInfo(DisplayName);
|
||||
|
||||
UE_LOG(LogChildHood, Log, TEXT("CollectionManager: 新收集 '%s',进度 %d/%d"),
|
||||
*DisplayName, Progress, Config.TotalCount);
|
||||
|
||||
// 步骤5: 完成判定 — progress 达到 TotalCount 则 isComplete = true
|
||||
if (Progress >= Config.TotalCount && !bIsComplete)
|
||||
{
|
||||
bIsComplete = true;
|
||||
CachedInfo.bJustCompleted = true; // 触发 HUDComponent 完成表现
|
||||
CachedInfo.bIsComplete = true;
|
||||
SaveStateToPersistence();
|
||||
UE_LOG(LogChildHood, Log, TEXT("CollectionManager: 全部收集完成!"));
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// 内部:刷新 Presentation 缓存
|
||||
// ============================================================================
|
||||
|
||||
void FCollectionManager::RefreshCachedInfo(const FString& DisplayName)
|
||||
{
|
||||
// 更新进度相关字段
|
||||
CachedInfo.Progress = Progress;
|
||||
CachedInfo.TotalCount = Config.TotalCount;
|
||||
CachedInfo.bIsComplete = bIsComplete;
|
||||
|
||||
// 生成收集通知文本(执行案第二节):「发现了新的杂物,进度 X/Y」
|
||||
CachedInfo.PendingNotificationText = FString::Printf(
|
||||
TEXT("发现了新的杂物,进度 %d/%d"), Progress, Config.TotalCount);
|
||||
|
||||
// 注意:LastCollectedItemId / bJustCompleted 由 ProcessNewCollection 控制,此处不碰
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// 内部:持久化
|
||||
// ============================================================================
|
||||
|
||||
void FCollectionManager::SaveStateToPersistence()
|
||||
{
|
||||
if (UPersistenceSubsystem* Subsystem = PersistencePtr.Get())
|
||||
{
|
||||
Subsystem->SetCollectionState(CollectedIDs, Progress, bIsComplete);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,84 @@
|
||||
// ChildHood Project - 收集系统逻辑核心(v1.0 架构解耦)
|
||||
// 对应技术方案: docs/technical/收集系统-技术方案.md 第3节(类设计)+ 第5节(数据结构)+ 第6节(行为规则实现)
|
||||
// 职责:品类检查、去重判定、记录持久化、完成判定、缓存 Presentation 数据
|
||||
// 架构:普通 C++ 类(非 UObject、非 UActorComponent),由 ASystemDriver 值持有。
|
||||
// 纯事件驱动无 Tick,不持有任何 UObject 强引用(与 FInteractionManager/FEffectManager 模式一致)。
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
#include "Collection/CollectionTypes.h"
|
||||
|
||||
class UPersistenceSubsystem;
|
||||
|
||||
/**
|
||||
* FCollectionManager
|
||||
*
|
||||
* 收集系统逻辑核心。追踪单一品类「杂物」的收集进度(执行案 v0.1 范围)。
|
||||
*
|
||||
* 数据流(技术方案第9节):
|
||||
* FEffectManager AddToBackpack Action
|
||||
* → ASystemDriver 回调 OnItemAdded(ItemId, DisplayName, Category)
|
||||
* → OnItemObtained()
|
||||
* ├─ 步骤1: 查品类 (Category == Config.TrackedCategory?)
|
||||
* ├─ 步骤2: 查重复 (CollectedIDs.Contains?)
|
||||
* ├─ 步骤3: 记录 (CollectedIDs.Add + Progress = Num()) + SaveStateToPersistence()
|
||||
* ├─ 步骤4: 刷新 Presentation 缓存(RefreshCachedInfo)
|
||||
* └─ 步骤5: 完成判定 (Progress >= TotalCount) → bJustCompleted
|
||||
*
|
||||
* Presentation 层(UCollectionHUDComponent)每帧 Tick 只读 GetCachedInfo(),
|
||||
* 通过比较 LastSeenProgress 检测变化驱动 UI,绝不访问 Manager 内部状态或 Subsystem。
|
||||
*/
|
||||
class FCollectionManager
|
||||
{
|
||||
public:
|
||||
|
||||
// === 初始化 ===
|
||||
|
||||
/** 重置本地运行时状态(由 ASystemDriver::BeginPlay 调用,先于 RestoreFromPersistence) */
|
||||
void Initialize();
|
||||
|
||||
/** 从持久层恢复收集状态,并缓存 PersistenceSubsystem 弱引用(供后续写入用)。
|
||||
* v0.1 单场景:持久层为空数据,等效于初始化为 0(技术方案第12节)。 */
|
||||
void RestoreFromPersistence(UPersistenceSubsystem* Subsystem);
|
||||
|
||||
// === 物品事件入口(由 ASystemDriver 回调触发)===
|
||||
|
||||
/** 物品获得事件 — 执行查品类→查重复→记录→完成判定(技术方案步骤1-5) */
|
||||
void OnItemObtained(FName ItemId, const FString& DisplayName, const FName& Category);
|
||||
|
||||
// === Presentation 层访问(只读)===
|
||||
|
||||
/** 获取 Presentation 缓存(HUDComponent 每帧只读) */
|
||||
const FCollectionCachedInfo& GetCachedInfo() const { return CachedInfo; }
|
||||
|
||||
/** 获取配置参数(HUDComponent 读取通知/动画参数) */
|
||||
const FCollectionConfig& GetConfig() const { return Config; }
|
||||
|
||||
private:
|
||||
|
||||
// === 配置 ===
|
||||
FCollectionConfig Config;
|
||||
|
||||
// === 运行时状态 ===
|
||||
int32 Progress = 0; // 当前已收集数(= CollectedIDs.Num(),权威值)
|
||||
TSet<FName> CollectedIDs; // 已收集物品 ID 集合(仅追踪品类)
|
||||
bool bIsComplete = false; // 是否全部收集完成
|
||||
|
||||
// === Presentation 缓存(供 HUDComponent 只读)===
|
||||
FCollectionCachedInfo CachedInfo;
|
||||
|
||||
// === 持久层弱引用(RestoreFromPersistence 时设置,SaveStateToPersistence 用)===
|
||||
TWeakObjectPtr<UPersistenceSubsystem> PersistencePtr;
|
||||
|
||||
// === 内部方法 ===
|
||||
|
||||
/** 处理新收集:记录 + 持久化 + 刷新缓存 + 完成判定(步骤2-5) */
|
||||
void ProcessNewCollection(FName ItemId, const FString& DisplayName);
|
||||
|
||||
/** 刷新 Presentation 缓存(更新进度、生成通知文本)— 保持技术方案签名(DisplayName) */
|
||||
void RefreshCachedInfo(const FString& DisplayName);
|
||||
|
||||
/** 将当前状态写入持久层(记录后 + 完成判定后调用) */
|
||||
void SaveStateToPersistence();
|
||||
};
|
||||
@@ -0,0 +1,67 @@
|
||||
// ChildHood Project - 收集通知 UI 实现
|
||||
// 三段时序:淡入 → 停留 → 淡出 → 广播 OnFinished + 移除
|
||||
|
||||
#include "Collection/CollectionNotifWidget.h"
|
||||
#include "Components/TextBlock.h"
|
||||
#include "Engine/World.h"
|
||||
#include "TimerManager.h"
|
||||
|
||||
void UCollectionNotifWidget::NativeOnInitialized()
|
||||
{
|
||||
Super::NativeOnInitialized();
|
||||
}
|
||||
|
||||
void UCollectionNotifWidget::InitializeNotif(const FString& Text, FLinearColor TextColor,
|
||||
float InFadeInDuration, float InStayDuration, float InFadeOutDuration)
|
||||
{
|
||||
CachedFadeOutDuration = InFadeOutDuration;
|
||||
|
||||
// 设置文字 + 颜色
|
||||
if (NotifTextBlock)
|
||||
{
|
||||
NotifTextBlock->SetText(FText::FromString(Text));
|
||||
NotifTextBlock->SetColorAndOpacity(TextColor);
|
||||
}
|
||||
|
||||
// 淡入动画
|
||||
if (FadeInAnim)
|
||||
{
|
||||
PlayAnimation(FadeInAnim);
|
||||
}
|
||||
|
||||
// 停留时长后触发淡出
|
||||
if (UWorld* World = GetWorld())
|
||||
{
|
||||
World->GetTimerManager().SetTimer(StayTimerHandle, [this]()
|
||||
{
|
||||
StartFadeOut();
|
||||
}, InStayDuration, false);
|
||||
}
|
||||
}
|
||||
|
||||
void UCollectionNotifWidget::StartFadeOut()
|
||||
{
|
||||
// 淡出动画
|
||||
if (FadeOutAnim)
|
||||
{
|
||||
PlayAnimation(FadeOutAnim);
|
||||
}
|
||||
|
||||
// 淡出时长后移除(用计时器而非动画完成委托,避免委托绑定复杂度)
|
||||
if (UWorld* World = GetWorld())
|
||||
{
|
||||
World->GetTimerManager().SetTimer(FadeOutTimerHandle, [this]()
|
||||
{
|
||||
OnFadeOutComplete();
|
||||
}, CachedFadeOutDuration, false);
|
||||
}
|
||||
}
|
||||
|
||||
void UCollectionNotifWidget::OnFadeOutComplete()
|
||||
{
|
||||
// 通知 HUDComponent 从 ActiveNotifications 移除本 Widget
|
||||
OnFinished.ExecuteIfBound(this);
|
||||
|
||||
// 从视口移除自己
|
||||
RemoveFromParent();
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
// ChildHood Project - 收集通知 UI 基类
|
||||
// 对应技术方案: docs/technical/收集系统-技术方案.md 第3节(UCollectionNotifWidget)+ 第7节(美术资源)
|
||||
// 职责:单条收集通知的三段生命周期(淡入→停留→淡出→移除)
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
#include "Blueprint/UserWidget.h"
|
||||
#include "CollectionNotifWidget.generated.h"
|
||||
|
||||
class UTextBlock;
|
||||
class UCollectionNotifWidget;
|
||||
|
||||
/** 收集通知单条完成事件 — 淡出移除后广播,HUDComponent 据此从 ActiveNotifications 清理 */
|
||||
DECLARE_DELEGATE_OneParam(FOnCollectionNotifFinished, UCollectionNotifWidget*);
|
||||
|
||||
/**
|
||||
* UCollectionNotifWidget — 收集通知 UI 单条实例(执行案第二节「收集通知 UI」)。
|
||||
*
|
||||
* 三段生命周期:淡入(0.2s) → 停留(2.0s) → 淡出(0.3s) → 广播 OnFinished + RemoveFromParent。
|
||||
* 文字浅金色 #F0D060(与拾取通知白色区分)。
|
||||
*
|
||||
* 绑定要求(蓝图 WBP_CollectionNotification 继承本类):
|
||||
* - TextBlock 名 "NotifTextBlock" — 通知文字
|
||||
* - WidgetAnimation 名 "FadeInAnim" — 淡入动画
|
||||
* - WidgetAnimation 名 "FadeOutAnim" — 淡出动画
|
||||
*/
|
||||
UCLASS(Abstract, Blueprintable)
|
||||
class UCollectionNotifWidget : public UUserWidget
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
public:
|
||||
|
||||
/** 初始化通知:设置文字/颜色,启动淡入→停留→淡出三段时序 */
|
||||
UFUNCTION(BlueprintCallable, Category = "Collection|Notification")
|
||||
void InitializeNotif(const FString& Text, FLinearColor TextColor,
|
||||
float InFadeInDuration, float InStayDuration, float InFadeOutDuration);
|
||||
|
||||
/** 通知完成事件(淡出移除后广播)— HUDComponent 绑定后清理 ActiveNotifications */
|
||||
FOnCollectionNotifFinished OnFinished;
|
||||
|
||||
protected:
|
||||
|
||||
/** 通知文字 TextBlock,蓝图子类必须有同名 TextBlock */
|
||||
UPROPERTY(meta = (BindWidget))
|
||||
UTextBlock* NotifTextBlock;
|
||||
|
||||
/** 淡入动画,蓝图子类必须有同名 WidgetAnimation */
|
||||
UPROPERTY(meta = (BindWidgetAnim), Transient)
|
||||
UWidgetAnimation* FadeInAnim;
|
||||
|
||||
/** 淡出动画,蓝图子类必须有同名 WidgetAnimation */
|
||||
UPROPERTY(meta = (BindWidgetAnim), Transient)
|
||||
UWidgetAnimation* FadeOutAnim;
|
||||
|
||||
/** 停留计时器(淡入结束后启动,到时触发淡出)*/
|
||||
FTimerHandle StayTimerHandle;
|
||||
|
||||
/** 淡出计时器(淡出开始后启动,到时移除 Widget)*/
|
||||
FTimerHandle FadeOutTimerHandle;
|
||||
|
||||
/** 淡出时长(缓存,供淡出计时器用)*/
|
||||
float CachedFadeOutDuration = 0.3f;
|
||||
|
||||
virtual void NativeOnInitialized() override;
|
||||
|
||||
private:
|
||||
|
||||
/** 停留结束 → 播放淡出动画 */
|
||||
void StartFadeOut();
|
||||
|
||||
/** 淡出时长到 → 广播 OnFinished + RemoveFromParent */
|
||||
void OnFadeOutComplete();
|
||||
};
|
||||
@@ -0,0 +1,44 @@
|
||||
// ChildHood Project - 收集进度 UI 实现
|
||||
|
||||
#include "Collection/CollectionProgressWidget.h"
|
||||
#include "Components/TextBlock.h"
|
||||
|
||||
void UCollectionProgressWidget::NativeOnInitialized()
|
||||
{
|
||||
Super::NativeOnInitialized();
|
||||
}
|
||||
|
||||
void UCollectionProgressWidget::UpdateProgress(int32 Current, int32 Total)
|
||||
{
|
||||
if (ProgressTextBlock)
|
||||
{
|
||||
// 格式:「📦 杂物 X/Y」(执行案第三节)。图标 emoji 在文本中,品类名 v0.1 固定「杂物」。
|
||||
ProgressTextBlock->SetText(FText::FromString(
|
||||
FString::Printf(TEXT("📦 杂物 %d/%d"), Current, Total)));
|
||||
}
|
||||
}
|
||||
|
||||
void UCollectionProgressWidget::SetCompleteStyle()
|
||||
{
|
||||
if (ProgressTextBlock)
|
||||
{
|
||||
// 文字变金色(全部收集完成表现)
|
||||
ProgressTextBlock->SetColorAndOpacity(CompleteColor);
|
||||
}
|
||||
}
|
||||
|
||||
void UCollectionProgressWidget::PlayBounceAnimation()
|
||||
{
|
||||
if (BounceAnim)
|
||||
{
|
||||
PlayAnimation(BounceAnim);
|
||||
}
|
||||
}
|
||||
|
||||
void UCollectionProgressWidget::PlayCompleteAnimation()
|
||||
{
|
||||
if (CompleteAnim)
|
||||
{
|
||||
PlayAnimation(CompleteAnim);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
// ChildHood Project - 收集进度 UI 基类
|
||||
// 对应技术方案: docs/technical/收集系统-技术方案.md 第3节(UCollectionProgressWidget)+ 第7节
|
||||
// 职责:常驻进度 HUD(图标+品类名+进度文本)+ 数字弹性动画 + 完成金色变色
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
#include "Blueprint/UserWidget.h"
|
||||
#include "CollectionProgressWidget.generated.h"
|
||||
|
||||
class UTextBlock;
|
||||
|
||||
/**
|
||||
* UCollectionProgressWidget — 收集进度常驻 HUD(执行案第三节「收集进度 UI」)。
|
||||
*
|
||||
* 显示格式:「📦 杂物 X/Y」,游戏开始后常驻左上角。
|
||||
* 进度变化时数字弹性缩放;全部完成时文字变金色 + 完成动画。
|
||||
*
|
||||
* 绑定要求(蓝图 WBP_CollectionProgress 继承本类):
|
||||
* - TextBlock 名 "ProgressTextBlock" — 进度文本(如 "📦 杂物 3/10")
|
||||
* - WidgetAnimation 名 "BounceAnim" — 数字弹性动画(1.2x, 0.2s)
|
||||
* - WidgetAnimation 名 "CompleteAnim" — 完成金色动画(1.4x, 0.5s)
|
||||
*
|
||||
* 布局要求:Anchors Top Left,距左/上各 3% 屏幕边距(执行案第三节)。
|
||||
*/
|
||||
UCLASS(Abstract, Blueprintable)
|
||||
class UCollectionProgressWidget : public UUserWidget
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
public:
|
||||
|
||||
/** 更新进度文本(格式:「📦 杂物 X/Y」)*/
|
||||
UFUNCTION(BlueprintCallable, Category = "Collection|Progress")
|
||||
void UpdateProgress(int32 Current, int32 Total);
|
||||
|
||||
/** 设为完成样式:文字变金色(全部收集完成时)*/
|
||||
UFUNCTION(BlueprintCallable, Category = "Collection|Progress")
|
||||
void SetCompleteStyle();
|
||||
|
||||
/** 播放数字弹性动画(单次新收集时)*/
|
||||
UFUNCTION(BlueprintCallable, Category = "Collection|Progress")
|
||||
void PlayBounceAnimation();
|
||||
|
||||
/** 播放完成动画(全部收集完成时,配合 SetCompleteStyle)*/
|
||||
UFUNCTION(BlueprintCallable, Category = "Collection|Progress")
|
||||
void PlayCompleteAnimation();
|
||||
|
||||
protected:
|
||||
|
||||
/** 进度文本 TextBlock,蓝图子类必须有同名 TextBlock */
|
||||
UPROPERTY(meta = (BindWidget))
|
||||
UTextBlock* ProgressTextBlock;
|
||||
|
||||
/** 数字弹性动画,蓝图子类必须有同名 WidgetAnimation */
|
||||
UPROPERTY(meta = (BindWidgetAnim), Transient)
|
||||
UWidgetAnimation* BounceAnim;
|
||||
|
||||
/** 完成金色动画,蓝图子类必须有同名 WidgetAnimation */
|
||||
UPROPERTY(meta = (BindWidgetAnim), Transient)
|
||||
UWidgetAnimation* CompleteAnim;
|
||||
|
||||
/** 正常文字颜色(白色)*/
|
||||
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "Collection|Progress")
|
||||
FLinearColor NormalColor = FLinearColor::White;
|
||||
|
||||
/** 完成文字颜色(浅金色 #F0D060)*/
|
||||
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "Collection|Progress")
|
||||
FLinearColor CompleteColor = FLinearColor(0.94f, 0.82f, 0.38f, 1.f);
|
||||
|
||||
virtual void NativeOnInitialized() override;
|
||||
};
|
||||
@@ -0,0 +1,162 @@
|
||||
// ChildHood Project - 收集系统数据类型定义(v1.0 架构解耦)
|
||||
// 对应技术方案: docs/technical/收集系统-技术方案.md 第5节(核心数据结构)
|
||||
// 职责:FCollectionConfig(配置,USTRUCT)+ FCollectionCachedInfo(Presentation 缓存,纯 C++)
|
||||
//
|
||||
// 设计说明:
|
||||
// - FCollectionConfig 为 USTRUCT,承载全部可调参数,默认值与执行案「可调参数汇总」一一对应。
|
||||
// v0.1 以代码默认值承载,后续版本可迁入 DataAsset / 配置表(数据驱动原则)。
|
||||
// - FCollectionCachedInfo 为普通 struct(非 USTRUCT):仅在 FCollectionManager(普通 C++)
|
||||
// 与 UCollectionHUDComponent(UActorComponent)之间传递,无需反射。
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
#include "CollectionTypes.generated.h"
|
||||
|
||||
// ============================================================================
|
||||
// FCollectionConfig — 收集系统配置(FCollectionManager 使用)
|
||||
// ============================================================================
|
||||
|
||||
/**
|
||||
* FCollectionConfig
|
||||
*
|
||||
* 收集系统全部可调参数。承载追踪品类、收集通知、进度 UI、完成表现四组参数。
|
||||
* 所有 ClampMin/ClampMax 与执行案「可调参数汇总」的可调范围一致。
|
||||
*/
|
||||
USTRUCT(BlueprintType)
|
||||
struct FCollectionConfig
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
// === 追踪品类 ===
|
||||
|
||||
/** 追踪的物品品类名称(v0.1 固定为 "杂物") */
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Collection|Category")
|
||||
FName TrackedCategory = FName("杂物");
|
||||
|
||||
/** 品类总收集数(v0.1 = 10) */
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Collection|Category",
|
||||
meta = (ClampMin = "1"))
|
||||
int32 TotalCount = 10;
|
||||
|
||||
// === 收集通知参数 ===
|
||||
|
||||
/** 拾取通知后到收集通知弹出的间隔(秒) */
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Collection|Notification",
|
||||
meta = (ClampMin = "0.1", ClampMax = "0.5"))
|
||||
float NotifDelay = 0.3f;
|
||||
|
||||
/** 通知淡入时长(秒) */
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Collection|Notification",
|
||||
meta = (ClampMin = "0.1", ClampMax = "0.3"))
|
||||
float NotifFadeInDuration = 0.2f;
|
||||
|
||||
/** 通知停留时长(秒) */
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Collection|Notification",
|
||||
meta = (ClampMin = "1.5", ClampMax = "3.0"))
|
||||
float NotifStayDuration = 2.0f;
|
||||
|
||||
/** 通知淡出时长(秒) */
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Collection|Notification",
|
||||
meta = (ClampMin = "0.2", ClampMax = "0.5"))
|
||||
float NotifFadeOutDuration = 0.3f;
|
||||
|
||||
/** 通知文字颜色(浅金色 #F0D060,与拾取通知白色区分) */
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Collection|Notification")
|
||||
FLinearColor NotifTextColor = FLinearColor(0.94f, 0.82f, 0.38f, 1.f);
|
||||
|
||||
/** 最大同屏收集通知数 */
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Collection|Notification",
|
||||
meta = (ClampMin = "1", ClampMax = "3"))
|
||||
int32 MaxSimultaneousNotifs = 2;
|
||||
|
||||
// === 进度 UI 参数 ===
|
||||
|
||||
/** 进度 UI 距左边距(屏幕宽度比例) */
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Collection|Progress",
|
||||
meta = (ClampMin = "0.02", ClampMax = "0.05"))
|
||||
float ProgressMarginX = 0.03f;
|
||||
|
||||
/** 进度 UI 距上边距(屏幕高度比例) */
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Collection|Progress",
|
||||
meta = (ClampMin = "0.02", ClampMax = "0.05"))
|
||||
float ProgressMarginY = 0.03f;
|
||||
|
||||
/** 进度数字弹性缩放倍率(单次新收集时) */
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Collection|Progress",
|
||||
meta = (ClampMin = "1.1", ClampMax = "1.5"))
|
||||
float BounceScale = 1.2f;
|
||||
|
||||
/** 进度数字弹性时长(秒) */
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Collection|Progress",
|
||||
meta = (ClampMin = "0.1", ClampMax = "0.4"))
|
||||
float BounceDuration = 0.2f;
|
||||
|
||||
// === 完成表现参数 ===
|
||||
|
||||
/** 完成时金色缩放倍率(全部收集完成时) */
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Collection|Complete",
|
||||
meta = (ClampMin = "1.2", ClampMax = "1.8"))
|
||||
float CompleteScale = 1.4f;
|
||||
|
||||
/** 完成缩放时长(秒) */
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Collection|Complete",
|
||||
meta = (ClampMin = "0.3", ClampMax = "0.8"))
|
||||
float CompleteScaleDuration = 0.5f;
|
||||
|
||||
/** 收集通知消失后到完成提示弹出的间隔(秒) */
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Collection|Complete",
|
||||
meta = (ClampMin = "0.3", ClampMax = "1.0"))
|
||||
float CompletePromptDelay = 0.5f;
|
||||
|
||||
/** 完成提示显示时长(秒) */
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Collection|Complete",
|
||||
meta = (ClampMin = "1.5", ClampMax = "3.0"))
|
||||
float CompletePromptDuration = 2.0f;
|
||||
};
|
||||
|
||||
// ============================================================================
|
||||
// FCollectionCachedInfo — Presentation 层缓存(非 UObject,纯 C++)
|
||||
// ============================================================================
|
||||
|
||||
/**
|
||||
* FCollectionCachedInfo
|
||||
*
|
||||
* 收集系统 Presentation 层缓存。与交互系统的 FSelectedInfo 模式一致。
|
||||
*
|
||||
* 写入方:FCollectionManager 在每次 OnItemObtained 成功记录新收集后,由
|
||||
* RefreshCachedInfo() 刷新此结构体。
|
||||
* 读取方:UCollectionHUDComponent 的 TickComponent 每帧只读此缓存,通过比较
|
||||
* LastSeenProgress 检测变化,驱动 UI 更新。
|
||||
*
|
||||
* 表现层绝不直接访问 FCollectionManager 的内部状态或 UPersistenceSubsystem,
|
||||
* 只通过此缓存 + GetConfig() 读取参数。
|
||||
*/
|
||||
struct FCollectionCachedInfo
|
||||
{
|
||||
/** 当前进度(已收集数) */
|
||||
int32 Progress = 0;
|
||||
|
||||
/** 总数 */
|
||||
int32 TotalCount = 10;
|
||||
|
||||
/** 是否已全部完成 */
|
||||
bool bIsComplete = false;
|
||||
|
||||
/** 是否刚刚完成(本次 OnItemObtained 触发了完成判定) */
|
||||
bool bJustCompleted = false;
|
||||
|
||||
/** 待显示的通知文本(如 "发现了新的杂物,进度 3/10") */
|
||||
FString PendingNotificationText;
|
||||
|
||||
/** 本次收集的物品 ID(用于通知回调定位 / 调试) */
|
||||
FName LastCollectedItemId;
|
||||
|
||||
/** 重置瞬时态(每次消费后调用,保留 Progress/TotalCount/bIsComplete) */
|
||||
void ResetTransient()
|
||||
{
|
||||
bJustCompleted = false;
|
||||
PendingNotificationText.Empty();
|
||||
LastCollectedItemId = FName();
|
||||
}
|
||||
};
|
||||
@@ -23,6 +23,11 @@ void FEffectManager::SetOnEffectComplete(FOnEffectComplete&& Callback)
|
||||
OnEffectComplete = MoveTemp(Callback);
|
||||
}
|
||||
|
||||
void FEffectManager::SetOnItemAddedCallback(FOnItemAdded&& Callback)
|
||||
{
|
||||
OnItemAdded = MoveTemp(Callback);
|
||||
}
|
||||
|
||||
void FEffectManager::PlayEffect(const FInteractEffect& Effect, const FEffectContext& Context)
|
||||
{
|
||||
// 如果有正在播放的 Effect,先强制清理
|
||||
@@ -147,14 +152,19 @@ void FEffectManager::ExecuteAction(const FInteractAction& Action)
|
||||
case EInteractActionType::ToggleDoor:
|
||||
ExecuteToggleDoor();
|
||||
break;
|
||||
// v0.1 用日志占位的 Action
|
||||
// AddToBackpack:通过 Driver 回调链通知收集系统(收集系统技术方案第13节)
|
||||
case EInteractActionType::AddToBackpack:
|
||||
// TODO: 后续集成收集系统(Issue #33 P-013 收集系统程序实现)。
|
||||
// 届时由 ASystemDriver 回调链通知 FCollectionManager,EffectManager 不直接处理业务逻辑。
|
||||
UE_LOG(LogTemp, Log, TEXT("EffectManager: AddToBackpack - Item: %s"),
|
||||
CurrentContext.TargetComp.IsValid()
|
||||
? *CurrentContext.TargetComp->GetInteractableData().DisplayName
|
||||
: TEXT("Unknown"));
|
||||
if (CurrentContext.TargetComp.IsValid())
|
||||
{
|
||||
const FInteractableData& ItemData = CurrentContext.TargetComp->GetInteractableData();
|
||||
if (OnItemAdded.IsBound())
|
||||
{
|
||||
// 回调连线:EffectManager → ASystemDriver → FCollectionManager::OnItemObtained
|
||||
OnItemAdded.Execute(ItemData.ItemId, ItemData.DisplayName, ItemData.Category);
|
||||
}
|
||||
UE_LOG(LogTemp, Log, TEXT("EffectManager: AddToBackpack - Item: %s (Category: %s)"),
|
||||
*ItemData.DisplayName, *ItemData.Category.ToString());
|
||||
}
|
||||
break;
|
||||
case EInteractActionType::PlayMontageOnTarget:
|
||||
case EInteractActionType::PlayDialogue:
|
||||
|
||||
@@ -31,6 +31,10 @@ public:
|
||||
/** 设置完成回调(由 ASystemDriver 在 BeginPlay 中设置) */
|
||||
void SetOnEffectComplete(FOnEffectComplete&& Callback);
|
||||
|
||||
/** 设置物品获得回调(AddToBackpack Action 执行时触发)。
|
||||
* 由 ASystemDriver 绑定到 FCollectionManager::OnItemObtained(收集系统技术方案第13节)。 */
|
||||
void SetOnItemAddedCallback(FOnItemAdded&& Callback);
|
||||
|
||||
/** 播放 Effect — 初始化状态,开始 Timeline 推进 */
|
||||
void PlayEffect(const FInteractEffect& Effect, const FEffectContext& Context);
|
||||
|
||||
@@ -86,6 +90,9 @@ private:
|
||||
/** Effect 完成回调(正常结束或取消后清理完毕时触发) */
|
||||
FOnEffectComplete OnEffectComplete;
|
||||
|
||||
/** 物品获得回调(AddToBackpack Action 触发,传 ItemId/DisplayName/Category) */
|
||||
FOnItemAdded OnItemAdded;
|
||||
|
||||
// === 内部方法 ===
|
||||
|
||||
/** 执行单个 Action(按 Type 分发到对应 ExecuteXxx) */
|
||||
|
||||
@@ -164,6 +164,12 @@ struct FInteractableData
|
||||
UPROPERTY(EditAnywhere, Category = "Basic")
|
||||
FString DisplayName;
|
||||
|
||||
/** 物品品类(如 "杂物"),用于收集系统品类过滤。
|
||||
* 拾取/翻找物填 "杂物"(收集系统追踪品类),观察物/门等非收集物留空。
|
||||
* P-013 收集系统通过 AddToBackpack 回调读取此字段做「查品类」。 */
|
||||
UPROPERTY(EditAnywhere, Category = "Basic")
|
||||
FName Category;
|
||||
|
||||
// === 触发 ===
|
||||
|
||||
/** 触发半径(cm),默认1.5m */
|
||||
@@ -299,3 +305,9 @@ DECLARE_DYNAMIC_MULTICAST_DELEGATE_ThreeParams(
|
||||
|
||||
/** Effect 完成回调 */
|
||||
DECLARE_DELEGATE(FOnEffectComplete);
|
||||
|
||||
/** 物品获得回调 — AddToBackpack Action 执行时触发,参数 (ItemId, DisplayName, Category)。
|
||||
* 由 FEffectManager 持有,ASystemDriver 在 BeginPlay 中绑定到 FCollectionManager::OnItemObtained,
|
||||
* 实现「交互系统 → 收集系统」的解耦连线(收集系统技术方案第13节)。
|
||||
* 普通委托(非动态多播):纯 C++ lambda 绑定,EffectManager 为普通 C++ 类。 */
|
||||
DECLARE_DELEGATE_ThreeParams(FOnItemAdded, FName, const FString&, const FName&);
|
||||
|
||||
@@ -35,6 +35,26 @@ void UPersistenceSubsystem::SetDoorOpen(FName DoorId, bool bIsOpen)
|
||||
*DoorId.ToString(), bIsOpen ? TEXT("开") : TEXT("关"));
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// 收集系统状态
|
||||
// ============================================================================
|
||||
|
||||
void UPersistenceSubsystem::GetCollectionState(TSet<FName>& OutIDs, int32& OutProgress, bool& OutComplete) const
|
||||
{
|
||||
OutIDs = CollectionCollectedIDs;
|
||||
OutProgress = CollectionProgress;
|
||||
OutComplete = bCollectionComplete;
|
||||
}
|
||||
|
||||
void UPersistenceSubsystem::SetCollectionState(const TSet<FName>& InIDs, int32 InProgress, bool bInComplete)
|
||||
{
|
||||
CollectionCollectedIDs = InIDs;
|
||||
CollectionProgress = InProgress;
|
||||
bCollectionComplete = bInComplete;
|
||||
UE_LOG(LogChildHood, Log, TEXT("PersistenceSubsystem: 收集状态更新 - 进度 %d, 完成 %s"),
|
||||
CollectionProgress, bCollectionComplete ? TEXT("是") : TEXT("否"));
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// 重置
|
||||
// ============================================================================
|
||||
@@ -43,5 +63,8 @@ void UPersistenceSubsystem::ResetAll()
|
||||
{
|
||||
CollectedItemIDs.Empty();
|
||||
DoorStates.Empty();
|
||||
CollectionCollectedIDs.Empty();
|
||||
CollectionProgress = 0;
|
||||
bCollectionComplete = false;
|
||||
UE_LOG(LogChildHood, Log, TEXT("PersistenceSubsystem: 所有持久数据已重置"));
|
||||
}
|
||||
|
||||
@@ -51,6 +51,18 @@ public:
|
||||
UFUNCTION(BlueprintCallable, Category = "Persistence")
|
||||
void ResetAll();
|
||||
|
||||
// === 收集系统状态 ===
|
||||
// 与上方 CollectedItemIDs 区分:CollectedItemIDs 记录交互物消耗状态(交互系统用),
|
||||
// 下方三个成员记录追踪品类的收集进度(收集系统用,技术方案第12节)。
|
||||
|
||||
/** 获取收集状态(恢复用):追踪品类的已收集 ID 集合、当前进度、是否全部完成 */
|
||||
UFUNCTION(BlueprintCallable, Category = "Persistence|Collection")
|
||||
void GetCollectionState(TSet<FName>& OutIDs, int32& OutProgress, bool& OutComplete) const;
|
||||
|
||||
/** 设置收集状态(收集时写入) */
|
||||
UFUNCTION(BlueprintCallable, Category = "Persistence|Collection")
|
||||
void SetCollectionState(const TSet<FName>& InIDs, int32 InProgress, bool bInComplete);
|
||||
|
||||
private:
|
||||
|
||||
/** 已收集物品 ID 集合 */
|
||||
@@ -60,4 +72,18 @@ private:
|
||||
/** 门开关状态映射(true=打开,false=关闭) */
|
||||
UPROPERTY()
|
||||
TMap<FName, bool> DoorStates;
|
||||
|
||||
// === 收集系统数据 ===
|
||||
|
||||
/** 追踪品类的已收集物品 ID 集合(收集系统用) */
|
||||
UPROPERTY()
|
||||
TSet<FName> CollectionCollectedIDs;
|
||||
|
||||
/** 当前收集进度(收集系统用) */
|
||||
UPROPERTY()
|
||||
int32 CollectionProgress = 0;
|
||||
|
||||
/** 是否已全部收集完成(收集系统用) */
|
||||
UPROPERTY()
|
||||
bool bCollectionComplete = false;
|
||||
};
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
#include "GameFramework/PlayerController.h"
|
||||
#include "GameFramework/Character.h"
|
||||
#include "ChildHoodCharacter.h"
|
||||
#include "Interaction/PersistenceSubsystem.h"
|
||||
|
||||
// ============================================================================
|
||||
// 构造函数
|
||||
@@ -60,6 +61,32 @@ void ASystemDriver::BeginPlay()
|
||||
|
||||
UE_LOG(LogChildHood, Log, TEXT("SystemDriver: EffectManager 初始化完成"));
|
||||
}
|
||||
|
||||
// --- 初始化 CollectionManager(收集系统,技术方案第13节回调连线)---
|
||||
{
|
||||
// 1. 本地状态重置
|
||||
CollectionManager.Initialize();
|
||||
|
||||
// 2. 从持久层恢复进度 + 缓存 Subsystem 弱引用
|
||||
if (UGameInstance* GI = GetGameInstance())
|
||||
{
|
||||
if (UPersistenceSubsystem* Persistence = GI->GetSubsystem<UPersistenceSubsystem>())
|
||||
{
|
||||
CollectionManager.RestoreFromPersistence(Persistence);
|
||||
}
|
||||
}
|
||||
|
||||
// 3. 回调连线:EffectManager AddToBackpack Action → CollectionManager.OnItemObtained
|
||||
// 交互系统与收集系统不直接耦合,通过 Driver 的 lambda 连线(v1.0 解耦核心)
|
||||
FOnItemAdded ItemAddedCallback;
|
||||
ItemAddedCallback.BindLambda([this](FName ItemId, const FString& DisplayName, const FName& Category)
|
||||
{
|
||||
CollectionManager.OnItemObtained(ItemId, DisplayName, Category);
|
||||
});
|
||||
EffectManager.SetOnItemAddedCallback(MoveTemp(ItemAddedCallback));
|
||||
|
||||
UE_LOG(LogChildHood, Log, TEXT("SystemDriver: CollectionManager 初始化完成 + AddToBackpack 回调连线"));
|
||||
}
|
||||
}
|
||||
|
||||
void ASystemDriver::Tick(float DeltaTime)
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
#include "GameFramework/Info.h"
|
||||
#include "Interaction/InteractionManager.h"
|
||||
#include "Interaction/EffectManager.h"
|
||||
#include "Collection/CollectionManager.h"
|
||||
#include "SystemDriver.generated.h"
|
||||
|
||||
/**
|
||||
@@ -58,6 +59,10 @@ public:
|
||||
FEffectManager& GetEffectManager() { return EffectManager; }
|
||||
const FEffectManager& GetEffectManager() const { return EffectManager; }
|
||||
|
||||
/** 获取收集管理器(值引用)— 收集系统逻辑核心 */
|
||||
FCollectionManager& GetCollectionManager() { return CollectionManager; }
|
||||
const FCollectionManager& GetCollectionManager() const { return CollectionManager; }
|
||||
|
||||
private:
|
||||
|
||||
/** 交互管理器 */
|
||||
@@ -66,6 +71,9 @@ private:
|
||||
/** 效果管理器 */
|
||||
FEffectManager EffectManager;
|
||||
|
||||
/** 收集管理器(收集系统逻辑核心) */
|
||||
FCollectionManager CollectionManager;
|
||||
|
||||
// === Effect 完成回调连线 ===
|
||||
|
||||
/** EffectManager 的 OnEffectComplete 回调目标 */
|
||||
|
||||
Reference in New Issue
Block a user