feat(interaction): 新增交互系统核心C++框架(6a-6f)

This commit is contained in:
2026-06-11 11:25:15 +08:00
parent a4fd3d3092
commit 00ca7044c7
12 changed files with 1776 additions and 0 deletions
@@ -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);
}
}