Files
ChildHood/Source/ChildHood/Interaction/InteractionManager.cpp
T
starmen1265 893ef1eac2 fix(interaction): 修复SetInteracting双调用等4个审查退回问题
对应 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>
2026-06-14 23:48:55 +08:00

260 lines
7.9 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// 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;
}