feat(interaction): 新增UI Widget基类并集成到InteractionManager
This commit is contained in:
@@ -5,6 +5,8 @@
|
||||
#include "Interaction/InteractableComponent.h"
|
||||
#include "Interaction/InteractableActor.h"
|
||||
#include "Interaction/PushCameraActor.h"
|
||||
#include "Interaction/InteractionPromptWidget.h"
|
||||
#include "Interaction/NarrativeTextWidget.h"
|
||||
#include "ChildHoodCharacter.h"
|
||||
#include "ChildHoodGameInstance.h"
|
||||
#include "ChildHood.h"
|
||||
@@ -17,6 +19,7 @@
|
||||
#include "Engine/World.h"
|
||||
#include "TimerManager.h"
|
||||
#include "Kismet/GameplayStatics.h"
|
||||
#include "Blueprint/WidgetBlueprintLibrary.h"
|
||||
|
||||
// 定义交互系统日志类别
|
||||
DECLARE_LOG_CATEGORY_EXTERN(LogInteraction, Log, All);
|
||||
@@ -56,6 +59,39 @@ void UInteractionManager::BeginPlay()
|
||||
|
||||
UE_LOG(LogInteraction, Log, TEXT("InteractionManager: BeginPlay 完成, PushCameraActor=%s"),
|
||||
PushCameraActor ? *PushCameraActor->GetName() : TEXT("nullptr"));
|
||||
|
||||
// --- 创建 UI Widget ---
|
||||
APlayerController* PC = GetWorld()->GetFirstPlayerController();
|
||||
if (PC)
|
||||
{
|
||||
// 创建提示 UI
|
||||
if (PromptWidgetClass)
|
||||
{
|
||||
PromptWidget = CreateWidget<UInteractionPromptWidget>(PC, PromptWidgetClass);
|
||||
if (PromptWidget)
|
||||
{
|
||||
PromptWidget->AddToViewport(10);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
UE_LOG(LogInteraction, Warning, TEXT("InteractionManager: PromptWidgetClass 未设置,提示 UI 不可用"));
|
||||
}
|
||||
|
||||
// 创建文本底框 UI
|
||||
if (NarrativeWidgetClass)
|
||||
{
|
||||
NarrativeWidget = CreateWidget<UNarrativeTextWidget>(PC, NarrativeWidgetClass);
|
||||
if (NarrativeWidget)
|
||||
{
|
||||
NarrativeWidget->AddToViewport(20);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
UE_LOG(LogInteraction, Warning, TEXT("InteractionManager: NarrativeWidgetClass 未设置,文本 UI 不可用"));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
@@ -66,9 +102,18 @@ void UInteractionManager::TickComponent(float DeltaTime, ELevelTick TickType, FA
|
||||
{
|
||||
Super::TickComponent(DeltaTime, TickType, ThisTickFunction);
|
||||
|
||||
// Free 状态不需要 Tick
|
||||
if (State == EInteractionState::Free)
|
||||
{
|
||||
// Free 状态下更新提示 UI 的屏幕位置
|
||||
if (PromptWidget && PromptWidget->IsVisible() &&
|
||||
NearbyInteractables.Num() > 0 && NearbyInteractables.IsValidIndex(SelectedIndex))
|
||||
{
|
||||
UInteractableComponent* Selected = NearbyInteractables[SelectedIndex];
|
||||
if (Selected)
|
||||
{
|
||||
UpdatePromptScreenPosition(Selected);
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -651,32 +696,49 @@ void UInteractionManager::UpdatePromptUI()
|
||||
return;
|
||||
}
|
||||
|
||||
// v0.1: 用日志输出替代 UI
|
||||
FString PromptText = Selected->GetPromptText();
|
||||
UE_LOG(LogInteraction, Log, TEXT("UpdatePromptUI: [提示] %s"), *PromptText);
|
||||
|
||||
if (PromptWidget)
|
||||
{
|
||||
PromptWidget->SetPromptText(PromptText);
|
||||
PromptWidget->Show();
|
||||
UpdatePromptScreenPosition(Selected);
|
||||
}
|
||||
|
||||
UE_LOG(LogInteraction, Verbose, TEXT("UpdatePromptUI: %s"), *PromptText);
|
||||
}
|
||||
|
||||
void UInteractionManager::HidePrompt()
|
||||
{
|
||||
// v0.1: 用日志输出替代 UI
|
||||
UE_LOG(LogInteraction, Verbose, TEXT("HidePrompt: 隐藏交互提示"));
|
||||
if (PromptWidget)
|
||||
{
|
||||
PromptWidget->Hide();
|
||||
}
|
||||
}
|
||||
|
||||
void UInteractionManager::ShowNarrativeText(const FString& Text)
|
||||
{
|
||||
// v0.1: 用日志输出替代 UI
|
||||
if (Text.IsEmpty())
|
||||
{
|
||||
UE_LOG(LogInteraction, Warning, TEXT("ShowNarrativeText: 文本为空"));
|
||||
UE_LOG(LogInteraction, Warning, TEXT("ShowNarrativeText: text is empty"));
|
||||
return;
|
||||
}
|
||||
UE_LOG(LogInteraction, Log, TEXT("ShowNarrativeText: [文本] %s"), *Text);
|
||||
|
||||
if (NarrativeWidget)
|
||||
{
|
||||
NarrativeWidget->SetNarrativeText(Text);
|
||||
NarrativeWidget->Show();
|
||||
}
|
||||
|
||||
UE_LOG(LogInteraction, Verbose, TEXT("ShowNarrativeText: %s"), *Text);
|
||||
}
|
||||
|
||||
void UInteractionManager::HideNarrativeText()
|
||||
{
|
||||
// v0.1: 用日志输出替代 UI
|
||||
UE_LOG(LogInteraction, Verbose, TEXT("HideNarrativeText: 隐藏叙事文本"));
|
||||
if (NarrativeWidget)
|
||||
{
|
||||
NarrativeWidget->Hide();
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
@@ -813,3 +875,23 @@ void UInteractionManager::OnExitTimerComplete()
|
||||
UpdatePromptUI();
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// 提示 UI 屏幕位置更新
|
||||
// ============================================================================
|
||||
|
||||
void UInteractionManager::UpdatePromptScreenPosition(UInteractableComponent* Interactable)
|
||||
{
|
||||
if (!Interactable || !PromptWidget) return;
|
||||
|
||||
APlayerController* PC = GetWorld()->GetFirstPlayerController();
|
||||
if (!PC) return;
|
||||
|
||||
// 交互物世界位置 + 高度偏移 → 屏幕坐标
|
||||
FVector WorldLoc = Interactable->GetWorldLocation() + FVector(0.f, 0.f, PromptHeightOffset);
|
||||
FVector2D ScreenPos;
|
||||
if (UGameplayStatics::ProjectWorldToScreen(PC, WorldLoc, ScreenPos, true))
|
||||
{
|
||||
PromptWidget->SetScreenPosition(ScreenPos);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,6 +12,8 @@
|
||||
class UInteractableComponent;
|
||||
class APushCameraActor;
|
||||
class AChildHoodCharacter;
|
||||
class UInteractionPromptWidget;
|
||||
class UNarrativeTextWidget;
|
||||
|
||||
/**
|
||||
* UInteractionManager
|
||||
@@ -136,15 +138,29 @@ private:
|
||||
/** 动画事件是否已触发 */
|
||||
bool bAnimEventFired = false;
|
||||
|
||||
// === UI 引用(v0.1 后续任务创建) ===
|
||||
// === UI 配置 ===
|
||||
|
||||
/** 交互提示 Widget(v0.1 用日志替代) */
|
||||
UPROPERTY()
|
||||
TObjectPtr<UUserWidget> PromptWidget;
|
||||
/** 提示 UI 蓝图类(需在编辑器中指定为 WBP_InteractionPrompt) */
|
||||
UPROPERTY(EditAnywhere, Category = "Interaction|UI")
|
||||
TSubclassOf<UInteractionPromptWidget> PromptWidgetClass;
|
||||
|
||||
/** 文本底框 Widget(v0.1 用日志替代) */
|
||||
/** 文本底框 UI 蓝图类(需在编辑器中指定为 WBP_NarrativeText) */
|
||||
UPROPERTY(EditAnywhere, Category = "Interaction|UI")
|
||||
TSubclassOf<UNarrativeTextWidget> NarrativeWidgetClass;
|
||||
|
||||
/** 提示 UI 高度偏移(交互物上方的世界空间 Z 偏移,cm) */
|
||||
UPROPERTY(EditAnywhere, Category = "Interaction|UI")
|
||||
float PromptHeightOffset = 80.0f;
|
||||
|
||||
// === UI 实例 ===
|
||||
|
||||
/** 交互提示 Widget 实例 */
|
||||
UPROPERTY()
|
||||
TObjectPtr<UUserWidget> NarrativeWidget;
|
||||
TObjectPtr<UInteractionPromptWidget> PromptWidget;
|
||||
|
||||
/** 文本底框 Widget 实例 */
|
||||
UPROPERTY()
|
||||
TObjectPtr<UNarrativeTextWidget> NarrativeWidget;
|
||||
|
||||
// === 文本选择 ===
|
||||
|
||||
@@ -156,12 +172,15 @@ private:
|
||||
/** 更新选中交互物(排序后 SelectedIndex = 0) */
|
||||
void UpdateSelection();
|
||||
|
||||
/** 更新提示 UI(v0.1 用日志输出) */
|
||||
/** 更新提示 UI 内容和位置 */
|
||||
void UpdatePromptUI();
|
||||
|
||||
/** 隐藏提示 UI(v0.1 用日志输出) */
|
||||
/** 隐藏提示 UI */
|
||||
void HidePrompt();
|
||||
|
||||
/** 更新提示 UI 的屏幕空间位置(跟随交互物世界位置投影) */
|
||||
void UpdatePromptScreenPosition(UInteractableComponent* Interactable);
|
||||
|
||||
/** Enter 阶段 Tick:转向插值 + 推镜 + 等待 */
|
||||
void TickEnterPhase(float DeltaTime);
|
||||
|
||||
|
||||
@@ -0,0 +1,67 @@
|
||||
// ChildHood Project - 交互提示 UI 基类 实现
|
||||
|
||||
#include "Interaction/InteractionPromptWidget.h"
|
||||
#include "Components/TextBlock.h"
|
||||
|
||||
void UInteractionPromptWidget::NativeOnInitialized()
|
||||
{
|
||||
Super::NativeOnInitialized();
|
||||
|
||||
// 初始隐藏
|
||||
SetVisibility(ESlateVisibility::Hidden);
|
||||
}
|
||||
|
||||
void UInteractionPromptWidget::SetPromptText(const FString& Text)
|
||||
{
|
||||
if (PromptTextBlock)
|
||||
{
|
||||
PromptTextBlock->SetText(FText::FromString(Text));
|
||||
}
|
||||
}
|
||||
|
||||
void UInteractionPromptWidget::SetScreenPosition(const FVector2D& Position)
|
||||
{
|
||||
// 使用 RenderTranslation 偏移 Widget 位置
|
||||
// 蓝图中 CanvasPanel 的 Anchors 应设置为屏幕左上角
|
||||
// 此处直接设置 RenderTranslation 来移动位置
|
||||
SetRenderTranslation(Position);
|
||||
}
|
||||
|
||||
void UInteractionPromptWidget::Show()
|
||||
{
|
||||
if (bIsVisible) return;
|
||||
bIsVisible = true;
|
||||
|
||||
SetVisibility(ESlateVisibility::HitTestInvisible);
|
||||
|
||||
if (FadeInAnim)
|
||||
{
|
||||
PlayAnimation(FadeInAnim, 0.0f, 1, EUMGSequencePlayMode::Forward, 1.0f);
|
||||
}
|
||||
}
|
||||
|
||||
void UInteractionPromptWidget::Hide()
|
||||
{
|
||||
if (!bIsVisible) return;
|
||||
|
||||
if (FadeOutAnim)
|
||||
{
|
||||
PlayAnimation(FadeOutAnim, 0.0f, 1, EUMGSequencePlayMode::Forward, 1.0f);
|
||||
// 动画播完后隐藏(简单方案:直接设置定时器隐藏)
|
||||
// 动画时长由蓝图中的 FadeOutAnim 决定,这里用固定 0.2s
|
||||
FTimerHandle TempHandle;
|
||||
if (UWorld* World = GetWorld())
|
||||
{
|
||||
World->GetTimerManager().SetTimer(TempHandle, [this]()
|
||||
{
|
||||
bIsVisible = false;
|
||||
SetVisibility(ESlateVisibility::Hidden);
|
||||
}, 0.2f, false);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
bIsVisible = false;
|
||||
SetVisibility(ESlateVisibility::Hidden);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
// ChildHood Project - 交互提示 UI 基类
|
||||
// 对应技术方案: docs/technical/交互系统-技术方案.md 第6节(提示UI显示/隐藏)
|
||||
// 职责:屏幕空间悬浮提示 "[E] 动作词 名称",跟随交互物位置
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
#include "Blueprint/UserWidget.h"
|
||||
#include "InteractionPromptWidget.generated.h"
|
||||
|
||||
class UTextBlock;
|
||||
|
||||
/**
|
||||
* UInteractionPromptWidget
|
||||
*
|
||||
* 交互提示 UI 的 C++ 基类。蓝图子类(WBP_InteractionPrompt)继承此类,
|
||||
* 在 UMG 编辑器中布局可视组件,并确保组件名与 BindWidget 匹配。
|
||||
*
|
||||
* 绑定要求:
|
||||
* - TextBlock 名为 "PromptTextBlock" — 显示提示文字
|
||||
* - WidgetAnimation 名为 "FadeInAnim" — 淡入动画(0.2s)
|
||||
* - WidgetAnimation 名为 "FadeOutAnim" — 淡出动画(0.2s)
|
||||
*/
|
||||
UCLASS(Abstract, Blueprintable)
|
||||
class UInteractionPromptWidget : public UUserWidget
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
public:
|
||||
|
||||
/** 设置提示文字内容 */
|
||||
UFUNCTION(BlueprintCallable, Category = "Interaction|Prompt")
|
||||
void SetPromptText(const FString& Text);
|
||||
|
||||
/** 设置屏幕空间位置(由 InteractionManager 每帧调用) */
|
||||
UFUNCTION(BlueprintCallable, Category = "Interaction|Prompt")
|
||||
void SetScreenPosition(const FVector2D& Position);
|
||||
|
||||
/** 显示提示(播放淡入动画) */
|
||||
UFUNCTION(BlueprintCallable, Category = "Interaction|Prompt")
|
||||
void Show();
|
||||
|
||||
/** 隐藏提示(播放淡出动画) */
|
||||
UFUNCTION(BlueprintCallable, Category = "Interaction|Prompt")
|
||||
void Hide();
|
||||
|
||||
protected:
|
||||
|
||||
/** 提示文字 TextBlock,蓝图子类必须有一个同名 TextBlock */
|
||||
UPROPERTY(meta = (BindWidget))
|
||||
UTextBlock* PromptTextBlock;
|
||||
|
||||
/** 淡入动画(0.2s Opacity 0→1),蓝图子类必须有同名 WidgetAnimation */
|
||||
UPROPERTY(meta = (BindWidgetAnim), Transient)
|
||||
UWidgetAnimation* FadeInAnim;
|
||||
|
||||
/** 淡出动画(0.2s Opacity 1→0),蓝图子类必须有同名 WidgetAnimation */
|
||||
UPROPERTY(meta = (BindWidgetAnim), Transient)
|
||||
UWidgetAnimation* FadeOutAnim;
|
||||
|
||||
/** 是否正在显示中 */
|
||||
bool bIsVisible = false;
|
||||
|
||||
virtual void NativeOnInitialized() override;
|
||||
};
|
||||
@@ -0,0 +1,57 @@
|
||||
// ChildHood Project - 文本底框 UI 基类 实现
|
||||
|
||||
#include "Interaction/NarrativeTextWidget.h"
|
||||
#include "Components/TextBlock.h"
|
||||
|
||||
void UNarrativeTextWidget::NativeOnInitialized()
|
||||
{
|
||||
Super::NativeOnInitialized();
|
||||
|
||||
// 初始隐藏
|
||||
SetVisibility(ESlateVisibility::Hidden);
|
||||
}
|
||||
|
||||
void UNarrativeTextWidget::SetNarrativeText(const FString& Text)
|
||||
{
|
||||
if (NarrativeTextBlock)
|
||||
{
|
||||
NarrativeTextBlock->SetText(FText::FromString(Text));
|
||||
}
|
||||
}
|
||||
|
||||
void UNarrativeTextWidget::Show()
|
||||
{
|
||||
if (bIsVisible) return;
|
||||
bIsVisible = true;
|
||||
|
||||
SetVisibility(ESlateVisibility::HitTestInvisible);
|
||||
|
||||
if (FadeInAnim)
|
||||
{
|
||||
PlayAnimation(FadeInAnim, 0.0f, 1, EUMGSequencePlayMode::Forward, 1.0f);
|
||||
}
|
||||
}
|
||||
|
||||
void UNarrativeTextWidget::Hide()
|
||||
{
|
||||
if (!bIsVisible) return;
|
||||
|
||||
if (FadeOutAnim)
|
||||
{
|
||||
PlayAnimation(FadeOutAnim, 0.0f, 1, EUMGSequencePlayMode::Forward, 1.0f);
|
||||
FTimerHandle TempHandle;
|
||||
if (UWorld* World = GetWorld())
|
||||
{
|
||||
World->GetTimerManager().SetTimer(TempHandle, [this]()
|
||||
{
|
||||
bIsVisible = false;
|
||||
SetVisibility(ESlateVisibility::Hidden);
|
||||
}, 0.2f, false);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
bIsVisible = false;
|
||||
SetVisibility(ESlateVisibility::Hidden);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
// ChildHood Project - 文本底框 UI 基类
|
||||
// 对应技术方案: docs/technical/交互系统-技术方案.md 第6节(通用文本显示UI)
|
||||
// 职责:半透明底框 + 一句话叙事/对话文本,带淡入淡出
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
#include "Blueprint/UserWidget.h"
|
||||
#include "NarrativeTextWidget.generated.h"
|
||||
|
||||
class UTextBlock;
|
||||
|
||||
/**
|
||||
* UNarrativeTextWidget
|
||||
*
|
||||
* 文本底框 UI 的 C++ 基类。蓝图子类(WBP_NarrativeText)继承此类,
|
||||
* 在 UMG 编辑器中布局可视组件,并确保组件名与 BindWidget 匹配。
|
||||
*
|
||||
* 绑定要求:
|
||||
* - TextBlock 名为 "NarrativeTextBlock" — 显示叙事/对话文字
|
||||
* - WidgetAnimation 名为 "FadeInAnim" — 淡入动画(0.2s)
|
||||
* - WidgetAnimation 名为 "FadeOutAnim" — 淡出动画(0.2s)
|
||||
*
|
||||
* 布局要求:
|
||||
* - Anchors: Horizontal Center, Vertical ~0.6(屏幕中心偏下)
|
||||
* - Border: 半透明黑色背景(~60%),圆角,SizeToContent
|
||||
* - TextBlock: 白色文字,居中,自动换行
|
||||
*/
|
||||
UCLASS(Abstract, Blueprintable)
|
||||
class UNarrativeTextWidget : public UUserWidget
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
public:
|
||||
|
||||
/** 设置叙事文本内容 */
|
||||
UFUNCTION(BlueprintCallable, Category = "Interaction|Narrative")
|
||||
void SetNarrativeText(const FString& Text);
|
||||
|
||||
/** 显示文本(播放淡入动画) */
|
||||
UFUNCTION(BlueprintCallable, Category = "Interaction|Narrative")
|
||||
void Show();
|
||||
|
||||
/** 隐藏文本(播放淡出动画) */
|
||||
UFUNCTION(BlueprintCallable, Category = "Interaction|Narrative")
|
||||
void Hide();
|
||||
|
||||
protected:
|
||||
|
||||
/** 叙事文字 TextBlock,蓝图子类必须有一个同名 TextBlock */
|
||||
UPROPERTY(meta = (BindWidget))
|
||||
UTextBlock* NarrativeTextBlock;
|
||||
|
||||
/** 淡入动画(0.2s),蓝图子类必须有同名 WidgetAnimation */
|
||||
UPROPERTY(meta = (BindWidgetAnim), Transient)
|
||||
UWidgetAnimation* FadeInAnim;
|
||||
|
||||
/** 淡出动画(0.2s),蓝图子类必须有同名 WidgetAnimation */
|
||||
UPROPERTY(meta = (BindWidgetAnim), Transient)
|
||||
UWidgetAnimation* FadeOutAnim;
|
||||
|
||||
/** 是否正在显示中 */
|
||||
bool bIsVisible = false;
|
||||
|
||||
virtual void NativeOnInitialized() override;
|
||||
};
|
||||
Reference in New Issue
Block a user