feat(interaction): 新增交互系统核心C++框架(6a-6f)
This commit is contained in:
@@ -0,0 +1,221 @@
|
||||
// ChildHood Project - 可交互物组件 实现
|
||||
|
||||
#include "Interaction/InteractableComponent.h"
|
||||
#include "Interaction/InteractableActor.h"
|
||||
#include "Interaction/InteractionManager.h"
|
||||
#include "ChildHoodGameInstance.h"
|
||||
#include "ChildHood.h"
|
||||
|
||||
#include "Components/SphereComponent.h"
|
||||
#include "Engine/World.h"
|
||||
#include "GameFramework/Character.h"
|
||||
#include "Kismet/GameplayStatics.h"
|
||||
|
||||
// ============================================================================
|
||||
// 构造函数
|
||||
// ============================================================================
|
||||
|
||||
UInteractableComponent::UInteractableComponent()
|
||||
{
|
||||
// 不需要 Tick,检测由碰撞事件驱动
|
||||
PrimaryComponentTick.bCanEverTick = false;
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// 生命周期
|
||||
// ============================================================================
|
||||
|
||||
void UInteractableComponent::BeginPlay()
|
||||
{
|
||||
Super::BeginPlay();
|
||||
|
||||
AActor* Owner = GetOwner();
|
||||
if (!Owner)
|
||||
{
|
||||
UE_LOG(LogChildHood, Warning, TEXT("InteractableComponent: 无 Owner,跳过初始化"));
|
||||
return;
|
||||
}
|
||||
|
||||
// --- 创建检测球 ---
|
||||
DetectionSphere = NewObject<USphereComponent>(this, USphereComponent::StaticClass(), TEXT("DetectionSphere"));
|
||||
if (DetectionSphere)
|
||||
{
|
||||
DetectionSphere->SetupAttachment(Owner->GetRootComponent());
|
||||
DetectionSphere->RegisterComponent();
|
||||
DetectionSphere->SetSphereRadius(Data.TriggerRadius);
|
||||
DetectionSphere->SetCollisionProfileName(TEXT("OverlapAllDynamic"));
|
||||
// 关闭物理模拟,仅用于 Overlap 检测
|
||||
DetectionSphere->SetGenerateOverlapEvents(true);
|
||||
DetectionSphere->SetCanEverAffectNavigation(false);
|
||||
|
||||
// 绑定碰撞事件
|
||||
DetectionSphere->OnComponentBeginOverlap.AddDynamic(this, &UInteractableComponent::OnDetectionOverlapBegin);
|
||||
DetectionSphere->OnComponentEndOverlap.AddDynamic(this, &UInteractableComponent::OnDetectionOverlapEnd);
|
||||
}
|
||||
else
|
||||
{
|
||||
UE_LOG(LogChildHood, Error, TEXT("InteractableComponent: DetectionSphere 创建失败"));
|
||||
}
|
||||
|
||||
// --- 恢复跨场景持久状态 ---
|
||||
if (UChildHoodGameInstance* GI = GetWorld()->GetGameInstance<UChildHoodGameInstance>())
|
||||
{
|
||||
RestorePersistentState(GI);
|
||||
}
|
||||
|
||||
UE_LOG(LogChildHood, Log, TEXT("InteractableComponent Init: %s (Type=%s, Radius=%f)"),
|
||||
Data.DisplayName.IsEmpty() ? TEXT("(unnamed)") : *Data.DisplayName,
|
||||
*UEnum::GetValueAsString(Data.Type),
|
||||
Data.TriggerRadius);
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// 提示文本
|
||||
// ============================================================================
|
||||
|
||||
FString UInteractableComponent::GetPromptText() 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);
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// 消耗管理
|
||||
// ============================================================================
|
||||
|
||||
void UInteractableComponent::Consume()
|
||||
{
|
||||
bIsConsumed = true;
|
||||
|
||||
// 禁用检测球,不再触发 Overlap
|
||||
if (DetectionSphere)
|
||||
{
|
||||
DetectionSphere->SetCollisionEnabled(ECollisionEnabled::NoCollision);
|
||||
}
|
||||
|
||||
// 写入持久层
|
||||
if (UWorld* World = GetWorld())
|
||||
{
|
||||
if (UChildHoodGameInstance* GI = World->GetGameInstance<UChildHoodGameInstance>())
|
||||
{
|
||||
GI->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("关"));
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// 位置查询
|
||||
// ============================================================================
|
||||
|
||||
FVector UInteractableComponent::GetWorldLocation() const
|
||||
{
|
||||
AActor* Owner = GetOwner();
|
||||
return Owner ? Owner->GetActorLocation() : FVector::ZeroVector;
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// 跨场景持久状态恢复
|
||||
// ============================================================================
|
||||
|
||||
void UInteractableComponent::RestorePersistentState(UChildHoodGameInstance* GI)
|
||||
{
|
||||
if (!GI) return;
|
||||
|
||||
// oneShot 物品:已消耗 → 隐藏 + 禁用检测
|
||||
if (Data.bOneShot && GI->IsItemCollected(Data.ItemId))
|
||||
{
|
||||
bIsConsumed = true;
|
||||
if (DetectionSphere)
|
||||
{
|
||||
DetectionSphere->SetCollisionEnabled(ECollisionEnabled::NoCollision);
|
||||
}
|
||||
if (AInteractableActor* Owner = Cast<AInteractableActor>(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("关"));
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// 碰撞回调
|
||||
// ============================================================================
|
||||
|
||||
void UInteractableComponent::OnDetectionOverlapBegin(UPrimitiveComponent* OverlappedComponent,
|
||||
AActor* OtherActor,
|
||||
UPrimitiveComponent* OtherComp,
|
||||
int32 OtherBodyIndex,
|
||||
bool bFromSweep,
|
||||
const FHitResult& SweepResult)
|
||||
{
|
||||
if (!OtherActor) return;
|
||||
|
||||
// 只响应 Character(玩家)的进入
|
||||
ACharacter* Character = Cast<ACharacter>(OtherActor);
|
||||
if (!Character) return;
|
||||
|
||||
// 已消耗的交互物不再注册
|
||||
if (bIsConsumed) return;
|
||||
|
||||
UE_LOG(LogChildHood, Verbose, TEXT("检测到角色进入范围: %s ← %s"),
|
||||
*Data.DisplayName, *OtherActor->GetName());
|
||||
|
||||
// 获取角色的 InteractionManager 并注册自己
|
||||
if (UInteractionManager* IM = Character->FindComponentByClass<UInteractionManager>())
|
||||
{
|
||||
IM->RegisterInteractable(this);
|
||||
}
|
||||
}
|
||||
|
||||
void UInteractableComponent::OnDetectionOverlapEnd(UPrimitiveComponent* OverlappedComponent,
|
||||
AActor* OtherActor,
|
||||
UPrimitiveComponent* OtherComp,
|
||||
int32 OtherBodyIndex)
|
||||
{
|
||||
if (!OtherActor) return;
|
||||
|
||||
ACharacter* Character = Cast<ACharacter>(OtherActor);
|
||||
if (!Character) return;
|
||||
|
||||
UE_LOG(LogChildHood, Verbose, TEXT("角色离开范围: %s ← %s"),
|
||||
*Data.DisplayName, *OtherActor->GetName());
|
||||
|
||||
// 获取角色的 InteractionManager 并注销自己
|
||||
if (UInteractionManager* IM = Character->FindComponentByClass<UInteractionManager>())
|
||||
{
|
||||
IM->UnregisterInteractable(this);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user