893ef1eac2
对应 Issue #32 审查退回(第二次)的4个问题: 1.(重大)移除 InteractionManager 反射调用 SetInteracting,SetInteracting(false) 改由 SystemDriver.OnEffectComplete 协调触发 2.(重大)DoorActor::OnInteractStarted 改为空实现,门开关业务由 EffectManager::ExecuteToggleDoor 驱动 3.(中等)EffectManager::ExecuteCameraReturn 销毁 PushCam 并清空 CachedPushCamera,避免冗余回位 4.(中等)EffectManager::AddToBackpack 添加收集系统集成 TODO 注释 编译验证通过 (UE5.7 ChildHoodEditor Development) Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
260 lines
7.9 KiB
C++
260 lines
7.9 KiB
C++
// ChildHood Project - 交互管理器 实现(v1.1 Effect/Action 架构重写)
|
||
// 对应技术方案: docs/technical/交互系统-技术方案.md 第6节(行为规则实现)
|
||
// 架构:普通 C++ 类,由 ASystemDriver 值持有,纯事件驱动无 Tick
|
||
|
||
#include "Interaction/InteractionManager.h"
|
||
#include "Interaction/InteractableComponent.h"
|
||
#include "ChildHood.h"
|
||
|
||
#include "GameFramework/Character.h"
|
||
#include "Engine/World.h"
|
||
|
||
// ============================================================================
|
||
// 日志类别
|
||
// ============================================================================
|
||
|
||
// InteractionManager 使用项目全局日志 LogChildHood
|
||
// 如需独立日志类别可在 .h 中 DECLARE_LOG_CATEGORY_EXTERN,此处 DEFINE
|
||
|
||
// ============================================================================
|
||
// 初始化
|
||
// ============================================================================
|
||
|
||
void FInteractionManager::Initialize(ACharacter* InPlayerCharacter)
|
||
{
|
||
PlayerCharacter = InPlayerCharacter;
|
||
|
||
UE_LOG(LogChildHood, Log, TEXT("InteractionManager: 初始化完成, Player=%s"),
|
||
InPlayerCharacter ? *InPlayerCharacter->GetName() : TEXT("nullptr"));
|
||
}
|
||
|
||
// ============================================================================
|
||
// 交互物注册
|
||
// ============================================================================
|
||
|
||
void FInteractionManager::RegisterInteractable(UInteractableComponent* Interactable)
|
||
{
|
||
if (!Interactable)
|
||
{
|
||
return;
|
||
}
|
||
|
||
// 去重检查
|
||
if (NearbyInteractables.Contains(Interactable))
|
||
{
|
||
return;
|
||
}
|
||
|
||
// 已消耗的交互物不注册
|
||
if (Interactable->IsConsumed())
|
||
{
|
||
return;
|
||
}
|
||
|
||
NearbyInteractables.Add(Interactable);
|
||
|
||
// 排序 + 重置选中索引 + 刷新缓存
|
||
UpdateSelection();
|
||
RefreshCachedSelection();
|
||
|
||
UE_LOG(LogChildHood, Log, TEXT("InteractionManager: 注册 '%s', 列表大小=%d"),
|
||
*Interactable->GetInteractableData().DisplayName, NearbyInteractables.Num());
|
||
}
|
||
|
||
void FInteractionManager::UnregisterInteractable(UInteractableComponent* Interactable)
|
||
{
|
||
if (!Interactable)
|
||
{
|
||
return;
|
||
}
|
||
|
||
// 从列表中移除
|
||
int32 RemovedCount = NearbyInteractables.Remove(Interactable);
|
||
if (RemovedCount == 0)
|
||
{
|
||
return;
|
||
}
|
||
|
||
// Executing 状态下:如果移除的是 CurrentInteractable,
|
||
// 仅从检测列表移除,不清理 CurrentInteractable(Effect 可能仍在执行)
|
||
// CurrentInteractable 将在 OnInteractionFinished 中统一清理
|
||
// 这里不做额外的 CurrentInteractable 清理
|
||
|
||
// 重新排序 + 刷新缓存
|
||
UpdateSelection();
|
||
RefreshCachedSelection();
|
||
|
||
UE_LOG(LogChildHood, Log, TEXT("InteractionManager: 注销 '%s', 列表大小=%d"),
|
||
*Interactable->GetInteractableData().DisplayName, NearbyInteractables.Num());
|
||
}
|
||
|
||
// ============================================================================
|
||
// 交互触发
|
||
// ============================================================================
|
||
|
||
UInteractableComponent* FInteractionManager::BeginInteract()
|
||
{
|
||
// 前置检查:必须 Free 状态
|
||
if (State != EInteractionState::Free)
|
||
{
|
||
UE_LOG(LogChildHood, Verbose, TEXT("InteractionManager::BeginInteract: 拒绝,当前状态不是 Free"));
|
||
return nullptr;
|
||
}
|
||
|
||
// 前置检查:必须有选中的交互物
|
||
if (!NearbyInteractables.IsValidIndex(SelectedIndex))
|
||
{
|
||
UE_LOG(LogChildHood, Verbose, TEXT("InteractionManager::BeginInteract: 拒绝,没有选中的交互物"));
|
||
return nullptr;
|
||
}
|
||
|
||
UInteractableComponent* Target = NearbyInteractables[SelectedIndex].Get();
|
||
|
||
// 前置检查:弱引用有效且未被消耗
|
||
if (!Target || Target->IsConsumed())
|
||
{
|
||
UE_LOG(LogChildHood, Verbose, TEXT("InteractionManager::BeginInteract: 拒绝,交互物无效或已消耗"));
|
||
return nullptr;
|
||
}
|
||
|
||
// 进入 Executing 状态
|
||
State = EInteractionState::Executing;
|
||
CurrentInteractable = Target;
|
||
|
||
// SetInteracting(true) 由 Character 侧 OnInteract 直接调用(见 ChildHoodCharacter::OnInteract)。
|
||
// FInteractionManager 作为纯逻辑层不操作 Character 表现状态,避免 SetInteracting(true) 双调用。
|
||
|
||
UE_LOG(LogChildHood, Log, TEXT("InteractionManager::BeginInteract: 开始交互 '%s'"),
|
||
*Target->GetInteractableData().DisplayName);
|
||
|
||
return Target;
|
||
}
|
||
|
||
void FInteractionManager::CancelInteract()
|
||
{
|
||
// 前置检查:必须 Executing 状态
|
||
if (State != EInteractionState::Executing)
|
||
{
|
||
return;
|
||
}
|
||
|
||
UE_LOG(LogChildHood, Log, TEXT("InteractionManager::CancelInteract: 请求取消交互"));
|
||
|
||
// v0.1 简化:仅记录日志,实际取消由 ASystemDriver 协调 EffectManager
|
||
// ASystemDriver::CancelInteract 会先调用 EffectManager.Cancel(),
|
||
// EffectManager 完成后会回调 OnInteractionFinished
|
||
// 这里不做状态变更,保持 Executing 直到 OnInteractionFinished 被调用
|
||
}
|
||
|
||
void FInteractionManager::SwitchTarget(int32 Direction)
|
||
{
|
||
int32 Num = NearbyInteractables.Num();
|
||
if (Num == 0)
|
||
{
|
||
return;
|
||
}
|
||
|
||
// Executing 状态下忽略切换
|
||
if (State != EInteractionState::Free)
|
||
{
|
||
return;
|
||
}
|
||
|
||
SelectedIndex = (SelectedIndex + Direction + Num) % Num;
|
||
RefreshCachedSelection();
|
||
|
||
UE_LOG(LogChildHood, Log, TEXT("InteractionManager::SwitchTarget: 切换到 %d/%d"),
|
||
SelectedIndex, Num);
|
||
}
|
||
|
||
// ============================================================================
|
||
// Effect 完成回调
|
||
// ============================================================================
|
||
|
||
void FInteractionManager::OnInteractionFinished()
|
||
{
|
||
UE_LOG(LogChildHood, Log, TEXT("InteractionManager::OnInteractionFinished: Effect 执行完毕"));
|
||
|
||
// 回到 Free 状态
|
||
State = EInteractionState::Free;
|
||
|
||
// 清理当前交互物引用
|
||
CurrentInteractable.Reset();
|
||
|
||
// SetInteracting(false) 由 ASystemDriver::OnEffectComplete 协调调用,
|
||
// FInteractionManager 作为纯逻辑层不操作 Character 表现状态。
|
||
|
||
// 重新排序和刷新缓存(消耗可能改变了列表)
|
||
UpdateSelection();
|
||
RefreshCachedSelection();
|
||
}
|
||
|
||
// ============================================================================
|
||
// 状态查询
|
||
// ============================================================================
|
||
|
||
UInteractableComponent* FInteractionManager::GetCurrentInteractable() const
|
||
{
|
||
return CurrentInteractable.Get();
|
||
}
|
||
|
||
// ============================================================================
|
||
// 内部方法
|
||
// ============================================================================
|
||
|
||
void FInteractionManager::UpdateSelection()
|
||
{
|
||
// 清理已失效的弱引用
|
||
NearbyInteractables.RemoveAll([](const TWeakObjectPtr<UInteractableComponent>& WeakPtr)
|
||
{
|
||
return !WeakPtr.IsValid();
|
||
});
|
||
|
||
// 清理已消耗的交互物
|
||
NearbyInteractables.RemoveAll([](const TWeakObjectPtr<UInteractableComponent>& WeakPtr)
|
||
{
|
||
UInteractableComponent* Comp = WeakPtr.Get();
|
||
return Comp && Comp->IsConsumed();
|
||
});
|
||
|
||
// 按与玩家的水平距离排序(DistSquared2D,避免 sqrt)
|
||
if (ACharacter* Char = PlayerCharacter.Get())
|
||
{
|
||
FVector PlayerLoc = Char->GetActorLocation();
|
||
NearbyInteractables.Sort([PlayerLoc](const TWeakObjectPtr<UInteractableComponent>& A, const TWeakObjectPtr<UInteractableComponent>& B)
|
||
{
|
||
if (!A.IsValid()) return false;
|
||
if (!B.IsValid()) return true;
|
||
float DistA = FVector::DistSquared2D(PlayerLoc, A->GetWorldLocation());
|
||
float DistB = FVector::DistSquared2D(PlayerLoc, B->GetWorldLocation());
|
||
return DistA < DistB;
|
||
});
|
||
}
|
||
|
||
// 重置选中索引为 0(最近的)
|
||
SelectedIndex = 0;
|
||
}
|
||
|
||
void FInteractionManager::RefreshCachedSelection()
|
||
{
|
||
// 清理无效项后的安全检查
|
||
if (NearbyInteractables.Num() == 0 || !NearbyInteractables.IsValidIndex(SelectedIndex))
|
||
{
|
||
CachedSelected.Reset();
|
||
return;
|
||
}
|
||
|
||
UInteractableComponent* Selected = NearbyInteractables[SelectedIndex].Get();
|
||
if (!Selected)
|
||
{
|
||
CachedSelected.Reset();
|
||
return;
|
||
}
|
||
|
||
// 从 Component 读取信息并缓存
|
||
CachedSelected.PromptText = Selected->GetPromptText();
|
||
CachedSelected.WorldPosition = Selected->GetWorldLocation();
|
||
CachedSelected.bIsConsumed = Selected->IsConsumed();
|
||
CachedSelected.bHasSelection = true;
|
||
}
|