feat(interaction): 交互系统v1.1 Effect/Action架构实现
新增: - EffectManager: Effect Timeline调度 + Action执行分发 - SystemDriver: AInfo全局驱动器, 持有FInteractionManager/FEffectManager - InteractionPromptComponent: 独立Prompt UI组件(只读Manager缓存) - DoorActor/DoorInteractableComponent: 门派生类 修改: - InteractionManager: 从UActorComponent重写为普通C++ FInteractionManager - Character: 替换旧InteractionManager为PromptComponent, 通过Driver访问Manager - GameInstance: 空壳化, 持久数据迁移至PersistenceSubsystem - InteractableComponent: 碰撞回调接入ASystemDriver注册路径 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,49 @@
|
||||
// ChildHood Project - 门 Actor 实现
|
||||
|
||||
#include "Interaction/DoorActor.h"
|
||||
#include "Interaction/DoorInteractableComponent.h"
|
||||
#include "Interaction/InteractableComponent.h"
|
||||
#include "ChildHood.h"
|
||||
|
||||
// ============================================================================
|
||||
// 构造函数
|
||||
// ============================================================================
|
||||
|
||||
ADoorActor::ADoorActor()
|
||||
{
|
||||
PrimaryActorTick.bCanEverTick = false;
|
||||
|
||||
// 基类构造函数已创建 MeshComp(根组件)和 InteractableComp
|
||||
|
||||
// 创建门专用交互组件,替代基类的通用 InteractableComp
|
||||
DoorInteractableComp = CreateDefaultSubobject<UDoorInteractableComponent>(TEXT("DoorInteractableComp"));
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// 交互回调
|
||||
// ============================================================================
|
||||
|
||||
void ADoorActor::OnInteractStarted_Implementation(UInteractableComponent* Comp)
|
||||
{
|
||||
// 只响应门组件的交互
|
||||
if (UDoorInteractableComponent* DoorComp = Cast<UDoorInteractableComponent>(Comp))
|
||||
{
|
||||
DoorComp->ToggleDoor();
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// 视觉更新
|
||||
// ============================================================================
|
||||
|
||||
void ADoorActor::UpdateDoorVisual(bool bIsOpen)
|
||||
{
|
||||
if (!MeshComp) return;
|
||||
|
||||
// 瞬时旋转:开门偏移 OpenYawOffset 度,关门回到 0 度
|
||||
const float TargetYaw = bIsOpen ? OpenYawOffset : 0.f;
|
||||
MeshComp->SetRelativeRotation(FRotator(0.f, TargetYaw, 0.f));
|
||||
|
||||
UE_LOG(LogChildHood, Verbose, TEXT("DoorActor Visual: Yaw=%.0f (%s)"),
|
||||
TargetYaw, bIsOpen ? TEXT("Open") : TEXT("Closed"));
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
// ChildHood Project - 门 Actor
|
||||
// 对应技术方案: docs/technical/交互系统-技术方案.md 第3节(类设计)
|
||||
// 职责:门的视觉表现(Mesh 旋转)、交互回调分发
|
||||
// 派生自 AInteractableActor,使用 UDoorInteractableComponent 替代基类组件
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
#include "Interaction/InteractableActor.h"
|
||||
#include "DoorActor.generated.h"
|
||||
|
||||
class UDoorInteractableComponent;
|
||||
|
||||
/**
|
||||
* ADoorActor
|
||||
*
|
||||
* 门类型的交互物 Actor。在基类基础上:
|
||||
* - 使用 UDoorInteractableComponent 替代 UInteractableComponent
|
||||
* - OnInteractStarted 中调用 ToggleDoor 并更新 Mesh 旋转
|
||||
* - UpdateDoorVisual 执行实际的 Mesh Yaw 旋转(开门+90, 关门0)
|
||||
*
|
||||
* v0.1 阶段旋转为瞬时 SetRelativeRotation,不使用动画插值。
|
||||
*/
|
||||
UCLASS()
|
||||
class ADoorActor : public AInteractableActor
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
public:
|
||||
|
||||
ADoorActor();
|
||||
|
||||
// === 组件访问 ===
|
||||
|
||||
/** 获取门交互组件 */
|
||||
UFUNCTION(BlueprintCallable, Category = "Door")
|
||||
UDoorInteractableComponent* GetDoorInteractableComp() const { return DoorInteractableComp; }
|
||||
|
||||
// === 交互回调 ===
|
||||
|
||||
/**
|
||||
* 交互开始时调用。
|
||||
* Cast 到 UDoorInteractableComponent 后调用 ToggleDoor,
|
||||
* 由 Component 负责状态切换和持久化,Actor 只负责视觉。
|
||||
*/
|
||||
virtual void OnInteractStarted_Implementation(UInteractableComponent* Comp) override;
|
||||
|
||||
// === 视觉更新 ===
|
||||
|
||||
/**
|
||||
* 更新门 Mesh 的视觉旋转。
|
||||
* bIsOpen=true → Yaw +90 度(打开)
|
||||
* bIsOpen=false → Yaw 0 度(关闭)
|
||||
* 由 UDoorInteractableComponent 在 ToggleDoor/RestorePersistentState 中调用。
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, Category = "Door")
|
||||
void UpdateDoorVisual(bool bIsOpen);
|
||||
|
||||
protected:
|
||||
|
||||
/** 门交互组件(替代基类的 InteractableComp,提供门特有状态和行为) */
|
||||
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Components", meta = (AllowPrivateAccess = "true"))
|
||||
TObjectPtr<UDoorInteractableComponent> DoorInteractableComp;
|
||||
|
||||
/** 门打开时的 Yaw 偏移(度),可在编辑器中调整 */
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Door", meta = (ClampMin = "0", ClampMax = "180"))
|
||||
float OpenYawOffset = 90.f;
|
||||
};
|
||||
@@ -0,0 +1,89 @@
|
||||
// ChildHood Project - 门交互组件 实现
|
||||
|
||||
#include "Interaction/DoorInteractableComponent.h"
|
||||
#include "Interaction/DoorActor.h"
|
||||
#include "Interaction/PersistenceSubsystem.h"
|
||||
#include "ChildHood.h"
|
||||
|
||||
#include "Engine/World.h"
|
||||
#include "GameFramework/GameInstance.h"
|
||||
|
||||
// ============================================================================
|
||||
// 构造函数
|
||||
// ============================================================================
|
||||
|
||||
UDoorInteractableComponent::UDoorInteractableComponent()
|
||||
{
|
||||
// 门不需要 Tick,状态变更由交互事件驱动
|
||||
PrimaryComponentTick.bCanEverTick = false;
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// 门操作
|
||||
// ============================================================================
|
||||
|
||||
void UDoorInteractableComponent::ToggleDoor()
|
||||
{
|
||||
bDoorIsOpen = !bDoorIsOpen;
|
||||
|
||||
// 通知 ADoorActor 更新视觉旋转
|
||||
if (AActor* Owner = GetOwner())
|
||||
{
|
||||
if (ADoorActor* DoorActor = Cast<ADoorActor>(Owner))
|
||||
{
|
||||
DoorActor->UpdateDoorVisual(bDoorIsOpen);
|
||||
}
|
||||
}
|
||||
|
||||
// 持久化门状态
|
||||
if (UWorld* World = GetWorld())
|
||||
{
|
||||
if (UPersistenceSubsystem* Persistence = World->GetGameInstance()->GetSubsystem<UPersistenceSubsystem>())
|
||||
{
|
||||
Persistence->SetDoorOpen(Data.ItemId, bDoorIsOpen);
|
||||
}
|
||||
}
|
||||
|
||||
UE_LOG(LogChildHood, Log, TEXT("Door Toggle: %s → %s"),
|
||||
*Data.ItemId.ToString(),
|
||||
bDoorIsOpen ? TEXT("Open") : TEXT("Closed"));
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// 提示文本
|
||||
// ============================================================================
|
||||
|
||||
FString UDoorInteractableComponent::GetPromptText_Implementation() const
|
||||
{
|
||||
// 根据门状态选择动词:已打开用 Alt 动词(如"关上"),已关闭用主动词(如"打开")
|
||||
const FString& Verb = bDoorIsOpen ? Data.InteractVerbAlt : Data.InteractVerb;
|
||||
return FString::Printf(TEXT("[E] %s %s"), *Verb, *Data.DisplayName);
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// 跨场景持久状态恢复
|
||||
// ============================================================================
|
||||
|
||||
void UDoorInteractableComponent::RestorePersistentState(UPersistenceSubsystem* Persistence)
|
||||
{
|
||||
if (!Persistence) return;
|
||||
|
||||
// 1. 先调用基类恢复消耗状态
|
||||
Super::RestorePersistentState(Persistence);
|
||||
|
||||
// 2. 恢复门开关状态
|
||||
bDoorIsOpen = Persistence->IsDoorOpen(Data.ItemId);
|
||||
|
||||
// 3. 通知 DoorActor 更新视觉旋转
|
||||
if (AActor* Owner = GetOwner())
|
||||
{
|
||||
if (ADoorActor* DoorActor = Cast<ADoorActor>(Owner))
|
||||
{
|
||||
DoorActor->UpdateDoorVisual(bDoorIsOpen);
|
||||
}
|
||||
}
|
||||
|
||||
UE_LOG(LogChildHood, Log, TEXT("Door RestorePersistentState: %s → %s"),
|
||||
*Data.ItemId.ToString(),
|
||||
bDoorIsOpen ? TEXT("Open") : TEXT("Closed"));
|
||||
}
|
||||
@@ -0,0 +1,447 @@
|
||||
// ChildHood Project - Effect Timeline 调度器 实现
|
||||
// 对应技术方案: docs/technical/交互系统-技术方案.md 第3节 + 第6节
|
||||
|
||||
#include "Interaction/EffectManager.h"
|
||||
#include "Engine/World.h"
|
||||
#include "GameFramework/PlayerController.h"
|
||||
#include "GameFramework/Character.h"
|
||||
#include "Interaction/InteractableComponent.h"
|
||||
#include "Interaction/PersistenceSubsystem.h"
|
||||
#include "Interaction/PushCameraActor.h"
|
||||
#include "Components/SphereComponent.h"
|
||||
#include "Kismet/GameplayStatics.h"
|
||||
#include "Animation/AnimInstance.h"
|
||||
|
||||
// ============================================================================
|
||||
// 公开接口
|
||||
// ============================================================================
|
||||
|
||||
void FEffectManager::SetOnEffectComplete(FOnEffectComplete&& Callback)
|
||||
{
|
||||
OnEffectComplete = MoveTemp(Callback);
|
||||
}
|
||||
|
||||
void FEffectManager::PlayEffect(const FInteractEffect& Effect, const FEffectContext& Context)
|
||||
{
|
||||
// 如果有正在播放的 Effect,先强制清理
|
||||
if (bIsPlaying)
|
||||
{
|
||||
PerformCleanup();
|
||||
}
|
||||
|
||||
CurrentEffect = Effect;
|
||||
CurrentContext = Context;
|
||||
ElapsedTime = 0.f;
|
||||
NextActionIndex = 0;
|
||||
bIsPlaying = true;
|
||||
bIsWaitingForInput = false;
|
||||
TurnState.Reset();
|
||||
LastNarrativeIndex = -1;
|
||||
CachedPushCamera = nullptr;
|
||||
|
||||
UE_LOG(LogTemp, Verbose, TEXT("EffectManager: PlayEffect '%s' (Duration=%.2f, Actions=%d)"),
|
||||
*CurrentEffect.EffectName, CurrentEffect.TotalDuration, CurrentEffect.Actions.Num());
|
||||
}
|
||||
|
||||
void FEffectManager::CancelEffect()
|
||||
{
|
||||
if (!bIsPlaying) return;
|
||||
if (!CurrentEffect.bCancellable) return;
|
||||
|
||||
UE_LOG(LogTemp, Verbose, TEXT("EffectManager: CancelEffect (WaitingForInput=%s)"),
|
||||
bIsWaitingForInput ? TEXT("true") : TEXT("false"));
|
||||
|
||||
if (bIsWaitingForInput)
|
||||
{
|
||||
// 用户输入是期望的恢复信号 -> 走正常恢复
|
||||
ResumeFromWait();
|
||||
return;
|
||||
}
|
||||
|
||||
// 非暂停态强制取消
|
||||
bIsPlaying = false;
|
||||
PerformCleanup();
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// 每帧更新
|
||||
// ============================================================================
|
||||
|
||||
void FEffectManager::Update(float DeltaTime)
|
||||
{
|
||||
if (!bIsPlaying) return;
|
||||
if (bIsWaitingForInput) return; // 暂停等待用户输入
|
||||
|
||||
// 1. 推进时间
|
||||
ElapsedTime += DeltaTime;
|
||||
|
||||
// 2. 检查是否到达 TotalDuration
|
||||
if (ElapsedTime >= CurrentEffect.TotalDuration)
|
||||
{
|
||||
FinishEffect();
|
||||
return;
|
||||
}
|
||||
|
||||
// 3. 触发到时的 Action
|
||||
while (NextActionIndex < CurrentEffect.Actions.Num())
|
||||
{
|
||||
const FInteractAction& Action = CurrentEffect.Actions[NextActionIndex];
|
||||
if (ElapsedTime >= Action.TriggerTime)
|
||||
{
|
||||
ExecuteAction(Action);
|
||||
NextActionIndex++;
|
||||
}
|
||||
else
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// 4. 更新 TurnToTarget 插值
|
||||
UpdateTurn(DeltaTime);
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Action 分发
|
||||
// ============================================================================
|
||||
|
||||
void FEffectManager::ExecuteAction(const FInteractAction& Action)
|
||||
{
|
||||
switch (Action.Type)
|
||||
{
|
||||
case EInteractActionType::Wait:
|
||||
ExecuteWait(Action);
|
||||
break;
|
||||
case EInteractActionType::TurnToTarget:
|
||||
ExecuteTurnToTarget(Action);
|
||||
break;
|
||||
case EInteractActionType::PlayMontage:
|
||||
ExecutePlayMontage(Action);
|
||||
break;
|
||||
case EInteractActionType::CameraPush:
|
||||
ExecuteCameraPush(Action);
|
||||
break;
|
||||
case EInteractActionType::CameraReturn:
|
||||
ExecuteCameraReturn(Action);
|
||||
break;
|
||||
case EInteractActionType::Consume:
|
||||
ExecuteConsume();
|
||||
break;
|
||||
case EInteractActionType::HideActor:
|
||||
ExecuteHideActor();
|
||||
break;
|
||||
case EInteractActionType::ShowActor:
|
||||
ExecuteShowActor();
|
||||
break;
|
||||
case EInteractActionType::ShowNarrative:
|
||||
ExecuteShowNarrative(Action);
|
||||
break;
|
||||
case EInteractActionType::HideNarrative:
|
||||
ExecuteHideNarrative();
|
||||
break;
|
||||
case EInteractActionType::ShowPickupNotification:
|
||||
ExecuteShowPickupNotification();
|
||||
break;
|
||||
case EInteractActionType::ToggleDoor:
|
||||
ExecuteToggleDoor();
|
||||
break;
|
||||
// v0.1 用日志占位的 Action
|
||||
case EInteractActionType::AddToBackpack:
|
||||
UE_LOG(LogTemp, Log, TEXT("EffectManager: AddToBackpack - Item: %s"),
|
||||
CurrentContext.TargetComp.IsValid()
|
||||
? *CurrentContext.TargetComp->GetInteractableData().DisplayName
|
||||
: TEXT("Unknown"));
|
||||
break;
|
||||
case EInteractActionType::PlayMontageOnTarget:
|
||||
case EInteractActionType::PlayDialogue:
|
||||
case EInteractActionType::PlayStory:
|
||||
case EInteractActionType::BroadcastEvent:
|
||||
UE_LOG(LogTemp, Log, TEXT("EffectManager: Action %d not implemented in v0.1"),
|
||||
static_cast<int32>(Action.Type));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Effect 结束 / 清理 / 恢复
|
||||
// ============================================================================
|
||||
|
||||
void FEffectManager::FinishEffect()
|
||||
{
|
||||
bIsPlaying = false;
|
||||
PerformCleanup();
|
||||
|
||||
UE_LOG(LogTemp, Verbose, TEXT("EffectManager: FinishEffect '%s'"), *CurrentEffect.EffectName);
|
||||
|
||||
// 广播完成回调
|
||||
if (OnEffectComplete.IsBound())
|
||||
{
|
||||
OnEffectComplete.Broadcast();
|
||||
}
|
||||
}
|
||||
|
||||
void FEffectManager::ResumeFromWait()
|
||||
{
|
||||
bIsWaitingForInput = false;
|
||||
// Timeline 继续推进,后续 Action 按 TriggerTime 正常触发
|
||||
}
|
||||
|
||||
void FEffectManager::PerformCleanup()
|
||||
{
|
||||
// 1. 文本底框淡出移除(v0.1 日志占位,后续替换为 Widget 操作)
|
||||
// TODO: 如果 NarrativeWidget 显示中,触发淡出动画
|
||||
|
||||
// 2. 相机回位(如果推镜过)
|
||||
if (CachedPushCamera.IsValid())
|
||||
{
|
||||
if (CurrentContext.Instigator.IsValid())
|
||||
{
|
||||
APlayerController* PC = Cast<APlayerController>(
|
||||
CurrentContext.Instigator.Get()->GetController());
|
||||
if (PC)
|
||||
{
|
||||
PC->SetViewTargetWithBlend(CurrentContext.Instigator.Get(), 0.2f);
|
||||
}
|
||||
}
|
||||
|
||||
// 销毁推镜摄像机
|
||||
APushCameraActor* PushCam = CachedPushCamera.Get();
|
||||
CachedPushCamera = nullptr;
|
||||
if (PushCam && PushCam->GetWorld())
|
||||
{
|
||||
PushCam->Destroy();
|
||||
}
|
||||
}
|
||||
|
||||
// 3. 蒙太奇停止
|
||||
if (CurrentContext.Instigator.IsValid())
|
||||
{
|
||||
ACharacter* Char = CurrentContext.Instigator.Get();
|
||||
if (Char->GetMesh() && Char->GetMesh()->GetAnimInstance())
|
||||
{
|
||||
Char->GetMesh()->GetAnimInstance()->Montage_Stop(0.2f);
|
||||
}
|
||||
}
|
||||
|
||||
// 4. TurnToTarget 未完成 -> 瞬移到目标旋转
|
||||
if (TurnState.bIsActive && CurrentContext.Instigator.IsValid())
|
||||
{
|
||||
CurrentContext.Instigator.Get()->SetActorRotation(TurnState.TargetRotation);
|
||||
TurnState.Reset();
|
||||
}
|
||||
|
||||
// 5. 重置所有状态变量
|
||||
ElapsedTime = 0.f;
|
||||
NextActionIndex = 0;
|
||||
bIsWaitingForInput = false;
|
||||
TurnState.Reset();
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// 各 Action 实现
|
||||
// ============================================================================
|
||||
|
||||
void FEffectManager::ExecuteWait(const FInteractAction& Action)
|
||||
{
|
||||
// Wait Action 由 Timeline 自然推进处理,此处仅做日志
|
||||
UE_LOG(LogTemp, Verbose, TEXT("EffectManager: Wait (%.2fs)"), Action.WaitDuration);
|
||||
}
|
||||
|
||||
void FEffectManager::ExecuteTurnToTarget(const FInteractAction& Action)
|
||||
{
|
||||
if (!CurrentContext.Instigator.IsValid() || !CurrentContext.TargetActor.IsValid()) return;
|
||||
|
||||
ACharacter* Char = CurrentContext.Instigator.Get();
|
||||
FVector DirToTarget = CurrentContext.TargetActor->GetActorLocation() - Char->GetActorLocation();
|
||||
DirToTarget.Z = 0;
|
||||
DirToTarget.Normalize();
|
||||
|
||||
TurnState.bIsActive = true;
|
||||
TurnState.StartRotation = Char->GetActorRotation();
|
||||
TurnState.TargetRotation = DirToTarget.Rotation();
|
||||
TurnState.ElapsedTime = 0.f;
|
||||
TurnState.Duration = Action.TurnDuration;
|
||||
|
||||
UE_LOG(LogTemp, Verbose, TEXT("EffectManager: TurnToTarget (Duration=%.2fs)"), Action.TurnDuration);
|
||||
}
|
||||
|
||||
void FEffectManager::ExecutePlayMontage(const FInteractAction& Action)
|
||||
{
|
||||
if (!CurrentContext.Instigator.IsValid()) return;
|
||||
if (!Action.Montage) return;
|
||||
|
||||
ACharacter* Char = CurrentContext.Instigator.Get();
|
||||
if (Char->GetMesh() && Char->GetMesh()->GetAnimInstance())
|
||||
{
|
||||
Char->GetMesh()->GetAnimInstance()->Montage_Play(Action.Montage);
|
||||
UE_LOG(LogTemp, Verbose, TEXT("EffectManager: PlayMontage '%s'"),
|
||||
*Action.Montage->GetName());
|
||||
}
|
||||
}
|
||||
|
||||
void FEffectManager::ExecuteCameraPush(const FInteractAction& Action)
|
||||
{
|
||||
if (!CurrentContext.Instigator.IsValid()) return;
|
||||
|
||||
APlayerController* PC = Cast<APlayerController>(
|
||||
CurrentContext.Instigator.Get()->GetController());
|
||||
if (!PC) return;
|
||||
|
||||
// 计算推镜摄像机位置:沿角色到目标方向偏移
|
||||
FVector CharLoc = CurrentContext.Instigator.Get()->GetActorLocation();
|
||||
FVector DirToTarget(0.f, 0.f, 0.f);
|
||||
if (CurrentContext.TargetActor.IsValid())
|
||||
{
|
||||
DirToTarget = (CurrentContext.TargetActor->GetActorLocation() - CharLoc).GetSafeNormal();
|
||||
}
|
||||
else
|
||||
{
|
||||
// 没有目标 Actor 时使用角色朝向
|
||||
DirToTarget = CurrentContext.Instigator.Get()->GetActorForwardVector();
|
||||
}
|
||||
|
||||
FVector PushLoc = CharLoc + DirToTarget * Action.PushDistance + FVector(0.f, 0.f, 80.f);
|
||||
|
||||
FActorSpawnParameters Params;
|
||||
Params.Owner = CurrentContext.Instigator.Get();
|
||||
APushCameraActor* PushCam = CurrentContext.Instigator.Get()->GetWorld()->SpawnActor<APushCameraActor>(
|
||||
PushLoc, DirToTarget.Rotation(), Params);
|
||||
|
||||
if (PushCam)
|
||||
{
|
||||
PC->SetViewTargetWithBlend(PushCam, Action.PushDuration);
|
||||
CachedPushCamera = PushCam;
|
||||
UE_LOG(LogTemp, Verbose, TEXT("EffectManager: CameraPush (Dist=%.1f, Dur=%.2fs)"),
|
||||
Action.PushDistance, Action.PushDuration);
|
||||
}
|
||||
}
|
||||
|
||||
void FEffectManager::ExecuteCameraReturn(const FInteractAction& Action)
|
||||
{
|
||||
if (!CurrentContext.Instigator.IsValid()) return;
|
||||
|
||||
APlayerController* PC = Cast<APlayerController>(
|
||||
CurrentContext.Instigator.Get()->GetController());
|
||||
if (!PC) return;
|
||||
|
||||
PC->SetViewTargetWithBlend(CurrentContext.Instigator.Get(), Action.ReturnDuration);
|
||||
|
||||
// 延迟销毁 PushCamera(在 PerformCleanup 中统一清理)
|
||||
UE_LOG(LogTemp, Verbose, TEXT("EffectManager: CameraReturn (Dur=%.2fs)"), Action.ReturnDuration);
|
||||
}
|
||||
|
||||
void FEffectManager::ExecuteConsume()
|
||||
{
|
||||
if (!CurrentContext.TargetComp.IsValid()) return;
|
||||
|
||||
CurrentContext.TargetComp->Consume();
|
||||
UE_LOG(LogTemp, Verbose, TEXT("EffectManager: Consume - %s"),
|
||||
*CurrentContext.TargetComp->GetInteractableData().DisplayName);
|
||||
}
|
||||
|
||||
void FEffectManager::ExecuteHideActor()
|
||||
{
|
||||
if (!CurrentContext.TargetActor.IsValid()) return;
|
||||
|
||||
CurrentContext.TargetActor.Get()->SetActorHiddenInGame(true);
|
||||
UE_LOG(LogTemp, Verbose, TEXT("EffectManager: HideActor"));
|
||||
}
|
||||
|
||||
void FEffectManager::ExecuteShowActor()
|
||||
{
|
||||
if (!CurrentContext.TargetActor.IsValid()) return;
|
||||
|
||||
CurrentContext.TargetActor.Get()->SetActorHiddenInGame(false);
|
||||
UE_LOG(LogTemp, Verbose, TEXT("EffectManager: ShowActor"));
|
||||
}
|
||||
|
||||
void FEffectManager::ExecuteShowNarrative(const FInteractAction& Action)
|
||||
{
|
||||
FString SelectedText;
|
||||
if (Action.bRandomSelect && Action.NarrativeTexts.Num() > 1)
|
||||
{
|
||||
SelectedText = PickRandomNarrative(Action.NarrativeTexts);
|
||||
}
|
||||
else if (Action.NarrativeTexts.Num() > 0)
|
||||
{
|
||||
SelectedText = Action.NarrativeTexts[0];
|
||||
}
|
||||
|
||||
// v0.1: 日志输出(后续替换为 Widget 显示)
|
||||
UE_LOG(LogTemp, Log, TEXT("EffectManager: ShowNarrative - %s"), *SelectedText);
|
||||
|
||||
if (Action.bWaitForInput)
|
||||
{
|
||||
bIsWaitingForInput = true;
|
||||
UE_LOG(LogTemp, Verbose, TEXT("EffectManager: WaitForInput paused"));
|
||||
}
|
||||
}
|
||||
|
||||
void FEffectManager::ExecuteHideNarrative()
|
||||
{
|
||||
// v0.1: 日志占位(后续替换为 Widget 操作)
|
||||
UE_LOG(LogTemp, Verbose, TEXT("EffectManager: HideNarrative"));
|
||||
}
|
||||
|
||||
void FEffectManager::ExecuteShowPickupNotification()
|
||||
{
|
||||
// v0.1: 日志占位(后续替换为 Widget 显示)
|
||||
if (CurrentContext.TargetComp.IsValid())
|
||||
{
|
||||
UE_LOG(LogTemp, Log, TEXT("EffectManager: ShowPickupNotification - %s"),
|
||||
*CurrentContext.TargetComp->GetInteractableData().DisplayName);
|
||||
}
|
||||
}
|
||||
|
||||
void FEffectManager::ExecuteToggleDoor()
|
||||
{
|
||||
// v0.1: 日志占位(门逻辑由 UDoorInteractableComponent 派生类实现,Phase 2)
|
||||
UE_LOG(LogTemp, Log, TEXT("EffectManager: ToggleDoor (placeholder)"));
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// TurnToTarget 插值
|
||||
// ============================================================================
|
||||
|
||||
void FEffectManager::UpdateTurn(float DeltaTime)
|
||||
{
|
||||
if (!TurnState.bIsActive) return;
|
||||
|
||||
TurnState.ElapsedTime += DeltaTime;
|
||||
|
||||
if (TurnState.IsComplete())
|
||||
{
|
||||
if (CurrentContext.Instigator.IsValid())
|
||||
{
|
||||
CurrentContext.Instigator.Get()->SetActorRotation(TurnState.TargetRotation);
|
||||
}
|
||||
TurnState.bIsActive = false;
|
||||
return;
|
||||
}
|
||||
|
||||
float Alpha = TurnState.GetAlpha();
|
||||
FRotator NewRot = FMath::Lerp(TurnState.StartRotation, TurnState.TargetRotation, Alpha);
|
||||
if (CurrentContext.Instigator.IsValid())
|
||||
{
|
||||
CurrentContext.Instigator.Get()->SetActorRotation(NewRot);
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// 辅助方法
|
||||
// ============================================================================
|
||||
|
||||
FString FEffectManager::PickRandomNarrative(const TArray<FString>& Texts)
|
||||
{
|
||||
if (Texts.Num() == 0) return TEXT("");
|
||||
if (Texts.Num() == 1) return Texts[0];
|
||||
|
||||
int32 CandidateIndex;
|
||||
do
|
||||
{
|
||||
CandidateIndex = FMath::RandRange(0, Texts.Num() - 1);
|
||||
} while (CandidateIndex == LastNarrativeIndex && Texts.Num() > 1);
|
||||
|
||||
LastNarrativeIndex = CandidateIndex;
|
||||
return Texts[CandidateIndex];
|
||||
}
|
||||
@@ -0,0 +1,123 @@
|
||||
// ChildHood Project - Effect Timeline 调度器(v1.1 Effect/Action 架构)
|
||||
// 对应技术方案: docs/technical/交互系统-技术方案.md 第3节(类设计)+ 第6节(复杂接口描述)
|
||||
// 职责:Effect Timeline 调度、Action 执行分发、TurnToTarget 插值、WaitForInput 暂停机制
|
||||
// 注意:普通 C++ 类,非 UObject,由 ASystemDriver 持有并 Tick 驱动
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
#include "InteractionTypes.h"
|
||||
|
||||
class APushCameraActor;
|
||||
|
||||
/**
|
||||
* FEffectManager
|
||||
*
|
||||
* Effect Timeline 调度器,管理一次交互 Effect 的完整生命周期:
|
||||
* - 按 TriggerTime 依次触发各 Action
|
||||
* - 维护 TurnToTarget 跨帧插值
|
||||
* - 支持 WaitForInput 暂停/恢复机制
|
||||
* - 支持取消(CancelEffect)
|
||||
* - Effect 结束时通过 OnEffectComplete 回调通知上层
|
||||
*
|
||||
* 生命周期:由 ASystemDriver 在 BeginPlay 中构造,每帧由 ASystemDriver::Tick 调用 Update。
|
||||
*/
|
||||
class FEffectManager
|
||||
{
|
||||
public:
|
||||
|
||||
// === 公开接口 ===
|
||||
|
||||
/** 设置完成回调(由 ASystemDriver 在 BeginPlay 中设置) */
|
||||
void SetOnEffectComplete(FOnEffectComplete&& Callback);
|
||||
|
||||
/** 播放 Effect — 初始化状态,开始 Timeline 推进 */
|
||||
void PlayEffect(const FInteractEffect& Effect, const FEffectContext& Context);
|
||||
|
||||
/** 取消 Effect(用户按 E/ESC) — 区分 WaitForInput 恢复 vs 强制取消 */
|
||||
void CancelEffect();
|
||||
|
||||
/** 当前是否正在播放 Effect */
|
||||
bool IsPlaying() const { return bIsPlaying; }
|
||||
|
||||
/** 当前是否暂停等待用户输入 */
|
||||
bool IsWaitingForInput() const { return bIsWaitingForInput; }
|
||||
|
||||
/** 每帧更新(由 ASystemDriver::Tick 调用) */
|
||||
void Update(float DeltaTime);
|
||||
|
||||
private:
|
||||
|
||||
// === Effect 状态 ===
|
||||
|
||||
/** 当前播放中的 Effect 数据 */
|
||||
FInteractEffect CurrentEffect;
|
||||
|
||||
/** 当前 Effect 的运行时上下文 */
|
||||
FEffectContext CurrentContext;
|
||||
|
||||
/** 从 Effect 开始累计的经过时间 */
|
||||
float ElapsedTime = 0.f;
|
||||
|
||||
/** 下一个待触发的 Action 索引(Actions 按 TriggerTime 排序) */
|
||||
int32 NextActionIndex = 0;
|
||||
|
||||
/** 是否正在播放 Effect */
|
||||
bool bIsPlaying = false;
|
||||
|
||||
/** 是否暂停等待用户输入(ShowNarrative bWaitForInput=true 时设置) */
|
||||
bool bIsWaitingForInput = false;
|
||||
|
||||
/** 上一次随机选取的叙事文本索引(用于避免连续重复) */
|
||||
int32 LastNarrativeIndex = -1;
|
||||
|
||||
// === Turn 插值 ===
|
||||
|
||||
/** TurnToTarget 跨帧插值状态 */
|
||||
FTurnState TurnState;
|
||||
|
||||
// === 推镜缓存 ===
|
||||
|
||||
/** 推镜期间 Spawn 的临时摄像机(弱引用,Effect 结束时销毁) */
|
||||
TWeakObjectPtr<APushCameraActor> CachedPushCamera;
|
||||
|
||||
// === 回调 ===
|
||||
|
||||
/** Effect 完成回调(正常结束或取消后清理完毕时触发) */
|
||||
FOnEffectComplete OnEffectComplete;
|
||||
|
||||
// === 内部方法 ===
|
||||
|
||||
/** 执行单个 Action(按 Type 分发到对应 ExecuteXxx) */
|
||||
void ExecuteAction(const FInteractAction& Action);
|
||||
|
||||
/** Effect 正常结束 — 清理状态 + 广播回调 */
|
||||
void FinishEffect();
|
||||
|
||||
/** 从 WaitForInput 暂停恢复 */
|
||||
void ResumeFromWait();
|
||||
|
||||
/** 清理所有运行时状态(推镜相机、蒙太奇、转向等) */
|
||||
void PerformCleanup();
|
||||
|
||||
/** 从 NarrativeTexts 中随机选取一条(避免与上次重复) */
|
||||
FString PickRandomNarrative(const TArray<FString>& Texts);
|
||||
|
||||
// === 各 Action 处理 ===
|
||||
|
||||
void ExecuteWait(const FInteractAction& Action);
|
||||
void ExecuteTurnToTarget(const FInteractAction& Action);
|
||||
void ExecutePlayMontage(const FInteractAction& Action);
|
||||
void ExecuteCameraPush(const FInteractAction& Action);
|
||||
void ExecuteCameraReturn(const FInteractAction& Action);
|
||||
void ExecuteConsume();
|
||||
void ExecuteHideActor();
|
||||
void ExecuteShowActor();
|
||||
void ExecuteShowNarrative(const FInteractAction& Action);
|
||||
void ExecuteHideNarrative();
|
||||
void ExecuteShowPickupNotification();
|
||||
void ExecuteToggleDoor();
|
||||
|
||||
/** 更新 TurnToTarget 跨帧插值(在 Update 末尾调用) */
|
||||
void UpdateTurn(float DeltaTime);
|
||||
};
|
||||
@@ -0,0 +1,160 @@
|
||||
// ChildHood Project - 交互提示追踪组件 实现
|
||||
// 对应技术方案: docs/technical/交互系统-技术方案.md 第6节(提示UI显示/隐藏)
|
||||
// 职责:Free 状态下追踪 Manager 缓存,更新 Prompt Widget 位置和文本
|
||||
|
||||
#include "Interaction/InteractionPromptComponent.h"
|
||||
#include "Interaction/InteractionPromptWidget.h"
|
||||
#include "SystemDriver.h"
|
||||
#include "Interaction/InteractionManager.h"
|
||||
#include "ChildHood.h"
|
||||
|
||||
#include "Engine/World.h"
|
||||
#include "Blueprint/WidgetLayoutLibrary.h"
|
||||
#include "Kismet/GameplayStatics.h"
|
||||
#include "GameFramework/PlayerController.h"
|
||||
|
||||
// ============================================================================
|
||||
// 构造函数
|
||||
// ============================================================================
|
||||
|
||||
UInteractionPromptComponent::UInteractionPromptComponent()
|
||||
{
|
||||
// 启用 Tick,每帧更新 Prompt 位置
|
||||
PrimaryComponentTick.bCanEverTick = true;
|
||||
PrimaryComponentTick.bStartWithTickEnabled = true;
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// 初始化
|
||||
// ============================================================================
|
||||
|
||||
void UInteractionPromptComponent::Initialize(ASystemDriver* InDriver)
|
||||
{
|
||||
Driver = InDriver;
|
||||
|
||||
UE_LOG(LogChildHood, Log, TEXT("InteractionPromptComponent: 初始化完成, Driver=%s"),
|
||||
InDriver ? *InDriver->GetName() : TEXT("nullptr"));
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// 生命周期
|
||||
// ============================================================================
|
||||
|
||||
void UInteractionPromptComponent::BeginPlay()
|
||||
{
|
||||
Super::BeginPlay();
|
||||
|
||||
// 根据 PromptWidgetClass 创建 Prompt Widget
|
||||
if (PromptWidgetClass)
|
||||
{
|
||||
if (APlayerController* PC = GetWorld()
|
||||
? GetWorld()->GetFirstPlayerController()
|
||||
: nullptr)
|
||||
{
|
||||
PromptWidget = CreateWidget<UInteractionPromptWidget>(PC, PromptWidgetClass);
|
||||
if (PromptWidget)
|
||||
{
|
||||
PromptWidget->AddToViewport();
|
||||
PromptWidget->SetVisibility(ESlateVisibility::Hidden);
|
||||
UE_LOG(LogChildHood, Log, TEXT("InteractionPromptComponent: Prompt Widget 创建完成"));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
UE_LOG(LogChildHood, Warning, TEXT("InteractionPromptComponent: 无法获取 PlayerController,Prompt Widget 未创建"));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
UE_LOG(LogChildHood, Warning, TEXT("InteractionPromptComponent: PromptWidgetClass 未设置,Prompt Widget 未创建"));
|
||||
}
|
||||
}
|
||||
|
||||
void UInteractionPromptComponent::TickComponent(float DeltaTime, ELevelTick TickType,
|
||||
FActorComponentTickFunction* ThisTickFunction)
|
||||
{
|
||||
Super::TickComponent(DeltaTime, TickType, ThisTickFunction);
|
||||
|
||||
// 没有 Widget 则跳过
|
||||
if (!PromptWidget)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// 获取 Driver(弱引用检查)
|
||||
ASystemDriver* DriverPtr = Driver.Get();
|
||||
if (!DriverPtr)
|
||||
{
|
||||
HidePrompt();
|
||||
return;
|
||||
}
|
||||
|
||||
// 获取 InteractionManager 引用
|
||||
FInteractionManager& Manager = DriverPtr->GetInteractionManager();
|
||||
|
||||
// 检查 State == Free 且 HasSelection()
|
||||
if (Manager.GetState() == EInteractionState::Free && Manager.HasSelection())
|
||||
{
|
||||
UpdatePrompt();
|
||||
}
|
||||
else
|
||||
{
|
||||
HidePrompt();
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Prompt 显示/隐藏
|
||||
// ============================================================================
|
||||
|
||||
void UInteractionPromptComponent::UpdatePrompt()
|
||||
{
|
||||
ASystemDriver* DriverPtr = Driver.Get();
|
||||
if (!DriverPtr || !PromptWidget)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
FInteractionManager& Manager = DriverPtr->GetInteractionManager();
|
||||
|
||||
// 从 Manager 缓存读取数据
|
||||
FString PromptText = Manager.GetSelectedPromptText();
|
||||
FVector WorldPos = Manager.GetSelectedWorldPosition();
|
||||
|
||||
// 应用高度偏移
|
||||
FVector AdjustedPos = WorldPos + FVector(0.f, 0.f, PromptHeightOffset);
|
||||
|
||||
// ProjectWorldToScreen
|
||||
APlayerController* PC = GetWorld()
|
||||
? GetWorld()->GetFirstPlayerController()
|
||||
: nullptr;
|
||||
if (!PC)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
FVector2D ScreenPos;
|
||||
bool bOnScreen = PC->ProjectWorldLocationToScreen(AdjustedPos, ScreenPos, true);
|
||||
|
||||
if (!bOnScreen)
|
||||
{
|
||||
HidePrompt();
|
||||
return;
|
||||
}
|
||||
|
||||
// 更新 Widget 位置和文本
|
||||
PromptWidget->SetPromptText(PromptText);
|
||||
PromptWidget->SetScreenPosition(ScreenPos);
|
||||
PromptWidget->Show();
|
||||
|
||||
bWasShowingPrompt = true;
|
||||
}
|
||||
|
||||
void UInteractionPromptComponent::HidePrompt()
|
||||
{
|
||||
if (PromptWidget && bWasShowingPrompt)
|
||||
{
|
||||
PromptWidget->Hide();
|
||||
bWasShowingPrompt = false;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
// ChildHood Project - 交互提示追踪组件
|
||||
// 对应技术方案: docs/technical/交互系统-技术方案.md 第6节(提示UI显示/隐藏)
|
||||
// 职责:独立于 Effect 执行,只负责 Free 状态下的 Prompt UI 追踪
|
||||
// 架构:UActorComponent,自有 Tick,只读 InteractionManager 缓存
|
||||
// 挂载:Character 上,由 Character::BeginPlay 中调用 Initialize
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
#include "Components/ActorComponent.h"
|
||||
#include "InteractionPromptComponent.generated.h"
|
||||
|
||||
class ASystemDriver;
|
||||
class UInteractionPromptWidget;
|
||||
|
||||
/**
|
||||
* UInteractionPromptComponent
|
||||
*
|
||||
* 交互提示追踪组件。每帧检查 InteractionManager 的缓存数据,
|
||||
* 在 Free 状态且有选中交互物时显示 Prompt Widget,Executing 时隐藏。
|
||||
*
|
||||
* 数据流:
|
||||
* InteractionManager 缓存 (CachedSelected) → 此组件 Tick 只读 → Prompt Widget 显示/隐藏
|
||||
*
|
||||
* 不直接访问任何 InteractableComponent,所有数据来自 Manager 缓存。
|
||||
*/
|
||||
UCLASS(ClassGroup=(Interaction), meta=(BlueprintSpawnableComponent))
|
||||
class UInteractionPromptComponent : public UActorComponent
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
public:
|
||||
|
||||
UInteractionPromptComponent();
|
||||
|
||||
/** 初始化(Character::BeginPlay 中调用) */
|
||||
void Initialize(ASystemDriver* InDriver);
|
||||
|
||||
protected:
|
||||
|
||||
virtual void BeginPlay() override;
|
||||
virtual void TickComponent(float DeltaTime, ELevelTick TickType,
|
||||
FActorComponentTickFunction* ThisTickFunction) override;
|
||||
|
||||
private:
|
||||
|
||||
/** 弱引用 Driver */
|
||||
TWeakObjectPtr<ASystemDriver> Driver;
|
||||
|
||||
/** Prompt Widget 实例 */
|
||||
UPROPERTY()
|
||||
TObjectPtr<UInteractionPromptWidget> PromptWidget;
|
||||
|
||||
/** Prompt Widget 蓝图类(编辑器中指定 WBP_InteractionPrompt) */
|
||||
UPROPERTY(EditAnywhere, Category = "Interaction|UI")
|
||||
TSubclassOf<UInteractionPromptWidget> PromptWidgetClass;
|
||||
|
||||
/** 提示 UI 高度偏移(cm) */
|
||||
UPROPERTY(EditAnywhere, Category = "Interaction|UI")
|
||||
float PromptHeightOffset = 80.0f;
|
||||
|
||||
/** 上一次记录的状态(用于检测状态变化) */
|
||||
bool bWasShowingPrompt = false;
|
||||
|
||||
/** 更新 Prompt 显示 */
|
||||
void UpdatePrompt();
|
||||
|
||||
/** 隐藏 Prompt */
|
||||
void HidePrompt();
|
||||
};
|
||||
@@ -0,0 +1,148 @@
|
||||
// ChildHood Project - 系统驱动器 实现(v1.1 Effect/Action 架构)
|
||||
// 对应技术方案: docs/technical/交互系统-技术方案.md 第3节(类设计)
|
||||
// 职责:全局唯一的 World 级驱动器,持有所有 Manager,负责初始化、Tick、跨 Manager 协调
|
||||
|
||||
#include "SystemDriver.h"
|
||||
#include "ChildHood.h"
|
||||
|
||||
// FEffectManager 尚未创建时,此 include 会失败。
|
||||
// 临时处理:条件编译,EffectManager.h 存在时才包含。
|
||||
// 当 EffectManager 子代理完成后,去掉 __has_include 判断直接包含。
|
||||
#if __has_include("Interaction/EffectManager.h")
|
||||
#include "Interaction/EffectManager.h"
|
||||
#define HAS_EFFECT_MANAGER 1
|
||||
#else
|
||||
#define HAS_EFFECT_MANAGER 0
|
||||
#endif
|
||||
|
||||
#include "Engine/World.h"
|
||||
#include "Kismet/GameplayStatics.h"
|
||||
#include "GameFramework/PlayerController.h"
|
||||
#include "GameFramework/Character.h"
|
||||
|
||||
// ============================================================================
|
||||
// 构造函数
|
||||
// ============================================================================
|
||||
|
||||
ASystemDriver::ASystemDriver()
|
||||
{
|
||||
// AInfo 默认不需要 Tick,我们在构造函数中开启
|
||||
PrimaryActorTick.bCanEverTick = true;
|
||||
PrimaryActorTick.bStartWithTickEnabled = true;
|
||||
|
||||
// 如果 FEffectManager 可用,在构造时创建实例
|
||||
#if HAS_EFFECT_MANAGER
|
||||
EffectManagerPtr = MakeUnique<FEffectManager>();
|
||||
#endif
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// 生命周期
|
||||
// ============================================================================
|
||||
|
||||
void ASystemDriver::BeginPlay()
|
||||
{
|
||||
Super::BeginPlay();
|
||||
|
||||
// --- 初始化 InteractionManager ---
|
||||
if (UWorld* World = GetWorld())
|
||||
{
|
||||
if (APlayerController* PC = World->GetFirstPlayerController())
|
||||
{
|
||||
if (ACharacter* Char = PC->GetCharacter())
|
||||
{
|
||||
InteractionManager.Initialize(Char);
|
||||
UE_LOG(LogChildHood, Log, TEXT("SystemDriver: InteractionManager 初始化完成"));
|
||||
}
|
||||
else
|
||||
{
|
||||
UE_LOG(LogChildHood, Warning, TEXT("SystemDriver: PlayerController 无 Character,InteractionManager 未初始化"));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
UE_LOG(LogChildHood, Warning, TEXT("SystemDriver: World 中无 PlayerController"));
|
||||
}
|
||||
}
|
||||
|
||||
// --- 初始化 EffectManager ---
|
||||
#if HAS_EFFECT_MANAGER
|
||||
if (EffectManagerPtr.IsValid())
|
||||
{
|
||||
// 绑定 Effect 完成回调
|
||||
FOnEffectComplete Callback;
|
||||
Callback.BindLambda([this]() { this->OnEffectComplete(); });
|
||||
EffectManagerPtr->SetOnEffectComplete(MoveTemp(Callback));
|
||||
|
||||
UE_LOG(LogChildHood, Log, TEXT("SystemDriver: EffectManager 初始化完成"));
|
||||
}
|
||||
#else
|
||||
UE_LOG(LogChildHood, Log, TEXT("SystemDriver: EffectManager 尚未创建(EffectManager.h 不存在)"));
|
||||
#endif
|
||||
}
|
||||
|
||||
void ASystemDriver::Tick(float DeltaTime)
|
||||
{
|
||||
Super::Tick(DeltaTime);
|
||||
|
||||
// --- Tick EffectManager ---
|
||||
// InteractionManager 无 Tick(纯事件驱动)
|
||||
#if HAS_EFFECT_MANAGER
|
||||
if (EffectManagerPtr.IsValid())
|
||||
{
|
||||
EffectManagerPtr->Update(DeltaTime);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// 全局访问
|
||||
// ============================================================================
|
||||
|
||||
ASystemDriver* ASystemDriver::Get(const UObject* WorldContextObject)
|
||||
{
|
||||
if (!WorldContextObject)
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
UWorld* World = GEngine->GetWorldFromContextObject(WorldContextObject, EGetWorldErrorMode::LogAndReturnNull);
|
||||
if (!World)
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// 使用 TActorIterator 查找场景中的 ASystemDriver
|
||||
// 全局唯一,取第一个即可
|
||||
for (TActorIterator<ASystemDriver> It(World); It; ++It)
|
||||
{
|
||||
return *It;
|
||||
}
|
||||
|
||||
// 场景中不存在时返回 nullptr
|
||||
// 应在关卡中放置一个 ASystemDriver,或在 GameMode::BeginPlay 中 Spawn
|
||||
UE_LOG(LogChildHood, Verbose, TEXT("SystemDriver::Get: 场景中未找到 ASystemDriver"));
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Manager 访问
|
||||
// ============================================================================
|
||||
|
||||
FEffectManager& ASystemDriver::GetEffectManager()
|
||||
{
|
||||
checkf(EffectManagerPtr.IsValid(),
|
||||
TEXT("ASystemDriver::GetEffectManager: EffectManager 未初始化。")
|
||||
TEXT("请确保 EffectManager.h 已创建且 HAS_EFFECT_MANAGER == 1"));
|
||||
return *EffectManagerPtr;
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Effect 完成回调
|
||||
// ============================================================================
|
||||
|
||||
void ASystemDriver::OnEffectComplete()
|
||||
{
|
||||
// Effect 完成后通知 InteractionManager 恢复 Free 状态
|
||||
InteractionManager.OnInteractionFinished();
|
||||
}
|
||||
@@ -0,0 +1,81 @@
|
||||
// ChildHood Project - 系统驱动器(v1.1 Effect/Action 架构)
|
||||
// 对应技术方案: docs/technical/交互系统-技术方案.md 第3节(类设计)
|
||||
// 职责:全局唯一的 World 级驱动器,持有所有 Manager(FInteractionManager, FEffectManager),
|
||||
// 负责初始化、Tick 驱动、跨 Manager 协调。
|
||||
// 架构:AInfo 子类(不渲染、不占场景空间),每关只有一个实例。
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
#include "GameFramework/Info.h"
|
||||
#include "Interaction/InteractionManager.h"
|
||||
#include "SystemDriver.generated.h"
|
||||
|
||||
// FEffectManager 由另一个子代理并行创建,此处只前向声明
|
||||
class FEffectManager;
|
||||
|
||||
/**
|
||||
* ASystemDriver
|
||||
*
|
||||
* 全局唯一的 World 级系统驱动器。持有 FInteractionManager 和 FEffectManager,
|
||||
* 负责它们的初始化、Tick 驱动和跨 Manager 协调。
|
||||
*
|
||||
* 访问方式:ASystemDriver::Get(WorldContextObject) 静态方法。
|
||||
*
|
||||
* 数据流:
|
||||
* InteractableComponent 碰撞回调 → Get() → GetInteractionManager().Register/Unregister
|
||||
* Character 输入回调 → Get() → GetInteractionManager().BeginInteract/Cancel/Switch
|
||||
* EffectManager 完成 → OnEffectComplete → GetInteractionManager().OnInteractionFinished
|
||||
*/
|
||||
UCLASS()
|
||||
class ASystemDriver : public AInfo
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
public:
|
||||
|
||||
ASystemDriver();
|
||||
|
||||
// === 生命周期 ===
|
||||
|
||||
virtual void BeginPlay() override;
|
||||
virtual void Tick(float DeltaTime) override;
|
||||
|
||||
// === 全局访问 ===
|
||||
|
||||
/**
|
||||
* 获取当前 World 中的 ASystemDriver 实例。
|
||||
* WorldContextObject 用于获取 World 上下文(通常传 this 或 GetWorld())。
|
||||
*/
|
||||
UFUNCTION(BlueprintPure, Category = "System", meta = (WorldContext = "WorldContextObject"))
|
||||
static ASystemDriver* Get(const UObject* WorldContextObject);
|
||||
|
||||
// === Manager 访问 ===
|
||||
|
||||
/** 获取交互管理器(值引用) */
|
||||
FInteractionManager& GetInteractionManager() { return InteractionManager; }
|
||||
const FInteractionManager& GetInteractionManager() const { return InteractionManager; }
|
||||
|
||||
/**
|
||||
* 获取效果管理器(值引用)。
|
||||
* FEffectManager 尚未创建时调用会触发链接错误,仅在 EffectManager.h 存在时使用。
|
||||
*/
|
||||
FEffectManager& GetEffectManager();
|
||||
|
||||
private:
|
||||
|
||||
/** 交互管理器 — 普通 C++ 值持有 */
|
||||
FInteractionManager InteractionManager;
|
||||
|
||||
/**
|
||||
* 效果管理器 — 普通 C++ 值持有。
|
||||
* FEffectManager 定义在 Interaction/EffectManager.h 中(由另一个子代理创建)。
|
||||
* 使用 unique_ptr 延迟构造,避免头文件中需要完整定义。
|
||||
*/
|
||||
TUniquePtr<FEffectManager> EffectManagerPtr;
|
||||
|
||||
// === Effect 完成回调连线 ===
|
||||
|
||||
/** EffectManager 的 OnEffectComplete 回调目标 */
|
||||
void OnEffectComplete();
|
||||
};
|
||||
Reference in New Issue
Block a user