Compare commits

...

7 Commits

41 changed files with 2976 additions and 9 deletions

View File

@@ -8,7 +8,7 @@
- Marketplace 插件源码只允许读取和适配,不直接修改 Engine 下的插件文件。
- Git 远程仓库固定为 `https://git.codable.cn/cit110/PHY.git`
- `Content` 目录只管理 `Content/AGame` 及其子文件;`Content/Collections``Content/Developers` 等其他目录默认不纳入版本控制。
- 每次完成修改并通过对应验证后,必须提交并推送到 `origin`;如果远程认证或冲突阻塞推送,必须在最终汇报中说明
- 每次完成修改并通过对应验证后,只提交到本地 Git默认不推送到 `origin`,除非用户在当前任务中明确要求推送
## 插件源码位置
- Generic Combat System`D:\ue\UE_5.7\Engine\Plugins\Marketplace\GenericCd51b9e2c8181V4`
@@ -42,7 +42,7 @@
- 专家交付必须包含:`改动范围``新增/修改接口``配置文件``蓝图/DataAsset 使用原因``验证结果``遗留风险`
- 验证 Agent 通过构建和必要测试后Git/仓库 Agent 才能提交;总架构最终向用户汇报结果。
- Git/仓库 Agent 初始化仓库时必须设置 `origin=https://git.codable.cn/cit110/PHY.git`,启用 Git LFS并确认 `Content` 跟踪范围仅限 `Content/AGame/**`
- Git/仓库 Agent 在每次修改完成后负责提交并推送当前改动,不保留已完成但未提交的工作树。
- Git/仓库 Agent 在每次修改完成后负责提交当前改动,不保留已完成但未提交的工作树;默认不推送,除非用户在当前任务中明确要求
- 相机/输入/运动/战斗之间的接口冲突、是否使用蓝图/DataAsset、是否引入插件模块依赖统一由总架构裁决。
## UE-MCP 使用约定
@@ -51,7 +51,7 @@
- UE Editor 侧桥接端口为 `ws://localhost:9877`,由项目插件 `UE_MCP_Bridge` 提供。
- 如果 MCP 工具未暴露,检查 `C:\Users\cit11\.codex\config.toml``[mcp_servers.ue_mcp]` 是否存在并启用,然后重启 Codex。
- 使用 MCP 修改资产、蓝图或关卡前必须先读取现状C++ 和配置文件仍按项目优先级优先于蓝图/DataAsset。
- MCP 写操作完成后仍需按本文件的验证提交和推送规则收尾。
- MCP 写操作完成后仍需按本文件的验证提交规则收尾;默认不推送,除非用户在当前任务中明确要求
## 相机规则
- 项目相机主入口统一使用 Ultimate Gameplay Camera`AuroraDevs_UGC`

View File

@@ -4,3 +4,4 @@ bRouteInputThroughGenericInputSystem=True
bBlockGameplayInputWhenUIFocused=True
DefaultMoveInputProcessorClass="/Script/PHY.PHYInputProcessor_Move"
DefaultLookInputProcessorClass="/Script/PHY.PHYInputProcessor_Look"
DefaultAttributeMenuInputProcessorClass="/Script/PHY.PHYInputProcessor_OpenAttributeMenu"

View File

@@ -4,6 +4,7 @@ bUseGenericUISystem=True
bOpenMenusWithGameplayPause=False
RootLayoutClass="/Script/PHY.PHYGameUILayout"
DefaultHUDWidgetClass="/Script/PHY.PHYHUDStatusWidget"
AttributeMenuWidgetClass="/Script/PHY.PHYAttributeMenuWidget"
GameplayLayerTag=(TagName="UI.Layer.Game")
HUDLayerTag=(TagName="UI.Layer.HUD")
MenuLayerTag=(TagName="UI.Layer.Menu")

View File

@@ -11,6 +11,7 @@
#include "UI/GUIS_GameUILayout.h"
#include "UI/GUIS_GameUIPolicy.h"
#include "UI/GUIS_GameUISubsystem.h"
#include "UI/PHYAttributeMenuWidget.h"
#include "UI/PHYHUDStatusWidget.h"
#include "UI/PHYPlayerUIContext.h"
@@ -40,6 +41,38 @@ void APHYHUD::InitializeHUDForPlayer(APlayerController* OwningPlayerController)
}
}
UCommonActivatableWidget* APHYHUD::OpenAttributeMenu(APlayerController* OwningPlayerController)
{
APlayerController* TargetPlayerController = OwningPlayerController ? OwningPlayerController : GetOwningPlayerController();
if (!TargetPlayerController)
{
return nullptr;
}
if (!bHUDInitialized)
{
InitializeHUDForPlayer(TargetPlayerController);
}
const UPHYUISettings* UISettings = GetDefault<UPHYUISettings>();
TSubclassOf<UCommonActivatableWidget> AttributeMenuClass = UPHYAttributeMenuWidget::StaticClass();
if (UISettings && !UISettings->AttributeMenuWidgetClass.IsNull())
{
if (UClass* LoadedAttributeMenuClass = UISettings->AttributeMenuWidgetClass.LoadSynchronous())
{
AttributeMenuClass = LoadedAttributeMenuClass;
}
}
UCommonActivatableWidget* PushedWidget = PushMenuWidget(TargetPlayerController, AttributeMenuClass);
if (UPHYAttributeMenuWidget* AttributeMenuWidget = Cast<UPHYAttributeMenuWidget>(PushedWidget))
{
AttributeMenuWidget->InitializeFromPlayer(TargetPlayerController);
}
return PushedWidget;
}
bool APHYHUD::InitializeGenericUIForPlayer(APlayerController* OwningPlayerController)
{
if (!OwningPlayerController || !OwningPlayerController->IsLocalController())
@@ -109,5 +142,36 @@ UCommonActivatableWidget* APHYHUD::PushDefaultHUDWidget(APlayerController* Ownin
}
const FGameplayTag HUDLayerTag = (UISettings && UISettings->HUDLayerTag.IsValid()) ? UISettings->HUDLayerTag : PHYGameplayTags::UI_Layer_HUD;
return RootLayout->PushWidgetToLayerStack<UCommonActivatableWidget>(HUDLayerTag, HUDWidgetClass);
UCommonActivatableWidget* PushedWidget = RootLayout->PushWidgetToLayerStack<UCommonActivatableWidget>(HUDLayerTag, HUDWidgetClass);
if (UPHYHUDStatusWidget* StatusWidget = Cast<UPHYHUDStatusWidget>(PushedWidget))
{
StatusWidget->InitializeFromPlayer(OwningPlayerController);
}
return PushedWidget;
}
UCommonActivatableWidget* APHYHUD::PushMenuWidget(APlayerController* OwningPlayerController, TSubclassOf<UCommonActivatableWidget> WidgetClass)
{
if (!OwningPlayerController || !WidgetClass)
{
return nullptr;
}
const UPHYUISettings* UISettings = GetDefault<UPHYUISettings>();
ULocalPlayer* LocalPlayer = OwningPlayerController->GetLocalPlayer();
if (!LocalPlayer)
{
return nullptr;
}
UGUIS_GameUIPolicy* UIPolicy = UGUIS_GameUIPolicy::GetGameUIPolicy(OwningPlayerController);
UGUIS_GameUILayout* RootLayout = UIPolicy ? UIPolicy->GetRootLayout(LocalPlayer) : nullptr;
if (!RootLayout)
{
return nullptr;
}
const FGameplayTag MenuLayerTag = (UISettings && UISettings->MenuLayerTag.IsValid()) ? UISettings->MenuLayerTag : PHYGameplayTags::UI_Layer_Menu;
return RootLayout->PushWidgetToLayerStack<UCommonActivatableWidget>(MenuLayerTag, WidgetClass);
}

View File

@@ -15,4 +15,5 @@ namespace PHYGameplayTags
UE_DEFINE_GAMEPLAY_TAG(Input_Aim, "Input.Aim");
UE_DEFINE_GAMEPLAY_TAG(Input_LockOn, "Input.LockOn");
UE_DEFINE_GAMEPLAY_TAG(Input_Inventory_Toggle, "Input.Inventory.Toggle");
UE_DEFINE_GAMEPLAY_TAG(Input_Menu_Attribute, "Input.Menu.Attribute");
}

View File

@@ -0,0 +1,55 @@
// Copyright Epic Games, Inc. All Rights Reserved.
#include "Input/PHYInputProcessor_OpenAttributeMenu.h"
#include UE_INLINE_GENERATED_CPP_BY_NAME(PHYInputProcessor_OpenAttributeMenu)
#include "Characters/PHYPlayerCharacter.h"
#include "GIPS_InputSystemComponent.h"
#include "PHYGameplayTags.h"
#include "Player/PHYPlayerController.h"
UPHYInputProcessor_OpenAttributeMenu::UPHYInputProcessor_OpenAttributeMenu()
{
InputTags.AddTag(PHYGameplayTags::Input_Menu_Attribute);
TriggerEvents = {
ETriggerEvent::Started
};
}
bool UPHYInputProcessor_OpenAttributeMenu::CheckCanHandleInput_Implementation(UGIPS_InputSystemComponent* IC, const FInputActionInstance& ActionData, const FGameplayTag InputTag, const ETriggerEvent TriggerEvent) const
{
(void)ActionData;
if (!IC || InputTag != PHYGameplayTags::Input_Menu_Attribute || TriggerEvent != ETriggerEvent::Started)
{
return false;
}
const APHYPlayerCharacter* PlayerCharacter = Cast<APHYPlayerCharacter>(IC->GetControlledPawn());
const APHYPlayerController* PlayerController = PlayerCharacter ? PlayerCharacter->GetController<APHYPlayerController>() : nullptr;
return PlayerController && PlayerController->IsLocalController();
}
void UPHYInputProcessor_OpenAttributeMenu::HandleInputStarted_Implementation(UGIPS_InputSystemComponent* IC, const FInputActionInstance& ActionData, const FGameplayTag InputTag) const
{
(void)InputTag;
RouteOpenAttributeMenuInput(IC, ActionData, ETriggerEvent::Started);
}
FString UPHYInputProcessor_OpenAttributeMenu::GetEditorFriendlyName_Implementation() const
{
return TEXT("PHY Open Attribute Menu");
}
bool UPHYInputProcessor_OpenAttributeMenu::RouteOpenAttributeMenuInput(UGIPS_InputSystemComponent* IC, const FInputActionInstance& ActionData, const ETriggerEvent TriggerEvent) const
{
if (!IC)
{
return false;
}
APHYPlayerCharacter* PlayerCharacter = Cast<APHYPlayerCharacter>(IC->GetControlledPawn());
APHYPlayerController* PlayerController = PlayerCharacter ? PlayerCharacter->GetController<APHYPlayerController>() : nullptr;
return PlayerController ? PlayerController->RouteOpenAttributeMenuInputFromProcessor(ActionData, TriggerEvent) : false;
}

View File

@@ -7,6 +7,7 @@
#include "Characters/PHYPlayerCharacter.h"
#include "Characters/PHYCharacterSettings.h"
#include "Components/SLSCharacterMovementComponent.h"
#include "Game/PHYHUD.h"
#include "GIPS_InputSystemComponent.h"
#include "InputActionValue.h"
#include "PHYGameplayTags.h"
@@ -115,6 +116,15 @@ void APHYPlayerController::OnReceivedInput(const FInputActionInstance& ActionDat
return;
}
if (InputTag == PHYGameplayTags::Input_Menu_Attribute)
{
if (!ConsumeAttributeMenuProcessorGuard(TriggerEvent))
{
RouteOpenAttributeMenuInput(ActionData, TriggerEvent);
}
return;
}
if (APHYPlayerCharacter* PHYCharacter = GetPHYPlayerCharacter())
{
PHYCharacter->HandleInputTag(InputTag, TriggerEvent);
@@ -145,6 +155,18 @@ bool APHYPlayerController::RouteLookInputFromProcessor(const FInputActionInstanc
return RouteLookInput(ActionData, TriggerEvent);
}
bool APHYPlayerController::RouteOpenAttributeMenuInput(const FInputActionInstance& ActionData, const ETriggerEvent TriggerEvent)
{
return HandleOpenAttributeMenuInput(ActionData, TriggerEvent);
}
bool APHYPlayerController::RouteOpenAttributeMenuInputFromProcessor(const FInputActionInstance& ActionData, const ETriggerEvent TriggerEvent)
{
bAttributeMenuInputHandledByProcessor = true;
LastAttributeMenuInputProcessorTriggerEvent = TriggerEvent;
return RouteOpenAttributeMenuInput(ActionData, TriggerEvent);
}
bool APHYPlayerController::HandleMoveInput(const FInputActionInstance& ActionData, const ETriggerEvent TriggerEvent)
{
APHYPlayerCharacter* PHYCharacter = GetPHYPlayerCharacter();
@@ -199,6 +221,33 @@ bool APHYPlayerController::ConsumeLookProcessorGuard(const ETriggerEvent Trigger
return false;
}
bool APHYPlayerController::ConsumeAttributeMenuProcessorGuard(const ETriggerEvent TriggerEvent)
{
if (bAttributeMenuInputHandledByProcessor && LastAttributeMenuInputProcessorTriggerEvent == TriggerEvent)
{
bAttributeMenuInputHandledByProcessor = false;
LastAttributeMenuInputProcessorTriggerEvent = ETriggerEvent::None;
return true;
}
bAttributeMenuInputHandledByProcessor = false;
LastAttributeMenuInputProcessorTriggerEvent = ETriggerEvent::None;
return false;
}
bool APHYPlayerController::HandleOpenAttributeMenuInput(const FInputActionInstance& ActionData, const ETriggerEvent TriggerEvent)
{
(void)ActionData;
if (TriggerEvent != ETriggerEvent::Started || !IsLocalController())
{
return false;
}
APHYHUD* PHYHUD = GetHUD<APHYHUD>();
return PHYHUD && PHYHUD->OpenAttributeMenu(this);
}
bool APHYPlayerController::HandleLookInput(const FInputActionInstance& ActionData, const ETriggerEvent TriggerEvent)
{
CachedLookInput = IsInputReleased(TriggerEvent) ? FVector2D::ZeroVector : ExtractAxis2D(ActionData.GetValue());

View File

@@ -10,6 +10,7 @@
#include "Class/PHYClassComponent.h"
#include "Class/PHYClassSettings.h"
#include "GGA_AbilitySystemComponent.h"
#include "Net/UnrealNetwork.h"
APHYPlayerState::APHYPlayerState(const FObjectInitializer& ObjectInitializer)
: Super(ObjectInitializer)
@@ -28,7 +29,69 @@ APHYPlayerState::APHYPlayerState(const FObjectInitializer& ObjectInitializer)
ClassComponent->SetClassTag(GetDefault<UPHYClassSettings>()->DefaultPlayerClassTag);
}
void APHYPlayerState::GetLifetimeReplicatedProps(TArray<FLifetimeProperty>& OutLifetimeProps) const
{
Super::GetLifetimeReplicatedProps(OutLifetimeProps);
DOREPLIFETIME(ThisClass, PlayerLevel);
DOREPLIFETIME(ThisClass, Experience);
DOREPLIFETIME(ThisClass, ExperienceForNextLevel);
}
UAbilitySystemComponent* APHYPlayerState::GetAbilitySystemComponent() const
{
return AbilitySystemComponent;
}
void APHYPlayerState::SetPlayerLevel(const int32 NewLevel)
{
SetProgression(NewLevel, Experience, ExperienceForNextLevel);
}
void APHYPlayerState::SetExperience(const int32 NewExperience)
{
SetProgression(PlayerLevel, NewExperience, ExperienceForNextLevel);
}
void APHYPlayerState::AddExperience(const int32 DeltaExperience)
{
SetExperience(Experience + DeltaExperience);
}
void APHYPlayerState::SetExperienceForNextLevel(const int32 NewExperienceForNextLevel)
{
SetProgression(PlayerLevel, Experience, NewExperienceForNextLevel);
}
void APHYPlayerState::SetProgression(const int32 NewLevel, const int32 NewExperience, const int32 NewExperienceForNextLevel)
{
if (!HasAuthority())
{
return;
}
const int32 ClampedLevel = FMath::Max(1, NewLevel);
const int32 ClampedExperience = FMath::Max(0, NewExperience);
const int32 ClampedExperienceForNextLevel = FMath::Max(1, NewExperienceForNextLevel);
if (PlayerLevel == ClampedLevel && Experience == ClampedExperience && ExperienceForNextLevel == ClampedExperienceForNextLevel)
{
return;
}
PlayerLevel = ClampedLevel;
Experience = ClampedExperience;
ExperienceForNextLevel = ClampedExperienceForNextLevel;
BroadcastProgressionChanged();
}
void APHYPlayerState::OnRep_Progression()
{
BroadcastProgressionChanged();
}
void APHYPlayerState::BroadcastProgressionChanged()
{
OnProgressionChanged.Broadcast(PlayerLevel, Experience, ExperienceForNextLevel);
}

View File

@@ -0,0 +1,135 @@
// Copyright Epic Games, Inc. All Rights Reserved.
#include "UI/PHYAttributeGroupWidget.h"
#include UE_INLINE_GENERATED_CPP_BY_NAME(PHYAttributeGroupWidget)
#include "Blueprint/WidgetTree.h"
#include "Components/Border.h"
#include "Components/TextBlock.h"
#include "Components/VerticalBox.h"
#include "Components/VerticalBoxSlot.h"
#include "Engine/Texture2D.h"
#include "Fonts/SlateFontInfo.h"
#include "Styling/CoreStyle.h"
#include "Styling/SlateBrush.h"
#include "UI/PHYAttributeRowWidget.h"
#include "Widgets/SWidget.h"
namespace
{
const TCHAR* const AttributeGroupPaperTexturePath = TEXT("/Game/AGame/UI/Attributes/Textures/T_PHY_UI_PaperPanel.T_PHY_UI_PaperPanel");
const TCHAR* const AttributeGroupGoldRuleTexturePath = TEXT("/Game/AGame/UI/Attributes/Textures/T_PHY_UI_GoldRule.T_PHY_UI_GoldRule");
FSlateBrush MakeAttributeGroupBrush(const TCHAR* TexturePath, const FVector2D ImageSize, const FLinearColor Tint, const ESlateBrushDrawType::Type DrawAs = ESlateBrushDrawType::Box, const FMargin Margin = FMargin(0.08f))
{
FSlateBrush Brush;
Brush.DrawAs = DrawAs;
Brush.Margin = Margin;
Brush.ImageSize = ImageSize;
Brush.TintColor = FSlateColor(Tint);
if (UTexture2D* Texture = LoadObject<UTexture2D>(nullptr, TexturePath))
{
Brush.SetResourceObject(Texture);
}
return Brush;
}
}
UPHYAttributeGroupWidget::UPHYAttributeGroupWidget(const FObjectInitializer& ObjectInitializer)
: Super(ObjectInitializer)
{
}
TSharedRef<SWidget> UPHYAttributeGroupWidget::RebuildWidget()
{
BuildNativeWidgetTree();
return Super::RebuildWidget();
}
void UPHYAttributeGroupWidget::SynchronizeProperties()
{
Super::SynchronizeProperties();
UpdateGroupWidgets();
}
void UPHYAttributeGroupWidget::SetGroupTitle(FText InGroupTitle)
{
GroupTitle = MoveTemp(InGroupTitle);
UpdateGroupWidgets();
}
void UPHYAttributeGroupWidget::ClearRows()
{
if (RowsBox)
{
RowsBox->ClearChildren();
}
}
UPHYAttributeRowWidget* UPHYAttributeGroupWidget::AddRow(FText Label, FText Value, const bool bCanUpgrade)
{
BuildNativeWidgetTree();
if (!RowsBox || !WidgetTree)
{
return nullptr;
}
UPHYAttributeRowWidget* RowWidget = WidgetTree->ConstructWidget<UPHYAttributeRowWidget>(UPHYAttributeRowWidget::StaticClass());
RowWidget->SetRow(MoveTemp(Label), MoveTemp(Value), bCanUpgrade);
if (UVerticalBoxSlot* RowSlot = RowsBox->AddChildToVerticalBox(RowWidget))
{
RowSlot->SetPadding(FMargin(0.0f, 2.0f));
}
return RowWidget;
}
void UPHYAttributeGroupWidget::BuildNativeWidgetTree()
{
if (!WidgetTree)
{
WidgetTree = NewObject<UWidgetTree>(this, TEXT("WidgetTree"), RF_Transient);
}
if (WidgetTree->RootWidget && TitleText && RowsBox)
{
return;
}
UBorder* RootBorder = WidgetTree->ConstructWidget<UBorder>(UBorder::StaticClass(), TEXT("RootBorder"));
RootBorder->SetBrushColor(FLinearColor::Transparent);
RootBorder->SetPadding(FMargin(14.0f, 8.0f));
WidgetTree->RootWidget = RootBorder;
UVerticalBox* RootBox = WidgetTree->ConstructWidget<UVerticalBox>(UVerticalBox::StaticClass(), TEXT("RootBox"));
RootBorder->SetContent(RootBox);
TitleText = WidgetTree->ConstructWidget<UTextBlock>(UTextBlock::StaticClass(), TEXT("TitleText"));
TitleText->SetFont(FSlateFontInfo(FCoreStyle::GetDefaultFont(), 19));
TitleText->SetColorAndOpacity(FSlateColor(FLinearColor(0.48f, 0.27f, 0.11f, 1.0f)));
if (UVerticalBoxSlot* TitleSlot = RootBox->AddChildToVerticalBox(TitleText))
{
TitleSlot->SetPadding(FMargin(2.0f, 0.0f, 0.0f, 2.0f));
}
UBorder* HeaderRule = WidgetTree->ConstructWidget<UBorder>(UBorder::StaticClass(), TEXT("HeaderRule"));
HeaderRule->SetBrush(MakeAttributeGroupBrush(AttributeGroupGoldRuleTexturePath, FVector2D(512.0f, 24.0f), FLinearColor(0.87f, 0.69f, 0.34f, 0.72f), ESlateBrushDrawType::Image, FMargin(0.0f)));
if (UVerticalBoxSlot* RuleSlot = RootBox->AddChildToVerticalBox(HeaderRule))
{
RuleSlot->SetPadding(FMargin(0.0f, 0.0f, 0.0f, 8.0f));
}
RowsBox = WidgetTree->ConstructWidget<UVerticalBox>(UVerticalBox::StaticClass(), TEXT("RowsBox"));
RootBox->AddChildToVerticalBox(RowsBox);
UpdateGroupWidgets();
}
void UPHYAttributeGroupWidget::UpdateGroupWidgets()
{
if (TitleText)
{
TitleText->SetText(GroupTitle);
}
}

View File

@@ -0,0 +1,625 @@
// Copyright Epic Games, Inc. All Rights Reserved.
#include "UI/PHYAttributeMenuWidget.h"
#include UE_INLINE_GENERATED_CPP_BY_NAME(PHYAttributeMenuWidget)
#include "AbilitySystemComponent.h"
#include "AbilitySystem/Attributes/PHYCombatAttributeSet.h"
#include "AbilitySystem/Attributes/PHYCoreAttributeSet.h"
#include "AbilitySystem/Attributes/PHYElementAttributeSet.h"
#include "Blueprint/WidgetTree.h"
#include "Components/Border.h"
#include "Components/Button.h"
#include "Components/CanvasPanel.h"
#include "Components/CanvasPanelSlot.h"
#include "Components/HorizontalBox.h"
#include "Components/HorizontalBoxSlot.h"
#include "Components/SafeZone.h"
#include "Components/ScaleBox.h"
#include "Components/SizeBox.h"
#include "Components/Spacer.h"
#include "Components/TextBlock.h"
#include "Components/VerticalBox.h"
#include "Components/VerticalBoxSlot.h"
#include "Components/WidgetSwitcher.h"
#include "Engine/Texture2D.h"
#include "Fonts/SlateFontInfo.h"
#include "GameFramework/PlayerController.h"
#include "Player/PHYPlayerState.h"
#include "Styling/CoreStyle.h"
#include "Styling/SlateBrush.h"
#include "Styling/SlateTypes.h"
#include "UI/PHYAttributeResourceBarWidget.h"
#include "UI/PHYAttributeOverviewPageWidget.h"
#include "UI/PHYAttributeSummaryWidget.h"
#include "UI/PHYCombatDerivedAttributePageWidget.h"
#include "UI/PHYElementDerivedAttributePageWidget.h"
#include "Widgets/SWidget.h"
namespace
{
constexpr float AttributeDesignWidth = 1693.0f;
constexpr float AttributeDesignHeight = 942.0f;
const TCHAR* const AttributeBackplateTexturePath = TEXT("/Game/AGame/UI/Attributes/Textures/T_PHY_UI_AttributeBackplate.T_PHY_UI_AttributeBackplate");
const TCHAR* const AttributePaperTexturePath = TEXT("/Game/AGame/UI/Attributes/Textures/T_PHY_UI_PaperPanel.T_PHY_UI_PaperPanel");
const TCHAR* const AttributeJadeTexturePath = TEXT("/Game/AGame/UI/Attributes/Textures/T_PHY_UI_JadePanel.T_PHY_UI_JadePanel");
const TCHAR* const AttributeCinnabarTexturePath = TEXT("/Game/AGame/UI/Attributes/Textures/T_PHY_UI_CinnabarTab.T_PHY_UI_CinnabarTab");
const TCHAR* const AttributeGoldRuleTexturePath = TEXT("/Game/AGame/UI/Attributes/Textures/T_PHY_UI_GoldRule.T_PHY_UI_GoldRule");
FSlateBrush MakeAttributeMenuBrush(const TCHAR* TexturePath, const FVector2D ImageSize, const FLinearColor Tint, const ESlateBrushDrawType::Type DrawAs = ESlateBrushDrawType::Box, const FMargin Margin = FMargin(0.08f))
{
FSlateBrush Brush;
Brush.DrawAs = DrawAs;
Brush.Margin = Margin;
Brush.ImageSize = ImageSize;
Brush.TintColor = FSlateColor(Tint);
if (UTexture2D* Texture = LoadObject<UTexture2D>(nullptr, TexturePath))
{
Brush.SetResourceObject(Texture);
}
return Brush;
}
FSlateBrush MakeAttributeTransparentBrush()
{
FSlateBrush Brush;
Brush.DrawAs = ESlateBrushDrawType::NoDrawType;
Brush.TintColor = FSlateColor(FLinearColor::Transparent);
return Brush;
}
void AddAttributeMenuCanvasChild(UCanvasPanel* CanvasPanel, UWidget* ChildWidget, const FVector2D Position, const FVector2D Size, const int32 ZOrder = 0)
{
if (!CanvasPanel || !ChildWidget)
{
return;
}
if (UCanvasPanelSlot* CanvasSlot = CanvasPanel->AddChildToCanvas(ChildWidget))
{
CanvasSlot->SetAutoSize(false);
CanvasSlot->SetPosition(Position);
CanvasSlot->SetSize(Size);
CanvasSlot->SetZOrder(ZOrder);
}
}
UTextBlock* CreateAttributeMenuText(UWidgetTree* WidgetTree, const FName WidgetName, const FText Text, const int32 FontSize, const FLinearColor Color)
{
UTextBlock* TextBlock = WidgetTree->ConstructWidget<UTextBlock>(UTextBlock::StaticClass(), WidgetName);
TextBlock->SetText(Text);
TextBlock->SetFont(FSlateFontInfo(FCoreStyle::GetDefaultFont(), FontSize));
TextBlock->SetColorAndOpacity(FSlateColor(Color));
return TextBlock;
}
UButton* CreateAttributeNavButton(UWidgetTree* WidgetTree, const FName WidgetName, const FText Label, const bool bActive)
{
UButton* Button = WidgetTree->ConstructWidget<UButton>(UButton::StaticClass(), WidgetName);
FButtonStyle ButtonStyle;
const FSlateBrush TransparentBrush = MakeAttributeTransparentBrush();
ButtonStyle.SetNormal(TransparentBrush);
ButtonStyle.SetHovered(TransparentBrush);
ButtonStyle.SetPressed(TransparentBrush);
Button->SetStyle(ButtonStyle);
UTextBlock* ButtonText = CreateAttributeMenuText(WidgetTree, NAME_None, Label, 25, bActive ? FLinearColor(0.04f, 0.72f, 0.66f, 1.0f) : FLinearColor(0.34f, 0.32f, 0.26f, 0.62f));
Button->SetContent(ButtonText);
return Button;
}
void ApplyAttributeSubTabStyle(UButton* Button, const bool bActive)
{
if (!Button)
{
return;
}
FButtonStyle ButtonStyle;
const FLinearColor NormalTint = bActive ? FLinearColor(0.16f, 0.46f, 0.39f, 0.92f) : FLinearColor(0.90f, 0.83f, 0.66f, 0.76f);
const FLinearColor HoverTint = bActive ? FLinearColor(0.19f, 0.54f, 0.46f, 0.95f) : FLinearColor(0.96f, 0.89f, 0.72f, 0.90f);
ButtonStyle.SetNormal(MakeAttributeMenuBrush(bActive ? AttributeJadeTexturePath : AttributePaperTexturePath, FVector2D(210.0f, 54.0f), NormalTint));
ButtonStyle.SetHovered(MakeAttributeMenuBrush(bActive ? AttributeJadeTexturePath : AttributePaperTexturePath, FVector2D(210.0f, 54.0f), HoverTint));
ButtonStyle.SetPressed(MakeAttributeMenuBrush(AttributeCinnabarTexturePath, FVector2D(210.0f, 54.0f), FLinearColor(0.72f, 0.24f, 0.14f, 0.92f)));
Button->SetStyle(ButtonStyle);
Button->SetBackgroundColor(FLinearColor::White);
}
UBorder* CreateAttributeMenuLine(UWidgetTree* WidgetTree, const FName WidgetName)
{
UBorder* Line = WidgetTree->ConstructWidget<UBorder>(UBorder::StaticClass(), WidgetName);
Line->SetBrush(MakeAttributeMenuBrush(AttributeGoldRuleTexturePath, FVector2D(512.0f, 24.0f), FLinearColor(1.0f, 0.86f, 0.44f, 0.80f), ESlateBrushDrawType::Image, FMargin(0.0f)));
return Line;
}
TArray<FGameplayAttribute> MakeTrackedAttributes()
{
return {
UPHYCoreAttributeSet::GetStrengthAttribute(),
UPHYCoreAttributeSet::GetDexterityAttribute(),
UPHYCoreAttributeSet::GetVitalityAttribute(),
UPHYCoreAttributeSet::GetIntelligenceAttribute(),
UPHYCoreAttributeSet::GetSpiritAttribute(),
UPHYCoreAttributeSet::GetPerceptionAttribute(),
UPHYCombatAttributeSet::GetHealthAttribute(),
UPHYCombatAttributeSet::GetMaxHealthAttribute(),
UPHYCombatAttributeSet::GetManaAttribute(),
UPHYCombatAttributeSet::GetMaxManaAttribute(),
UPHYCombatAttributeSet::GetStaminaAttribute(),
UPHYCombatAttributeSet::GetMaxStaminaAttribute(),
UPHYCombatAttributeSet::GetPhysicalAttackPowerAttribute(),
UPHYCombatAttributeSet::GetSpellPowerAttribute(),
UPHYCombatAttributeSet::GetArmorAttribute(),
UPHYCombatAttributeSet::GetMagicResistanceAttribute(),
UPHYCombatAttributeSet::GetAccuracyAttribute(),
UPHYCombatAttributeSet::GetEvasionAttribute(),
UPHYCombatAttributeSet::GetCriticalChanceAttribute(),
UPHYCombatAttributeSet::GetCriticalDamageAttribute(),
UPHYCombatAttributeSet::GetAttackSpeedAttribute(),
UPHYCombatAttributeSet::GetCooldownReductionAttribute(),
UPHYCombatAttributeSet::GetBlockPowerAttribute(),
UPHYCombatAttributeSet::GetGuardBreakPowerAttribute(),
UPHYCombatAttributeSet::GetPoiseAttribute(),
UPHYCombatAttributeSet::GetPoiseDamageAttribute(),
UPHYElementAttributeSet::GetFireDamageBonusAttribute(),
UPHYElementAttributeSet::GetWaterDamageBonusAttribute(),
UPHYElementAttributeSet::GetIceDamageBonusAttribute(),
UPHYElementAttributeSet::GetLightningDamageBonusAttribute(),
UPHYElementAttributeSet::GetEarthDamageBonusAttribute(),
UPHYElementAttributeSet::GetWindDamageBonusAttribute(),
UPHYElementAttributeSet::GetLightDamageBonusAttribute(),
UPHYElementAttributeSet::GetDarkDamageBonusAttribute(),
UPHYElementAttributeSet::GetFireResistanceAttribute(),
UPHYElementAttributeSet::GetWaterResistanceAttribute(),
UPHYElementAttributeSet::GetIceResistanceAttribute(),
UPHYElementAttributeSet::GetLightningResistanceAttribute(),
UPHYElementAttributeSet::GetEarthResistanceAttribute(),
UPHYElementAttributeSet::GetWindResistanceAttribute(),
UPHYElementAttributeSet::GetLightResistanceAttribute(),
UPHYElementAttributeSet::GetDarkResistanceAttribute(),
UPHYElementAttributeSet::GetElementPenetrationAttribute()
};
}
}
UPHYAttributeMenuWidget::UPHYAttributeMenuWidget(const FObjectInitializer& ObjectInitializer)
: Super(ObjectInitializer)
{
InputConfig = EGUIS_ActivatableWidgetInputMode::Menu;
SetIsBackHandler(true);
}
TSharedRef<SWidget> UPHYAttributeMenuWidget::RebuildWidget()
{
BuildNativeWidgetTree();
return Super::RebuildWidget();
}
void UPHYAttributeMenuWidget::SynchronizeProperties()
{
Super::SynchronizeProperties();
RefreshAttributePages();
UpdateTabVisuals();
}
void UPHYAttributeMenuWidget::InitializeFromPlayer(APlayerController* InPlayerController)
{
if (!InPlayerController)
{
BoundPlayerController.Reset();
UnbindFromPlayerState();
UnbindFromAbilitySystem();
RefreshAttributePages();
return;
}
BoundPlayerController = InPlayerController;
RefreshFromBoundSources();
}
void UPHYAttributeMenuWidget::RefreshFromBoundSources()
{
APlayerController* PlayerController = BoundPlayerController.Get();
if (!PlayerController)
{
UnbindFromPlayerState();
UnbindFromAbilitySystem();
RefreshAttributePages();
return;
}
APHYPlayerState* PHYPlayerState = PlayerController->GetPlayerState<APHYPlayerState>();
BindToPlayerState(PHYPlayerState);
BindToAbilitySystem(PHYPlayerState ? PHYPlayerState->GetAbilitySystemComponent() : nullptr);
RefreshAttributePages();
}
void UPHYAttributeMenuWidget::NativeConstruct()
{
Super::NativeConstruct();
if (!BoundPlayerController.IsValid())
{
InitializeFromPlayer(GetOwningPlayer());
}
else
{
RefreshFromBoundSources();
}
}
void UPHYAttributeMenuWidget::NativeDestruct()
{
UnbindFromPlayerState();
UnbindFromAbilitySystem();
BoundPlayerController.Reset();
Super::NativeDestruct();
}
void UPHYAttributeMenuWidget::NativeTick(const FGeometry& MyGeometry, const float InDeltaTime)
{
Super::NativeTick(MyGeometry, InDeltaTime);
APlayerController* PlayerController = BoundPlayerController.Get();
if (!PlayerController)
{
return;
}
APHYPlayerState* CurrentPlayerState = PlayerController->GetPlayerState<APHYPlayerState>();
if (CurrentPlayerState != BoundPlayerState.Get() || !BoundAbilitySystemComponent.IsValid())
{
RefreshFromBoundSources();
}
}
void UPHYAttributeMenuWidget::BuildNativeWidgetTree()
{
if (!WidgetTree)
{
WidgetTree = NewObject<UWidgetTree>(this, TEXT("WidgetTree"), RF_Transient);
}
if (WidgetTree->RootWidget && SummaryWidget && PageSwitcher && OverviewPage && CombatPage && ElementPage && HeaderHealthBar && HeaderManaBar && HeaderStaminaBar)
{
return;
}
USafeZone* RootSafeZone = WidgetTree->ConstructWidget<USafeZone>(USafeZone::StaticClass(), TEXT("RootSafeZone"));
WidgetTree->RootWidget = RootSafeZone;
UBorder* RootWash = WidgetTree->ConstructWidget<UBorder>(UBorder::StaticClass(), TEXT("RootWash"));
RootWash->SetBrushColor(FLinearColor(0.08f, 0.10f, 0.09f, 0.96f));
RootSafeZone->SetContent(RootWash);
UScaleBox* DesignScaleBox = WidgetTree->ConstructWidget<UScaleBox>(UScaleBox::StaticClass(), TEXT("DesignScaleBox"));
DesignScaleBox->SetStretch(EStretch::ScaleToFit);
DesignScaleBox->SetStretchDirection(EStretchDirection::Both);
RootWash->SetContent(DesignScaleBox);
USizeBox* DesignSizeBox = WidgetTree->ConstructWidget<USizeBox>(USizeBox::StaticClass(), TEXT("DesignSizeBox"));
DesignSizeBox->SetWidthOverride(AttributeDesignWidth);
DesignSizeBox->SetHeightOverride(AttributeDesignHeight);
DesignScaleBox->AddChild(DesignSizeBox);
UCanvasPanel* DesignCanvas = WidgetTree->ConstructWidget<UCanvasPanel>(UCanvasPanel::StaticClass(), TEXT("DesignCanvas"));
DesignSizeBox->AddChild(DesignCanvas);
UBorder* BackgroundPanel = WidgetTree->ConstructWidget<UBorder>(UBorder::StaticClass(), TEXT("BackgroundPanel"));
BackgroundPanel->SetBrush(MakeAttributeMenuBrush(AttributeBackplateTexturePath, FVector2D(AttributeDesignWidth, AttributeDesignHeight), FLinearColor::White, ESlateBrushDrawType::Image, FMargin(0.0f)));
AddAttributeMenuCanvasChild(DesignCanvas, BackgroundPanel, FVector2D(0.0f, 0.0f), FVector2D(AttributeDesignWidth, AttributeDesignHeight), 0);
UBorder* TitlePlaque = WidgetTree->ConstructWidget<UBorder>(UBorder::StaticClass(), TEXT("TitlePlaque"));
TitlePlaque->SetBrushColor(FLinearColor::Transparent);
TitlePlaque->SetPadding(FMargin(78.0f, 0.0f, 0.0f, 4.0f));
AddAttributeMenuCanvasChild(DesignCanvas, TitlePlaque, FVector2D(0.0f, 7.0f), FVector2D(402.0f, 88.0f), 3);
UTextBlock* TitleText = CreateAttributeMenuText(WidgetTree, TEXT("TitleText"), FText::FromString(TEXT("角色属性")), 31, FLinearColor(0.99f, 0.88f, 0.61f, 1.0f));
TitlePlaque->SetContent(TitleText);
UButton* AttributeNavButton = CreateAttributeNavButton(WidgetTree, TEXT("AttributeNavButton"), FText::FromString(TEXT("属性")), true);
AddAttributeMenuCanvasChild(DesignCanvas, AttributeNavButton, FVector2D(552.0f, 9.0f), FVector2D(170.0f, 74.0f), 3);
UButton* EquipNavButton = CreateAttributeNavButton(WidgetTree, TEXT("EquipNavButton"), FText::FromString(TEXT("装备")), false);
AddAttributeMenuCanvasChild(DesignCanvas, EquipNavButton, FVector2D(764.0f, 11.0f), FVector2D(155.0f, 70.0f), 3);
UButton* SkillNavButton = CreateAttributeNavButton(WidgetTree, TEXT("SkillNavButton"), FText::FromString(TEXT("技能")), false);
AddAttributeMenuCanvasChild(DesignCanvas, SkillNavButton, FVector2D(978.0f, 11.0f), FVector2D(155.0f, 70.0f), 3);
UButton* BagNavButton = CreateAttributeNavButton(WidgetTree, TEXT("BagNavButton"), FText::FromString(TEXT("背包")), false);
AddAttributeMenuCanvasChild(DesignCanvas, BagNavButton, FVector2D(1188.0f, 11.0f), FVector2D(155.0f, 70.0f), 3);
UBorder* HeaderRule = CreateAttributeMenuLine(WidgetTree, TEXT("HeaderRule"));
HeaderRule->SetBrushColor(FLinearColor::Transparent);
AddAttributeMenuCanvasChild(DesignCanvas, HeaderRule, FVector2D(392.0f, 85.0f), FVector2D(1245.0f, 18.0f), 1);
SummaryWidget = WidgetTree->ConstructWidget<UPHYAttributeSummaryWidget>(UPHYAttributeSummaryWidget::StaticClass(), TEXT("SummaryWidget"));
UBorder* SummaryPanel = WidgetTree->ConstructWidget<UBorder>(UBorder::StaticClass(), TEXT("SummaryPanel"));
SummaryPanel->SetBrushColor(FLinearColor::Transparent);
SummaryPanel->SetPadding(FMargin(27.0f, 24.0f, 27.0f, 22.0f));
SummaryPanel->SetContent(SummaryWidget);
AddAttributeMenuCanvasChild(DesignCanvas, SummaryPanel, FVector2D(35.0f, 103.0f), FVector2D(462.0f, 710.0f), 2);
UBorder* PageBorder = WidgetTree->ConstructWidget<UBorder>(UBorder::StaticClass(), TEXT("PageBorder"));
PageBorder->SetBrushColor(FLinearColor::Transparent);
PageBorder->SetPadding(FMargin(28.0f, 22.0f, 28.0f, 24.0f));
AddAttributeMenuCanvasChild(DesignCanvas, PageBorder, FVector2D(515.0f, 103.0f), FVector2D(1120.0f, 735.0f), 2);
UVerticalBox* PageBox = WidgetTree->ConstructWidget<UVerticalBox>(UVerticalBox::StaticClass(), TEXT("PageBox"));
PageBorder->SetContent(PageBox);
UTextBlock* ResourceTitleText = CreateAttributeMenuText(WidgetTree, TEXT("ResourceTitleText"), FText::FromString(TEXT("战斗资源")), 22, FLinearColor(0.39f, 0.22f, 0.10f, 1.0f));
PageBox->AddChildToVerticalBox(ResourceTitleText);
UBorder* ResourceRule = CreateAttributeMenuLine(WidgetTree, TEXT("ResourceRule"));
if (UVerticalBoxSlot* ResourceRuleSlot = PageBox->AddChildToVerticalBox(ResourceRule))
{
ResourceRuleSlot->SetPadding(FMargin(0.0f, 4.0f, 0.0f, 8.0f));
}
HeaderHealthBar = WidgetTree->ConstructWidget<UPHYAttributeResourceBarWidget>(UPHYAttributeResourceBarWidget::StaticClass(), TEXT("HeaderHealthBar"));
HeaderManaBar = WidgetTree->ConstructWidget<UPHYAttributeResourceBarWidget>(UPHYAttributeResourceBarWidget::StaticClass(), TEXT("HeaderManaBar"));
HeaderStaminaBar = WidgetTree->ConstructWidget<UPHYAttributeResourceBarWidget>(UPHYAttributeResourceBarWidget::StaticClass(), TEXT("HeaderStaminaBar"));
if (UVerticalBoxSlot* HealthSlot = PageBox->AddChildToVerticalBox(HeaderHealthBar))
{
HealthSlot->SetPadding(FMargin(4.0f, 0.0f, 0.0f, 2.0f));
}
if (UVerticalBoxSlot* ManaSlot = PageBox->AddChildToVerticalBox(HeaderManaBar))
{
ManaSlot->SetPadding(FMargin(4.0f, 0.0f, 0.0f, 2.0f));
}
if (UVerticalBoxSlot* StaminaSlot = PageBox->AddChildToVerticalBox(HeaderStaminaBar))
{
StaminaSlot->SetPadding(FMargin(4.0f, 0.0f, 0.0f, 12.0f));
}
UHorizontalBox* TabBox = WidgetTree->ConstructWidget<UHorizontalBox>(UHorizontalBox::StaticClass(), TEXT("TabBox"));
if (UVerticalBoxSlot* TabSlot = PageBox->AddChildToVerticalBox(TabBox))
{
TabSlot->SetPadding(FMargin(0.0f, 2.0f, 0.0f, 14.0f));
}
OverviewTabButton = CreateTabButton(TEXT("OverviewTabButton"), FText::FromString(TEXT("核心属性")));
CombatTabButton = CreateTabButton(TEXT("CombatTabButton"), FText::FromString(TEXT("战斗衍生")));
ElementTabButton = CreateTabButton(TEXT("ElementTabButton"), FText::FromString(TEXT("元素衍生")));
if (UHorizontalBoxSlot* OverviewTabSlot = TabBox->AddChildToHorizontalBox(OverviewTabButton))
{
OverviewTabSlot->SetPadding(FMargin(0.0f, 0.0f, 10.0f, 0.0f));
}
if (UHorizontalBoxSlot* CombatTabSlot = TabBox->AddChildToHorizontalBox(CombatTabButton))
{
CombatTabSlot->SetPadding(FMargin(0.0f, 0.0f, 10.0f, 0.0f));
}
TabBox->AddChildToHorizontalBox(ElementTabButton);
PageSwitcher = WidgetTree->ConstructWidget<UWidgetSwitcher>(UWidgetSwitcher::StaticClass(), TEXT("PageSwitcher"));
if (UVerticalBoxSlot* SwitcherSlot = PageBox->AddChildToVerticalBox(PageSwitcher))
{
SwitcherSlot->SetSize(FSlateChildSize(ESlateSizeRule::Fill));
}
OverviewPage = WidgetTree->ConstructWidget<UPHYAttributeOverviewPageWidget>(UPHYAttributeOverviewPageWidget::StaticClass(), TEXT("OverviewPage"));
CombatPage = WidgetTree->ConstructWidget<UPHYCombatDerivedAttributePageWidget>(UPHYCombatDerivedAttributePageWidget::StaticClass(), TEXT("CombatPage"));
ElementPage = WidgetTree->ConstructWidget<UPHYElementDerivedAttributePageWidget>(UPHYElementDerivedAttributePageWidget::StaticClass(), TEXT("ElementPage"));
PageSwitcher->AddChild(OverviewPage);
PageSwitcher->AddChild(CombatPage);
PageSwitcher->AddChild(ElementPage);
OverviewTabButton->OnClicked.AddUniqueDynamic(this, &UPHYAttributeMenuWidget::HandleOverviewTabClicked);
CombatTabButton->OnClicked.AddUniqueDynamic(this, &UPHYAttributeMenuWidget::HandleCombatTabClicked);
ElementTabButton->OnClicked.AddUniqueDynamic(this, &UPHYAttributeMenuWidget::HandleElementTabClicked);
UButton* ConfirmButton = CreateAttributeNavButton(WidgetTree, TEXT("ConfirmButton"), FText::FromString(TEXT("确认")), true);
AddAttributeMenuCanvasChild(DesignCanvas, ConfirmButton, FVector2D(484.0f, 865.0f), FVector2D(255.0f, 58.0f), 3);
UButton* BackButton = CreateAttributeNavButton(WidgetTree, TEXT("BackButton"), FText::FromString(TEXT("返回")), false);
AddAttributeMenuCanvasChild(DesignCanvas, BackButton, FVector2D(774.0f, 865.0f), FVector2D(210.0f, 58.0f), 3);
UButton* ResetButton = CreateAttributeNavButton(WidgetTree, TEXT("ResetButton"), FText::FromString(TEXT("重置加点")), false);
AddAttributeMenuCanvasChild(DesignCanvas, ResetButton, FVector2D(1014.0f, 865.0f), FVector2D(260.0f, 58.0f), 3);
SetActivePage(ActivePageIndex);
RefreshAttributePages();
}
UButton* UPHYAttributeMenuWidget::CreateTabButton(const FName ButtonName, const FText Label)
{
UButton* TabButton = WidgetTree->ConstructWidget<UButton>(UButton::StaticClass(), ButtonName);
ApplyAttributeSubTabStyle(TabButton, false);
UTextBlock* TabText = WidgetTree->ConstructWidget<UTextBlock>(UTextBlock::StaticClass());
TabText->SetText(Label);
TabText->SetFont(FSlateFontInfo(FCoreStyle::GetDefaultFont(), 18));
TabText->SetColorAndOpacity(FSlateColor(FLinearColor(0.30f, 0.23f, 0.15f, 1.0f)));
TabButton->SetContent(TabText);
return TabButton;
}
void UPHYAttributeMenuWidget::SetActivePage(const int32 NewPageIndex)
{
ActivePageIndex = FMath::Clamp(NewPageIndex, 0, 2);
if (PageSwitcher)
{
PageSwitcher->SetActiveWidgetIndex(ActivePageIndex);
}
UpdateTabVisuals();
}
void UPHYAttributeMenuWidget::UpdateTabVisuals()
{
ApplyAttributeSubTabStyle(OverviewTabButton, ActivePageIndex == 0);
ApplyAttributeSubTabStyle(CombatTabButton, ActivePageIndex == 1);
ApplyAttributeSubTabStyle(ElementTabButton, ActivePageIndex == 2);
}
void UPHYAttributeMenuWidget::BindToPlayerState(APHYPlayerState* NewPlayerState)
{
if (BoundPlayerState.Get() == NewPlayerState)
{
return;
}
UnbindFromPlayerState();
BoundPlayerState = NewPlayerState;
if (NewPlayerState)
{
NewPlayerState->OnProgressionChanged.AddUniqueDynamic(this, &UPHYAttributeMenuWidget::HandleProgressionChanged);
}
}
void UPHYAttributeMenuWidget::UnbindFromPlayerState()
{
if (APHYPlayerState* PlayerState = BoundPlayerState.Get())
{
PlayerState->OnProgressionChanged.RemoveDynamic(this, &UPHYAttributeMenuWidget::HandleProgressionChanged);
}
BoundPlayerState.Reset();
}
void UPHYAttributeMenuWidget::BindToAbilitySystem(UAbilitySystemComponent* NewAbilitySystemComponent)
{
if (BoundAbilitySystemComponent.Get() == NewAbilitySystemComponent && AttributeChangedHandles.Num() > 0)
{
return;
}
UnbindFromAbilitySystem();
BoundAbilitySystemComponent = NewAbilitySystemComponent;
if (!NewAbilitySystemComponent)
{
return;
}
BoundAttributes = MakeTrackedAttributes();
AttributeChangedHandles.Reserve(BoundAttributes.Num());
for (const FGameplayAttribute& Attribute : BoundAttributes)
{
AttributeChangedHandles.Add(NewAbilitySystemComponent->GetGameplayAttributeValueChangeDelegate(Attribute).AddUObject(this, &UPHYAttributeMenuWidget::HandleAttributeChanged));
}
}
void UPHYAttributeMenuWidget::UnbindFromAbilitySystem()
{
if (UAbilitySystemComponent* AbilitySystemComponent = BoundAbilitySystemComponent.Get())
{
for (int32 Index = 0; Index < BoundAttributes.Num() && Index < AttributeChangedHandles.Num(); ++Index)
{
if (AttributeChangedHandles[Index].IsValid())
{
AbilitySystemComponent->GetGameplayAttributeValueChangeDelegate(BoundAttributes[Index]).Remove(AttributeChangedHandles[Index]);
}
}
}
AttributeChangedHandles.Reset();
BoundAttributes.Reset();
BoundAbilitySystemComponent.Reset();
}
void UPHYAttributeMenuWidget::RefreshAttributePages()
{
if (SummaryWidget)
{
const APHYPlayerState* PlayerState = BoundPlayerState.Get();
const FString PlayerName = PlayerState ? PlayerState->GetPlayerName() : TEXT("修行者");
const int32 Level = PlayerState ? PlayerState->GetPlayerLevel() : 1;
const int32 Experience = PlayerState ? PlayerState->GetExperience() : 0;
const int32 ExperienceForNextLevel = PlayerState ? PlayerState->GetExperienceForNextLevel() : 100;
SummaryWidget->SetSummary(FText::FromString(PlayerName), Level, Experience, ExperienceForNextLevel, CalculateDisplayCombatPower());
SummaryWidget->SetResources(
GetAttributeValue(UPHYCombatAttributeSet::GetHealthAttribute()),
GetAttributeValue(UPHYCombatAttributeSet::GetMaxHealthAttribute()),
GetAttributeValue(UPHYCombatAttributeSet::GetManaAttribute()),
GetAttributeValue(UPHYCombatAttributeSet::GetMaxManaAttribute()),
GetAttributeValue(UPHYCombatAttributeSet::GetStaminaAttribute()),
GetAttributeValue(UPHYCombatAttributeSet::GetMaxStaminaAttribute()));
}
const float Health = GetAttributeValue(UPHYCombatAttributeSet::GetHealthAttribute());
const float MaxHealth = GetAttributeValue(UPHYCombatAttributeSet::GetMaxHealthAttribute());
const float Mana = GetAttributeValue(UPHYCombatAttributeSet::GetManaAttribute());
const float MaxMana = GetAttributeValue(UPHYCombatAttributeSet::GetMaxManaAttribute());
const float Stamina = GetAttributeValue(UPHYCombatAttributeSet::GetStaminaAttribute());
const float MaxStamina = GetAttributeValue(UPHYCombatAttributeSet::GetMaxStaminaAttribute());
if (HeaderHealthBar)
{
HeaderHealthBar->SetResource(FText::FromString(TEXT("生命值 (HP)")), Health, MaxHealth, FLinearColor(0.78f, 0.18f, 0.14f, 1.0f));
}
if (HeaderManaBar)
{
HeaderManaBar->SetResource(FText::FromString(TEXT("法力值 (MP)")), Mana, MaxMana, FLinearColor(0.12f, 0.45f, 0.88f, 1.0f));
}
if (HeaderStaminaBar)
{
HeaderStaminaBar->SetResource(FText::FromString(TEXT("耐力值 (ST)")), Stamina, MaxStamina, FLinearColor(0.12f, 0.67f, 0.58f, 1.0f));
}
UAbilitySystemComponent* AbilitySystemComponent = BoundAbilitySystemComponent.Get();
if (OverviewPage)
{
OverviewPage->RefreshAttributes(AbilitySystemComponent);
}
if (CombatPage)
{
CombatPage->RefreshAttributes(AbilitySystemComponent);
}
if (ElementPage)
{
ElementPage->RefreshAttributes(AbilitySystemComponent);
}
}
void UPHYAttributeMenuWidget::HandleAttributeChanged(const FOnAttributeChangeData& /*ChangeData*/)
{
RefreshAttributePages();
}
void UPHYAttributeMenuWidget::HandleProgressionChanged(const int32 /*NewLevel*/, const int32 /*NewExperience*/, const int32 /*NewExperienceForNextLevel*/)
{
RefreshAttributePages();
}
void UPHYAttributeMenuWidget::HandleOverviewTabClicked()
{
SetActivePage(0);
}
void UPHYAttributeMenuWidget::HandleCombatTabClicked()
{
SetActivePage(1);
}
void UPHYAttributeMenuWidget::HandleElementTabClicked()
{
SetActivePage(2);
}
float UPHYAttributeMenuWidget::GetAttributeValue(const FGameplayAttribute& Attribute) const
{
UAbilitySystemComponent* AbilitySystemComponent = BoundAbilitySystemComponent.Get();
return AbilitySystemComponent ? AbilitySystemComponent->GetNumericAttribute(Attribute) : 0.0f;
}
int32 UPHYAttributeMenuWidget::CalculateDisplayCombatPower() const
{
const float OffensivePower =
GetAttributeValue(UPHYCombatAttributeSet::GetPhysicalAttackPowerAttribute()) +
GetAttributeValue(UPHYCombatAttributeSet::GetSpellPowerAttribute()) +
GetAttributeValue(UPHYCombatAttributeSet::GetGuardBreakPowerAttribute()) +
GetAttributeValue(UPHYCombatAttributeSet::GetPoiseDamageAttribute());
const float DefensivePower =
GetAttributeValue(UPHYCombatAttributeSet::GetArmorAttribute()) +
GetAttributeValue(UPHYCombatAttributeSet::GetMagicResistanceAttribute()) +
GetAttributeValue(UPHYCombatAttributeSet::GetBlockPowerAttribute()) +
GetAttributeValue(UPHYCombatAttributeSet::GetPoiseAttribute());
const float ResourcePower =
GetAttributeValue(UPHYCombatAttributeSet::GetMaxHealthAttribute()) * 0.20f +
GetAttributeValue(UPHYCombatAttributeSet::GetMaxManaAttribute()) * 0.10f +
GetAttributeValue(UPHYCombatAttributeSet::GetMaxStaminaAttribute()) * 0.10f;
return FMath::RoundToInt(OffensivePower + DefensivePower + ResourcePower);
}

View File

@@ -0,0 +1,50 @@
// Copyright Epic Games, Inc. All Rights Reserved.
#include "UI/PHYAttributeOverviewPageWidget.h"
#include UE_INLINE_GENERATED_CPP_BY_NAME(PHYAttributeOverviewPageWidget)
#include "AbilitySystemComponent.h"
#include "AbilitySystem/Attributes/PHYCombatAttributeSet.h"
#include "AbilitySystem/Attributes/PHYCoreAttributeSet.h"
#include "UI/PHYAttributeGroupWidget.h"
namespace
{
float GetOverviewAttributeValue(const UAbilitySystemComponent* AbilitySystemComponent, const FGameplayAttribute& Attribute)
{
return AbilitySystemComponent ? AbilitySystemComponent->GetNumericAttribute(Attribute) : 0.0f;
}
FText MakeOverviewNumberText(const float Value)
{
return FText::FromString(FString::Printf(TEXT("%.0f"), Value));
}
FText MakeOverviewPairText(const float CurrentValue, const float MaxValue)
{
return FText::FromString(FString::Printf(TEXT("%.0f / %.0f"), FMath::Max(0.0f, CurrentValue), FMath::Max(0.0f, MaxValue)));
}
void AddOverviewAttributeRow(UPHYAttributeGroupWidget* GroupWidget, const TCHAR* Label, const FText& Value, const bool bCanUpgrade = false)
{
if (GroupWidget)
{
GroupWidget->AddRow(FText::FromString(Label), Value, bCanUpgrade);
}
}
}
void UPHYAttributeOverviewPageWidget::RefreshAttributes(UAbilitySystemComponent* AbilitySystemComponent)
{
SetPageTitle(FText::FromString(TEXT("核心属性")));
ClearGroups();
UPHYAttributeGroupWidget* CoreGroup = AddGroup(FText::FromString(TEXT("核心属性")));
AddOverviewAttributeRow(CoreGroup, TEXT("力量"), MakeOverviewNumberText(GetOverviewAttributeValue(AbilitySystemComponent, UPHYCoreAttributeSet::GetStrengthAttribute())), true);
AddOverviewAttributeRow(CoreGroup, TEXT("敏捷"), MakeOverviewNumberText(GetOverviewAttributeValue(AbilitySystemComponent, UPHYCoreAttributeSet::GetDexterityAttribute())), true);
AddOverviewAttributeRow(CoreGroup, TEXT("体质"), MakeOverviewNumberText(GetOverviewAttributeValue(AbilitySystemComponent, UPHYCoreAttributeSet::GetVitalityAttribute())), true);
AddOverviewAttributeRow(CoreGroup, TEXT("智力"), MakeOverviewNumberText(GetOverviewAttributeValue(AbilitySystemComponent, UPHYCoreAttributeSet::GetIntelligenceAttribute())), true);
AddOverviewAttributeRow(CoreGroup, TEXT("精神"), MakeOverviewNumberText(GetOverviewAttributeValue(AbilitySystemComponent, UPHYCoreAttributeSet::GetSpiritAttribute())), true);
AddOverviewAttributeRow(CoreGroup, TEXT("感知"), MakeOverviewNumberText(GetOverviewAttributeValue(AbilitySystemComponent, UPHYCoreAttributeSet::GetPerceptionAttribute())), true);
}

View File

@@ -0,0 +1,122 @@
// Copyright Epic Games, Inc. All Rights Reserved.
#include "UI/PHYAttributePageWidget.h"
#include UE_INLINE_GENERATED_CPP_BY_NAME(PHYAttributePageWidget)
#include "Blueprint/WidgetTree.h"
#include "Components/Border.h"
#include "Components/ScrollBox.h"
#include "Components/ScrollBoxSlot.h"
#include "Components/TextBlock.h"
#include "Components/UniformGridPanel.h"
#include "Components/UniformGridSlot.h"
#include "Components/VerticalBox.h"
#include "Components/VerticalBoxSlot.h"
#include "Fonts/SlateFontInfo.h"
#include "Styling/CoreStyle.h"
#include "UI/PHYAttributeGroupWidget.h"
#include "Widgets/SWidget.h"
UPHYAttributePageWidget::UPHYAttributePageWidget(const FObjectInitializer& ObjectInitializer)
: Super(ObjectInitializer)
{
}
TSharedRef<SWidget> UPHYAttributePageWidget::RebuildWidget()
{
BuildNativeWidgetTree();
return Super::RebuildWidget();
}
void UPHYAttributePageWidget::SynchronizeProperties()
{
Super::SynchronizeProperties();
UpdatePageWidgets();
}
void UPHYAttributePageWidget::SetPageTitle(FText InPageTitle)
{
PageTitle = MoveTemp(InPageTitle);
UpdatePageWidgets();
}
void UPHYAttributePageWidget::ClearGroups()
{
GroupCount = 0;
if (GroupsGrid)
{
GroupsGrid->ClearChildren();
}
}
UPHYAttributeGroupWidget* UPHYAttributePageWidget::AddGroup(FText GroupTitle)
{
BuildNativeWidgetTree();
if (!GroupsGrid || !WidgetTree)
{
return nullptr;
}
UPHYAttributeGroupWidget* GroupWidget = WidgetTree->ConstructWidget<UPHYAttributeGroupWidget>(UPHYAttributeGroupWidget::StaticClass());
GroupWidget->SetGroupTitle(MoveTemp(GroupTitle));
UBorder* GroupPadding = WidgetTree->ConstructWidget<UBorder>(UBorder::StaticClass());
GroupPadding->SetBrushColor(FLinearColor(1.0f, 1.0f, 1.0f, 0.0f));
GroupPadding->SetPadding(FMargin(0.0f, 0.0f, 14.0f, 14.0f));
GroupPadding->SetContent(GroupWidget);
const int32 GroupIndex = GroupCount++;
if (UUniformGridSlot* GroupSlot = GroupsGrid->AddChildToUniformGrid(GroupPadding, GroupIndex / 2, GroupIndex % 2))
{
GroupSlot->SetHorizontalAlignment(HAlign_Fill);
GroupSlot->SetVerticalAlignment(VAlign_Top);
}
return GroupWidget;
}
void UPHYAttributePageWidget::BuildNativeWidgetTree()
{
if (!WidgetTree)
{
WidgetTree = NewObject<UWidgetTree>(this, TEXT("WidgetTree"), RF_Transient);
}
if (WidgetTree->RootWidget && TitleText && GroupsScrollBox && GroupsGrid)
{
return;
}
UVerticalBox* RootBox = WidgetTree->ConstructWidget<UVerticalBox>(UVerticalBox::StaticClass(), TEXT("RootBox"));
WidgetTree->RootWidget = RootBox;
TitleText = WidgetTree->ConstructWidget<UTextBlock>(UTextBlock::StaticClass(), TEXT("TitleText"));
TitleText->SetFont(FSlateFontInfo(FCoreStyle::GetDefaultFont(), 12));
TitleText->SetColorAndOpacity(FSlateColor(FLinearColor(0.42f, 0.18f, 0.08f, 0.0f)));
TitleText->SetVisibility(ESlateVisibility::Collapsed);
if (UVerticalBoxSlot* TitleSlot = RootBox->AddChildToVerticalBox(TitleText))
{
TitleSlot->SetPadding(FMargin(0.0f));
}
GroupsScrollBox = WidgetTree->ConstructWidget<UScrollBox>(UScrollBox::StaticClass(), TEXT("GroupsScrollBox"));
if (UVerticalBoxSlot* ScrollSlot = RootBox->AddChildToVerticalBox(GroupsScrollBox))
{
ScrollSlot->SetSize(FSlateChildSize(ESlateSizeRule::Fill));
}
GroupsGrid = WidgetTree->ConstructWidget<UUniformGridPanel>(UUniformGridPanel::StaticClass(), TEXT("GroupsGrid"));
if (UScrollBoxSlot* GroupsSlot = Cast<UScrollBoxSlot>(GroupsScrollBox->AddChild(GroupsGrid)))
{
GroupsSlot->SetPadding(FMargin(0.0f));
}
UpdatePageWidgets();
}
void UPHYAttributePageWidget::UpdatePageWidgets()
{
if (TitleText)
{
TitleText->SetText(PageTitle);
}
}

View File

@@ -0,0 +1,141 @@
// Copyright Epic Games, Inc. All Rights Reserved.
#include "UI/PHYAttributeResourceBarWidget.h"
#include UE_INLINE_GENERATED_CPP_BY_NAME(PHYAttributeResourceBarWidget)
#include "Blueprint/WidgetTree.h"
#include "Components/Border.h"
#include "Components/HorizontalBox.h"
#include "Components/HorizontalBoxSlot.h"
#include "Components/ProgressBar.h"
#include "Components/SizeBox.h"
#include "Components/TextBlock.h"
#include "Components/VerticalBox.h"
#include "Components/VerticalBoxSlot.h"
#include "Engine/Texture2D.h"
#include "Fonts/SlateFontInfo.h"
#include "Styling/CoreStyle.h"
#include "Styling/SlateBrush.h"
#include "Widgets/SWidget.h"
namespace
{
const TCHAR* const AttributeResourceRowTexturePath = TEXT("/Game/AGame/UI/Attributes/Textures/T_PHY_UI_RowStrip.T_PHY_UI_RowStrip");
float MakePercent(const float CurrentValue, const float MaxValue)
{
return MaxValue > UE_SMALL_NUMBER ? FMath::Clamp(CurrentValue / MaxValue, 0.0f, 1.0f) : 0.0f;
}
FSlateBrush MakeAttributeResourceBrush()
{
FSlateBrush Brush;
Brush.DrawAs = ESlateBrushDrawType::Box;
Brush.Margin = FMargin(0.08f);
Brush.ImageSize = FVector2D(512.0f, 64.0f);
Brush.TintColor = FSlateColor(FLinearColor(1.0f, 0.98f, 0.90f, 0.24f));
if (UTexture2D* Texture = LoadObject<UTexture2D>(nullptr, AttributeResourceRowTexturePath))
{
Brush.SetResourceObject(Texture);
}
return Brush;
}
}
UPHYAttributeResourceBarWidget::UPHYAttributeResourceBarWidget(const FObjectInitializer& ObjectInitializer)
: Super(ObjectInitializer)
{
}
TSharedRef<SWidget> UPHYAttributeResourceBarWidget::RebuildWidget()
{
BuildNativeWidgetTree();
return Super::RebuildWidget();
}
void UPHYAttributeResourceBarWidget::SynchronizeProperties()
{
Super::SynchronizeProperties();
UpdateResourceWidgets();
}
void UPHYAttributeResourceBarWidget::SetResource(FText InLabel, const float InCurrentValue, const float InMaxValue, const FLinearColor InBarTint)
{
Label = MoveTemp(InLabel);
CurrentValue = InCurrentValue;
MaxValue = FMath::Max(1.0f, InMaxValue);
BarTint = InBarTint;
UpdateResourceWidgets();
}
void UPHYAttributeResourceBarWidget::BuildNativeWidgetTree()
{
if (!WidgetTree)
{
WidgetTree = NewObject<UWidgetTree>(this, TEXT("WidgetTree"), RF_Transient);
}
if (WidgetTree->RootWidget && LabelText && ValueText && ResourceBar)
{
return;
}
UBorder* RootBorder = WidgetTree->ConstructWidget<UBorder>(UBorder::StaticClass(), TEXT("RootBorder"));
RootBorder->SetBrush(MakeAttributeResourceBrush());
RootBorder->SetPadding(FMargin(10.0f, 5.0f));
WidgetTree->RootWidget = RootBorder;
UHorizontalBox* HeaderBox = WidgetTree->ConstructWidget<UHorizontalBox>(UHorizontalBox::StaticClass(), TEXT("HeaderBox"));
RootBorder->SetContent(HeaderBox);
LabelText = WidgetTree->ConstructWidget<UTextBlock>(UTextBlock::StaticClass(), TEXT("LabelText"));
LabelText->SetFont(FSlateFontInfo(FCoreStyle::GetDefaultFont(), 16));
LabelText->SetColorAndOpacity(FSlateColor(FLinearColor(0.34f, 0.27f, 0.17f, 1.0f)));
if (UHorizontalBoxSlot* LabelSlot = HeaderBox->AddChildToHorizontalBox(LabelText))
{
LabelSlot->SetSize(FSlateChildSize(ESlateSizeRule::Automatic));
LabelSlot->SetPadding(FMargin(0.0f, 0.0f, 18.0f, 0.0f));
LabelSlot->SetVerticalAlignment(VAlign_Center);
}
USizeBox* BarSizeBox = WidgetTree->ConstructWidget<USizeBox>(USizeBox::StaticClass(), TEXT("BarSizeBox"));
BarSizeBox->SetHeightOverride(13.0f);
if (UHorizontalBoxSlot* BarOuterSlot = HeaderBox->AddChildToHorizontalBox(BarSizeBox))
{
BarOuterSlot->SetSize(FSlateChildSize(ESlateSizeRule::Fill));
BarOuterSlot->SetPadding(FMargin(0.0f, 2.0f, 18.0f, 0.0f));
BarOuterSlot->SetVerticalAlignment(VAlign_Center);
}
ResourceBar = WidgetTree->ConstructWidget<UProgressBar>(UProgressBar::StaticClass(), TEXT("ResourceBar"));
BarSizeBox->AddChild(ResourceBar);
ValueText = WidgetTree->ConstructWidget<UTextBlock>(UTextBlock::StaticClass(), TEXT("ValueText"));
ValueText->SetFont(FSlateFontInfo(FCoreStyle::GetDefaultFont(), 14));
ValueText->SetColorAndOpacity(FSlateColor(FLinearColor(0.23f, 0.20f, 0.16f, 1.0f)));
if (UHorizontalBoxSlot* ValueSlot = HeaderBox->AddChildToHorizontalBox(ValueText))
{
ValueSlot->SetSize(FSlateChildSize(ESlateSizeRule::Automatic));
ValueSlot->SetVerticalAlignment(VAlign_Center);
}
UpdateResourceWidgets();
}
void UPHYAttributeResourceBarWidget::UpdateResourceWidgets()
{
if (LabelText)
{
LabelText->SetText(Label);
}
if (ValueText)
{
ValueText->SetText(FText::FromString(FString::Printf(TEXT("%.0f / %.0f"), FMath::Max(0.0f, CurrentValue), FMath::Max(1.0f, MaxValue))));
}
if (ResourceBar)
{
ResourceBar->SetPercent(MakePercent(CurrentValue, MaxValue));
ResourceBar->SetFillColorAndOpacity(BarTint);
}
}

View File

@@ -0,0 +1,140 @@
// Copyright Epic Games, Inc. All Rights Reserved.
#include "UI/PHYAttributeRowWidget.h"
#include UE_INLINE_GENERATED_CPP_BY_NAME(PHYAttributeRowWidget)
#include "Blueprint/WidgetTree.h"
#include "Components/Border.h"
#include "Components/Button.h"
#include "Components/HorizontalBox.h"
#include "Components/HorizontalBoxSlot.h"
#include "Components/TextBlock.h"
#include "Engine/Texture2D.h"
#include "Fonts/SlateFontInfo.h"
#include "Styling/CoreStyle.h"
#include "Styling/SlateBrush.h"
#include "Styling/SlateTypes.h"
#include "Widgets/SWidget.h"
namespace
{
const TCHAR* const AttributeRowTexturePath = TEXT("/Game/AGame/UI/Attributes/Textures/T_PHY_UI_RowStrip.T_PHY_UI_RowStrip");
const TCHAR* const AttributeRowCinnabarTexturePath = TEXT("/Game/AGame/UI/Attributes/Textures/T_PHY_UI_CinnabarTab.T_PHY_UI_CinnabarTab");
FSlateBrush MakeAttributeRowBrush(const TCHAR* TexturePath, const FVector2D ImageSize, const FLinearColor Tint, const ESlateBrushDrawType::Type DrawAs = ESlateBrushDrawType::Box, const FMargin Margin = FMargin(0.08f))
{
FSlateBrush Brush;
Brush.DrawAs = DrawAs;
Brush.Margin = Margin;
Brush.ImageSize = ImageSize;
Brush.TintColor = FSlateColor(Tint);
if (UTexture2D* Texture = LoadObject<UTexture2D>(nullptr, TexturePath))
{
Brush.SetResourceObject(Texture);
}
return Brush;
}
}
UPHYAttributeRowWidget::UPHYAttributeRowWidget(const FObjectInitializer& ObjectInitializer)
: Super(ObjectInitializer)
{
}
TSharedRef<SWidget> UPHYAttributeRowWidget::RebuildWidget()
{
BuildNativeWidgetTree();
return Super::RebuildWidget();
}
void UPHYAttributeRowWidget::SynchronizeProperties()
{
Super::SynchronizeProperties();
UpdateRowWidgets();
}
void UPHYAttributeRowWidget::SetRow(FText InLabel, FText InValue, const bool bInCanUpgrade)
{
Label = MoveTemp(InLabel);
Value = MoveTemp(InValue);
bCanUpgrade = bInCanUpgrade;
UpdateRowWidgets();
}
void UPHYAttributeRowWidget::BuildNativeWidgetTree()
{
if (!WidgetTree)
{
WidgetTree = NewObject<UWidgetTree>(this, TEXT("WidgetTree"), RF_Transient);
}
if (WidgetTree->RootWidget && RowBorder && RowBox && LabelText && ValueText && UpgradeButton && UpgradeText)
{
return;
}
RowBorder = WidgetTree->ConstructWidget<UBorder>(UBorder::StaticClass(), TEXT("RowBorder"));
RowBorder->SetBrush(MakeAttributeRowBrush(AttributeRowTexturePath, FVector2D(512.0f, 64.0f), FLinearColor(1.0f, 0.98f, 0.90f, 0.42f)));
RowBorder->SetPadding(FMargin(9.0f, 3.0f));
WidgetTree->RootWidget = RowBorder;
RowBox = WidgetTree->ConstructWidget<UHorizontalBox>(UHorizontalBox::StaticClass(), TEXT("RowBox"));
RowBorder->SetContent(RowBox);
LabelText = WidgetTree->ConstructWidget<UTextBlock>(UTextBlock::StaticClass(), TEXT("LabelText"));
LabelText->SetFont(FSlateFontInfo(FCoreStyle::GetDefaultFont(), 16));
LabelText->SetColorAndOpacity(FSlateColor(FLinearColor(0.31f, 0.25f, 0.16f, 1.0f)));
if (UHorizontalBoxSlot* LabelSlot = RowBox->AddChildToHorizontalBox(LabelText))
{
LabelSlot->SetSize(FSlateChildSize(ESlateSizeRule::Fill));
LabelSlot->SetPadding(FMargin(0.0f, 3.0f, 10.0f, 3.0f));
LabelSlot->SetVerticalAlignment(VAlign_Center);
}
ValueText = WidgetTree->ConstructWidget<UTextBlock>(UTextBlock::StaticClass(), TEXT("ValueText"));
ValueText->SetFont(FSlateFontInfo(FCoreStyle::GetDefaultFont(), 16));
ValueText->SetColorAndOpacity(FSlateColor(FLinearColor(0.08f, 0.36f, 0.28f, 1.0f)));
if (UHorizontalBoxSlot* ValueSlot = RowBox->AddChildToHorizontalBox(ValueText))
{
ValueSlot->SetSize(FSlateChildSize(ESlateSizeRule::Automatic));
ValueSlot->SetPadding(FMargin(0.0f, 3.0f, 12.0f, 3.0f));
ValueSlot->SetVerticalAlignment(VAlign_Center);
}
UpgradeButton = WidgetTree->ConstructWidget<UButton>(UButton::StaticClass(), TEXT("UpgradeButton"));
FButtonStyle UpgradeButtonStyle;
UpgradeButtonStyle.SetNormal(MakeAttributeRowBrush(AttributeRowCinnabarTexturePath, FVector2D(34.0f, 34.0f), FLinearColor(0.20f, 0.35f, 0.31f, 0.96f)));
UpgradeButtonStyle.SetHovered(MakeAttributeRowBrush(AttributeRowCinnabarTexturePath, FVector2D(34.0f, 34.0f), FLinearColor(0.27f, 0.48f, 0.42f, 1.0f)));
UpgradeButtonStyle.SetPressed(MakeAttributeRowBrush(AttributeRowCinnabarTexturePath, FVector2D(34.0f, 34.0f), FLinearColor(0.74f, 0.24f, 0.13f, 1.0f)));
UpgradeButton->SetStyle(UpgradeButtonStyle);
UpgradeText = WidgetTree->ConstructWidget<UTextBlock>(UTextBlock::StaticClass(), TEXT("UpgradeText"));
UpgradeText->SetText(FText::FromString(TEXT("+")));
UpgradeText->SetFont(FSlateFontInfo(FCoreStyle::GetDefaultFont(), 20));
UpgradeText->SetColorAndOpacity(FSlateColor(FLinearColor(0.98f, 0.92f, 0.78f, 1.0f)));
UpgradeButton->SetContent(UpgradeText);
if (UHorizontalBoxSlot* UpgradeSlot = RowBox->AddChildToHorizontalBox(UpgradeButton))
{
UpgradeSlot->SetSize(FSlateChildSize(ESlateSizeRule::Automatic));
UpgradeSlot->SetPadding(FMargin(0.0f, 3.0f, 0.0f, 3.0f));
UpgradeSlot->SetVerticalAlignment(VAlign_Center);
}
UpdateRowWidgets();
}
void UPHYAttributeRowWidget::UpdateRowWidgets()
{
if (LabelText)
{
LabelText->SetText(Label);
}
if (ValueText)
{
ValueText->SetText(Value);
}
if (UpgradeButton)
{
UpgradeButton->SetVisibility(bCanUpgrade ? ESlateVisibility::Visible : ESlateVisibility::Collapsed);
}
}

View File

@@ -0,0 +1,207 @@
// Copyright Epic Games, Inc. All Rights Reserved.
#include "UI/PHYAttributeSummaryWidget.h"
#include UE_INLINE_GENERATED_CPP_BY_NAME(PHYAttributeSummaryWidget)
#include "Blueprint/WidgetTree.h"
#include "Components/Border.h"
#include "Components/HorizontalBox.h"
#include "Components/HorizontalBoxSlot.h"
#include "Components/SizeBox.h"
#include "Components/Spacer.h"
#include "Components/TextBlock.h"
#include "Components/VerticalBox.h"
#include "Components/VerticalBoxSlot.h"
#include "Engine/Texture2D.h"
#include "Fonts/SlateFontInfo.h"
#include "Styling/CoreStyle.h"
#include "Styling/SlateBrush.h"
#include "UI/PHYAttributeResourceBarWidget.h"
#include "Widgets/SWidget.h"
namespace
{
const TCHAR* const AttributeSummaryJadeTexturePath = TEXT("/Game/AGame/UI/Attributes/Textures/T_PHY_UI_JadePanel.T_PHY_UI_JadePanel");
const TCHAR* const AttributeSummaryPortraitTexturePath = TEXT("/Game/AGame/UI/Attributes/Textures/T_PHY_UI_PortraitFrame.T_PHY_UI_PortraitFrame");
FSlateBrush MakeAttributeSummaryBrush(const TCHAR* TexturePath, const FVector2D ImageSize, const FLinearColor Tint, const ESlateBrushDrawType::Type DrawAs = ESlateBrushDrawType::Box, const FMargin Margin = FMargin(0.08f))
{
FSlateBrush Brush;
Brush.DrawAs = DrawAs;
Brush.Margin = Margin;
Brush.ImageSize = ImageSize;
Brush.TintColor = FSlateColor(Tint);
if (UTexture2D* Texture = LoadObject<UTexture2D>(nullptr, TexturePath))
{
Brush.SetResourceObject(Texture);
}
return Brush;
}
FText MakeAttributeSummaryDisplayName(const FText& SourceName)
{
FString NameString = SourceName.IsEmpty() ? FString(TEXT("修行者")) : SourceName.ToString();
if (NameString.Len() > 10)
{
NameString = NameString.Left(10) + TEXT("...");
}
return FText::FromString(NameString);
}
}
UPHYAttributeSummaryWidget::UPHYAttributeSummaryWidget(const FObjectInitializer& ObjectInitializer)
: Super(ObjectInitializer)
{
}
TSharedRef<SWidget> UPHYAttributeSummaryWidget::RebuildWidget()
{
BuildNativeWidgetTree();
return Super::RebuildWidget();
}
void UPHYAttributeSummaryWidget::SynchronizeProperties()
{
Super::SynchronizeProperties();
UpdateSummaryWidgets();
}
void UPHYAttributeSummaryWidget::SetSummary(FText InPlayerName, const int32 InLevel, const int32 InExperience, const int32 InExperienceForNextLevel, const int32 InCombatPower)
{
PlayerName = MoveTemp(InPlayerName);
Level = FMath::Max(1, InLevel);
Experience = FMath::Max(0, InExperience);
ExperienceForNextLevel = FMath::Max(1, InExperienceForNextLevel);
CombatPower = FMath::Max(0, InCombatPower);
UpdateSummaryWidgets();
}
void UPHYAttributeSummaryWidget::SetResources(const float InHealth, const float InMaxHealth, const float InMana, const float InMaxMana, const float InStamina, const float InMaxStamina)
{
Health = InHealth;
MaxHealth = FMath::Max(1.0f, InMaxHealth);
Mana = InMana;
MaxMana = FMath::Max(1.0f, InMaxMana);
Stamina = InStamina;
MaxStamina = FMath::Max(1.0f, InMaxStamina);
UpdateSummaryWidgets();
}
void UPHYAttributeSummaryWidget::BuildNativeWidgetTree()
{
if (!WidgetTree)
{
WidgetTree = NewObject<UWidgetTree>(this, TEXT("WidgetTree"), RF_Transient);
}
if (WidgetTree->RootWidget && PlayerNameText && LevelText && CombatPowerText && ExperienceBar && ResourceBox)
{
return;
}
UBorder* RootBorder = WidgetTree->ConstructWidget<UBorder>(UBorder::StaticClass(), TEXT("RootBorder"));
RootBorder->SetBrushColor(FLinearColor(1.0f, 1.0f, 1.0f, 0.0f));
RootBorder->SetPadding(FMargin(0.0f));
WidgetTree->RootWidget = RootBorder;
UVerticalBox* RootBox = WidgetTree->ConstructWidget<UVerticalBox>(UVerticalBox::StaticClass(), TEXT("RootBox"));
RootBorder->SetContent(RootBox);
UHorizontalBox* HeaderBox = WidgetTree->ConstructWidget<UHorizontalBox>(UHorizontalBox::StaticClass(), TEXT("HeaderBox"));
if (UVerticalBoxSlot* HeaderSlot = RootBox->AddChildToVerticalBox(HeaderBox))
{
HeaderSlot->SetPadding(FMargin(0.0f, 0.0f, 0.0f, 14.0f));
}
UBorder* LevelSeal = WidgetTree->ConstructWidget<UBorder>(UBorder::StaticClass(), TEXT("LevelSeal"));
LevelSeal->SetBrush(MakeAttributeSummaryBrush(AttributeSummaryJadeTexturePath, FVector2D(92.0f, 92.0f), FLinearColor(0.18f, 0.33f, 0.30f, 0.96f)));
LevelSeal->SetPadding(FMargin(10.0f));
if (UHorizontalBoxSlot* SealSlot = HeaderBox->AddChildToHorizontalBox(LevelSeal))
{
SealSlot->SetSize(FSlateChildSize(ESlateSizeRule::Automatic));
SealSlot->SetPadding(FMargin(0.0f, 0.0f, 18.0f, 0.0f));
SealSlot->SetVerticalAlignment(VAlign_Center);
}
LevelText = WidgetTree->ConstructWidget<UTextBlock>(UTextBlock::StaticClass(), TEXT("LevelText"));
LevelText->SetFont(FSlateFontInfo(FCoreStyle::GetDefaultFont(), 31));
LevelText->SetColorAndOpacity(FSlateColor(FLinearColor(1.0f, 0.86f, 0.55f, 1.0f)));
LevelSeal->SetContent(LevelText);
UVerticalBox* HeaderTextBox = WidgetTree->ConstructWidget<UVerticalBox>(UVerticalBox::StaticClass(), TEXT("HeaderTextBox"));
if (UHorizontalBoxSlot* HeaderTextSlot = HeaderBox->AddChildToHorizontalBox(HeaderTextBox))
{
HeaderTextSlot->SetSize(FSlateChildSize(ESlateSizeRule::Fill));
HeaderTextSlot->SetVerticalAlignment(VAlign_Center);
}
PlayerNameText = WidgetTree->ConstructWidget<UTextBlock>(UTextBlock::StaticClass(), TEXT("PlayerNameText"));
PlayerNameText->SetFont(FSlateFontInfo(FCoreStyle::GetDefaultFont(), 24));
PlayerNameText->SetColorAndOpacity(FSlateColor(FLinearColor(0.26f, 0.20f, 0.12f, 1.0f)));
HeaderTextBox->AddChildToVerticalBox(PlayerNameText);
CombatPowerText = WidgetTree->ConstructWidget<UTextBlock>(UTextBlock::StaticClass(), TEXT("CombatPowerText"));
CombatPowerText->SetFont(FSlateFontInfo(FCoreStyle::GetDefaultFont(), 17));
CombatPowerText->SetColorAndOpacity(FSlateColor(FLinearColor(0.60f, 0.36f, 0.11f, 1.0f)));
if (UVerticalBoxSlot* PowerSlot = HeaderTextBox->AddChildToVerticalBox(CombatPowerText))
{
PowerSlot->SetPadding(FMargin(0.0f, 8.0f, 0.0f, 0.0f));
}
USizeBox* PortraitSizeBox = WidgetTree->ConstructWidget<USizeBox>(USizeBox::StaticClass(), TEXT("PortraitSizeBox"));
PortraitSizeBox->SetHeightOverride(438.0f);
if (UVerticalBoxSlot* PortraitSlot = RootBox->AddChildToVerticalBox(PortraitSizeBox))
{
PortraitSlot->SetPadding(FMargin(0.0f, 2.0f, 0.0f, 18.0f));
}
UBorder* PortraitBorder = WidgetTree->ConstructWidget<UBorder>(UBorder::StaticClass(), TEXT("PortraitBorder"));
PortraitBorder->SetBrush(MakeAttributeSummaryBrush(AttributeSummaryPortraitTexturePath, FVector2D(380.0f, 438.0f), FLinearColor(1.0f, 1.0f, 1.0f, 0.98f), ESlateBrushDrawType::Image, FMargin(0.0f)));
PortraitBorder->SetPadding(FMargin(0.0f));
PortraitSizeBox->AddChild(PortraitBorder);
ResourceBox = WidgetTree->ConstructWidget<UVerticalBox>(UVerticalBox::StaticClass(), TEXT("ResourceBox"));
RootBox->AddChildToVerticalBox(ResourceBox);
ExperienceBar = WidgetTree->ConstructWidget<UPHYAttributeResourceBarWidget>(UPHYAttributeResourceBarWidget::StaticClass(), TEXT("ExperienceBar"));
HealthBar = WidgetTree->ConstructWidget<UPHYAttributeResourceBarWidget>(UPHYAttributeResourceBarWidget::StaticClass(), TEXT("HealthBar"));
ManaBar = WidgetTree->ConstructWidget<UPHYAttributeResourceBarWidget>(UPHYAttributeResourceBarWidget::StaticClass(), TEXT("ManaBar"));
StaminaBar = WidgetTree->ConstructWidget<UPHYAttributeResourceBarWidget>(UPHYAttributeResourceBarWidget::StaticClass(), TEXT("StaminaBar"));
ResourceBox->AddChildToVerticalBox(ExperienceBar);
UpdateSummaryWidgets();
}
void UPHYAttributeSummaryWidget::UpdateSummaryWidgets()
{
if (PlayerNameText)
{
PlayerNameText->SetText(MakeAttributeSummaryDisplayName(PlayerName));
}
if (LevelText)
{
LevelText->SetText(FText::FromString(FString::Printf(TEXT("%d"), Level)));
}
if (CombatPowerText)
{
CombatPowerText->SetText(FText::FromString(FString::Printf(TEXT("战斗评分\n%d"), CombatPower)));
}
if (ExperienceBar)
{
ExperienceBar->SetResource(FText::FromString(TEXT("经验")), static_cast<float>(Experience), static_cast<float>(ExperienceForNextLevel), FLinearColor(0.80f, 0.55f, 0.18f, 1.0f));
}
if (HealthBar)
{
HealthBar->SetResource(FText::FromString(TEXT("生命")), Health, MaxHealth, FLinearColor(0.72f, 0.16f, 0.12f, 1.0f));
}
if (ManaBar)
{
ManaBar->SetResource(FText::FromString(TEXT("法力")), Mana, MaxMana, FLinearColor(0.22f, 0.38f, 0.86f, 1.0f));
}
if (StaminaBar)
{
StaminaBar->SetResource(FText::FromString(TEXT("耐力")), Stamina, MaxStamina, FLinearColor(0.18f, 0.60f, 0.54f, 1.0f));
}
}

View File

@@ -0,0 +1,72 @@
// Copyright Epic Games, Inc. All Rights Reserved.
#include "UI/PHYCombatDerivedAttributePageWidget.h"
#include UE_INLINE_GENERATED_CPP_BY_NAME(PHYCombatDerivedAttributePageWidget)
#include "AbilitySystemComponent.h"
#include "AbilitySystem/Attributes/PHYCombatAttributeSet.h"
#include "UI/PHYAttributeGroupWidget.h"
namespace
{
float GetCombatDerivedAttributeValue(const UAbilitySystemComponent* AbilitySystemComponent, const FGameplayAttribute& Attribute)
{
return AbilitySystemComponent ? AbilitySystemComponent->GetNumericAttribute(Attribute) : 0.0f;
}
FText MakeCombatNumberText(const float Value)
{
return FText::FromString(FString::Printf(TEXT("%.0f"), Value));
}
FText MakeCombatPercentText(const float Value)
{
return FText::FromString(FString::Printf(TEXT("%.1f%%"), Value * 100.0f));
}
FText MakeCombatMultiplierText(const float Value)
{
return FText::FromString(FString::Printf(TEXT("x%.2f"), Value));
}
void AddCombatAttributeRow(UPHYAttributeGroupWidget* GroupWidget, const TCHAR* Label, const FText& Value)
{
if (GroupWidget)
{
GroupWidget->AddRow(FText::FromString(Label), Value);
}
}
}
void UPHYCombatDerivedAttributePageWidget::RefreshAttributes(UAbilitySystemComponent* AbilitySystemComponent)
{
SetPageTitle(FText::FromString(TEXT("战斗衍生属性")));
ClearGroups();
UPHYAttributeGroupWidget* ResourceGroup = AddGroup(FText::FromString(TEXT("资源上限")));
AddCombatAttributeRow(ResourceGroup, TEXT("当前生命"), MakeCombatNumberText(GetCombatDerivedAttributeValue(AbilitySystemComponent, UPHYCombatAttributeSet::GetHealthAttribute())));
AddCombatAttributeRow(ResourceGroup, TEXT("生命上限"), MakeCombatNumberText(GetCombatDerivedAttributeValue(AbilitySystemComponent, UPHYCombatAttributeSet::GetMaxHealthAttribute())));
AddCombatAttributeRow(ResourceGroup, TEXT("当前法力"), MakeCombatNumberText(GetCombatDerivedAttributeValue(AbilitySystemComponent, UPHYCombatAttributeSet::GetManaAttribute())));
AddCombatAttributeRow(ResourceGroup, TEXT("法力上限"), MakeCombatNumberText(GetCombatDerivedAttributeValue(AbilitySystemComponent, UPHYCombatAttributeSet::GetMaxManaAttribute())));
AddCombatAttributeRow(ResourceGroup, TEXT("当前耐力"), MakeCombatNumberText(GetCombatDerivedAttributeValue(AbilitySystemComponent, UPHYCombatAttributeSet::GetStaminaAttribute())));
AddCombatAttributeRow(ResourceGroup, TEXT("耐力上限"), MakeCombatNumberText(GetCombatDerivedAttributeValue(AbilitySystemComponent, UPHYCombatAttributeSet::GetMaxStaminaAttribute())));
UPHYAttributeGroupWidget* AttackGroup = AddGroup(FText::FromString(TEXT("攻击能力")));
AddCombatAttributeRow(AttackGroup, TEXT("物理攻击"), MakeCombatNumberText(GetCombatDerivedAttributeValue(AbilitySystemComponent, UPHYCombatAttributeSet::GetPhysicalAttackPowerAttribute())));
AddCombatAttributeRow(AttackGroup, TEXT("法术强度"), MakeCombatNumberText(GetCombatDerivedAttributeValue(AbilitySystemComponent, UPHYCombatAttributeSet::GetSpellPowerAttribute())));
AddCombatAttributeRow(AttackGroup, TEXT("命中率"), MakeCombatPercentText(GetCombatDerivedAttributeValue(AbilitySystemComponent, UPHYCombatAttributeSet::GetAccuracyAttribute())));
AddCombatAttributeRow(AttackGroup, TEXT("暴击率"), MakeCombatPercentText(GetCombatDerivedAttributeValue(AbilitySystemComponent, UPHYCombatAttributeSet::GetCriticalChanceAttribute())));
AddCombatAttributeRow(AttackGroup, TEXT("暴击伤害"), MakeCombatMultiplierText(GetCombatDerivedAttributeValue(AbilitySystemComponent, UPHYCombatAttributeSet::GetCriticalDamageAttribute())));
AddCombatAttributeRow(AttackGroup, TEXT("攻击速度"), MakeCombatMultiplierText(GetCombatDerivedAttributeValue(AbilitySystemComponent, UPHYCombatAttributeSet::GetAttackSpeedAttribute())));
AddCombatAttributeRow(AttackGroup, TEXT("冷却缩减"), MakeCombatPercentText(GetCombatDerivedAttributeValue(AbilitySystemComponent, UPHYCombatAttributeSet::GetCooldownReductionAttribute())));
AddCombatAttributeRow(AttackGroup, TEXT("破防强度"), MakeCombatNumberText(GetCombatDerivedAttributeValue(AbilitySystemComponent, UPHYCombatAttributeSet::GetGuardBreakPowerAttribute())));
AddCombatAttributeRow(AttackGroup, TEXT("韧性伤害"), MakeCombatNumberText(GetCombatDerivedAttributeValue(AbilitySystemComponent, UPHYCombatAttributeSet::GetPoiseDamageAttribute())));
UPHYAttributeGroupWidget* DefenseGroup = AddGroup(FText::FromString(TEXT("防御能力")));
AddCombatAttributeRow(DefenseGroup, TEXT("护甲"), MakeCombatNumberText(GetCombatDerivedAttributeValue(AbilitySystemComponent, UPHYCombatAttributeSet::GetArmorAttribute())));
AddCombatAttributeRow(DefenseGroup, TEXT("魔法抗性"), MakeCombatNumberText(GetCombatDerivedAttributeValue(AbilitySystemComponent, UPHYCombatAttributeSet::GetMagicResistanceAttribute())));
AddCombatAttributeRow(DefenseGroup, TEXT("闪避率"), MakeCombatPercentText(GetCombatDerivedAttributeValue(AbilitySystemComponent, UPHYCombatAttributeSet::GetEvasionAttribute())));
AddCombatAttributeRow(DefenseGroup, TEXT("格挡强度"), MakeCombatNumberText(GetCombatDerivedAttributeValue(AbilitySystemComponent, UPHYCombatAttributeSet::GetBlockPowerAttribute())));
AddCombatAttributeRow(DefenseGroup, TEXT("韧性"), MakeCombatNumberText(GetCombatDerivedAttributeValue(AbilitySystemComponent, UPHYCombatAttributeSet::GetPoiseAttribute())));
}

View File

@@ -0,0 +1,57 @@
// Copyright Epic Games, Inc. All Rights Reserved.
#include "UI/PHYElementDerivedAttributePageWidget.h"
#include UE_INLINE_GENERATED_CPP_BY_NAME(PHYElementDerivedAttributePageWidget)
#include "AbilitySystemComponent.h"
#include "AbilitySystem/Attributes/PHYElementAttributeSet.h"
#include "UI/PHYAttributeGroupWidget.h"
namespace
{
float GetElementDerivedAttributeValue(const UAbilitySystemComponent* AbilitySystemComponent, const FGameplayAttribute& Attribute)
{
return AbilitySystemComponent ? AbilitySystemComponent->GetNumericAttribute(Attribute) : 0.0f;
}
FText MakeElementPercentText(const float Value)
{
return FText::FromString(FString::Printf(TEXT("%.1f%%"), Value * 100.0f));
}
void AddElementAttributeRow(UPHYAttributeGroupWidget* GroupWidget, const TCHAR* Label, const FText& Value)
{
if (GroupWidget)
{
GroupWidget->AddRow(FText::FromString(Label), Value);
}
}
}
void UPHYElementDerivedAttributePageWidget::RefreshAttributes(UAbilitySystemComponent* AbilitySystemComponent)
{
SetPageTitle(FText::FromString(TEXT("元素衍生属性")));
ClearGroups();
UPHYAttributeGroupWidget* BonusGroup = AddGroup(FText::FromString(TEXT("元素伤害加成")));
AddElementAttributeRow(BonusGroup, TEXT("火伤加成"), MakeElementPercentText(GetElementDerivedAttributeValue(AbilitySystemComponent, UPHYElementAttributeSet::GetFireDamageBonusAttribute())));
AddElementAttributeRow(BonusGroup, TEXT("水伤加成"), MakeElementPercentText(GetElementDerivedAttributeValue(AbilitySystemComponent, UPHYElementAttributeSet::GetWaterDamageBonusAttribute())));
AddElementAttributeRow(BonusGroup, TEXT("冰伤加成"), MakeElementPercentText(GetElementDerivedAttributeValue(AbilitySystemComponent, UPHYElementAttributeSet::GetIceDamageBonusAttribute())));
AddElementAttributeRow(BonusGroup, TEXT("雷伤加成"), MakeElementPercentText(GetElementDerivedAttributeValue(AbilitySystemComponent, UPHYElementAttributeSet::GetLightningDamageBonusAttribute())));
AddElementAttributeRow(BonusGroup, TEXT("地伤加成"), MakeElementPercentText(GetElementDerivedAttributeValue(AbilitySystemComponent, UPHYElementAttributeSet::GetEarthDamageBonusAttribute())));
AddElementAttributeRow(BonusGroup, TEXT("风伤加成"), MakeElementPercentText(GetElementDerivedAttributeValue(AbilitySystemComponent, UPHYElementAttributeSet::GetWindDamageBonusAttribute())));
AddElementAttributeRow(BonusGroup, TEXT("光伤加成"), MakeElementPercentText(GetElementDerivedAttributeValue(AbilitySystemComponent, UPHYElementAttributeSet::GetLightDamageBonusAttribute())));
AddElementAttributeRow(BonusGroup, TEXT("暗伤加成"), MakeElementPercentText(GetElementDerivedAttributeValue(AbilitySystemComponent, UPHYElementAttributeSet::GetDarkDamageBonusAttribute())));
AddElementAttributeRow(BonusGroup, TEXT("元素穿透"), MakeElementPercentText(GetElementDerivedAttributeValue(AbilitySystemComponent, UPHYElementAttributeSet::GetElementPenetrationAttribute())));
UPHYAttributeGroupWidget* ResistanceGroup = AddGroup(FText::FromString(TEXT("元素抗性")));
AddElementAttributeRow(ResistanceGroup, TEXT("火抗"), MakeElementPercentText(GetElementDerivedAttributeValue(AbilitySystemComponent, UPHYElementAttributeSet::GetFireResistanceAttribute())));
AddElementAttributeRow(ResistanceGroup, TEXT("水抗"), MakeElementPercentText(GetElementDerivedAttributeValue(AbilitySystemComponent, UPHYElementAttributeSet::GetWaterResistanceAttribute())));
AddElementAttributeRow(ResistanceGroup, TEXT("冰抗"), MakeElementPercentText(GetElementDerivedAttributeValue(AbilitySystemComponent, UPHYElementAttributeSet::GetIceResistanceAttribute())));
AddElementAttributeRow(ResistanceGroup, TEXT("雷抗"), MakeElementPercentText(GetElementDerivedAttributeValue(AbilitySystemComponent, UPHYElementAttributeSet::GetLightningResistanceAttribute())));
AddElementAttributeRow(ResistanceGroup, TEXT("地抗"), MakeElementPercentText(GetElementDerivedAttributeValue(AbilitySystemComponent, UPHYElementAttributeSet::GetEarthResistanceAttribute())));
AddElementAttributeRow(ResistanceGroup, TEXT("风抗"), MakeElementPercentText(GetElementDerivedAttributeValue(AbilitySystemComponent, UPHYElementAttributeSet::GetWindResistanceAttribute())));
AddElementAttributeRow(ResistanceGroup, TEXT("光抗"), MakeElementPercentText(GetElementDerivedAttributeValue(AbilitySystemComponent, UPHYElementAttributeSet::GetLightResistanceAttribute())));
AddElementAttributeRow(ResistanceGroup, TEXT("暗抗"), MakeElementPercentText(GetElementDerivedAttributeValue(AbilitySystemComponent, UPHYElementAttributeSet::GetDarkResistanceAttribute())));
}

View File

@@ -4,6 +4,8 @@
#include UE_INLINE_GENERATED_CPP_BY_NAME(PHYHUDStatusWidget)
#include "AbilitySystemComponent.h"
#include "AbilitySystem/Attributes/PHYCombatAttributeSet.h"
#include "Blueprint/WidgetTree.h"
#include "Components/Overlay.h"
#include "Components/OverlaySlot.h"
@@ -13,6 +15,9 @@
#include "Components/VerticalBox.h"
#include "Components/VerticalBoxSlot.h"
#include "Fonts/SlateFontInfo.h"
#include "GameFramework/PlayerController.h"
#include "GameplayEffectTypes.h"
#include "Player/PHYPlayerState.h"
#include "Styling/CoreStyle.h"
#include "Widgets/SWidget.h"
@@ -23,10 +28,29 @@ namespace
return FText::FromString(FString::Printf(TEXT("%s %.0f / %.0f"), Label, FMath::Max(0.0f, Value), FMath::Max(0.0f, MaxValue)));
}
FText MakeProgressionText(const int32 CurrentExperience, const int32 RequiredExperience)
{
return FText::FromString(FString::Printf(TEXT("XP %d / %d"), FMath::Max(0, CurrentExperience), FMath::Max(1, RequiredExperience)));
}
float MakeResourcePercent(const float Value, const float MaxValue)
{
return MaxValue > UE_SMALL_NUMBER ? FMath::Clamp(Value / MaxValue, 0.0f, 1.0f) : 0.0f;
}
float MakeProgressionPercent(const int32 CurrentExperience, const int32 RequiredExperience)
{
return RequiredExperience > 0 ? FMath::Clamp(static_cast<float>(CurrentExperience) / static_cast<float>(RequiredExperience), 0.0f, 1.0f) : 0.0f;
}
void RemoveAttributeChangedDelegate(UAbilitySystemComponent* AbilitySystemComponent, const FGameplayAttribute& Attribute, FDelegateHandle& DelegateHandle)
{
if (AbilitySystemComponent && DelegateHandle.IsValid())
{
AbilitySystemComponent->GetGameplayAttributeValueChangeDelegate(Attribute).Remove(DelegateHandle);
DelegateHandle.Reset();
}
}
}
UPHYHUDStatusWidget::UPHYHUDStatusWidget(const FObjectInitializer& ObjectInitializer)
@@ -47,6 +71,35 @@ void UPHYHUDStatusWidget::SynchronizeProperties()
UpdateResourceWidgets();
}
void UPHYHUDStatusWidget::InitializeFromPlayer(APlayerController* InPlayerController)
{
if (!InPlayerController)
{
BoundPlayerController.Reset();
UnbindFromPlayerState();
UnbindFromAbilitySystem();
return;
}
BoundPlayerController = InPlayerController;
RefreshFromBoundSources();
}
void UPHYHUDStatusWidget::RefreshFromBoundSources()
{
APlayerController* PlayerController = BoundPlayerController.Get();
if (!PlayerController)
{
UnbindFromPlayerState();
UnbindFromAbilitySystem();
return;
}
APHYPlayerState* PHYPlayerState = PlayerController->GetPlayerState<APHYPlayerState>();
BindToPlayerState(PHYPlayerState);
BindToAbilitySystem(PHYPlayerState ? PHYPlayerState->GetAbilitySystemComponent() : nullptr);
}
void UPHYHUDStatusWidget::SetHealth(const float NewHealth, const float NewMaxHealth)
{
Health = NewHealth;
@@ -54,6 +107,13 @@ void UPHYHUDStatusWidget::SetHealth(const float NewHealth, const float NewMaxHea
UpdateResourceWidgets();
}
void UPHYHUDStatusWidget::SetMana(const float NewMana, const float NewMaxMana)
{
Mana = NewMana;
MaxMana = NewMaxMana;
UpdateResourceWidgets();
}
void UPHYHUDStatusWidget::SetStamina(const float NewStamina, const float NewMaxStamina)
{
Stamina = NewStamina;
@@ -61,6 +121,54 @@ void UPHYHUDStatusWidget::SetStamina(const float NewStamina, const float NewMaxS
UpdateResourceWidgets();
}
void UPHYHUDStatusWidget::SetProgression(const int32 NewLevel, const int32 NewExperience, const int32 NewExperienceForNextLevel)
{
Level = FMath::Max(1, NewLevel);
Experience = FMath::Max(0, NewExperience);
ExperienceForNextLevel = FMath::Max(1, NewExperienceForNextLevel);
UpdateResourceWidgets();
}
void UPHYHUDStatusWidget::NativeConstruct()
{
Super::NativeConstruct();
if (!BoundPlayerController.IsValid())
{
InitializeFromPlayer(GetOwningPlayer());
}
else
{
RefreshFromBoundSources();
}
}
void UPHYHUDStatusWidget::NativeDestruct()
{
UnbindFromPlayerState();
UnbindFromAbilitySystem();
BoundPlayerController.Reset();
Super::NativeDestruct();
}
void UPHYHUDStatusWidget::NativeTick(const FGeometry& MyGeometry, const float InDeltaTime)
{
Super::NativeTick(MyGeometry, InDeltaTime);
APlayerController* PlayerController = BoundPlayerController.Get();
if (!PlayerController)
{
return;
}
APHYPlayerState* CurrentPlayerState = PlayerController->GetPlayerState<APHYPlayerState>();
if (CurrentPlayerState != BoundPlayerState.Get() || !BoundAbilitySystemComponent.IsValid())
{
RefreshFromBoundSources();
}
}
void UPHYHUDStatusWidget::BuildNativeWidgetTree()
{
if (!WidgetTree)
@@ -68,7 +176,7 @@ void UPHYHUDStatusWidget::BuildNativeWidgetTree()
WidgetTree = NewObject<UWidgetTree>(this, TEXT("WidgetTree"), RF_Transient);
}
if (WidgetTree->RootWidget && TitleText && HealthText && HealthBar && StaminaText && StaminaBar)
if (WidgetTree->RootWidget && TitleText && LevelText && HealthText && HealthBar && ManaText && ManaBar && StaminaText && StaminaBar && ExperienceText && ExperienceBar)
{
return;
}
@@ -93,7 +201,15 @@ void UPHYHUDStatusWidget::BuildNativeWidgetTree()
TitleText->SetColorAndOpacity(FSlateColor(FLinearColor(0.85f, 0.95f, 1.0f, 1.0f)));
if (UVerticalBoxSlot* TitleSlot = StatusBox->AddChildToVerticalBox(TitleText))
{
TitleSlot->SetPadding(FMargin(0.0f, 0.0f, 0.0f, 8.0f));
TitleSlot->SetPadding(FMargin(0.0f, 0.0f, 0.0f, 4.0f));
}
LevelText = WidgetTree->ConstructWidget<UTextBlock>(UTextBlock::StaticClass(), TEXT("LevelText"));
LevelText->SetFont(FSlateFontInfo(FCoreStyle::GetDefaultFont(), 16));
LevelText->SetColorAndOpacity(FSlateColor(FLinearColor(0.95f, 0.95f, 0.95f, 1.0f)));
if (UVerticalBoxSlot* LevelSlot = StatusBox->AddChildToVerticalBox(LevelText))
{
LevelSlot->SetPadding(FMargin(0.0f, 0.0f, 0.0f, 8.0f));
}
HealthText = WidgetTree->ConstructWidget<UTextBlock>(UTextBlock::StaticClass(), TEXT("HealthText"));
@@ -108,6 +224,18 @@ void UPHYHUDStatusWidget::BuildNativeWidgetTree()
HealthBarSlot->SetPadding(FMargin(0.0f, 2.0f, 0.0f, 8.0f));
}
ManaText = WidgetTree->ConstructWidget<UTextBlock>(UTextBlock::StaticClass(), TEXT("ManaText"));
ManaText->SetFont(FSlateFontInfo(FCoreStyle::GetDefaultFont(), 16));
ManaText->SetColorAndOpacity(FSlateColor(FLinearColor(0.95f, 0.95f, 0.95f, 1.0f)));
StatusBox->AddChildToVerticalBox(ManaText);
ManaBar = WidgetTree->ConstructWidget<UProgressBar>(UProgressBar::StaticClass(), TEXT("ManaBar"));
ManaBar->SetFillColorAndOpacity(FLinearColor(0.26f, 0.34f, 0.86f, 1.0f));
if (UVerticalBoxSlot* ManaBarSlot = StatusBox->AddChildToVerticalBox(ManaBar))
{
ManaBarSlot->SetPadding(FMargin(0.0f, 2.0f, 0.0f, 8.0f));
}
StaminaText = WidgetTree->ConstructWidget<UTextBlock>(UTextBlock::StaticClass(), TEXT("StaminaText"));
StaminaText->SetFont(FSlateFontInfo(FCoreStyle::GetDefaultFont(), 16));
StaminaText->SetColorAndOpacity(FSlateColor(FLinearColor(0.95f, 0.95f, 0.95f, 1.0f)));
@@ -117,14 +245,146 @@ void UPHYHUDStatusWidget::BuildNativeWidgetTree()
StaminaBar->SetFillColorAndOpacity(FLinearColor(0.12f, 0.62f, 0.86f, 1.0f));
if (UVerticalBoxSlot* StaminaBarSlot = StatusBox->AddChildToVerticalBox(StaminaBar))
{
StaminaBarSlot->SetPadding(FMargin(0.0f, 2.0f, 0.0f, 0.0f));
StaminaBarSlot->SetPadding(FMargin(0.0f, 2.0f, 0.0f, 8.0f));
}
ExperienceText = WidgetTree->ConstructWidget<UTextBlock>(UTextBlock::StaticClass(), TEXT("ExperienceText"));
ExperienceText->SetFont(FSlateFontInfo(FCoreStyle::GetDefaultFont(), 16));
ExperienceText->SetColorAndOpacity(FSlateColor(FLinearColor(0.95f, 0.95f, 0.95f, 1.0f)));
StatusBox->AddChildToVerticalBox(ExperienceText);
ExperienceBar = WidgetTree->ConstructWidget<UProgressBar>(UProgressBar::StaticClass(), TEXT("ExperienceBar"));
ExperienceBar->SetFillColorAndOpacity(FLinearColor(0.93f, 0.62f, 0.18f, 1.0f));
if (UVerticalBoxSlot* ExperienceBarSlot = StatusBox->AddChildToVerticalBox(ExperienceBar))
{
ExperienceBarSlot->SetPadding(FMargin(0.0f, 2.0f, 0.0f, 0.0f));
}
UpdateResourceWidgets();
}
void UPHYHUDStatusWidget::BindToPlayerState(APHYPlayerState* NewPlayerState)
{
if (BoundPlayerState.Get() == NewPlayerState)
{
RefreshProgressionFromPlayerState();
return;
}
UnbindFromPlayerState();
BoundPlayerState = NewPlayerState;
if (NewPlayerState)
{
NewPlayerState->OnProgressionChanged.AddUniqueDynamic(this, &UPHYHUDStatusWidget::HandleProgressionChanged);
RefreshProgressionFromPlayerState();
}
}
void UPHYHUDStatusWidget::UnbindFromPlayerState()
{
if (APHYPlayerState* PlayerState = BoundPlayerState.Get())
{
PlayerState->OnProgressionChanged.RemoveDynamic(this, &UPHYHUDStatusWidget::HandleProgressionChanged);
}
BoundPlayerState.Reset();
}
void UPHYHUDStatusWidget::BindToAbilitySystem(UAbilitySystemComponent* NewAbilitySystemComponent)
{
if (BoundAbilitySystemComponent.Get() == NewAbilitySystemComponent && HealthChangedHandle.IsValid())
{
RefreshResourcesFromAbilitySystem();
return;
}
UnbindFromAbilitySystem();
BoundAbilitySystemComponent = NewAbilitySystemComponent;
if (!NewAbilitySystemComponent)
{
UpdateResourceWidgets();
return;
}
HealthChangedHandle = NewAbilitySystemComponent->GetGameplayAttributeValueChangeDelegate(UPHYCombatAttributeSet::GetHealthAttribute()).AddUObject(this, &UPHYHUDStatusWidget::HandleAttributeChanged);
MaxHealthChangedHandle = NewAbilitySystemComponent->GetGameplayAttributeValueChangeDelegate(UPHYCombatAttributeSet::GetMaxHealthAttribute()).AddUObject(this, &UPHYHUDStatusWidget::HandleAttributeChanged);
ManaChangedHandle = NewAbilitySystemComponent->GetGameplayAttributeValueChangeDelegate(UPHYCombatAttributeSet::GetManaAttribute()).AddUObject(this, &UPHYHUDStatusWidget::HandleAttributeChanged);
MaxManaChangedHandle = NewAbilitySystemComponent->GetGameplayAttributeValueChangeDelegate(UPHYCombatAttributeSet::GetMaxManaAttribute()).AddUObject(this, &UPHYHUDStatusWidget::HandleAttributeChanged);
StaminaChangedHandle = NewAbilitySystemComponent->GetGameplayAttributeValueChangeDelegate(UPHYCombatAttributeSet::GetStaminaAttribute()).AddUObject(this, &UPHYHUDStatusWidget::HandleAttributeChanged);
MaxStaminaChangedHandle = NewAbilitySystemComponent->GetGameplayAttributeValueChangeDelegate(UPHYCombatAttributeSet::GetMaxStaminaAttribute()).AddUObject(this, &UPHYHUDStatusWidget::HandleAttributeChanged);
RefreshResourcesFromAbilitySystem();
}
void UPHYHUDStatusWidget::UnbindFromAbilitySystem()
{
if (UAbilitySystemComponent* AbilitySystemComponent = BoundAbilitySystemComponent.Get())
{
RemoveAttributeChangedDelegate(AbilitySystemComponent, UPHYCombatAttributeSet::GetHealthAttribute(), HealthChangedHandle);
RemoveAttributeChangedDelegate(AbilitySystemComponent, UPHYCombatAttributeSet::GetMaxHealthAttribute(), MaxHealthChangedHandle);
RemoveAttributeChangedDelegate(AbilitySystemComponent, UPHYCombatAttributeSet::GetManaAttribute(), ManaChangedHandle);
RemoveAttributeChangedDelegate(AbilitySystemComponent, UPHYCombatAttributeSet::GetMaxManaAttribute(), MaxManaChangedHandle);
RemoveAttributeChangedDelegate(AbilitySystemComponent, UPHYCombatAttributeSet::GetStaminaAttribute(), StaminaChangedHandle);
RemoveAttributeChangedDelegate(AbilitySystemComponent, UPHYCombatAttributeSet::GetMaxStaminaAttribute(), MaxStaminaChangedHandle);
}
HealthChangedHandle.Reset();
MaxHealthChangedHandle.Reset();
ManaChangedHandle.Reset();
MaxManaChangedHandle.Reset();
StaminaChangedHandle.Reset();
MaxStaminaChangedHandle.Reset();
BoundAbilitySystemComponent.Reset();
}
void UPHYHUDStatusWidget::RefreshResourcesFromAbilitySystem()
{
UAbilitySystemComponent* AbilitySystemComponent = BoundAbilitySystemComponent.Get();
if (!AbilitySystemComponent)
{
UpdateResourceWidgets();
return;
}
Health = AbilitySystemComponent->GetNumericAttribute(UPHYCombatAttributeSet::GetHealthAttribute());
MaxHealth = AbilitySystemComponent->GetNumericAttribute(UPHYCombatAttributeSet::GetMaxHealthAttribute());
Mana = AbilitySystemComponent->GetNumericAttribute(UPHYCombatAttributeSet::GetManaAttribute());
MaxMana = AbilitySystemComponent->GetNumericAttribute(UPHYCombatAttributeSet::GetMaxManaAttribute());
Stamina = AbilitySystemComponent->GetNumericAttribute(UPHYCombatAttributeSet::GetStaminaAttribute());
MaxStamina = AbilitySystemComponent->GetNumericAttribute(UPHYCombatAttributeSet::GetMaxStaminaAttribute());
UpdateResourceWidgets();
}
void UPHYHUDStatusWidget::RefreshProgressionFromPlayerState()
{
APHYPlayerState* PlayerState = BoundPlayerState.Get();
if (!PlayerState)
{
UpdateResourceWidgets();
return;
}
SetProgression(PlayerState->GetPlayerLevel(), PlayerState->GetExperience(), PlayerState->GetExperienceForNextLevel());
}
void UPHYHUDStatusWidget::HandleAttributeChanged(const FOnAttributeChangeData& /*ChangeData*/)
{
RefreshResourcesFromAbilitySystem();
}
void UPHYHUDStatusWidget::HandleProgressionChanged(const int32 NewLevel, const int32 NewExperience, const int32 NewExperienceForNextLevel)
{
SetProgression(NewLevel, NewExperience, NewExperienceForNextLevel);
}
void UPHYHUDStatusWidget::UpdateResourceWidgets()
{
if (LevelText)
{
LevelText->SetText(FText::FromString(FString::Printf(TEXT("Lv %d"), FMath::Max(1, Level))));
}
if (HealthText)
{
HealthText->SetText(MakeResourceText(TEXT("HP"), Health, MaxHealth));
@@ -133,6 +393,14 @@ void UPHYHUDStatusWidget::UpdateResourceWidgets()
{
HealthBar->SetPercent(MakeResourcePercent(Health, MaxHealth));
}
if (ManaText)
{
ManaText->SetText(MakeResourceText(TEXT("MP"), Mana, MaxMana));
}
if (ManaBar)
{
ManaBar->SetPercent(MakeResourcePercent(Mana, MaxMana));
}
if (StaminaText)
{
StaminaText->SetText(MakeResourceText(TEXT("ST"), Stamina, MaxStamina));
@@ -141,4 +409,12 @@ void UPHYHUDStatusWidget::UpdateResourceWidgets()
{
StaminaBar->SetPercent(MakeResourcePercent(Stamina, MaxStamina));
}
if (ExperienceText)
{
ExperienceText->SetText(MakeProgressionText(Experience, ExperienceForNextLevel));
}
if (ExperienceBar)
{
ExperienceBar->SetPercent(MakeProgressionPercent(Experience, ExperienceForNextLevel));
}
}

View File

@@ -36,6 +36,10 @@ public:
UFUNCTION(BlueprintCallable, BlueprintPure, Category="PHY|UI")
bool IsHUDInitialized() const { return bHUDInitialized; }
/** @brief 打开角色属性菜单,默认推入 Generic UI Menu 层。 */
UFUNCTION(BlueprintCallable, Category="PHY|UI")
UCommonActivatableWidget* OpenAttributeMenu(APlayerController* OwningPlayerController);
protected:
/** @brief 注册 GenericUISystem 本地玩家根布局和项目 UI 上下文。 */
virtual bool InitializeGenericUIForPlayer(APlayerController* OwningPlayerController);
@@ -43,6 +47,9 @@ protected:
/** @brief 推入默认 HUD 控件。 */
virtual UCommonActivatableWidget* PushDefaultHUDWidget(APlayerController* OwningPlayerController);
/** @brief 推入指定 CommonUI 控件到 Menu 层。 */
virtual UCommonActivatableWidget* PushMenuWidget(APlayerController* OwningPlayerController, TSubclassOf<UCommonActivatableWidget> WidgetClass);
/** @brief 当前本地玩家 UI 上下文。 */
UPROPERTY(Transient)
TObjectPtr<UPHYPlayerUIContext> PlayerUIContext;

View File

@@ -38,4 +38,7 @@ namespace PHYGameplayTags
/** @brief 背包开关输入。 */
UE_DECLARE_GAMEPLAY_TAG_EXTERN(Input_Inventory_Toggle);
/** @brief 角色属性菜单开关输入。 */
UE_DECLARE_GAMEPLAY_TAG_EXTERN(Input_Menu_Attribute);
}

View File

@@ -0,0 +1,36 @@
// Copyright Epic Games, Inc. All Rights Reserved.
#pragma once
#include "CoreMinimal.h"
#include "GIPS_InputProcessor.h"
#include "PHYInputProcessor_OpenAttributeMenu.generated.h"
/**
* @brief PHY 角色属性菜单输入处理器。
*
* 该处理器处理 `Input.Menu.Attribute`,并把本地输入路由到玩家控制器打开 Generic UI Menu 层。
*/
UCLASS(BlueprintType, Blueprintable, EditInlineNew, DefaultToInstanced)
class PHY_API UPHYInputProcessor_OpenAttributeMenu : public UGIPS_InputProcessor
{
GENERATED_BODY()
public:
/** @brief 构造默认响应的输入 Tag 和触发事件。 */
UPHYInputProcessor_OpenAttributeMenu();
protected:
/** @brief 检查当前输入组件是否能处理角色属性菜单输入。 */
virtual bool CheckCanHandleInput_Implementation(UGIPS_InputSystemComponent* IC, const FInputActionInstance& ActionData, FGameplayTag InputTag, ETriggerEvent TriggerEvent) const override;
/** @brief 处理 Started 菜单输入。 */
virtual void HandleInputStarted_Implementation(UGIPS_InputSystemComponent* IC, const FInputActionInstance& ActionData, FGameplayTag InputTag) const override;
/** @brief 返回编辑器里显示的处理器名称。 */
virtual FString GetEditorFriendlyName_Implementation() const override;
private:
/** @brief 将菜单输入发送给玩家控制器。 */
bool RouteOpenAttributeMenuInput(UGIPS_InputSystemComponent* IC, const FInputActionInstance& ActionData, ETriggerEvent TriggerEvent) const;
};

View File

@@ -66,6 +66,10 @@ public:
/** @brief 默认视角输入处理器类,用于创建 GenericInputSystem 控制配置。 */
UPROPERTY(Config)
TSoftClassPtr<UGIPS_InputProcessor> DefaultLookInputProcessorClass;
/** @brief 默认角色属性菜单输入处理器类,用于推入 UI Menu 层。 */
UPROPERTY(Config)
TSoftClassPtr<UGIPS_InputProcessor> DefaultAttributeMenuInputProcessorClass;
};
/**
@@ -167,6 +171,10 @@ public:
UPROPERTY(Config)
TSoftClassPtr<UCommonActivatableWidget> DefaultHUDWidgetClass;
/** @brief 默认角色属性菜单控件类,用于推入 Menu 层。 */
UPROPERTY(Config)
TSoftClassPtr<UCommonActivatableWidget> AttributeMenuWidgetClass;
/** @brief 默认 gameplay 层 Tag。 */
UPROPERTY(Config)
FGameplayTag GameplayLayerTag;

View File

@@ -59,6 +59,12 @@ public:
/** @brief 从 GenericInputSystem 处理器路由视角输入,并阻止同一事件被 fallback 重复执行。 */
bool RouteLookInputFromProcessor(const FInputActionInstance& ActionData, ETriggerEvent TriggerEvent);
/** @brief 路由角色属性菜单输入。 */
bool RouteOpenAttributeMenuInput(const FInputActionInstance& ActionData, ETriggerEvent TriggerEvent);
/** @brief 从 GenericInputSystem 处理器路由角色属性菜单输入,并阻止同一事件被 fallback 重复执行。 */
bool RouteOpenAttributeMenuInputFromProcessor(const FInputActionInstance& ActionData, ETriggerEvent TriggerEvent);
protected:
/** @brief 绑定玩家角色身上的 GenericInputSystem 输入委托。 */
virtual void BindInputEvents();
@@ -73,12 +79,18 @@ protected:
/** @brief 处理视角输入。 */
virtual bool HandleLookInput(const FInputActionInstance& ActionData, ETriggerEvent TriggerEvent);
/** @brief 处理角色属性菜单输入,本地推入 Generic UI Menu 层。 */
virtual bool HandleOpenAttributeMenuInput(const FInputActionInstance& ActionData, ETriggerEvent TriggerEvent);
/** @brief 查询并消费移动处理器留下的重复执行保护。 */
virtual bool ConsumeMoveProcessorGuard(ETriggerEvent TriggerEvent);
/** @brief 查询并消费视角处理器留下的重复执行保护。 */
virtual bool ConsumeLookProcessorGuard(ETriggerEvent TriggerEvent);
/** @brief 查询并消费属性菜单处理器留下的重复执行保护。 */
virtual bool ConsumeAttributeMenuProcessorGuard(ETriggerEvent TriggerEvent);
/** @brief 当前已经绑定输入委托的角色输入组件。 */
UPROPERTY(Transient)
TObjectPtr<UGIPS_InputSystemComponent> BoundInputSystemComponent;
@@ -110,4 +122,12 @@ protected:
/** @brief 最近一次由输入处理器处理的 Look 触发事件。 */
UPROPERTY(Transient)
ETriggerEvent LastLookInputProcessorTriggerEvent = ETriggerEvent::None;
/** @brief 输入处理器是否已经处理了最近一次属性菜单输入。 */
UPROPERTY(Transient)
bool bAttributeMenuInputHandledByProcessor = false;
/** @brief 最近一次由输入处理器处理的属性菜单触发事件。 */
UPROPERTY(Transient)
ETriggerEvent LastAttributeMenuInputProcessorTriggerEvent = ETriggerEvent::None;
};

View File

@@ -13,6 +13,8 @@ class UPHYCoreAttributeSet;
class UPHYElementAttributeSet;
class UPHYClassComponent;
DECLARE_DYNAMIC_MULTICAST_DELEGATE_ThreeParams(FPHYPlayerProgressionChangedSignature, int32, NewLevel, int32, NewExperience, int32, NewExperienceForNextLevel);
/**
* @brief PHY 玩家状态。
*
@@ -27,6 +29,9 @@ public:
/** @brief 构造玩家状态与 ASC。 */
APHYPlayerState(const FObjectInitializer& ObjectInitializer = FObjectInitializer::Get());
/** @brief 注册 PlayerState 自身复制字段。 */
virtual void GetLifetimeReplicatedProps(TArray<FLifetimeProperty>& OutLifetimeProps) const override;
/** @brief 获取玩家持久 AbilitySystemComponent。 */
virtual UAbilitySystemComponent* GetAbilitySystemComponent() const override;
@@ -50,7 +55,50 @@ public:
UFUNCTION(BlueprintCallable, BlueprintPure, Category="PHY|Class")
UPHYClassComponent* GetClassComponent() const { return ClassComponent; }
/** @brief 获取当前角色等级。 */
UFUNCTION(BlueprintCallable, BlueprintPure, Category="PHY|Progression")
int32 GetPlayerLevel() const { return PlayerLevel; }
/** @brief 获取当前经验值。 */
UFUNCTION(BlueprintCallable, BlueprintPure, Category="PHY|Progression")
int32 GetExperience() const { return Experience; }
/** @brief 获取升到下一级所需经验值。 */
UFUNCTION(BlueprintCallable, BlueprintPure, Category="PHY|Progression")
int32 GetExperienceForNextLevel() const { return ExperienceForNextLevel; }
/** @brief 服务端设置角色等级。 */
UFUNCTION(BlueprintCallable, BlueprintAuthorityOnly, Category="PHY|Progression")
void SetPlayerLevel(int32 NewLevel);
/** @brief 服务端设置当前经验值。 */
UFUNCTION(BlueprintCallable, BlueprintAuthorityOnly, Category="PHY|Progression")
void SetExperience(int32 NewExperience);
/** @brief 服务端增加当前经验值。 */
UFUNCTION(BlueprintCallable, BlueprintAuthorityOnly, Category="PHY|Progression")
void AddExperience(int32 DeltaExperience);
/** @brief 服务端设置下一级所需经验值。 */
UFUNCTION(BlueprintCallable, BlueprintAuthorityOnly, Category="PHY|Progression")
void SetExperienceForNextLevel(int32 NewExperienceForNextLevel);
/** @brief 服务端一次性设置角色成长状态。 */
UFUNCTION(BlueprintCallable, BlueprintAuthorityOnly, Category="PHY|Progression")
void SetProgression(int32 NewLevel, int32 NewExperience, int32 NewExperienceForNextLevel);
/** @brief 等级或经验变化时通知本地 UI。 */
UPROPERTY(BlueprintAssignable, Category="PHY|Progression")
FPHYPlayerProgressionChangedSignature OnProgressionChanged;
protected:
/** @brief 复制后通知本地 UI 刷新成长信息。 */
UFUNCTION()
void OnRep_Progression();
/** @brief 广播当前成长状态。 */
void BroadcastProgressionChanged();
/** @brief 玩家持久 ASC。 */
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category="PHY|PlayerState")
TObjectPtr<UGGA_AbilitySystemComponent> AbilitySystemComponent;
@@ -70,4 +118,16 @@ protected:
/** @brief 玩家持久职业组件,职业不保存在 PlayerCharacter 上。 */
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category="PHY|Class")
TObjectPtr<UPHYClassComponent> ClassComponent;
/** @brief 当前角色等级,由服务端复制给客户端 UI。 */
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, ReplicatedUsing=OnRep_Progression, Category="PHY|Progression")
int32 PlayerLevel = 1;
/** @brief 当前经验值,由服务端复制给客户端 UI。 */
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, ReplicatedUsing=OnRep_Progression, Category="PHY|Progression")
int32 Experience = 0;
/** @brief 当前等级升到下一级所需经验值,由服务端复制给客户端 UI。 */
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, ReplicatedUsing=OnRep_Progression, Category="PHY|Progression")
int32 ExperienceForNextLevel = 100;
};

View File

@@ -0,0 +1,63 @@
// Copyright Epic Games, Inc. All Rights Reserved.
#pragma once
#include "CoreMinimal.h"
#include "Blueprint/UserWidget.h"
#include "PHYAttributeGroupWidget.generated.h"
class UPHYAttributeRowWidget;
class UTextBlock;
class UVerticalBox;
/**
* @brief 属性分组子控件。
*
* 每个分组负责一个标题和若干属性行,例如攻击能力、防御能力、元素抗性。
*/
UCLASS(BlueprintType, Blueprintable)
class PHY_API UPHYAttributeGroupWidget : public UUserWidget
{
GENERATED_BODY()
public:
/** @brief 构造属性分组控件。 */
UPHYAttributeGroupWidget(const FObjectInitializer& ObjectInitializer = FObjectInitializer::Get());
/** @brief 构建原生分组 WidgetTree。 */
virtual TSharedRef<SWidget> RebuildWidget() override;
/** @brief 同步分组显示。 */
virtual void SynchronizeProperties() override;
/** @brief 设置分组标题。 */
UFUNCTION(BlueprintCallable, Category="PHY|UI|Attributes")
void SetGroupTitle(FText InGroupTitle);
/** @brief 清理当前分组内所有属性行。 */
UFUNCTION(BlueprintCallable, Category="PHY|UI|Attributes")
void ClearRows();
/** @brief 添加一行属性。 */
UFUNCTION(BlueprintCallable, Category="PHY|UI|Attributes")
UPHYAttributeRowWidget* AddRow(FText Label, FText Value, bool bCanUpgrade = false);
protected:
/** @brief 构造原生分组树。 */
void BuildNativeWidgetTree();
/** @brief 刷新分组标题。 */
void UpdateGroupWidgets();
/** @brief 分组标题。 */
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category="PHY|UI|Attributes")
FText GroupTitle;
/** @brief 标题文本。 */
UPROPERTY(Transient)
TObjectPtr<UTextBlock> TitleText;
/** @brief 属性行容器。 */
UPROPERTY(Transient)
TObjectPtr<UVerticalBox> RowsBox;
};

View File

@@ -0,0 +1,177 @@
// Copyright Epic Games, Inc. All Rights Reserved.
#pragma once
#include "CoreMinimal.h"
#include "AttributeSet.h"
#include "GameplayEffectTypes.h"
#include "UI/GUIS_ActivatableWidget.h"
#include "PHYAttributeMenuWidget.generated.h"
class APlayerController;
class APHYPlayerState;
class UAbilitySystemComponent;
class UButton;
class UPHYAttributeResourceBarWidget;
class UHorizontalBox;
class UPHYAttributeOverviewPageWidget;
class UPHYCombatDerivedAttributePageWidget;
class UPHYElementDerivedAttributePageWidget;
class UPHYAttributeSummaryWidget;
class UTextBlock;
class UWidgetSwitcher;
struct FOnAttributeChangeData;
/**
* @brief PHY 角色属性主界面。
*
* 主界面只负责绑定 PlayerState/ASC、分页和数据分发具体显示拆到概览、分页、分组、属性行和资源条子控件。
*/
UCLASS(BlueprintType, Blueprintable)
class PHY_API UPHYAttributeMenuWidget : public UGUIS_ActivatableWidget
{
GENERATED_BODY()
public:
/** @brief 构造角色属性主界面。 */
UPHYAttributeMenuWidget(const FObjectInitializer& ObjectInitializer = FObjectInitializer::Get());
/** @brief 构建原生属性界面 WidgetTree。 */
virtual TSharedRef<SWidget> RebuildWidget() override;
/** @brief 同步属性界面显示。 */
virtual void SynchronizeProperties() override;
/** @brief 绑定本地玩家控制器,随后从 PlayerState 和 GAS 拉取属性。 */
UFUNCTION(BlueprintCallable, Category="PHY|UI|Attributes")
void InitializeFromPlayer(APlayerController* InPlayerController);
/** @brief 重新从当前绑定来源刷新所有分页。 */
UFUNCTION(BlueprintCallable, Category="PHY|UI|Attributes")
void RefreshFromBoundSources();
protected:
/** @brief Widget 构造后尝试自动绑定 OwningPlayer。 */
virtual void NativeConstruct() override;
/** @brief Widget 销毁前解除所有委托。 */
virtual void NativeDestruct() override;
/** @brief PlayerState 延迟到达时重试绑定。 */
virtual void NativeTick(const FGeometry& MyGeometry, float InDeltaTime) override;
/** @brief 构造原生属性界面树。 */
void BuildNativeWidgetTree();
/** @brief 创建一个分页按钮。 */
UButton* CreateTabButton(FName ButtonName, FText Label);
/** @brief 切换到指定分页。 */
void SetActivePage(int32 NewPageIndex);
/** @brief 刷新分页按钮颜色。 */
void UpdateTabVisuals();
/** @brief 绑定新的 PlayerState。 */
void BindToPlayerState(APHYPlayerState* NewPlayerState);
/** @brief 解除 PlayerState 委托。 */
void UnbindFromPlayerState();
/** @brief 绑定新的 AbilitySystemComponent。 */
void BindToAbilitySystem(UAbilitySystemComponent* NewAbilitySystemComponent);
/** @brief 解除 GAS 属性变化委托。 */
void UnbindFromAbilitySystem();
/** @brief 重建概览和所有属性分页内容。 */
void RefreshAttributePages();
/** @brief 属性变化后刷新分页。 */
void HandleAttributeChanged(const FOnAttributeChangeData& ChangeData);
/** @brief PlayerState 成长信息变化后刷新分页。 */
UFUNCTION()
void HandleProgressionChanged(int32 NewLevel, int32 NewExperience, int32 NewExperienceForNextLevel);
/** @brief 点击总览分页。 */
UFUNCTION()
void HandleOverviewTabClicked();
/** @brief 点击战斗分页。 */
UFUNCTION()
void HandleCombatTabClicked();
/** @brief 点击元素分页。 */
UFUNCTION()
void HandleElementTabClicked();
/** @brief 读取一个 GAS 属性数值。 */
float GetAttributeValue(const FGameplayAttribute& Attribute) const;
/** @brief 计算仅用于 UI 概览的战力评分。 */
int32 CalculateDisplayCombatPower() const;
/** @brief 当前绑定的本地玩家控制器弱引用。 */
TWeakObjectPtr<APlayerController> BoundPlayerController;
/** @brief 当前绑定的玩家状态弱引用。 */
TWeakObjectPtr<APHYPlayerState> BoundPlayerState;
/** @brief 当前绑定的 ASC 弱引用。 */
TWeakObjectPtr<UAbilitySystemComponent> BoundAbilitySystemComponent;
/** @brief GAS 属性变化委托句柄。 */
TArray<FDelegateHandle> AttributeChangedHandles;
/** @brief GAS 属性变化委托对应属性。 */
TArray<FGameplayAttribute> BoundAttributes;
/** @brief 当前分页索引。 */
UPROPERTY(Transient, BlueprintReadOnly, Category="PHY|UI|Attributes")
int32 ActivePageIndex = 0;
/** @brief 左侧概览子控件。 */
UPROPERTY(Transient)
TObjectPtr<UPHYAttributeSummaryWidget> SummaryWidget;
/** @brief 分页切换器。 */
UPROPERTY(Transient)
TObjectPtr<UWidgetSwitcher> PageSwitcher;
/** @brief 设计稿右侧顶部生命资源条。 */
UPROPERTY(Transient)
TObjectPtr<UPHYAttributeResourceBarWidget> HeaderHealthBar;
/** @brief 设计稿右侧顶部法力资源条。 */
UPROPERTY(Transient)
TObjectPtr<UPHYAttributeResourceBarWidget> HeaderManaBar;
/** @brief 设计稿右侧顶部耐力资源条。 */
UPROPERTY(Transient)
TObjectPtr<UPHYAttributeResourceBarWidget> HeaderStaminaBar;
/** @brief 总览分页。 */
UPROPERTY(Transient)
TObjectPtr<UPHYAttributeOverviewPageWidget> OverviewPage;
/** @brief 战斗衍生属性分页。 */
UPROPERTY(Transient)
TObjectPtr<UPHYCombatDerivedAttributePageWidget> CombatPage;
/** @brief 元素衍生属性分页。 */
UPROPERTY(Transient)
TObjectPtr<UPHYElementDerivedAttributePageWidget> ElementPage;
/** @brief 总览按钮。 */
UPROPERTY(Transient)
TObjectPtr<UButton> OverviewTabButton;
/** @brief 战斗按钮。 */
UPROPERTY(Transient)
TObjectPtr<UButton> CombatTabButton;
/** @brief 元素按钮。 */
UPROPERTY(Transient)
TObjectPtr<UButton> ElementTabButton;
};

View File

@@ -0,0 +1,25 @@
// Copyright Epic Games, Inc. All Rights Reserved.
#pragma once
#include "CoreMinimal.h"
#include "UI/PHYAttributePageWidget.h"
#include "PHYAttributeOverviewPageWidget.generated.h"
class UAbilitySystemComponent;
/**
* @brief 属性总览分页子控件。
*
* 展示核心属性和三项主要战斗资源,核心属性保留加点按钮占位。
*/
UCLASS(BlueprintType, Blueprintable)
class PHY_API UPHYAttributeOverviewPageWidget : public UPHYAttributePageWidget
{
GENERATED_BODY()
public:
/** @brief 从 ASC 刷新总览分页。 */
UFUNCTION(BlueprintCallable, Category="PHY|UI|Attributes")
void RefreshAttributes(UAbilitySystemComponent* AbilitySystemComponent);
};

View File

@@ -0,0 +1,72 @@
// Copyright Epic Games, Inc. All Rights Reserved.
#pragma once
#include "CoreMinimal.h"
#include "Blueprint/UserWidget.h"
#include "PHYAttributePageWidget.generated.h"
class UPHYAttributeGroupWidget;
class UScrollBox;
class UTextBlock;
class UUniformGridPanel;
class UVerticalBox;
/**
* @brief 属性分页子控件。
*
* 分页只负责标题和分组容器,具体属性项由分组和属性行子控件承载。
*/
UCLASS(BlueprintType, Blueprintable)
class PHY_API UPHYAttributePageWidget : public UUserWidget
{
GENERATED_BODY()
public:
/** @brief 构造属性分页控件。 */
UPHYAttributePageWidget(const FObjectInitializer& ObjectInitializer = FObjectInitializer::Get());
/** @brief 构建原生分页 WidgetTree。 */
virtual TSharedRef<SWidget> RebuildWidget() override;
/** @brief 同步分页显示。 */
virtual void SynchronizeProperties() override;
/** @brief 设置分页标题。 */
UFUNCTION(BlueprintCallable, Category="PHY|UI|Attributes")
void SetPageTitle(FText InPageTitle);
/** @brief 清理当前分页内所有分组。 */
UFUNCTION(BlueprintCallable, Category="PHY|UI|Attributes")
void ClearGroups();
/** @brief 添加一个属性分组。 */
UFUNCTION(BlueprintCallable, Category="PHY|UI|Attributes")
UPHYAttributeGroupWidget* AddGroup(FText GroupTitle);
protected:
/** @brief 构造原生分页树。 */
void BuildNativeWidgetTree();
/** @brief 刷新分页标题。 */
void UpdatePageWidgets();
/** @brief 分页标题。 */
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category="PHY|UI|Attributes")
FText PageTitle;
/** @brief 标题文本。 */
UPROPERTY(Transient)
TObjectPtr<UTextBlock> TitleText;
/** @brief 分组滚动区域。 */
UPROPERTY(Transient)
TObjectPtr<UScrollBox> GroupsScrollBox;
/** @brief 分组容器。 */
UPROPERTY(Transient)
TObjectPtr<UUniformGridPanel> GroupsGrid;
/** @brief 当前分页分组数量,用于双列排版。 */
int32 GroupCount = 0;
};

View File

@@ -0,0 +1,70 @@
// Copyright Epic Games, Inc. All Rights Reserved.
#pragma once
#include "CoreMinimal.h"
#include "Blueprint/UserWidget.h"
#include "PHYAttributeResourceBarWidget.generated.h"
class UProgressBar;
class UTextBlock;
/**
* @brief 资源条子控件。
*
* 用于生命、法力、耐力和经验等当前值/上限值显示。
*/
UCLASS(BlueprintType, Blueprintable)
class PHY_API UPHYAttributeResourceBarWidget : public UUserWidget
{
GENERATED_BODY()
public:
/** @brief 构造资源条控件。 */
UPHYAttributeResourceBarWidget(const FObjectInitializer& ObjectInitializer = FObjectInitializer::Get());
/** @brief 构建原生资源条 WidgetTree。 */
virtual TSharedRef<SWidget> RebuildWidget() override;
/** @brief 同步资源条显示。 */
virtual void SynchronizeProperties() override;
/** @brief 设置资源条内容。 */
UFUNCTION(BlueprintCallable, Category="PHY|UI|Attributes")
void SetResource(FText InLabel, float InCurrentValue, float InMaxValue, FLinearColor InBarTint);
protected:
/** @brief 构造原生资源条树。 */
void BuildNativeWidgetTree();
/** @brief 刷新资源条显示。 */
void UpdateResourceWidgets();
/** @brief 资源名称。 */
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category="PHY|UI|Attributes")
FText Label;
/** @brief 当前值。 */
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category="PHY|UI|Attributes")
float CurrentValue = 0.0f;
/** @brief 上限值。 */
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category="PHY|UI|Attributes")
float MaxValue = 1.0f;
/** @brief 填充颜色。 */
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category="PHY|UI|Attributes")
FLinearColor BarTint = FLinearColor(0.25f, 0.68f, 0.63f, 1.0f);
/** @brief 名称文本。 */
UPROPERTY(Transient)
TObjectPtr<UTextBlock> LabelText;
/** @brief 数值文本。 */
UPROPERTY(Transient)
TObjectPtr<UTextBlock> ValueText;
/** @brief 进度条。 */
UPROPERTY(Transient)
TObjectPtr<UProgressBar> ResourceBar;
};

View File

@@ -0,0 +1,80 @@
// Copyright Epic Games, Inc. All Rights Reserved.
#pragma once
#include "CoreMinimal.h"
#include "Blueprint/UserWidget.h"
#include "PHYAttributeRowWidget.generated.h"
class UButton;
class UBorder;
class UHorizontalBox;
class UTextBlock;
/**
* @brief 属性行子控件。
*
* 用于显示单个属性名称、当前数值和可选加点按钮,供属性页分组复用。
*/
UCLASS(BlueprintType, Blueprintable)
class PHY_API UPHYAttributeRowWidget : public UUserWidget
{
GENERATED_BODY()
public:
/** @brief 构造属性行控件。 */
UPHYAttributeRowWidget(const FObjectInitializer& ObjectInitializer = FObjectInitializer::Get());
/** @brief 构建原生属性行 WidgetTree。 */
virtual TSharedRef<SWidget> RebuildWidget() override;
/** @brief 同步属性行显示。 */
virtual void SynchronizeProperties() override;
/** @brief 设置属性行内容。 */
UFUNCTION(BlueprintCallable, Category="PHY|UI|Attributes")
void SetRow(FText InLabel, FText InValue, bool bInCanUpgrade = false);
protected:
/** @brief 构造原生属性行树。 */
void BuildNativeWidgetTree();
/** @brief 刷新属性行显示。 */
void UpdateRowWidgets();
/** @brief 属性名称。 */
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category="PHY|UI|Attributes")
FText Label;
/** @brief 属性数值文本。 */
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category="PHY|UI|Attributes")
FText Value;
/** @brief 是否显示加点按钮。 */
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category="PHY|UI|Attributes")
bool bCanUpgrade = false;
/** @brief 行根容器。 */
UPROPERTY(Transient)
TObjectPtr<UBorder> RowBorder;
/** @brief 行水平容器。 */
UPROPERTY(Transient)
TObjectPtr<UHorizontalBox> RowBox;
/** @brief 属性名称文本。 */
UPROPERTY(Transient)
TObjectPtr<UTextBlock> LabelText;
/** @brief 属性数值文本。 */
UPROPERTY(Transient)
TObjectPtr<UTextBlock> ValueText;
/** @brief 加点按钮。 */
UPROPERTY(Transient)
TObjectPtr<UButton> UpgradeButton;
/** @brief 加点按钮文本。 */
UPROPERTY(Transient)
TObjectPtr<UTextBlock> UpgradeText;
};

View File

@@ -0,0 +1,106 @@
// Copyright Epic Games, Inc. All Rights Reserved.
#pragma once
#include "CoreMinimal.h"
#include "Blueprint/UserWidget.h"
#include "PHYAttributeSummaryWidget.generated.h"
class UPHYAttributeResourceBarWidget;
class UTextBlock;
class UVerticalBox;
/**
* @brief 属性界面左侧概览子控件。
*
* 展示角色名称、等级、战力、经验条和三项主要战斗资源。
*/
UCLASS(BlueprintType, Blueprintable)
class PHY_API UPHYAttributeSummaryWidget : public UUserWidget
{
GENERATED_BODY()
public:
/** @brief 构造属性概览控件。 */
UPHYAttributeSummaryWidget(const FObjectInitializer& ObjectInitializer = FObjectInitializer::Get());
/** @brief 构建原生概览 WidgetTree。 */
virtual TSharedRef<SWidget> RebuildWidget() override;
/** @brief 同步概览显示。 */
virtual void SynchronizeProperties() override;
/** @brief 设置角色概览信息。 */
UFUNCTION(BlueprintCallable, Category="PHY|UI|Attributes")
void SetSummary(FText InPlayerName, int32 InLevel, int32 InExperience, int32 InExperienceForNextLevel, int32 InCombatPower);
/** @brief 设置三项主要资源。 */
UFUNCTION(BlueprintCallable, Category="PHY|UI|Attributes")
void SetResources(float InHealth, float InMaxHealth, float InMana, float InMaxMana, float InStamina, float InMaxStamina);
protected:
/** @brief 构造原生概览树。 */
void BuildNativeWidgetTree();
/** @brief 刷新概览显示。 */
void UpdateSummaryWidgets();
/** @brief 角色名称。 */
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category="PHY|UI|Attributes")
FText PlayerName;
/** @brief 当前等级。 */
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category="PHY|UI|Attributes")
int32 Level = 1;
/** @brief 当前经验。 */
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category="PHY|UI|Attributes")
int32 Experience = 0;
/** @brief 下一级所需经验。 */
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category="PHY|UI|Attributes")
int32 ExperienceForNextLevel = 100;
/** @brief 概览战力评分。 */
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category="PHY|UI|Attributes")
int32 CombatPower = 0;
float Health = 0.0f;
float MaxHealth = 1.0f;
float Mana = 0.0f;
float MaxMana = 1.0f;
float Stamina = 0.0f;
float MaxStamina = 1.0f;
/** @brief 角色名文本。 */
UPROPERTY(Transient)
TObjectPtr<UTextBlock> PlayerNameText;
/** @brief 等级文本。 */
UPROPERTY(Transient)
TObjectPtr<UTextBlock> LevelText;
/** @brief 战力文本。 */
UPROPERTY(Transient)
TObjectPtr<UTextBlock> CombatPowerText;
/** @brief 资源条容器。 */
UPROPERTY(Transient)
TObjectPtr<UVerticalBox> ResourceBox;
/** @brief 经验条。 */
UPROPERTY(Transient)
TObjectPtr<UPHYAttributeResourceBarWidget> ExperienceBar;
/** @brief 生命条。 */
UPROPERTY(Transient)
TObjectPtr<UPHYAttributeResourceBarWidget> HealthBar;
/** @brief 法力条。 */
UPROPERTY(Transient)
TObjectPtr<UPHYAttributeResourceBarWidget> ManaBar;
/** @brief 耐力条。 */
UPROPERTY(Transient)
TObjectPtr<UPHYAttributeResourceBarWidget> StaminaBar;
};

View File

@@ -0,0 +1,25 @@
// Copyright Epic Games, Inc. All Rights Reserved.
#pragma once
#include "CoreMinimal.h"
#include "UI/PHYAttributePageWidget.h"
#include "PHYCombatDerivedAttributePageWidget.generated.h"
class UAbilitySystemComponent;
/**
* @brief 战斗衍生属性分页子控件。
*
* 展示战斗资源、攻击能力和防御能力内的全部战斗衍生属性。
*/
UCLASS(BlueprintType, Blueprintable)
class PHY_API UPHYCombatDerivedAttributePageWidget : public UPHYAttributePageWidget
{
GENERATED_BODY()
public:
/** @brief 从 ASC 刷新战斗衍生属性分页。 */
UFUNCTION(BlueprintCallable, Category="PHY|UI|Attributes")
void RefreshAttributes(UAbilitySystemComponent* AbilitySystemComponent);
};

View File

@@ -0,0 +1,25 @@
// Copyright Epic Games, Inc. All Rights Reserved.
#pragma once
#include "CoreMinimal.h"
#include "UI/PHYAttributePageWidget.h"
#include "PHYElementDerivedAttributePageWidget.generated.h"
class UAbilitySystemComponent;
/**
* @brief 元素衍生属性分页子控件。
*
* 展示全部元素伤害加成、元素抗性和通用元素穿透。
*/
UCLASS(BlueprintType, Blueprintable)
class PHY_API UPHYElementDerivedAttributePageWidget : public UPHYAttributePageWidget
{
GENERATED_BODY()
public:
/** @brief 从 ASC 刷新元素衍生属性分页。 */
UFUNCTION(BlueprintCallable, Category="PHY|UI|Attributes")
void RefreshAttributes(UAbilitySystemComponent* AbilitySystemComponent);
};

View File

@@ -6,13 +6,17 @@
#include "UI/GUIS_ActivatableWidget.h"
#include "PHYHUDStatusWidget.generated.h"
class APlayerController;
class APHYPlayerState;
class UAbilitySystemComponent;
class UProgressBar;
class UTextBlock;
struct FOnAttributeChangeData;
/**
* @brief PHY 默认 HUD 状态控件。
*
* 作为基础 UI 的 C++ 可见占位,后续战斗、背包、队伍等系统可以替换或扩展为项目资产
* 作为基础 UI 的 C++ 可见占位,绑定 PlayerState 上的 GAS 资源属性和成长状态
*/
UCLASS(BlueprintType, Blueprintable)
class PHY_API UPHYHUDStatusWidget : public UGUIS_ActivatableWidget
@@ -29,19 +33,69 @@ public:
/** @brief 同步文本和进度条显示。 */
virtual void SynchronizeProperties() override;
/** @brief 绑定本地玩家控制器,随后从 PlayerState 和 GAS 拉取状态。 */
UFUNCTION(BlueprintCallable, Category="PHY|UI")
void InitializeFromPlayer(APlayerController* InPlayerController);
/** @brief 从当前绑定来源重新拉取 PlayerState、ASC、资源和成长信息。 */
UFUNCTION(BlueprintCallable, Category="PHY|UI")
void RefreshFromBoundSources();
/** @brief 设置生命值显示。 */
UFUNCTION(BlueprintCallable, Category="PHY|UI")
void SetHealth(float NewHealth, float NewMaxHealth);
/** @brief 设置法力值显示。 */
UFUNCTION(BlueprintCallable, Category="PHY|UI")
void SetMana(float NewMana, float NewMaxMana);
/** @brief 设置耐力值显示。 */
UFUNCTION(BlueprintCallable, Category="PHY|UI")
void SetStamina(float NewStamina, float NewMaxStamina);
/** @brief 设置等级和经验显示。 */
UFUNCTION(BlueprintCallable, Category="PHY|UI")
void SetProgression(int32 NewLevel, int32 NewExperience, int32 NewExperienceForNextLevel);
protected:
/** @brief Widget 构造后尝试自动绑定 OwningPlayer。 */
virtual void NativeConstruct() override;
/** @brief Widget 销毁前解除 GAS 与 PlayerState 委托。 */
virtual void NativeDestruct() override;
/** @brief PlayerState 延迟到达时重试绑定。 */
virtual void NativeTick(const FGeometry& MyGeometry, float InDeltaTime) override;
/** @brief 构造原生 HUD 树。 */
void BuildNativeWidgetTree();
/** @brief 刷新单条资源文本和进度条。 */
/** @brief 绑定新的 PlayerState 成长状态来源。 */
void BindToPlayerState(APHYPlayerState* NewPlayerState);
/** @brief 解除 PlayerState 成长状态来源。 */
void UnbindFromPlayerState();
/** @brief 绑定新的 AbilitySystemComponent 属性来源。 */
void BindToAbilitySystem(UAbilitySystemComponent* NewAbilitySystemComponent);
/** @brief 解除 AbilitySystemComponent 属性来源。 */
void UnbindFromAbilitySystem();
/** @brief 从 GAS 当前数值刷新生命、法力、耐力。 */
void RefreshResourcesFromAbilitySystem();
/** @brief 从 PlayerState 当前数值刷新等级、经验。 */
void RefreshProgressionFromPlayerState();
/** @brief GAS 属性变化后刷新资源条。 */
void HandleAttributeChanged(const FOnAttributeChangeData& ChangeData);
/** @brief PlayerState 成长信息变化后刷新显示。 */
UFUNCTION()
void HandleProgressionChanged(int32 NewLevel, int32 NewExperience, int32 NewExperienceForNextLevel);
/** @brief 刷新所有文本和进度条。 */
void UpdateResourceWidgets();
/** @brief 当前生命值。 */
@@ -52,6 +106,14 @@ protected:
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category="PHY|UI")
float MaxHealth = 100.0f;
/** @brief 当前法力值。 */
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category="PHY|UI")
float Mana = 100.0f;
/** @brief 当前最大法力值。 */
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category="PHY|UI")
float MaxMana = 100.0f;
/** @brief 当前耐力值。 */
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category="PHY|UI")
float Stamina = 100.0f;
@@ -60,10 +122,42 @@ protected:
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category="PHY|UI")
float MaxStamina = 100.0f;
/** @brief 当前角色等级。 */
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category="PHY|UI")
int32 Level = 1;
/** @brief 当前经验值。 */
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category="PHY|UI")
int32 Experience = 0;
/** @brief 当前等级升到下一级所需经验值。 */
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category="PHY|UI")
int32 ExperienceForNextLevel = 100;
/** @brief 当前绑定的本地玩家控制器弱引用。 */
TWeakObjectPtr<APlayerController> BoundPlayerController;
/** @brief 当前绑定的玩家状态弱引用。 */
TWeakObjectPtr<APHYPlayerState> BoundPlayerState;
/** @brief 当前绑定的 ASC 弱引用。 */
TWeakObjectPtr<UAbilitySystemComponent> BoundAbilitySystemComponent;
FDelegateHandle HealthChangedHandle;
FDelegateHandle MaxHealthChangedHandle;
FDelegateHandle ManaChangedHandle;
FDelegateHandle MaxManaChangedHandle;
FDelegateHandle StaminaChangedHandle;
FDelegateHandle MaxStaminaChangedHandle;
/** @brief 标题文本。 */
UPROPERTY(Transient)
TObjectPtr<UTextBlock> TitleText;
/** @brief 等级文本。 */
UPROPERTY(Transient)
TObjectPtr<UTextBlock> LevelText;
/** @brief 生命文本。 */
UPROPERTY(Transient)
TObjectPtr<UTextBlock> HealthText;
@@ -72,6 +166,14 @@ protected:
UPROPERTY(Transient)
TObjectPtr<UProgressBar> HealthBar;
/** @brief 法力文本。 */
UPROPERTY(Transient)
TObjectPtr<UTextBlock> ManaText;
/** @brief 法力进度条。 */
UPROPERTY(Transient)
TObjectPtr<UProgressBar> ManaBar;
/** @brief 耐力文本。 */
UPROPERTY(Transient)
TObjectPtr<UTextBlock> StaminaText;
@@ -79,4 +181,12 @@ protected:
/** @brief 耐力进度条。 */
UPROPERTY(Transient)
TObjectPtr<UProgressBar> StaminaBar;
/** @brief 经验文本。 */
UPROPERTY(Transient)
TObjectPtr<UTextBlock> ExperienceText;
/** @brief 经验进度条。 */
UPROPERTY(Transient)
TObjectPtr<UProgressBar> ExperienceBar;
};