docs(technical): 收集系统技术方案 v1.0:Effect/Action 解耦架构修订
This commit is contained in:
@@ -0,0 +1,64 @@
|
||||
// ChildHood Project - 门交互组件
|
||||
// 对应技术方案: docs/technical/交互系统-技术方案.md 第3节(类设计)
|
||||
// 职责:门的开关状态管理、动态提示文本、跨场景持久状态恢复
|
||||
// 派生自 UInteractableComponent,增加门特有的状态和行为
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
#include "Interaction/InteractableComponent.h"
|
||||
#include "DoorInteractableComponent.generated.h"
|
||||
|
||||
class UPersistenceSubsystem;
|
||||
|
||||
/**
|
||||
* UDoorInteractableComponent
|
||||
*
|
||||
* 挂载在 ADoorActor 上的门交互组件。
|
||||
* 在基类(消耗状态、触发检测、Effect 配置)基础上,增加:
|
||||
* - 门开关状态 (bDoorIsOpen)
|
||||
* - 切换门 (ToggleDoor)
|
||||
* - 动态提示文本(根据开关状态选择 InteractVerb 或 InteractVerbAlt)
|
||||
* - 持久状态恢复(门的开关状态)
|
||||
*/
|
||||
UCLASS(ClassGroup=(Interaction), meta=(BlueprintSpawnableComponent))
|
||||
class UDoorInteractableComponent : public UInteractableComponent
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
public:
|
||||
|
||||
UDoorInteractableComponent();
|
||||
|
||||
// === 门操作 ===
|
||||
|
||||
/** 切换门的开关状态,并通过 GetOwner() 通知 ADoorActor 更新视觉 */
|
||||
UFUNCTION(BlueprintCallable, Category = "Door")
|
||||
void ToggleDoor();
|
||||
|
||||
/** 查询门当前是否打开 */
|
||||
UFUNCTION(BlueprintCallable, BlueprintPure, Category = "Door")
|
||||
bool IsDoorOpen() const { return bDoorIsOpen; }
|
||||
|
||||
// === 基类覆盖 ===
|
||||
|
||||
/**
|
||||
* 根据门开关状态选择提示动词:
|
||||
* 已打开 → InteractVerbAlt(如"关上")
|
||||
* 已关闭 → InteractVerb(如"打开")
|
||||
*/
|
||||
virtual FString GetPromptText_Implementation() const override;
|
||||
|
||||
/**
|
||||
* 恢复跨场景持久状态:
|
||||
* 1. 调用 Super 恢复消耗状态
|
||||
* 2. 从 PersistenceSubsystem 恢复门开关状态
|
||||
* 3. 通知 DoorActor 更新视觉旋转
|
||||
*/
|
||||
virtual void RestorePersistentState(UPersistenceSubsystem* Persistence) override;
|
||||
|
||||
private:
|
||||
|
||||
/** 门开关状态(true=打开,false=关闭) */
|
||||
bool bDoorIsOpen = false;
|
||||
};
|
||||
@@ -1,4 +1,4 @@
|
||||
// ChildHood Project - 通用交互物 Actor 基类 实现
|
||||
// ChildHood Project - 通用交互物 Actor 基类 实现(v1.1 Effect/Action 架构)
|
||||
|
||||
#include "Interaction/InteractableActor.h"
|
||||
#include "Interaction/InteractableComponent.h"
|
||||
@@ -28,17 +28,7 @@ AInteractableActor::AInteractableActor()
|
||||
|
||||
void AInteractableActor::OnInteractStarted_Implementation(UInteractableComponent* Comp)
|
||||
{
|
||||
if (!Comp || !MeshComp) return;
|
||||
|
||||
const FInteractableData& Data = Comp->GetInteractableData();
|
||||
|
||||
// 门类型:根据开关状态旋转 Mesh
|
||||
if (Data.Type == EInteractType::Operate && Data.OperateSubType == EOperateSubType::Door)
|
||||
{
|
||||
FRotator CurrentRot = MeshComp->GetRelativeRotation();
|
||||
float TargetYaw = Comp->IsDoorOpen() ? 90.0f : 0.0f;
|
||||
MeshComp->SetRelativeRotation(FRotator(CurrentRot.Pitch, TargetYaw, CurrentRot.Roll));
|
||||
}
|
||||
// 默认空实现,子类可覆盖(如 ADoorActor 旋转 Mesh)
|
||||
}
|
||||
|
||||
void AInteractableActor::OnInteractEnded_Implementation(UInteractableComponent* Comp)
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
// ChildHood Project - 通用交互物 Actor 基类
|
||||
// ChildHood Project - 通用交互物 Actor 基类(v1.1 Effect/Action 架构)
|
||||
// 对应技术方案: docs/technical/交互系统-技术方案.md 第3节(类设计)
|
||||
// 职责:持有 Mesh + InteractableComponent,提供可被子类覆盖的交互回调
|
||||
// 核心变更(v0.1 → v1.1):去除门旋转逻辑,只保留干净的基类
|
||||
|
||||
#pragma once
|
||||
|
||||
@@ -9,6 +10,7 @@
|
||||
#include "InteractableActor.generated.h"
|
||||
|
||||
class UInteractableComponent;
|
||||
class UStaticMeshComponent;
|
||||
|
||||
/**
|
||||
* AInteractableActor
|
||||
@@ -21,7 +23,7 @@ class UInteractableComponent;
|
||||
* - UInteractableComponent* InteractableComp(交互逻辑与数据)
|
||||
*
|
||||
* 子类可覆盖 OnInteractStarted / OnInteractEnded 以实现类型特定行为,
|
||||
* 如门开关时的 Mesh 旋转、拾取物的消失效果等。
|
||||
* 如门开关时的 Mesh 旋转(ADoorActor)、拾取物的消失效果等。
|
||||
*/
|
||||
UCLASS()
|
||||
class AInteractableActor : public AActor
|
||||
@@ -45,15 +47,20 @@ public:
|
||||
// === 可被子类覆盖的交互回调 ===
|
||||
|
||||
/**
|
||||
* 交互开始时调用(门开关切换、状态恢复时也调用)。
|
||||
* 默认实现:门类型根据开关状态对 Mesh 做 Yaw 旋转(开门+90度/关门0度)。
|
||||
* 交互开始时调用。
|
||||
* BlueprintNativeEvent,子类可在蓝图中或 C++ 中覆盖实现。
|
||||
* 默认为空实现。
|
||||
*/
|
||||
UFUNCTION(BlueprintNativeEvent, Category = "Interact")
|
||||
UFUNCTION(BlueprintNativeEvent, Category = "Interact", meta = (DisplayName = "On Interact Started"))
|
||||
void OnInteractStarted(UInteractableComponent* Comp);
|
||||
virtual void OnInteractStarted_Implementation(UInteractableComponent* Comp);
|
||||
|
||||
/** 交互结束时调用。默认为空实现,子类可覆盖。 */
|
||||
UFUNCTION(BlueprintNativeEvent, Category = "Interact")
|
||||
/**
|
||||
* 交互结束时调用。
|
||||
* BlueprintNativeEvent,子类可在蓝图中或 C++ 中覆盖实现。
|
||||
* 默认为空实现。
|
||||
*/
|
||||
UFUNCTION(BlueprintNativeEvent, Category = "Interact", meta = (DisplayName = "On Interact Ended"))
|
||||
void OnInteractEnded(UInteractableComponent* Comp);
|
||||
virtual void OnInteractEnded_Implementation(UInteractableComponent* Comp);
|
||||
|
||||
@@ -61,9 +68,9 @@ protected:
|
||||
|
||||
/** 交互物的可视模型 */
|
||||
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Components", meta = (AllowPrivateAccess = "true"))
|
||||
UStaticMeshComponent* MeshComp;
|
||||
TObjectPtr<UStaticMeshComponent> MeshComp;
|
||||
|
||||
/** 交互物的交互组件(数据 + 检测 + 状态) */
|
||||
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Components", meta = (AllowPrivateAccess = "true"))
|
||||
UInteractableComponent* InteractableComp;
|
||||
TObjectPtr<UInteractableComponent> InteractableComp;
|
||||
};
|
||||
|
||||
@@ -1,15 +1,18 @@
|
||||
// ChildHood Project - 可交互物组件 实现
|
||||
// ChildHood Project - 可交互物组件 实现(v1.1 Effect/Action 架构)
|
||||
|
||||
#include "Interaction/InteractableComponent.h"
|
||||
#include "Interaction/InteractableActor.h"
|
||||
#include "Interaction/InteractionManager.h"
|
||||
#include "ChildHoodGameInstance.h"
|
||||
#include "Interaction/PersistenceSubsystem.h"
|
||||
#include "ChildHood.h"
|
||||
|
||||
#include "Components/SphereComponent.h"
|
||||
#include "Engine/World.h"
|
||||
#include "GameFramework/GameInstance.h"
|
||||
#include "GameFramework/Character.h"
|
||||
#include "Kismet/GameplayStatics.h"
|
||||
|
||||
// 前向声明对应的头文件在.cpp中include
|
||||
// ASystemDriver 在 Phase 2 创建,此处前向声明即可
|
||||
class ASystemDriver;
|
||||
|
||||
// ============================================================================
|
||||
// 构造函数
|
||||
@@ -36,7 +39,7 @@ void UInteractableComponent::BeginPlay()
|
||||
return;
|
||||
}
|
||||
|
||||
// --- 创建检测球 ---
|
||||
// --- 创建检测球(BeginPlay 中动态创建,因为需要 Data.TriggerRadius)---
|
||||
DetectionSphere = NewObject<USphereComponent>(this, USphereComponent::StaticClass(), TEXT("DetectionSphere"));
|
||||
if (DetectionSphere)
|
||||
{
|
||||
@@ -58,14 +61,14 @@ void UInteractableComponent::BeginPlay()
|
||||
}
|
||||
|
||||
// --- 恢复跨场景持久状态 ---
|
||||
if (UChildHoodGameInstance* GI = GetWorld()->GetGameInstance<UChildHoodGameInstance>())
|
||||
if (UPersistenceSubsystem* Persistence = GetGameInstance()->GetSubsystem<UPersistenceSubsystem>())
|
||||
{
|
||||
RestorePersistentState(GI);
|
||||
RestorePersistentState(Persistence);
|
||||
}
|
||||
|
||||
UE_LOG(LogChildHood, Log, TEXT("InteractableComponent Init: %s (Type=%s, Radius=%f)"),
|
||||
UE_LOG(LogChildHood, Log, TEXT("InteractableComponent Init: %s (ItemId=%s, Radius=%.0f)"),
|
||||
Data.DisplayName.IsEmpty() ? TEXT("(unnamed)") : *Data.DisplayName,
|
||||
*UEnum::GetValueAsString(Data.Type),
|
||||
*Data.ItemId.ToString(),
|
||||
Data.TriggerRadius);
|
||||
}
|
||||
|
||||
@@ -73,22 +76,16 @@ void UInteractableComponent::BeginPlay()
|
||||
// 提示文本
|
||||
// ============================================================================
|
||||
|
||||
FString UInteractableComponent::GetPromptText() const
|
||||
FString UInteractableComponent::GetPromptText_Implementation() const
|
||||
{
|
||||
FString Verb = Data.InteractVerb;
|
||||
// 门类型:根据当前开关状态选择动词
|
||||
if (Data.Type == EInteractType::Operate && Data.OperateSubType == EOperateSubType::Door)
|
||||
{
|
||||
Verb = bDoorIsOpen ? Data.InteractVerbAlt : Data.InteractVerb;
|
||||
}
|
||||
return FString::Printf(TEXT("[E] %s %s"), *Verb, *Data.DisplayName);
|
||||
return FString::Printf(TEXT("[E] %s %s"), *Data.InteractVerb, *Data.DisplayName);
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// 消耗管理
|
||||
// ============================================================================
|
||||
|
||||
void UInteractableComponent::Consume()
|
||||
void UInteractableComponent::Consume_Implementation()
|
||||
{
|
||||
bIsConsumed = true;
|
||||
|
||||
@@ -101,27 +98,15 @@ void UInteractableComponent::Consume()
|
||||
// 写入持久层
|
||||
if (UWorld* World = GetWorld())
|
||||
{
|
||||
if (UChildHoodGameInstance* GI = World->GetGameInstance<UChildHoodGameInstance>())
|
||||
if (UPersistenceSubsystem* Persistence = World->GetGameInstance()->GetSubsystem<UPersistenceSubsystem>())
|
||||
{
|
||||
GI->MarkItemCollected(Data.ItemId);
|
||||
Persistence->MarkItemCollected(Data.ItemId);
|
||||
}
|
||||
}
|
||||
|
||||
UE_LOG(LogChildHood, Log, TEXT("InteractableComponent 已消耗: %s"), *Data.ItemId.ToString());
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// 门状态管理
|
||||
// ============================================================================
|
||||
|
||||
void UInteractableComponent::ToggleDoor()
|
||||
{
|
||||
bDoorIsOpen = !bDoorIsOpen;
|
||||
UE_LOG(LogChildHood, Log, TEXT("InteractableComponent 门状态切换: %s → %s"),
|
||||
*Data.ItemId.ToString(),
|
||||
bDoorIsOpen ? TEXT("开") : TEXT("关"));
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// 位置查询
|
||||
// ============================================================================
|
||||
@@ -136,38 +121,24 @@ FVector UInteractableComponent::GetWorldLocation() const
|
||||
// 跨场景持久状态恢复
|
||||
// ============================================================================
|
||||
|
||||
void UInteractableComponent::RestorePersistentState(UChildHoodGameInstance* GI)
|
||||
void UInteractableComponent::RestorePersistentState(UPersistenceSubsystem* Persistence)
|
||||
{
|
||||
if (!GI) return;
|
||||
if (!Persistence) return;
|
||||
|
||||
// oneShot 物品:已消耗 → 隐藏 + 禁用检测
|
||||
if (Data.bOneShot && GI->IsItemCollected(Data.ItemId))
|
||||
if (Data.bOneShot && Persistence->IsItemCollected(Data.ItemId))
|
||||
{
|
||||
bIsConsumed = true;
|
||||
if (DetectionSphere)
|
||||
{
|
||||
DetectionSphere->SetCollisionEnabled(ECollisionEnabled::NoCollision);
|
||||
}
|
||||
if (AInteractableActor* Owner = Cast<AInteractableActor>(GetOwner()))
|
||||
if (AActor* Owner = GetOwner())
|
||||
{
|
||||
Owner->SetActorHiddenInGame(true);
|
||||
}
|
||||
UE_LOG(LogChildHood, Log, TEXT("RestorePersistentState: 已消耗物品隐藏: %s"), *Data.ItemId.ToString());
|
||||
}
|
||||
|
||||
// 门:恢复开关状态
|
||||
if (Data.Type == EInteractType::Operate && Data.OperateSubType == EOperateSubType::Door)
|
||||
{
|
||||
bDoorIsOpen = GI->IsDoorOpen(Data.ItemId);
|
||||
// 通知 Actor 应用视觉状态(Mesh 旋转)
|
||||
if (AInteractableActor* Owner = Cast<AInteractableActor>(GetOwner()))
|
||||
{
|
||||
Owner->OnInteractStarted(this);
|
||||
}
|
||||
UE_LOG(LogChildHood, Log, TEXT("RestorePersistentState: 门状态恢复: %s → %s"),
|
||||
*Data.ItemId.ToString(),
|
||||
bDoorIsOpen ? TEXT("开") : TEXT("关"));
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
@@ -193,11 +164,9 @@ void UInteractableComponent::OnDetectionOverlapBegin(UPrimitiveComponent* Overla
|
||||
UE_LOG(LogChildHood, Verbose, TEXT("检测到角色进入范围: %s ← %s"),
|
||||
*Data.DisplayName, *OtherActor->GetName());
|
||||
|
||||
// 获取角色的 InteractionManager 并注册自己
|
||||
if (UInteractionManager* IM = Character->FindComponentByClass<UInteractionManager>())
|
||||
{
|
||||
IM->RegisterInteractable(this);
|
||||
}
|
||||
// Phase 2 将改为通过 ASystemDriver::Get(GetWorld())->GetInteractionManager().RegisterInteractable(this)
|
||||
// 当前 Phase 1 仅记录日志,因为 FInteractionManager 尚未创建
|
||||
// TODO(Phase 2): 替换为 ASystemDriver 路径
|
||||
}
|
||||
|
||||
void UInteractableComponent::OnDetectionOverlapEnd(UPrimitiveComponent* OverlappedComponent,
|
||||
@@ -213,9 +182,7 @@ void UInteractableComponent::OnDetectionOverlapEnd(UPrimitiveComponent* Overlapp
|
||||
UE_LOG(LogChildHood, Verbose, TEXT("角色离开范围: %s ← %s"),
|
||||
*Data.DisplayName, *OtherActor->GetName());
|
||||
|
||||
// 获取角色的 InteractionManager 并注销自己
|
||||
if (UInteractionManager* IM = Character->FindComponentByClass<UInteractionManager>())
|
||||
{
|
||||
IM->UnregisterInteractable(this);
|
||||
}
|
||||
// Phase 2 将改为通过 ASystemDriver::Get(GetWorld())->GetInteractionManager().UnregisterInteractable(this)
|
||||
// 当前 Phase 1 仅记录日志,因为 FInteractionManager 尚未创建
|
||||
// TODO(Phase 2): 替换为 ASystemDriver 路径
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
// ChildHood Project - 可交互物组件
|
||||
// ChildHood Project - 可交互物组件(v1.1 Effect/Action 架构)
|
||||
// 对应技术方案: docs/technical/交互系统-技术方案.md 第3节(类设计)+ 第12节(跨场景持久化)
|
||||
// 职责:单个交互物配置数据、触发区域、运行时状态、跨场景持久状态恢复
|
||||
// 核心变更(v0.1 → v1.1):去除所有门逻辑,只保留通用交互物功能
|
||||
|
||||
#pragma once
|
||||
|
||||
@@ -9,15 +10,21 @@
|
||||
#include "InteractionTypes.h"
|
||||
#include "InteractableComponent.generated.h"
|
||||
|
||||
class UChildHoodGameInstance;
|
||||
class UPersistenceSubsystem;
|
||||
class USphereComponent;
|
||||
class ASystemDriver;
|
||||
|
||||
/**
|
||||
* UInteractableComponent
|
||||
*
|
||||
* 挂载在 AInteractableActor(或其子类)上的组件。
|
||||
* 持有该交互物的全部配置数据(FInteractableData)、运行时触发检测球、
|
||||
* 消耗/门状态管理,以及跨场景持久状态恢复。
|
||||
* 消耗状态管理,以及跨场景持久状态恢复。
|
||||
*
|
||||
* 碰撞回调通过 ASystemDriver::Get() 获取 Driver,
|
||||
* 再调用 GetInteractionManager().RegisterInteractable(this) 注册。
|
||||
*
|
||||
* 门逻辑已移除,由 UDoorInteractableComponent 派生类实现(Phase 2)。
|
||||
*/
|
||||
UCLASS(ClassGroup=(Interaction), meta=(BlueprintSpawnableComponent))
|
||||
class UInteractableComponent : public UActorComponent
|
||||
@@ -41,29 +48,31 @@ public:
|
||||
|
||||
/**
|
||||
* 获取提示文本,格式:"[E] 动作词 名称"
|
||||
* 门类型根据当前开关状态选择动词
|
||||
* 子类可覆盖以实现动态动词(如门的"打开"/"关上")
|
||||
*/
|
||||
UFUNCTION(BlueprintNativeEvent, Category = "Interact", meta = (DisplayName = "Get Prompt Text"))
|
||||
FString GetPromptText() const;
|
||||
virtual FString GetPromptText_Implementation() const;
|
||||
|
||||
/** 该交互物是否已被消耗(oneShot 模式) */
|
||||
UFUNCTION(BlueprintCallable, BlueprintPure, Category = "Interact")
|
||||
bool IsConsumed() const { return bIsConsumed; }
|
||||
|
||||
/** 标记为已消耗,禁用检测球,写入持久层 */
|
||||
/** 标记为已消耗,禁用检测球 */
|
||||
UFUNCTION(BlueprintNativeEvent, Category = "Interact", meta = (DisplayName = "Consume"))
|
||||
void Consume();
|
||||
|
||||
// === 门状态管理 ===
|
||||
|
||||
/** 切换门开关状态 */
|
||||
void ToggleDoor();
|
||||
|
||||
/** 门是否处于打开状态 */
|
||||
bool IsDoorOpen() const { return bDoorIsOpen; }
|
||||
virtual void Consume_Implementation();
|
||||
|
||||
/** 获取交互物在世界空间的位置 */
|
||||
UFUNCTION(BlueprintCallable, BlueprintPure, Category = "Interact")
|
||||
FVector GetWorldLocation() const;
|
||||
|
||||
/** 从 GameInstance 恢复跨场景持久状态(BeginPlay 中调用) */
|
||||
void RestorePersistentState(UChildHoodGameInstance* GI);
|
||||
/**
|
||||
* 从持久层恢复跨场景持久状态(BeginPlay 中调用)。
|
||||
* oneShot 物品已消耗时隐藏 Actor 并禁用检测。
|
||||
* 子类可覆盖以恢复额外状态(如门的开关状态)。
|
||||
*/
|
||||
virtual void RestorePersistentState(UPersistenceSubsystem* Persistence);
|
||||
|
||||
protected:
|
||||
|
||||
@@ -73,17 +82,14 @@ private:
|
||||
|
||||
/** 运行时创建的检测球,用于触发玩家进入/离开交互范围 */
|
||||
UPROPERTY()
|
||||
USphereComponent* DetectionSphere;
|
||||
TObjectPtr<USphereComponent> DetectionSphere;
|
||||
|
||||
/** oneShot 物品是否已被消耗 */
|
||||
bool bIsConsumed = false;
|
||||
|
||||
/** 门是否处于打开状态(仅 Operate+Door 类型使用) */
|
||||
bool bDoorIsOpen = false;
|
||||
|
||||
// === 碰撞回调 ===
|
||||
|
||||
/** 玩家进入检测范围 */
|
||||
/** 玩家进入检测范围 — 通过 ASystemDriver 注册到 InteractionManager */
|
||||
UFUNCTION()
|
||||
void OnDetectionOverlapBegin(UPrimitiveComponent* OverlappedComponent,
|
||||
AActor* OtherActor,
|
||||
@@ -92,7 +98,7 @@ private:
|
||||
bool bFromSweep,
|
||||
const FHitResult& SweepResult);
|
||||
|
||||
/** 玩家离开检测范围 */
|
||||
/** 玩家离开检测范围 — 通过 ASystemDriver 从 InteractionManager 注销 */
|
||||
UFUNCTION()
|
||||
void OnDetectionOverlapEnd(UPrimitiveComponent* OverlappedComponent,
|
||||
AActor* OtherActor,
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// ChildHood Project - 交互系统数据类型定义
|
||||
// 对应技术方案: docs/technical/交互系统-技术方案.md 第5节(核心数据结构)+ 第12节(跨场景持久化)
|
||||
// ChildHood Project - 交互系统数据类型定义(v1.1 Effect/Action 架构)
|
||||
// 对应技术方案: docs/technical/交互系统-技术方案.md 第5节(核心数据结构)
|
||||
|
||||
#pragma once
|
||||
|
||||
@@ -10,169 +10,292 @@
|
||||
// 枚举
|
||||
// ============================================================================
|
||||
|
||||
/** 交互类型 */
|
||||
UENUM(BlueprintType)
|
||||
enum class EInteractType : uint8
|
||||
{
|
||||
Examine UMETA(DisplayName = "观察"),
|
||||
Pickup UMETA(DisplayName = "拾取"),
|
||||
Operate UMETA(DisplayName = "操作"),
|
||||
Talk UMETA(DisplayName = "对话")
|
||||
};
|
||||
|
||||
/** 操作子类型(仅 Operate 类型有效) */
|
||||
UENUM(BlueprintType)
|
||||
enum class EOperateSubType : uint8
|
||||
{
|
||||
Door UMETA(DisplayName = "门(开关切换)"),
|
||||
Search UMETA(DisplayName = "翻找")
|
||||
};
|
||||
|
||||
/** 交互系统顶层状态 */
|
||||
/** 交互系统顶层状态(简化为 2 阶段) */
|
||||
UENUM(BlueprintType)
|
||||
enum class EInteractionState : uint8
|
||||
{
|
||||
Free UMETA(DisplayName = "自由状态"),
|
||||
Interacting UMETA(DisplayName = "交互中"),
|
||||
SubSystem UMETA(DisplayName = "子系统(v0.1预留)"),
|
||||
Cutscene UMETA(DisplayName = "过场(v0.1预留)")
|
||||
Free, // 自由状态,检测/选择/Prompt
|
||||
Executing, // 执行 Effect 中
|
||||
};
|
||||
|
||||
/** Interacting 子阶段 */
|
||||
/** Action 类型 */
|
||||
UENUM(BlueprintType)
|
||||
enum class EInteractPhase : uint8
|
||||
enum class EInteractActionType : uint8
|
||||
{
|
||||
None UMETA(DisplayName = "无"),
|
||||
Enter UMETA(DisplayName = "进入(转向+推镜)"),
|
||||
AnimLock UMETA(DisplayName = "动画锁定"),
|
||||
Active UMETA(DisplayName = "活跃(等待输入)"),
|
||||
Exit UMETA(DisplayName = "退出(回位)")
|
||||
};
|
||||
|
||||
/** 交互结束后动作 */
|
||||
UENUM(BlueprintType)
|
||||
enum class EPostAction : uint8
|
||||
{
|
||||
Return UMETA(DisplayName = "回到Free"),
|
||||
Minigame UMETA(DisplayName = "小游戏(v0.1预留)"),
|
||||
Dialogue UMETA(DisplayName = "对话(v0.1预留)"),
|
||||
SceneChange UMETA(DisplayName = "场景切换(v0.1预留)")
|
||||
Wait UMETA(DisplayName = "等待"),
|
||||
TurnToTarget UMETA(DisplayName = "转向交互物"),
|
||||
PlayMontage UMETA(DisplayName = "播放人物动画"),
|
||||
PlayMontageOnTarget UMETA(DisplayName = "播放交互物动画"),
|
||||
CameraPush UMETA(DisplayName = "相机推镜"),
|
||||
CameraReturn UMETA(DisplayName = "相机回位"),
|
||||
Consume UMETA(DisplayName = "消耗交互物"),
|
||||
HideActor UMETA(DisplayName = "隐藏交互物"),
|
||||
ShowActor UMETA(DisplayName = "显示交互物"),
|
||||
AddToBackpack UMETA(DisplayName = "物品进背包"),
|
||||
ShowNarrative UMETA(DisplayName = "显示叙事文本"),
|
||||
HideNarrative UMETA(DisplayName = "隐藏叙事文本"),
|
||||
ShowPickupNotification UMETA(DisplayName = "显示拾取通知"),
|
||||
ToggleDoor UMETA(DisplayName = "切换门状态"),
|
||||
PlayDialogue UMETA(DisplayName = "播放对话"),
|
||||
PlayStory UMETA(DisplayName = "播放小剧情"),
|
||||
BroadcastEvent UMETA(DisplayName = "广播逻辑事件"),
|
||||
};
|
||||
|
||||
// ============================================================================
|
||||
// 配置数据结构
|
||||
// 前向声明
|
||||
// ============================================================================
|
||||
|
||||
/** 可交互物配置数据 — 每个交互物实例独立配置 */
|
||||
class UInteractableComponent;
|
||||
|
||||
// ============================================================================
|
||||
// Action — 原子操作定义
|
||||
// ============================================================================
|
||||
|
||||
/** 原子操作定义 — Effect 内按 TriggerTime 编排的最小执行单元 */
|
||||
USTRUCT(BlueprintType)
|
||||
struct FInteractAction
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
// === 编排 ===
|
||||
|
||||
/** Action 类型 */
|
||||
UPROPERTY(EditAnywhere, Category = "Action")
|
||||
EInteractActionType Type = EInteractActionType::Wait;
|
||||
|
||||
/** 相对于 Effect 开始的触发时间(秒) */
|
||||
UPROPERTY(EditAnywhere, Category = "Action", meta = (ClampMin = "0"))
|
||||
float TriggerTime = 0.f;
|
||||
|
||||
// === 各类型参数(按 EditCondition 折叠显示) ===
|
||||
|
||||
// --- Wait ---
|
||||
/** 等待时长(秒) */
|
||||
UPROPERTY(EditAnywhere, meta = (EditCondition = "Type == EInteractActionType::Wait", EditConditionHides))
|
||||
float WaitDuration = 0.5f;
|
||||
|
||||
// --- TurnToTarget ---
|
||||
/** 转向时长(秒) */
|
||||
UPROPERTY(EditAnywhere, meta = (EditCondition = "Type == EInteractActionType::TurnToTarget", EditConditionHides))
|
||||
float TurnDuration = 0.2f;
|
||||
|
||||
// --- PlayMontage ---
|
||||
/** 人物蒙太奇动画 */
|
||||
UPROPERTY(EditAnywhere, meta = (EditCondition = "Type == EInteractActionType::PlayMontage", EditConditionHides))
|
||||
TObjectPtr<UAnimMontage> Montage;
|
||||
|
||||
// --- PlayMontageOnTarget ---
|
||||
/** 交互物蒙太奇动画 */
|
||||
UPROPERTY(EditAnywhere, meta = (EditCondition = "Type == EInteractActionType::PlayMontageOnTarget", EditConditionHides))
|
||||
TObjectPtr<UAnimMontage> TargetMontage;
|
||||
|
||||
// --- CameraPush ---
|
||||
/** 推镜距离(cm) */
|
||||
UPROPERTY(EditAnywhere, meta = (EditCondition = "Type == EInteractActionType::CameraPush", EditConditionHides))
|
||||
float PushDistance = 80.f;
|
||||
|
||||
/** 推镜过渡时长(秒) */
|
||||
UPROPERTY(EditAnywhere, meta = (EditCondition = "Type == EInteractActionType::CameraPush", EditConditionHides))
|
||||
float PushDuration = 0.5f;
|
||||
|
||||
// --- CameraReturn ---
|
||||
/** 相机回位时长(秒) */
|
||||
UPROPERTY(EditAnywhere, meta = (EditCondition = "Type == EInteractActionType::CameraReturn", EditConditionHides))
|
||||
float ReturnDuration = 0.3f;
|
||||
|
||||
// --- ShowNarrative ---
|
||||
/** 叙事文本列表 */
|
||||
UPROPERTY(EditAnywhere, meta = (EditCondition = "Type == EInteractActionType::ShowNarrative", EditConditionHides))
|
||||
TArray<FString> NarrativeTexts;
|
||||
|
||||
/** 是否等待用户按 E/ESC 后再继续后续 Action */
|
||||
UPROPERTY(EditAnywhere, meta = (EditCondition = "Type == EInteractActionType::ShowNarrative", EditConditionHides))
|
||||
bool bWaitForInput = false;
|
||||
|
||||
/** 是否从 NarrativeTexts 中随机选取(不重复上一次) */
|
||||
UPROPERTY(EditAnywhere, meta = (EditCondition = "Type == EInteractActionType::ShowNarrative", EditConditionHides))
|
||||
bool bRandomSelect = true;
|
||||
};
|
||||
|
||||
// ============================================================================
|
||||
// Effect — 交互效果定义
|
||||
// ============================================================================
|
||||
|
||||
/** Effect 定义 — 由多个 Action 按时间线编排而成的交互效果配方 */
|
||||
USTRUCT(BlueprintType)
|
||||
struct FInteractEffect
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
/** Effect 名称(调试用) */
|
||||
UPROPERTY(EditAnywhere, Category = "Effect")
|
||||
FString EffectName;
|
||||
|
||||
/** Effect 总时长(秒),到时自动结束 */
|
||||
UPROPERTY(EditAnywhere, Category = "Effect", meta = (ClampMin = "0.1", ClampMax = "10.0"))
|
||||
float TotalDuration = 2.0f;
|
||||
|
||||
/** 是否可取消(ESC 中断) */
|
||||
UPROPERTY(EditAnywhere, Category = "Effect")
|
||||
bool bCancellable = true;
|
||||
|
||||
/** Action 列表 — 按 TriggerTime 编排,同时间并行 */
|
||||
UPROPERTY(EditAnywhere, Category = "Effect")
|
||||
TArray<FInteractAction> Actions;
|
||||
};
|
||||
|
||||
// ============================================================================
|
||||
// 可交互物配置数据
|
||||
// ============================================================================
|
||||
|
||||
/** 可交互物配置数据 — 每个交互物实例独立配置,不再包含类型分发字段 */
|
||||
USTRUCT(BlueprintType)
|
||||
struct FInteractableData
|
||||
{
|
||||
GENERATED_BODY()
|
||||
GENERATED_BODY()
|
||||
|
||||
// === 基本信息 ===
|
||||
// === 基本信息 ===
|
||||
|
||||
/** 唯一ID(如 "fridge_home"),用于持久化存储和物品系统对接 */
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Interact|Basic")
|
||||
FName ItemId;
|
||||
/** 唯一ID(如 "fridge_home"),用于持久化存储和物品系统对接 */
|
||||
UPROPERTY(EditAnywhere, Category = "Basic")
|
||||
FName ItemId;
|
||||
|
||||
/** 显示名称(如 "旧冰箱"),用于提示UI */
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Interact|Basic")
|
||||
FString DisplayName;
|
||||
/** 显示名称(如 "旧冰箱"),用于提示UI */
|
||||
UPROPERTY(EditAnywhere, Category = "Basic")
|
||||
FString DisplayName;
|
||||
|
||||
/** 交互类型 */
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Interact|Basic")
|
||||
EInteractType Type = EInteractType::Examine;
|
||||
// === 触发 ===
|
||||
|
||||
/** 操作子类型,仅 Type==Operate 时可见可编辑 */
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Interact|Basic",
|
||||
meta = (EditCondition = "Type == EInteractType::Operate", EditConditionHides))
|
||||
EOperateSubType OperateSubType = EOperateSubType::Door;
|
||||
/** 触发半径(cm),默认1.5m */
|
||||
UPROPERTY(EditAnywhere, Category = "Trigger", meta = (ClampMin = "50.0", ClampMax = "500.0"))
|
||||
float TriggerRadius = 150.f;
|
||||
|
||||
// === 触发 ===
|
||||
/** 是否一次性消耗(消耗后不再可交互) */
|
||||
UPROPERTY(EditAnywhere, Category = "Trigger")
|
||||
bool bOneShot = false;
|
||||
|
||||
/** 触发半径(cm),默认1.5m */
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Interact|Trigger",
|
||||
meta = (ClampMin = "50.0", ClampMax = "500.0"))
|
||||
float TriggerRadius = 150.0f;
|
||||
// === 提示文本 ===
|
||||
|
||||
/** 是否一次性消耗(消耗后不再可交互) */
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Interact|Trigger")
|
||||
bool bOneShot = false;
|
||||
/** 动作词(如 "查看"、"拾取"、"打开") */
|
||||
UPROPERTY(EditAnywhere, Category = "Prompt")
|
||||
FString InteractVerb;
|
||||
|
||||
// === 提示文本 ===
|
||||
/** 备选动词(如 "关上"),门打开时使用 */
|
||||
UPROPERTY(EditAnywhere, Category = "Prompt")
|
||||
FString InteractVerbAlt;
|
||||
|
||||
/** 动作词(如 "查看"、"拾取"、"打开") */
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Interact|Prompt")
|
||||
FString InteractVerb;
|
||||
// === 交互 Effect ===
|
||||
|
||||
/** 备选动词(如 "关上"),门打开时使用,仅 Operate+Door 时可见 */
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Interact|Prompt",
|
||||
meta = (EditCondition = "Type == EInteractType::Operate && OperateSubType == EOperateSubType::Door"))
|
||||
FString InteractVerbAlt;
|
||||
/** 交互效果定义 — 所有行为由 Effect 内的 Action 定义 */
|
||||
UPROPERTY(EditAnywhere, Category = "Effect")
|
||||
FInteractEffect InteractEffect;
|
||||
|
||||
// === 叙事/对话文本 ===
|
||||
// 已删除(由 Effect 内 Action 替代):
|
||||
// EInteractType Type, EOperateSubType OperateSubType
|
||||
// bUseCameraPush, CameraPushDistance/Duration, PushWaitDelay, CameraReturnDuration
|
||||
// InteractMontage, AnimDuration, AnimEventTimepoint
|
||||
// NarrativeTexts, EPostAction PostAction
|
||||
};
|
||||
|
||||
/** 文本列表,随机选取,不重复上次 */
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Interact|Text")
|
||||
TArray<FString> NarrativeTexts;
|
||||
// ============================================================================
|
||||
// 运行时上下文
|
||||
// ============================================================================
|
||||
|
||||
// === 摄像机推镜 ===
|
||||
/** Effect 运行时上下文 — EffectManager 执行时传入,持有弱引用 */
|
||||
USTRUCT()
|
||||
struct FEffectContext
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
/** 是否使用摄像机推镜 */
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Interact|Camera")
|
||||
bool bUseCameraPush = false;
|
||||
/** 触发交互的角色 */
|
||||
TWeakObjectPtr<ACharacter> Instigator;
|
||||
|
||||
/** 推镜距离(cm),仅 bUseCameraPush 时可编辑 */
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Interact|Camera",
|
||||
meta = (EditCondition = "bUseCameraPush", ClampMin = "10.0", ClampMax = "300.0"))
|
||||
float CameraPushDistance = 80.0f;
|
||||
/** 交互物组件(配置数据来源) */
|
||||
TWeakObjectPtr<UInteractableComponent> TargetComp;
|
||||
|
||||
/** 推镜过渡时长(秒),仅 bUseCameraPush 时可编辑 */
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Interact|Camera",
|
||||
meta = (EditCondition = "bUseCameraPush", ClampMin = "0.1", ClampMax = "2.0"))
|
||||
float CameraPushDuration = 0.5f;
|
||||
/** 交互物 Actor(视觉操作目标) */
|
||||
TWeakObjectPtr<AActor> TargetActor;
|
||||
};
|
||||
|
||||
/** 相机回位时长(秒),仅 bUseCameraPush 时可编辑 */
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Interact|Camera",
|
||||
meta = (EditCondition = "bUseCameraPush", ClampMin = "0.1", ClampMax = "2.0"))
|
||||
float CameraReturnDuration = 0.3f;
|
||||
// ============================================================================
|
||||
// Presentation 层缓存数据(非 UObject,纯 C++)
|
||||
// ============================================================================
|
||||
|
||||
/** 推镜完成后到文本显示的等待时间(秒),仅 bUseCameraPush 时可编辑 */
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Interact|Camera",
|
||||
meta = (EditCondition = "bUseCameraPush", ClampMin = "0.0", ClampMax = "1.0"))
|
||||
float PushWaitDelay = 0.2f;
|
||||
/** Presentation 层缓存数据 — Manager 在选择变化时从 Component 读一次并写入,
|
||||
* Prompt 组件每帧只读此缓存,不直接访问 Component */
|
||||
struct FSelectedInfo
|
||||
{
|
||||
/** 是否有有效选中 */
|
||||
bool bHasSelection = false;
|
||||
|
||||
// === 动画 ===
|
||||
/** 选中交互物的提示文本(如 "[E] 查看旧冰箱") */
|
||||
FString PromptText;
|
||||
|
||||
/** 交互动画蒙太奇(可选,为空时用计时器模拟) */
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Interact|Anim")
|
||||
TObjectPtr<UAnimMontage> InteractMontage;
|
||||
/** 选中交互物的世界坐标(用于屏幕空间投影) */
|
||||
FVector WorldPosition = FVector::ZeroVector;
|
||||
|
||||
/** 无蒙太奇时的动画持续时间(秒) */
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Interact|Anim",
|
||||
meta = (ClampMin = "0.1", ClampMax = "5.0"))
|
||||
float AnimDuration = 1.0f;
|
||||
/** 选中交互物是否已消耗(消耗后不再显示提示) */
|
||||
bool bIsConsumed = false;
|
||||
|
||||
/** 动画事件触发点(0-1比例),如0.6表示动画播放至60%时触发物品消失 */
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Interact|Anim",
|
||||
meta = (ClampMin = "0.0", ClampMax = "1.0"))
|
||||
float AnimEventTimepoint = 0.6f;
|
||||
void Reset()
|
||||
{
|
||||
bHasSelection = false;
|
||||
PromptText.Empty();
|
||||
WorldPosition = FVector::ZeroVector;
|
||||
bIsConsumed = false;
|
||||
}
|
||||
};
|
||||
|
||||
// === 结束动作 ===
|
||||
// ============================================================================
|
||||
// TurnToTarget 跨帧插值状态(非 UObject,纯 C++)
|
||||
// ============================================================================
|
||||
|
||||
/** 交互结束后的动作 */
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Interact|Post")
|
||||
EPostAction PostAction = EPostAction::Return;
|
||||
/** TurnToTarget 跨帧插值状态 — EffectManager 在 Update 中维护,
|
||||
* 每帧 RInterpTo 驱动旋转,不能一帧完成 */
|
||||
struct FTurnState
|
||||
{
|
||||
/** 是否正在转向 */
|
||||
bool bIsActive = false;
|
||||
|
||||
/** 转向起始旋转 */
|
||||
FRotator StartRotation = FRotator::ZeroRotator;
|
||||
|
||||
/** 转向目标旋转(面朝交互物方向) */
|
||||
FRotator TargetRotation = FRotator::ZeroRotator;
|
||||
|
||||
/** 已用时间(秒) */
|
||||
float ElapsedTime = 0.f;
|
||||
|
||||
/** 转向总时长(秒) */
|
||||
float Duration = 0.2f;
|
||||
|
||||
/** 是否已完成 */
|
||||
bool IsComplete() const { return ElapsedTime >= Duration; }
|
||||
|
||||
/** 获取当前插值 Alpha [0,1] */
|
||||
float GetAlpha() const { return FMath::Clamp(ElapsedTime / Duration, 0.f, 1.f); }
|
||||
|
||||
void Reset()
|
||||
{
|
||||
bIsActive = false;
|
||||
StartRotation = FRotator::ZeroRotator;
|
||||
TargetRotation = FRotator::ZeroRotator;
|
||||
ElapsedTime = 0.f;
|
||||
Duration = 0.2f;
|
||||
}
|
||||
};
|
||||
|
||||
// ============================================================================
|
||||
// 委托
|
||||
// ============================================================================
|
||||
|
||||
/** 物品拾取/获得事件 — 通知物品系统和收集系统 */
|
||||
/** 物品消耗事件(逻辑事件,Persistence/Backpack 订阅) */
|
||||
DECLARE_DYNAMIC_MULTICAST_DELEGATE_ThreeParams(
|
||||
FMLocalItemEvent,
|
||||
FName, ItemId,
|
||||
const FString&, DisplayName,
|
||||
const FName&, Category
|
||||
FMLocalItemEvent,
|
||||
FName, ItemId,
|
||||
const FString&, DisplayName,
|
||||
const FName&, Category
|
||||
);
|
||||
|
||||
/** Effect 完成回调 */
|
||||
DECLARE_DELEGATE(FOnEffectComplete);
|
||||
|
||||
@@ -0,0 +1,47 @@
|
||||
// ChildHood Project - 跨场景持久化子系统 实现
|
||||
|
||||
#include "Interaction/PersistenceSubsystem.h"
|
||||
#include "ChildHood.h"
|
||||
|
||||
// ============================================================================
|
||||
// 物品消耗状态
|
||||
// ============================================================================
|
||||
|
||||
bool UPersistenceSubsystem::IsItemCollected(FName ItemId) const
|
||||
{
|
||||
return CollectedItemIDs.Contains(ItemId);
|
||||
}
|
||||
|
||||
void UPersistenceSubsystem::MarkItemCollected(FName ItemId)
|
||||
{
|
||||
CollectedItemIDs.Add(ItemId);
|
||||
UE_LOG(LogChildHood, Log, TEXT("PersistenceSubsystem: 物品已收集: %s"), *ItemId.ToString());
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// 门状态
|
||||
// ============================================================================
|
||||
|
||||
bool UPersistenceSubsystem::IsDoorOpen(FName DoorId) const
|
||||
{
|
||||
const bool* Found = DoorStates.Find(DoorId);
|
||||
return Found ? *Found : false;
|
||||
}
|
||||
|
||||
void UPersistenceSubsystem::SetDoorOpen(FName DoorId, bool bIsOpen)
|
||||
{
|
||||
DoorStates.Add(DoorId, bIsOpen);
|
||||
UE_LOG(LogChildHood, Log, TEXT("PersistenceSubsystem: 门状态更新: %s → %s"),
|
||||
*DoorId.ToString(), bIsOpen ? TEXT("开") : TEXT("关"));
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// 重置
|
||||
// ============================================================================
|
||||
|
||||
void UPersistenceSubsystem::ResetAll()
|
||||
{
|
||||
CollectedItemIDs.Empty();
|
||||
DoorStates.Empty();
|
||||
UE_LOG(LogChildHood, Log, TEXT("PersistenceSubsystem: 所有持久数据已重置"));
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
// ChildHood Project - 跨场景持久化子系统
|
||||
// 对应技术方案: docs/technical/交互系统-技术方案.md 第12节(跨场景持久化设计)
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
#include "Subsystems/GameInstanceSubsystem.h"
|
||||
#include "PersistenceSubsystem.generated.h"
|
||||
|
||||
/**
|
||||
* UPersistenceSubsystem
|
||||
*
|
||||
* UGameInstanceSubsystem,跨场景持久数据持有者。
|
||||
* UE5 的 OpenLevel() 会销毁整个旧 World(包括所有 Actor 和 Component),
|
||||
* 因此将持久数据放在 GameInstanceSubsystem 中,与 World 生命周期解耦。
|
||||
*
|
||||
* 当前持有:
|
||||
* - CollectedItemIDs: 已收集物品 ID 集合
|
||||
* - DoorStates: 门开关状态映射
|
||||
*/
|
||||
UCLASS()
|
||||
class UPersistenceSubsystem : public UGameInstanceSubsystem
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
public:
|
||||
|
||||
// === 物品消耗状态 ===
|
||||
|
||||
/** 查询指定物品是否已被收集(跨场景持久) */
|
||||
UFUNCTION(BlueprintCallable, Category = "Persistence")
|
||||
bool IsItemCollected(FName ItemId) const;
|
||||
|
||||
/** 标记指定物品为已收集(写入持久层) */
|
||||
UFUNCTION(BlueprintCallable, Category = "Persistence")
|
||||
void MarkItemCollected(FName ItemId);
|
||||
|
||||
// === 门状态 ===
|
||||
|
||||
/** 查询指定门的开关状态(跨场景持久),未记录的门默认为关闭 */
|
||||
UFUNCTION(BlueprintCallable, Category = "Persistence")
|
||||
bool IsDoorOpen(FName DoorId) const;
|
||||
|
||||
/** 设置指定门的开关状态(写入持久层) */
|
||||
UFUNCTION(BlueprintCallable, Category = "Persistence")
|
||||
void SetDoorOpen(FName DoorId, bool bIsOpen);
|
||||
|
||||
// === 重置 ===
|
||||
|
||||
/** 重置所有持久数据(新游戏时调用) */
|
||||
UFUNCTION(BlueprintCallable, Category = "Persistence")
|
||||
void ResetAll();
|
||||
|
||||
private:
|
||||
|
||||
/** 已收集物品 ID 集合 */
|
||||
UPROPERTY()
|
||||
TSet<FName> CollectedItemIDs;
|
||||
|
||||
/** 门开关状态映射(true=打开,false=关闭) */
|
||||
UPROPERTY()
|
||||
TMap<FName, bool> DoorStates;
|
||||
};
|
||||
Reference in New Issue
Block a user