fix/asset-catalog-pipeline #37

Merged
starmen1265 merged 8 commits from fix/asset-catalog-pipeline into main 2026-06-11 14:28:49 +08:00
6 changed files with 374 additions and 18 deletions
Showing only changes of commit 85c2062318 - Show all commits
@@ -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;
};