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>
This commit is contained in:
2026-06-14 23:48:55 +08:00
parent 46e9b33629
commit 893ef1eac2
6 changed files with 42 additions and 38 deletions
+5 -5
View File
@@ -24,11 +24,11 @@ ADoorActor::ADoorActor()
void ADoorActor::OnInteractStarted_Implementation(UInteractableComponent* Comp)
{
// 只响应门组件的交互
if (UDoorInteractableComponent* DoorComp = Cast<UDoorInteractableComponent>(Comp))
{
DoorComp->ToggleDoor();
}
(void)Comp; // 参数保留以匹配基类签名(OnInteractStarted 当前无调用方)
// v1.1 架构:门的开关业务逻辑由 EffectManager::ExecuteToggleDoor 统一驱动,
// 视觉旋转由 UDoorInteractableComponent::ToggleDoor 内部回调 UpdateDoorVisual 完成。
// 此处保留空实现,避免与 Effect 路径重复 ToggleDoor 导致状态抵消。
}
// ============================================================================
+4 -4
View File
@@ -16,7 +16,7 @@ class UDoorInteractableComponent;
*
* 门类型的交互物 Actor。在基类基础上:
* - 使用 UDoorInteractableComponent 替代 UInteractableComponent
* - OnInteractStarted 中调用 ToggleDoor 并更新 Mesh 旋转
* - v1.1:门开关业务由 EffectManager::ExecuteToggleDoor 驱动,OnInteractStarted 为空实现
* - UpdateDoorVisual 执行实际的 Mesh Yaw 旋转(开门+90, 关门0)
*
* v0.1 阶段旋转为瞬时 SetRelativeRotation,不使用动画插值。
@@ -39,9 +39,9 @@ public:
// === 交互回调 ===
/**
* 交互开始时调用。
* Cast 到 UDoorInteractableComponent 后调用 ToggleDoor,
* 由 Component 负责状态切换和持久化,Actor 只负责视觉。
* 交互开始回调(v1.1 为空实现)。
* 门开关业务逻辑由 EffectManager::ExecuteToggleDoor 统一驱动,
* 视觉旋转由 UDoorInteractableComponent::ToggleDoor 内部回调,Actor 不在此重复处理。
*/
virtual void OnInteractStarted_Implementation(UInteractableComponent* Comp) override;
+13 -1
View File
@@ -149,6 +149,8 @@ void FEffectManager::ExecuteAction(const FInteractAction& Action)
break;
// v0.1 用日志占位的 Action
case EInteractActionType::AddToBackpack:
// TODO: 后续集成收集系统(Issue #33 P-013 收集系统程序实现)。
// 届时由 ASystemDriver 回调链通知 FCollectionManager,EffectManager 不直接处理业务逻辑。
UE_LOG(LogTemp, Log, TEXT("EffectManager: AddToBackpack - Item: %s"),
CurrentContext.TargetComp.IsValid()
? *CurrentContext.TargetComp->GetInteractableData().DisplayName
@@ -328,7 +330,17 @@ void FEffectManager::ExecuteCameraReturn(const FInteractAction& Action)
PC->SetViewTargetWithBlend(CurrentContext.Instigator.Get(), Action.ReturnDuration);
// 延迟销毁 PushCamera(在 PerformCleanup 中统一清理)
// 相机已回到角色,立即销毁推镜摄像机并清空缓存,
// 避免 PerformCleanup 检测到有效缓存后冗余回位 + 重复销毁。
if (APushCameraActor* PushCam = CachedPushCamera.Get())
{
CachedPushCamera = nullptr;
if (PushCam->GetWorld())
{
PushCam->Destroy();
}
}
UE_LOG(LogTemp, Verbose, TEXT("EffectManager: CameraReturn (Dur=%.2fs)"), Action.ReturnDuration);
}
@@ -121,20 +121,8 @@ UInteractableComponent* FInteractionManager::BeginInteract()
State = EInteractionState::Executing;
CurrentInteractable = Target;
// 通知 Character 进入交互状态(屏蔽移动)
// FInteractionManager 只持有 ACharacter 基类指针,不直接依赖 AChildHoodCharacter。
// 通过 UE 反射调用 SetInteracting(UFUNCTION(BlueprintCallable))。
if (ACharacter* Char = PlayerCharacter.Get())
{
UFunction* Fn = Char->FindFunction(FName(TEXT("SetInteracting")));
if (Fn)
{
struct FSetInteractingParams { bool bValue; };
FSetInteractingParams Params;
Params.bValue = true;
Char->ProcessEvent(Fn, &Params);
}
}
// SetInteracting(true) 由 Character 侧 OnInteract 直接调用(见 ChildHoodCharacter::OnInteract)。
// FInteractionManager 作为纯逻辑层不操作 Character 表现状态,避免 SetInteracting(true) 双调用。
UE_LOG(LogChildHood, Log, TEXT("InteractionManager::BeginInteract: 开始交互 '%s'"),
*Target->GetInteractableData().DisplayName);
@@ -193,19 +181,8 @@ void FInteractionManager::OnInteractionFinished()
// 清理当前交互物引用
CurrentInteractable.Reset();
// 通知 Character 解除交互状态
// 通过 UE 反射调用 SetInteracting(false)
if (ACharacter* Char = PlayerCharacter.Get())
{
UFunction* Fn = Char->FindFunction(FName(TEXT("SetInteracting")));
if (Fn)
{
struct FSetInteractingParams { bool bValue; };
FSetInteractingParams Params;
Params.bValue = false;
Char->ProcessEvent(Fn, &Params);
}
}
// SetInteracting(false) 由 ASystemDriver::OnEffectComplete 协调调用,
// FInteractionManager 作为纯逻辑层不操作 Character 表现状态。
// 重新排序和刷新缓存(消耗可能改变了列表)
UpdateSelection();
@@ -113,7 +113,7 @@ private:
/** 当前正在交互的交互物(Executing 状态下锁定) */
TWeakObjectPtr<UInteractableComponent> CurrentInteractable;
/** 玩家角色弱引用(用于排序距离计算和 SetInteracting 调用) */
/** 玩家角色弱引用(用于按水平距离排序的选中计算) */
TWeakObjectPtr<ACharacter> PlayerCharacter;
/** Presentation 层缓存数据 — 选中交互物的摘要信息 */
+15
View File
@@ -10,6 +10,7 @@
#include "Kismet/GameplayStatics.h"
#include "GameFramework/PlayerController.h"
#include "GameFramework/Character.h"
#include "ChildHoodCharacter.h"
// ============================================================================
// 构造函数
@@ -108,4 +109,18 @@ void ASystemDriver::OnEffectComplete()
{
// Effect 完成后通知 InteractionManager 恢复 Free 状态
InteractionManager.OnInteractionFinished();
// 通知 Character 复位交互状态(解除移动屏蔽)。
// FInteractionManager 作为纯逻辑层不操作 Character 表现状态,
// 由 ASystemDriver 作为 World 级协调者统一驱动。
if (UWorld* World = GetWorld())
{
if (APlayerController* PC = World->GetFirstPlayerController())
{
if (AChildHoodCharacter* Char = Cast<AChildHoodCharacter>(PC->GetCharacter()))
{
Char->SetInteracting(false);
}
}
}
}