Compare commits
11 Commits
3499cf363c
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
| 63d845c910 | |||
| 6b19fe2d81 | |||
| 8a29b6eeb7 | |||
| baeb4098c9 | |||
| 57363d9e7f | |||
| c4f185736f | |||
| 7f3d9cd989 | |||
| ee12f1ad32 | |||
| 2ae207533c | |||
| 6ca0f21163 | |||
| 88d01f588b |
@@ -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`。
|
||||
|
||||
@@ -159,3 +159,15 @@ ManualIPAddress=
|
||||
+CollisionChannelRedirects=(OldName="VehicleMovement",NewName="Vehicle")
|
||||
+CollisionChannelRedirects=(OldName="PawnMovement",NewName="Pawn")
|
||||
|
||||
[/Script/GameplayDebugger.GameplayDebuggerConfig]
|
||||
CategorySlot0=F10
|
||||
CategorySlot1=F1
|
||||
CategorySlot2=F2
|
||||
CategorySlot3=F3
|
||||
CategorySlot4=F4
|
||||
CategorySlot5=F5
|
||||
CategorySlot6=F6
|
||||
CategorySlot7=F7
|
||||
CategorySlot8=F8
|
||||
CategorySlot9=F9
|
||||
|
||||
|
||||
@@ -2,6 +2,9 @@
|
||||
[/Script/CommonUI.CommonUISettings]
|
||||
CommonButtonAcceptKeyHandling=TriggerClick
|
||||
|
||||
[/Script/GenericUISystem.GUIS_GenericUISystemSettings]
|
||||
GameUIPolicyClass="/Script/PHY.PHYGameUIPolicy"
|
||||
|
||||
[/Script/EngineSettings.GeneralProjectSettings]
|
||||
ProjectID=E7C26E1F4D195F0DBE49C2A3E5017988
|
||||
CopyrightNotice=
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
[/Script/PHY.PHYAnimationSettings]
|
||||
DefaultSourceLocomotionMesh="/Game/AGame/Character/Locomotion/SKM_Quinn_Simple_Source.SKM_Quinn_Simple_Source"
|
||||
DefaultSourceLocomotionMesh="/Game/AGame/Character/SKM_Manny_Invis.SKM_Manny_Invis"
|
||||
DefaultSourceAnimClass="/Game/AGame/Animation/Locomotion/ABP_UE5_Main.ABP_UE5_Main_C"
|
||||
DefaultDisplayAnimClass="/Game/AGame/Animation/Retargeters/ABP_Retargeter.ABP_Retargeter_C"
|
||||
DefaultIKRetargeter="/Game/AGame/Animation/Retargeters/RTG_Manny_To_Ida.RTG_Manny_To_Ida"
|
||||
bHideSourceMeshInGame=True
|
||||
bHideSourceMeshInGame=False
|
||||
bPreferLeaderPoseForSameSkeleton=True
|
||||
bAllowRuntimeVisualMeshSwitch=True
|
||||
bApplyPlayerClassMeshToAI=False
|
||||
|
||||
@@ -4,3 +4,4 @@ bRouteInputThroughGenericInputSystem=True
|
||||
bBlockGameplayInputWhenUIFocused=True
|
||||
DefaultMoveInputProcessorClass="/Script/PHY.PHYInputProcessor_Move"
|
||||
DefaultLookInputProcessorClass="/Script/PHY.PHYInputProcessor_Look"
|
||||
DefaultAttributeMenuInputProcessorClass="/Script/PHY.PHYInputProcessor_OpenAttributeMenu"
|
||||
|
||||
@@ -1,4 +1,12 @@
|
||||
[/Script/PHY.PHYUISettings]
|
||||
bUseCommonUI=True
|
||||
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")
|
||||
ModalLayerTag=(TagName="UI.Layer.Modal")
|
||||
DefaultHUDLayerName=HUD
|
||||
|
||||
BIN
Content/AGame/Animation/DA_Ida_IntegrationData.uasset
LFS
Normal file
BIN
Content/AGame/Animation/DA_Ida_IntegrationData.uasset
LFS
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
Content/AGame/Map/Map_Test.umap
LFS
BIN
Content/AGame/Map/Map_Test.umap
LFS
Binary file not shown.
BIN
Content/AGame/UI/Attributes/Textures/T_PHY_UI_AttributeBackplate.uasset
LFS
Normal file
BIN
Content/AGame/UI/Attributes/Textures/T_PHY_UI_AttributeBackplate.uasset
LFS
Normal file
Binary file not shown.
BIN
Content/AGame/UI/Attributes/Textures/T_PHY_UI_CinnabarTab.uasset
LFS
Normal file
BIN
Content/AGame/UI/Attributes/Textures/T_PHY_UI_CinnabarTab.uasset
LFS
Normal file
Binary file not shown.
BIN
Content/AGame/UI/Attributes/Textures/T_PHY_UI_GoldRule.uasset
LFS
Normal file
BIN
Content/AGame/UI/Attributes/Textures/T_PHY_UI_GoldRule.uasset
LFS
Normal file
Binary file not shown.
BIN
Content/AGame/UI/Attributes/Textures/T_PHY_UI_JadePanel.uasset
LFS
Normal file
BIN
Content/AGame/UI/Attributes/Textures/T_PHY_UI_JadePanel.uasset
LFS
Normal file
Binary file not shown.
BIN
Content/AGame/UI/Attributes/Textures/T_PHY_UI_PaperPanel.uasset
LFS
Normal file
BIN
Content/AGame/UI/Attributes/Textures/T_PHY_UI_PaperPanel.uasset
LFS
Normal file
Binary file not shown.
BIN
Content/AGame/UI/Attributes/Textures/T_PHY_UI_PortraitFrame.uasset
LFS
Normal file
BIN
Content/AGame/UI/Attributes/Textures/T_PHY_UI_PortraitFrame.uasset
LFS
Normal file
Binary file not shown.
BIN
Content/AGame/UI/Attributes/Textures/T_PHY_UI_RowStrip.uasset
LFS
Normal file
BIN
Content/AGame/UI/Attributes/Textures/T_PHY_UI_RowStrip.uasset
LFS
Normal file
Binary file not shown.
24
PHY.uproject
24
PHY.uproject
@@ -11,6 +11,10 @@
|
||||
}
|
||||
],
|
||||
"Plugins": [
|
||||
{
|
||||
"Name": "PythonScriptPlugin",
|
||||
"Enabled": true
|
||||
},
|
||||
{
|
||||
"Name": "ModelingToolsEditorMode",
|
||||
"Enabled": true,
|
||||
@@ -57,6 +61,26 @@
|
||||
"SupportedTargetPlatforms": [
|
||||
"Win64"
|
||||
]
|
||||
},
|
||||
{
|
||||
"Name": "UE_MCP_Bridge",
|
||||
"Enabled": true
|
||||
},
|
||||
{
|
||||
"Name": "Niagara",
|
||||
"Enabled": true
|
||||
},
|
||||
{
|
||||
"Name": "PCG",
|
||||
"Enabled": true
|
||||
},
|
||||
{
|
||||
"Name": "EnhancedInput",
|
||||
"Enabled": true
|
||||
},
|
||||
{
|
||||
"Name": "CommonUI",
|
||||
"Enabled": true
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -19,10 +19,16 @@ public class PHY : ModuleRules
|
||||
"GameplayAbilities",
|
||||
"GameplayTasks",
|
||||
"ModularGameplay",
|
||||
"CommonUI",
|
||||
"CommonInput",
|
||||
"UMG",
|
||||
"Slate",
|
||||
"SlateCore",
|
||||
"GenericGameplayAbilities",
|
||||
"GenericCombatSystem",
|
||||
"GenericInputSystem",
|
||||
"GenericGameSystem",
|
||||
"GenericUISystem",
|
||||
"GenericEffectsSystem",
|
||||
"SLSCore",
|
||||
"SmoothLocomotionSystem",
|
||||
|
||||
@@ -1,7 +0,0 @@
|
||||
// Copyright Epic Games, Inc. All Rights Reserved.
|
||||
|
||||
#include "Camera/PHYUGCSpringArmComponent.h"
|
||||
|
||||
#include UE_INLINE_GENERATED_CPP_BY_NAME(PHYUGCSpringArmComponent)
|
||||
|
||||
// 当前包装层只解除抽象限制,后续相机专家可在这里集中扩展 UGC 项目逻辑。
|
||||
@@ -17,8 +17,8 @@
|
||||
#include "GGA_AbilitySystemComponent.h"
|
||||
#include "GameFramework/CharacterMovementComponent.h"
|
||||
#include "Interaction/GGS_InteractionSystemComponent.h"
|
||||
#include "Locomotion/PHYSLSMovementBridgeComponent.h"
|
||||
#include "Net/UnrealNetwork.h"
|
||||
#include "PHYConfigSettings.h"
|
||||
#include "PHYGameplayTags.h"
|
||||
#include "Ragdoll/GGS_RagdollComponent.h"
|
||||
#include "Components/SLSCharacterMovementComponent.h"
|
||||
@@ -27,6 +27,24 @@
|
||||
#include "Targeting/GCS_TargetingSystemComponent.h"
|
||||
#include "Team/GCS_CombatTeamAgentComponent.h"
|
||||
|
||||
namespace
|
||||
{
|
||||
ESLSRotationMode GetSLSRotationMode(const FGameplayTag NewRotationMode)
|
||||
{
|
||||
if (NewRotationMode == PHYGameplayTags::State_Rotation_Looking)
|
||||
{
|
||||
return ESLSRotationMode::LookingDirection;
|
||||
}
|
||||
|
||||
if (NewRotationMode == PHYGameplayTags::State_Rotation_Aiming)
|
||||
{
|
||||
return ESLSRotationMode::AimingDirection;
|
||||
}
|
||||
|
||||
return ESLSRotationMode::VelocityDirection;
|
||||
}
|
||||
}
|
||||
|
||||
APHYCharacterBase::APHYCharacterBase(const FObjectInitializer& ObjectInitializer)
|
||||
: Super(ObjectInitializer.SetDefaultSubobjectClass<USLSCharacterMovementComponent>(ACharacter::CharacterMovementComponentName))
|
||||
{
|
||||
@@ -47,7 +65,6 @@ APHYCharacterBase::APHYCharacterBase(const FObjectInitializer& ObjectInitializer
|
||||
RagdollComponent = CreateDefaultSubobject<UGGS_RagdollComponent>(TEXT("RagdollComponent"));
|
||||
ContextEffectComponent = CreateDefaultSubobject<UGES_ContextEffectComponent>(TEXT("ContextEffectComponent"));
|
||||
MeshBridgeComponent = CreateDefaultSubobject<UPHYCharacterMeshBridgeComponent>(TEXT("MeshBridgeComponent"));
|
||||
SLSMovementBridgeComponent = CreateDefaultSubobject<UPHYSLSMovementBridgeComponent>(TEXT("SLSMovementBridgeComponent"));
|
||||
SLSIntegrationComponent = CreateDefaultSubobject<USLSIntegrationComponent>(TEXT("SLSIntegrationComponent"));
|
||||
|
||||
USLSIntegrationDataAsset* SLSIntegrationRuntimeDataAsset = CreateDefaultSubobject<USLSIntegrationDataAsset>(TEXT("SLSIntegrationRuntimeDataAsset"));
|
||||
@@ -126,9 +143,20 @@ void APHYCharacterBase::BeginPlay()
|
||||
ContextEffectComponent->SetGameplayTagsProvider(this);
|
||||
}
|
||||
|
||||
if (SLSMovementBridgeComponent)
|
||||
if (const UPHYLocomotionSettings* LocomotionSettings = GetDefault<UPHYLocomotionSettings>(); LocomotionSettings && LocomotionSettings->bUseSmoothLocomotionSystem)
|
||||
{
|
||||
SLSMovementBridgeComponent->InitializeSLSMovementBridge(this, GenericIntegrationComponent, SLSIntegrationComponent);
|
||||
if (USLSCharacterMovementComponent* SLSMovementComponent = GetSLSCharacterMovementComponent())
|
||||
{
|
||||
const UPHYCharacterSettings* CharacterSettings = GetDefault<UPHYCharacterSettings>();
|
||||
SLSMovementComponent->ApplyBaseSLSCharacterMovementSettings();
|
||||
SLSMovementComponent->MaxWalkSpeed = CharacterSettings ? CharacterSettings->DefaultMaxWalkSpeed : SLSMovementComponent->MaxWalkSpeed;
|
||||
SLSMovementComponent->MaxAcceleration = CharacterSettings ? CharacterSettings->DefaultMaxAcceleration : SLSMovementComponent->MaxAcceleration;
|
||||
}
|
||||
}
|
||||
|
||||
if (CharacterStateComponent)
|
||||
{
|
||||
SetRotationMode_Implementation(CharacterStateComponent->GetRotationMode());
|
||||
}
|
||||
|
||||
UGameFrameworkComponentManager::SendGameFrameworkComponentExtensionEvent(this, UGameFrameworkComponentManager::NAME_GameActorReady);
|
||||
@@ -263,15 +291,10 @@ bool APHYCharacterBase::HandleInputTag(const FGameplayTag InputTag, const ETrigg
|
||||
|
||||
if (InputTag == PHYGameplayTags::Input_Aim)
|
||||
{
|
||||
if (USLSCharacterMovementComponent* SLSMovementComponent = GetSLSCharacterMovementComponent())
|
||||
{
|
||||
SLSMovementComponent->UpdateRotationMode(bPressed ? ESLSRotationMode::AimingDirection : ESLSRotationMode::VelocityDirection);
|
||||
}
|
||||
|
||||
SetRotationMode_Implementation(bPressed ? PHYGameplayTags::State_Rotation_Aiming : PHYGameplayTags::State_Rotation_OrientToMovement);
|
||||
if (CharacterStateComponent)
|
||||
{
|
||||
CharacterStateComponent->SetMovementSet(bPressed ? PHYGameplayTags::State_MovementSet_Aiming : PHYGameplayTags::State_MovementSet_Default);
|
||||
CharacterStateComponent->SetRotationMode(bPressed ? PHYGameplayTags::State_Rotation_Strafe : PHYGameplayTags::State_Rotation_OrientToMovement);
|
||||
}
|
||||
|
||||
return true;
|
||||
@@ -484,6 +507,11 @@ void APHYCharacterBase::SetRotationMode_Implementation(const FGameplayTag NewRot
|
||||
{
|
||||
CharacterStateComponent->SetRotationMode(NewRotationMode);
|
||||
}
|
||||
|
||||
if (USLSCharacterMovementComponent* SLSMovementComponent = GetSLSCharacterMovementComponent())
|
||||
{
|
||||
SLSMovementComponent->UpdateRotationMode(GetSLSRotationMode(NewRotationMode));
|
||||
}
|
||||
}
|
||||
|
||||
FGameplayTag APHYCharacterBase::GetRotationMode_Implementation() const
|
||||
|
||||
@@ -5,7 +5,6 @@
|
||||
#include UE_INLINE_GENERATED_CPP_BY_NAME(PHYPlayerCharacter)
|
||||
|
||||
#include "Camera/CameraComponent.h"
|
||||
#include "Camera/PHYUGCSpringArmComponent.h"
|
||||
#include "Class/PHYClassComponent.h"
|
||||
#include "GameFramework/SpringArmComponent.h"
|
||||
#include "GIPS_InputSystemComponent.h"
|
||||
@@ -16,7 +15,7 @@ APHYPlayerCharacter::APHYPlayerCharacter(const FObjectInitializer& ObjectInitial
|
||||
{
|
||||
InputSystemComponent = CreateDefaultSubobject<UGIPS_InputSystemComponent>(TEXT("InputSystemComponent"));
|
||||
|
||||
CameraBoom = CreateDefaultSubobject<UPHYUGCSpringArmComponent>(TEXT("CameraBoom"));
|
||||
CameraBoom = CreateDefaultSubobject<USpringArmComponent>(TEXT("CameraBoom"));
|
||||
CameraBoom->SetupAttachment(GetRootComponent());
|
||||
CameraBoom->TargetArmLength = 320.0f;
|
||||
CameraBoom->bUsePawnControlRotation = true;
|
||||
|
||||
@@ -4,6 +4,17 @@
|
||||
|
||||
#include UE_INLINE_GENERATED_CPP_BY_NAME(PHYHUD)
|
||||
|
||||
#include "CommonActivatableWidget.h"
|
||||
#include "Engine/GameInstance.h"
|
||||
#include "GameplayTags/PHYGameplayTags_UI.h"
|
||||
#include "PHYConfigSettings.h"
|
||||
#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"
|
||||
|
||||
APHYHUD::APHYHUD(const FObjectInitializer& ObjectInitializer)
|
||||
: Super(ObjectInitializer)
|
||||
{
|
||||
@@ -23,6 +34,144 @@ void APHYHUD::InitializeHUDForPlayer(APlayerController* OwningPlayerController)
|
||||
return;
|
||||
}
|
||||
|
||||
// UI 专家后续在这里接入 GenericUISystem/CommonUI 的根层级。
|
||||
if (InitializeGenericUIForPlayer(OwningPlayerController))
|
||||
{
|
||||
DefaultHUDWidget = PushDefaultHUDWidget(OwningPlayerController);
|
||||
bHUDInitialized = true;
|
||||
}
|
||||
}
|
||||
|
||||
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())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
const UPHYUISettings* UISettings = GetDefault<UPHYUISettings>();
|
||||
if (!UISettings || !UISettings->bUseCommonUI || !UISettings->bUseGenericUISystem)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
ULocalPlayer* LocalPlayer = OwningPlayerController->GetLocalPlayer();
|
||||
if (!LocalPlayer)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
UGameInstance* GameInstance = OwningPlayerController->GetGameInstance();
|
||||
UGUIS_GameUISubsystem* GameUISubsystem = GameInstance ? GameInstance->GetSubsystem<UGUIS_GameUISubsystem>() : nullptr;
|
||||
if (!GameUISubsystem)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
GameUISubsystem->AddPlayer(LocalPlayer);
|
||||
|
||||
if (!PlayerUIContext)
|
||||
{
|
||||
PlayerUIContext = NewObject<UPHYPlayerUIContext>(this);
|
||||
}
|
||||
PlayerUIContext->InitializeForPlayer(OwningPlayerController);
|
||||
|
||||
GameUISubsystem->RegisterUIContextForPlayer(LocalPlayer, PlayerUIContext, PlayerUIContextBindingHandle);
|
||||
return true;
|
||||
}
|
||||
|
||||
UCommonActivatableWidget* APHYHUD::PushDefaultHUDWidget(APlayerController* OwningPlayerController)
|
||||
{
|
||||
if (!OwningPlayerController || DefaultHUDWidget)
|
||||
{
|
||||
return DefaultHUDWidget;
|
||||
}
|
||||
|
||||
const UPHYUISettings* UISettings = GetDefault<UPHYUISettings>();
|
||||
TSubclassOf<UCommonActivatableWidget> HUDWidgetClass = UPHYHUDStatusWidget::StaticClass();
|
||||
if (UISettings && !UISettings->DefaultHUDWidgetClass.IsNull())
|
||||
{
|
||||
if (UClass* LoadedHUDWidgetClass = UISettings->DefaultHUDWidgetClass.LoadSynchronous())
|
||||
{
|
||||
HUDWidgetClass = LoadedHUDWidgetClass;
|
||||
}
|
||||
}
|
||||
|
||||
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 HUDLayerTag = (UISettings && UISettings->HUDLayerTag.IsValid()) ? UISettings->HUDLayerTag : PHYGameplayTags::UI_Layer_HUD;
|
||||
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);
|
||||
}
|
||||
|
||||
@@ -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");
|
||||
}
|
||||
|
||||
@@ -18,5 +18,6 @@ namespace PHYGameplayTags
|
||||
UE_DEFINE_GAMEPLAY_TAG(State_MovementSet_Default, "State.MovementSet.Default");
|
||||
UE_DEFINE_GAMEPLAY_TAG(State_MovementSet_Aiming, "State.MovementSet.Aiming");
|
||||
UE_DEFINE_GAMEPLAY_TAG(State_Rotation_OrientToMovement, "State.Rotation.OrientToMovement");
|
||||
UE_DEFINE_GAMEPLAY_TAG(State_Rotation_Strafe, "State.Rotation.Strafe");
|
||||
UE_DEFINE_GAMEPLAY_TAG(State_Rotation_Looking, "State.Rotation.Looking");
|
||||
UE_DEFINE_GAMEPLAY_TAG(State_Rotation_Aiming, "State.Rotation.Aiming");
|
||||
}
|
||||
|
||||
12
Source/PHY/Private/GameplayTags/PHYGameplayTags_UI.cpp
Normal file
12
Source/PHY/Private/GameplayTags/PHYGameplayTags_UI.cpp
Normal file
@@ -0,0 +1,12 @@
|
||||
// Copyright Epic Games, Inc. All Rights Reserved.
|
||||
|
||||
#include "GameplayTags/PHYGameplayTags_UI.h"
|
||||
|
||||
// UI 层级 Tag 是 GenericUISystem 注册和推送界面的稳定路由,不使用裸字符串。
|
||||
namespace PHYGameplayTags
|
||||
{
|
||||
UE_DEFINE_GAMEPLAY_TAG(UI_Layer_Game, "UI.Layer.Game");
|
||||
UE_DEFINE_GAMEPLAY_TAG(UI_Layer_HUD, "UI.Layer.HUD");
|
||||
UE_DEFINE_GAMEPLAY_TAG(UI_Layer_Menu, "UI.Layer.Menu");
|
||||
UE_DEFINE_GAMEPLAY_TAG(UI_Layer_Modal, "UI.Layer.Modal");
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -1,107 +0,0 @@
|
||||
// Copyright Epic Games, Inc. All Rights Reserved.
|
||||
|
||||
#include "Locomotion/PHYSLSMovementBridgeComponent.h"
|
||||
|
||||
#include UE_INLINE_GENERATED_CPP_BY_NAME(PHYSLSMovementBridgeComponent)
|
||||
|
||||
#include "Characters/PHYCharacterBase.h"
|
||||
#include "Characters/PHYCharacterSettings.h"
|
||||
#include "Components/SLSCharacterMovementComponent.h"
|
||||
#include "GameFramework/Character.h"
|
||||
#include "PHYConfigSettings.h"
|
||||
#include "SLSIntegrationComponent.h"
|
||||
|
||||
UPHYSLSMovementBridgeComponent::UPHYSLSMovementBridgeComponent()
|
||||
{
|
||||
PrimaryComponentTick.bCanEverTick = false;
|
||||
SetIsReplicatedByDefault(false);
|
||||
}
|
||||
|
||||
bool UPHYSLSMovementBridgeComponent::InitializeSLSMovementBridge(ACharacter* InCharacter, UActorComponent* InGenericIntegrationComponent, USLSIntegrationComponent* InSLSIntegrationComponent)
|
||||
{
|
||||
bInitialized = false;
|
||||
OwnerCharacter = InCharacter;
|
||||
GenericIntegrationComponent = InGenericIntegrationComponent;
|
||||
SLSIntegrationComponent = InSLSIntegrationComponent;
|
||||
SLSCharacterMovementComponent = nullptr;
|
||||
|
||||
const UPHYLocomotionSettings* LocomotionSettings = GetDefault<UPHYLocomotionSettings>();
|
||||
if (!LocomotionSettings || !LocomotionSettings->bUseSmoothLocomotionSystem || !OwnerCharacter)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
SLSCharacterMovementComponent = Cast<USLSCharacterMovementComponent>(OwnerCharacter->GetCharacterMovement());
|
||||
if (!SLSCharacterMovementComponent)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!SLSIntegrationComponent)
|
||||
{
|
||||
SLSIntegrationComponent = OwnerCharacter->FindComponentByClass<USLSIntegrationComponent>();
|
||||
}
|
||||
|
||||
const UPHYCharacterSettings* CharacterSettings = GetDefault<UPHYCharacterSettings>();
|
||||
|
||||
// 项目侧保留显式初始化,SLSIntegrationComponent 只作为运动层集中集成入口。
|
||||
SLSCharacterMovementComponent->Config.InitComponentType = ESLSComponentInitType::ManualInit;
|
||||
SLSCharacterMovementComponent->Config.MovementType = ESLSMovementType::Jog;
|
||||
SLSCharacterMovementComponent->Config.RotationMode = ESLSRotationMode::VelocityDirection;
|
||||
SLSCharacterMovementComponent->Config.bEnableSprintInLookingDirectionRotationMode = true;
|
||||
|
||||
SLSCharacterMovementComponent->ApplyBaseSLSCharacterMovementSettings();
|
||||
|
||||
if (CharacterSettings)
|
||||
{
|
||||
SLSCharacterMovementComponent->MaxWalkSpeed = CharacterSettings->DefaultMaxWalkSpeed;
|
||||
SLSCharacterMovementComponent->MaxAcceleration = CharacterSettings->DefaultMaxAcceleration;
|
||||
}
|
||||
|
||||
SLSCharacterMovementComponent->InitializeMovementComponent(SLSCharacterMovementComponent->Config);
|
||||
bInitialized = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool UPHYSLSMovementBridgeComponent::HandleMoveInput(const FVector2D MoveInput)
|
||||
{
|
||||
if (!EnsureSLSMovementBridgeReady())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
const FRotator YawRotation(0.0f, OwnerCharacter->GetControlRotation().Yaw, 0.0f);
|
||||
const FVector ForwardDirection = FRotationMatrix(YawRotation).GetUnitAxis(EAxis::X);
|
||||
const FVector RightDirection = FRotationMatrix(YawRotation).GetUnitAxis(EAxis::Y);
|
||||
const FVector WorldIntent = (ForwardDirection * MoveInput.Y) + (RightDirection * MoveInput.X);
|
||||
|
||||
if (APHYCharacterBase* PHYCharacter = Cast<APHYCharacterBase>(OwnerCharacter))
|
||||
{
|
||||
PHYCharacter->SetMovementIntent(WorldIntent);
|
||||
}
|
||||
|
||||
SLSCharacterMovementComponent->Input_OnMove(MoveInput);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool UPHYSLSMovementBridgeComponent::HandleLookInput(const FVector2D LookInput)
|
||||
{
|
||||
if (!EnsureSLSMovementBridgeReady())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
SLSCharacterMovementComponent->Input_OnLook(LookInput);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool UPHYSLSMovementBridgeComponent::EnsureSLSMovementBridgeReady()
|
||||
{
|
||||
if (bInitialized && OwnerCharacter && SLSCharacterMovementComponent)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
ACharacter* Character = OwnerCharacter ? OwnerCharacter.Get() : Cast<ACharacter>(GetOwner());
|
||||
return InitializeSLSMovementBridge(Character, GenericIntegrationComponent, SLSIntegrationComponent);
|
||||
}
|
||||
@@ -6,9 +6,10 @@
|
||||
|
||||
#include "Characters/PHYPlayerCharacter.h"
|
||||
#include "Characters/PHYCharacterSettings.h"
|
||||
#include "Components/SLSCharacterMovementComponent.h"
|
||||
#include "Game/PHYHUD.h"
|
||||
#include "GIPS_InputSystemComponent.h"
|
||||
#include "InputActionValue.h"
|
||||
#include "Locomotion/PHYSLSMovementBridgeComponent.h"
|
||||
#include "PHYGameplayTags.h"
|
||||
|
||||
namespace
|
||||
@@ -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();
|
||||
@@ -156,8 +178,19 @@ bool APHYPlayerController::HandleMoveInput(const FInputActionInstance& ActionDat
|
||||
|
||||
CachedMovementInput = IsInputReleased(TriggerEvent) ? FVector2D::ZeroVector : ExtractAxis2D(ActionData.GetValue());
|
||||
|
||||
UPHYSLSMovementBridgeComponent* SLSMovementBridge = PHYCharacter->GetSLSMovementBridgeComponent();
|
||||
return SLSMovementBridge ? SLSMovementBridge->HandleMoveInput(CachedMovementInput) : false;
|
||||
const FRotator YawRotation(0.0f, PHYCharacter->GetControlRotation().Yaw, 0.0f);
|
||||
const FVector ForwardDirection = FRotationMatrix(YawRotation).GetUnitAxis(EAxis::X);
|
||||
const FVector RightDirection = FRotationMatrix(YawRotation).GetUnitAxis(EAxis::Y);
|
||||
const FVector WorldIntent = (ForwardDirection * CachedMovementInput.Y) + (RightDirection * CachedMovementInput.X);
|
||||
PHYCharacter->SetMovementIntent(WorldIntent);
|
||||
|
||||
if (USLSCharacterMovementComponent* SLSMovementComponent = PHYCharacter->GetSLSCharacterMovementComponent())
|
||||
{
|
||||
SLSMovementComponent->Input_OnMove(CachedMovementInput);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool APHYPlayerController::ConsumeMoveProcessorGuard(const ETriggerEvent TriggerEvent)
|
||||
@@ -188,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());
|
||||
@@ -199,6 +259,11 @@ bool APHYPlayerController::HandleLookInput(const FInputActionInstance& ActionDat
|
||||
return false;
|
||||
}
|
||||
|
||||
UPHYSLSMovementBridgeComponent* SLSMovementBridge = PHYCharacter->GetSLSMovementBridgeComponent();
|
||||
return SLSMovementBridge ? SLSMovementBridge->HandleLookInput(CachedLookInput) : false;
|
||||
if (USLSCharacterMovementComponent* SLSMovementComponent = PHYCharacter->GetSLSCharacterMovementComponent())
|
||||
{
|
||||
SLSMovementComponent->Input_OnLook(CachedLookInput);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
135
Source/PHY/Private/UI/PHYAttributeGroupWidget.cpp
Normal file
135
Source/PHY/Private/UI/PHYAttributeGroupWidget.cpp
Normal 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);
|
||||
}
|
||||
}
|
||||
625
Source/PHY/Private/UI/PHYAttributeMenuWidget.cpp
Normal file
625
Source/PHY/Private/UI/PHYAttributeMenuWidget.cpp
Normal 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);
|
||||
}
|
||||
50
Source/PHY/Private/UI/PHYAttributeOverviewPageWidget.cpp
Normal file
50
Source/PHY/Private/UI/PHYAttributeOverviewPageWidget.cpp
Normal 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);
|
||||
}
|
||||
122
Source/PHY/Private/UI/PHYAttributePageWidget.cpp
Normal file
122
Source/PHY/Private/UI/PHYAttributePageWidget.cpp
Normal 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);
|
||||
}
|
||||
}
|
||||
141
Source/PHY/Private/UI/PHYAttributeResourceBarWidget.cpp
Normal file
141
Source/PHY/Private/UI/PHYAttributeResourceBarWidget.cpp
Normal 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);
|
||||
}
|
||||
}
|
||||
140
Source/PHY/Private/UI/PHYAttributeRowWidget.cpp
Normal file
140
Source/PHY/Private/UI/PHYAttributeRowWidget.cpp
Normal 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);
|
||||
}
|
||||
}
|
||||
207
Source/PHY/Private/UI/PHYAttributeSummaryWidget.cpp
Normal file
207
Source/PHY/Private/UI/PHYAttributeSummaryWidget.cpp
Normal 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));
|
||||
}
|
||||
}
|
||||
@@ -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())));
|
||||
}
|
||||
@@ -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())));
|
||||
}
|
||||
93
Source/PHY/Private/UI/PHYGameUILayout.cpp
Normal file
93
Source/PHY/Private/UI/PHYGameUILayout.cpp
Normal file
@@ -0,0 +1,93 @@
|
||||
// Copyright Epic Games, Inc. All Rights Reserved.
|
||||
|
||||
#include "UI/PHYGameUILayout.h"
|
||||
|
||||
#include UE_INLINE_GENERATED_CPP_BY_NAME(PHYGameUILayout)
|
||||
|
||||
#include "Blueprint/WidgetTree.h"
|
||||
#include "Components/Overlay.h"
|
||||
#include "Components/OverlaySlot.h"
|
||||
#include "GameplayTags/PHYGameplayTags_UI.h"
|
||||
#include "Widgets/CommonActivatableWidgetContainer.h"
|
||||
#include "Widgets/SWidget.h"
|
||||
|
||||
UPHYGameUILayout::UPHYGameUILayout(const FObjectInitializer& ObjectInitializer)
|
||||
: Super(ObjectInitializer)
|
||||
{
|
||||
}
|
||||
|
||||
TSharedRef<SWidget> UPHYGameUILayout::RebuildWidget()
|
||||
{
|
||||
BuildNativeLayoutTree();
|
||||
RegisterNativeLayers();
|
||||
|
||||
return Super::RebuildWidget();
|
||||
}
|
||||
|
||||
void UPHYGameUILayout::BuildNativeLayoutTree()
|
||||
{
|
||||
if (!WidgetTree)
|
||||
{
|
||||
WidgetTree = NewObject<UWidgetTree>(this, TEXT("WidgetTree"), RF_Transient);
|
||||
}
|
||||
|
||||
if (WidgetTree->RootWidget && GameLayerStack && HUDLayerStack && MenuLayerStack && ModalLayerStack)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
UOverlay* RootOverlay = WidgetTree->ConstructWidget<UOverlay>(UOverlay::StaticClass(), TEXT("RootOverlay"));
|
||||
WidgetTree->RootWidget = RootOverlay;
|
||||
|
||||
GameLayerStack = AddLayerStack(RootOverlay, TEXT("GameLayerStack"));
|
||||
HUDLayerStack = AddLayerStack(RootOverlay, TEXT("HUDLayerStack"));
|
||||
MenuLayerStack = AddLayerStack(RootOverlay, TEXT("MenuLayerStack"));
|
||||
ModalLayerStack = AddLayerStack(RootOverlay, TEXT("ModalLayerStack"));
|
||||
|
||||
bNativeLayersRegistered = false;
|
||||
}
|
||||
|
||||
UCommonActivatableWidgetStack* UPHYGameUILayout::AddLayerStack(UOverlay* RootOverlay, const FName StackName) const
|
||||
{
|
||||
if (!RootOverlay || !WidgetTree)
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
UCommonActivatableWidgetStack* LayerStack = WidgetTree->ConstructWidget<UCommonActivatableWidgetStack>(UCommonActivatableWidgetStack::StaticClass(), StackName);
|
||||
if (UOverlaySlot* LayerSlot = RootOverlay->AddChildToOverlay(LayerStack))
|
||||
{
|
||||
LayerSlot->SetHorizontalAlignment(HAlign_Fill);
|
||||
LayerSlot->SetVerticalAlignment(VAlign_Fill);
|
||||
LayerSlot->SetPadding(FMargin(0.0f));
|
||||
}
|
||||
|
||||
return LayerStack;
|
||||
}
|
||||
|
||||
void UPHYGameUILayout::RegisterNativeLayers()
|
||||
{
|
||||
if (bNativeLayersRegistered)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (GameLayerStack)
|
||||
{
|
||||
RegisterLayer(PHYGameplayTags::UI_Layer_Game, GameLayerStack);
|
||||
}
|
||||
if (HUDLayerStack)
|
||||
{
|
||||
RegisterLayer(PHYGameplayTags::UI_Layer_HUD, HUDLayerStack);
|
||||
}
|
||||
if (MenuLayerStack)
|
||||
{
|
||||
RegisterLayer(PHYGameplayTags::UI_Layer_Menu, MenuLayerStack);
|
||||
}
|
||||
if (ModalLayerStack)
|
||||
{
|
||||
RegisterLayer(PHYGameplayTags::UI_Layer_Modal, ModalLayerStack);
|
||||
}
|
||||
|
||||
bNativeLayersRegistered = true;
|
||||
}
|
||||
37
Source/PHY/Private/UI/PHYGameUIPolicy.cpp
Normal file
37
Source/PHY/Private/UI/PHYGameUIPolicy.cpp
Normal file
@@ -0,0 +1,37 @@
|
||||
// Copyright Epic Games, Inc. All Rights Reserved.
|
||||
|
||||
#include "UI/PHYGameUIPolicy.h"
|
||||
|
||||
#include UE_INLINE_GENERATED_CPP_BY_NAME(PHYGameUIPolicy)
|
||||
|
||||
#include "PHYConfigSettings.h"
|
||||
#include "UI/PHYGameUILayout.h"
|
||||
#include "UObject/UnrealType.h"
|
||||
|
||||
UPHYGameUIPolicy::UPHYGameUIPolicy(const FObjectInitializer& ObjectInitializer)
|
||||
: Super(ObjectInitializer)
|
||||
{
|
||||
ApplyConfiguredLayoutClass();
|
||||
}
|
||||
|
||||
void UPHYGameUIPolicy::ApplyConfiguredLayoutClass()
|
||||
{
|
||||
TSubclassOf<UGUIS_GameUILayout> SelectedLayoutClass = UPHYGameUILayout::StaticClass();
|
||||
|
||||
if (const UPHYUISettings* UISettings = GetDefault<UPHYUISettings>())
|
||||
{
|
||||
if (!UISettings->RootLayoutClass.IsNull())
|
||||
{
|
||||
if (UClass* LoadedLayoutClass = UISettings->RootLayoutClass.LoadSynchronous())
|
||||
{
|
||||
SelectedLayoutClass = LoadedLayoutClass;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// GenericUISystem 将 LayoutClass 设为私有 EditAnywhere 属性;这里通过反射写入 C++ 默认布局,避免创建仅用于赋默认值的蓝图资产。
|
||||
if (const FSoftClassProperty* LayoutClassProperty = FindFProperty<FSoftClassProperty>(UGUIS_GameUIPolicy::StaticClass(), TEXT("LayoutClass")))
|
||||
{
|
||||
LayoutClassProperty->SetPropertyValue_InContainer(this, FSoftObjectPtr(SelectedLayoutClass.Get()));
|
||||
}
|
||||
}
|
||||
420
Source/PHY/Private/UI/PHYHUDStatusWidget.cpp
Normal file
420
Source/PHY/Private/UI/PHYHUDStatusWidget.cpp
Normal file
@@ -0,0 +1,420 @@
|
||||
// Copyright Epic Games, Inc. All Rights Reserved.
|
||||
|
||||
#include "UI/PHYHUDStatusWidget.h"
|
||||
|
||||
#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"
|
||||
#include "Components/ProgressBar.h"
|
||||
#include "Components/SafeZone.h"
|
||||
#include "Components/TextBlock.h"
|
||||
#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"
|
||||
|
||||
namespace
|
||||
{
|
||||
FText MakeResourceText(const TCHAR* Label, const float Value, const float MaxValue)
|
||||
{
|
||||
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)
|
||||
: Super(ObjectInitializer)
|
||||
{
|
||||
InputConfig = EGUIS_ActivatableWidgetInputMode::Default;
|
||||
}
|
||||
|
||||
TSharedRef<SWidget> UPHYHUDStatusWidget::RebuildWidget()
|
||||
{
|
||||
BuildNativeWidgetTree();
|
||||
return Super::RebuildWidget();
|
||||
}
|
||||
|
||||
void UPHYHUDStatusWidget::SynchronizeProperties()
|
||||
{
|
||||
Super::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;
|
||||
MaxHealth = NewMaxHealth;
|
||||
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;
|
||||
MaxStamina = NewMaxStamina;
|
||||
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)
|
||||
{
|
||||
WidgetTree = NewObject<UWidgetTree>(this, TEXT("WidgetTree"), RF_Transient);
|
||||
}
|
||||
|
||||
if (WidgetTree->RootWidget && TitleText && LevelText && HealthText && HealthBar && ManaText && ManaBar && StaminaText && StaminaBar && ExperienceText && ExperienceBar)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
USafeZone* RootSafeZone = WidgetTree->ConstructWidget<USafeZone>(USafeZone::StaticClass(), TEXT("RootSafeZone"));
|
||||
UOverlay* RootOverlay = WidgetTree->ConstructWidget<UOverlay>(UOverlay::StaticClass(), TEXT("RootOverlay"));
|
||||
UVerticalBox* StatusBox = WidgetTree->ConstructWidget<UVerticalBox>(UVerticalBox::StaticClass(), TEXT("StatusBox"));
|
||||
|
||||
WidgetTree->RootWidget = RootSafeZone;
|
||||
RootSafeZone->SetContent(RootOverlay);
|
||||
|
||||
if (UOverlaySlot* StatusSlot = RootOverlay->AddChildToOverlay(StatusBox))
|
||||
{
|
||||
StatusSlot->SetHorizontalAlignment(HAlign_Left);
|
||||
StatusSlot->SetVerticalAlignment(VAlign_Top);
|
||||
StatusSlot->SetPadding(FMargin(32.0f, 24.0f, 0.0f, 0.0f));
|
||||
}
|
||||
|
||||
TitleText = WidgetTree->ConstructWidget<UTextBlock>(UTextBlock::StaticClass(), TEXT("TitleText"));
|
||||
TitleText->SetText(FText::FromString(TEXT("PHY")));
|
||||
TitleText->SetFont(FSlateFontInfo(FCoreStyle::GetDefaultFont(), 22));
|
||||
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, 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"));
|
||||
HealthText->SetFont(FSlateFontInfo(FCoreStyle::GetDefaultFont(), 16));
|
||||
HealthText->SetColorAndOpacity(FSlateColor(FLinearColor(0.95f, 0.95f, 0.95f, 1.0f)));
|
||||
StatusBox->AddChildToVerticalBox(HealthText);
|
||||
|
||||
HealthBar = WidgetTree->ConstructWidget<UProgressBar>(UProgressBar::StaticClass(), TEXT("HealthBar"));
|
||||
HealthBar->SetFillColorAndOpacity(FLinearColor(0.76f, 0.12f, 0.12f, 1.0f));
|
||||
if (UVerticalBoxSlot* HealthBarSlot = StatusBox->AddChildToVerticalBox(HealthBar))
|
||||
{
|
||||
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)));
|
||||
StatusBox->AddChildToVerticalBox(StaminaText);
|
||||
|
||||
StaminaBar = WidgetTree->ConstructWidget<UProgressBar>(UProgressBar::StaticClass(), TEXT("StaminaBar"));
|
||||
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, 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));
|
||||
}
|
||||
if (HealthBar)
|
||||
{
|
||||
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));
|
||||
}
|
||||
if (StaminaBar)
|
||||
{
|
||||
StaminaBar->SetPercent(MakeResourcePercent(Stamina, MaxStamina));
|
||||
}
|
||||
if (ExperienceText)
|
||||
{
|
||||
ExperienceText->SetText(MakeProgressionText(Experience, ExperienceForNextLevel));
|
||||
}
|
||||
if (ExperienceBar)
|
||||
{
|
||||
ExperienceBar->SetPercent(MakeProgressionPercent(Experience, ExperienceForNextLevel));
|
||||
}
|
||||
}
|
||||
18
Source/PHY/Private/UI/PHYPlayerUIContext.cpp
Normal file
18
Source/PHY/Private/UI/PHYPlayerUIContext.cpp
Normal file
@@ -0,0 +1,18 @@
|
||||
// Copyright Epic Games, Inc. All Rights Reserved.
|
||||
|
||||
#include "UI/PHYPlayerUIContext.h"
|
||||
|
||||
#include UE_INLINE_GENERATED_CPP_BY_NAME(PHYPlayerUIContext)
|
||||
|
||||
#include "GameFramework/PlayerController.h"
|
||||
|
||||
void UPHYPlayerUIContext::InitializeForPlayer(APlayerController* InPlayerController)
|
||||
{
|
||||
PlayerController = InPlayerController;
|
||||
RefreshPawn();
|
||||
}
|
||||
|
||||
void UPHYPlayerUIContext::RefreshPawn()
|
||||
{
|
||||
Pawn = PlayerController.IsValid() ? PlayerController->GetPawn() : nullptr;
|
||||
}
|
||||
@@ -1,18 +0,0 @@
|
||||
// Copyright Epic Games, Inc. All Rights Reserved.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
#include "Camera/Components/UGC_SpringArmComponentBase.h"
|
||||
#include "PHYUGCSpringArmComponent.generated.h"
|
||||
|
||||
/**
|
||||
* @brief PHY 对 UGC SpringArm 的项目级可实例化包装。
|
||||
*
|
||||
* UGC 基类是抽象组件,项目通过该薄包装在 C++ 构造玩家相机臂。
|
||||
*/
|
||||
UCLASS(BlueprintType, Blueprintable, meta=(BlueprintSpawnableComponent))
|
||||
class PHY_API UPHYUGCSpringArmComponent : public UUGC_SpringArmComponentBase
|
||||
{
|
||||
GENERATED_BODY()
|
||||
};
|
||||
@@ -19,15 +19,15 @@ class UGGS_InteractionSystemComponent;
|
||||
class UGGS_RagdollComponent;
|
||||
class UPHYCharacterMeshBridgeComponent;
|
||||
class UPHYCharacterStateComponent;
|
||||
class UPHYSLSMovementBridgeComponent;
|
||||
class USkeletalMeshComponent;
|
||||
class USLSCharacterMovementComponent;
|
||||
class USLSIntegrationComponent;
|
||||
|
||||
/**
|
||||
* @brief PHY 玩家和 AI 共用角色基类。
|
||||
* @brief PHY 玩家和 AI 共用的角色基类。
|
||||
*
|
||||
* 基类直接继承 ACharacter,并通过项目级组件和接口适配 GAS、GCS、GGS、UGC 与后续运动系统。
|
||||
* 基类直接继承 ACharacter,并通过项目级组件和接口适配 GAS、GCS、GGS、
|
||||
* UGC 与 Smooth Locomotion 等系统。
|
||||
*/
|
||||
UCLASS(Abstract, BlueprintType, Blueprintable)
|
||||
class PHY_API APHYCharacterBase : public ACharacter, public IAbilitySystemInterface, public IGameplayTagAssetInterface, public IGCS_CombatEntityInterface
|
||||
@@ -35,22 +35,22 @@ class PHY_API APHYCharacterBase : public ACharacter, public IAbilitySystemInterf
|
||||
GENERATED_BODY()
|
||||
|
||||
public:
|
||||
/** @brief 构造基础角色组件。 */
|
||||
/** @brief 创建角色基类并挂载项目核心组件。 */
|
||||
APHYCharacterBase(const FObjectInitializer& ObjectInitializer = FObjectInitializer::Get());
|
||||
|
||||
/** @brief 注册 ModularGameplay 组件接收者。 */
|
||||
virtual void PreInitializeComponents() override;
|
||||
|
||||
/** @brief 在 Possess 和 PlayerState 初始化前绑定显示层 Mesh 桥接组件。 */
|
||||
/** @brief 在 Possess 和 PlayerState 初始化前绑定显示 Mesh 桥接组件。 */
|
||||
virtual void PostInitializeComponents() override;
|
||||
|
||||
/** @brief 初始化角色运行时组件。 */
|
||||
/** @brief 初始化角色运行时状态和组件。 */
|
||||
virtual void BeginPlay() override;
|
||||
|
||||
/** @brief 移除 ModularGameplay 组件接收者。 */
|
||||
virtual void EndPlay(const EEndPlayReason::Type EndPlayReason) override;
|
||||
|
||||
/** @brief 控制器接管时刷新项目状态。 */
|
||||
/** @brief 控制器接管角色时刷新项目状态。 */
|
||||
virtual void PossessedBy(AController* NewController) override;
|
||||
|
||||
/** @brief 控制器复制到客户端后刷新项目状态。 */
|
||||
@@ -62,7 +62,11 @@ public:
|
||||
/** @brief 收集 ASC 与角色状态组件提供的 GameplayTag。 */
|
||||
virtual void GetOwnedGameplayTags(FGameplayTagContainer& TagContainer) const override;
|
||||
|
||||
/** @brief 初始化当前 Avatar 对应的 ASC。 */
|
||||
/**
|
||||
* @brief 初始化当前 Avatar 对应的 ASC。
|
||||
* @param NewAbilitySystemComponent 要绑定到该角色的 ASC。
|
||||
* @param OwnerActor ASC 的 OwnerActor,玩家通常为 PlayerState,AI 通常为自身。
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, Category="PHY|Character|Ability")
|
||||
virtual void InitializeAbilitySystem(UAbilitySystemComponent* NewAbilitySystemComponent, AActor* OwnerActor);
|
||||
|
||||
@@ -74,23 +78,42 @@ public:
|
||||
UFUNCTION(BlueprintCallable, BlueprintPure, Category="PHY|Character|Ability")
|
||||
bool IsAbilitySystemReady() const { return bAbilitySystemReady; }
|
||||
|
||||
/** @brief 接收输入 Tag 并路由到移动、交互或 GAS Ability。 */
|
||||
/**
|
||||
* @brief 接收输入 Tag 并路由到移动、交互或 GAS Ability。
|
||||
* @param InputTag 输入系统派发的 GameplayTag。
|
||||
* @param TriggerEvent Enhanced Input 触发事件。
|
||||
* @return 成功处理时返回 true。
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, Category="PHY|Character|Input")
|
||||
virtual bool HandleInputTag(FGameplayTag InputTag, ETriggerEvent TriggerEvent);
|
||||
|
||||
/** @brief 设置移动意图,并在客户端请求服务端同步。 */
|
||||
/**
|
||||
* @brief 设置角色移动意图,并在客户端请求服务端同步。
|
||||
* @param NewMovementIntent 世界空间移动意图。
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, Category="PHY|Character|Movement")
|
||||
virtual void SetMovementIntent(FVector NewMovementIntent);
|
||||
|
||||
/** @brief 设置项目级战斗目标。 */
|
||||
/**
|
||||
* @brief 设置项目级战斗目标。
|
||||
* @param NewCombatTargetActor 新的战斗目标 Actor。
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, Category="PHY|Character|Combat")
|
||||
virtual void SetCombatTargetActor(AActor* NewCombatTargetActor);
|
||||
|
||||
/** @brief 请求开始交互。 */
|
||||
/**
|
||||
* @brief 请求开始交互。
|
||||
* @param OptionIndex 交互选项索引。
|
||||
* @return 交互请求被接受时返回 true。
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, Category="PHY|Character|Interaction")
|
||||
virtual bool RequestInteraction(int32 OptionIndex = 0);
|
||||
|
||||
/** @brief 请求一次性即时交互。 */
|
||||
/**
|
||||
* @brief 请求执行一次即时交互。
|
||||
* @param OptionIndex 交互选项索引。
|
||||
* @return 交互请求被接受时返回 true。
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, Category="PHY|Character|Interaction")
|
||||
virtual bool RequestInstantInteraction(int32 OptionIndex = 0);
|
||||
|
||||
@@ -98,23 +121,26 @@ public:
|
||||
UFUNCTION(BlueprintCallable, Category="PHY|Character|Interaction")
|
||||
virtual void EndInteraction();
|
||||
|
||||
/** @brief 请求切换当前可交互对象。 */
|
||||
/**
|
||||
* @brief 请求切换当前可交互目标。
|
||||
* @param bNext 为 true 时切到下一个目标,否则切到上一个目标。
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, Category="PHY|Character|Interaction")
|
||||
virtual void CycleInteractionTarget(bool bNext);
|
||||
|
||||
/** @brief 获取项目角色状态组件。 */
|
||||
/** @brief 获取角色状态组件。 */
|
||||
UFUNCTION(BlueprintCallable, BlueprintPure, Category="PHY|Character")
|
||||
UPHYCharacterStateComponent* GetCharacterStateComponent() const { return CharacterStateComponent; }
|
||||
|
||||
/** @brief 获取 GCS 战斗组件。 */
|
||||
/** @brief 获取 GCS 战斗系统组件。 */
|
||||
UFUNCTION(BlueprintCallable, BlueprintPure, Category="PHY|Character")
|
||||
UGCS_CombatSystemComponent* GetCombatSystemComponent() const { return CombatSystemComponent; }
|
||||
|
||||
/** @brief 获取 GCS 目标组件。 */
|
||||
/** @brief 获取 GCS 目标系统组件。 */
|
||||
UFUNCTION(BlueprintCallable, BlueprintPure, Category="PHY|Character")
|
||||
UGCS_TargetingSystemComponent* GetTargetingSystemComponent() const { return TargetingSystemComponent; }
|
||||
|
||||
/** @brief 获取 GGS 交互组件。 */
|
||||
/** @brief 获取 GGS 交互系统组件。 */
|
||||
UFUNCTION(BlueprintCallable, BlueprintPure, Category="PHY|Character")
|
||||
UGGS_InteractionSystemComponent* GetInteractionSystemComponent() const { return InteractionSystemComponent; }
|
||||
|
||||
@@ -122,7 +148,7 @@ public:
|
||||
UFUNCTION(BlueprintCallable, BlueprintPure, Category="PHY|Character")
|
||||
UGGS_RagdollComponent* GetRagdollComponent() const { return RagdollComponent; }
|
||||
|
||||
/** @brief 获取上下文效果组件。 */
|
||||
/** @brief 获取 GES 上下文效果组件。 */
|
||||
UFUNCTION(BlueprintCallable, BlueprintPure, Category="PHY|Character")
|
||||
UGES_ContextEffectComponent* GetContextEffectComponent() const { return ContextEffectComponent; }
|
||||
|
||||
@@ -130,11 +156,7 @@ public:
|
||||
UFUNCTION(BlueprintCallable, BlueprintPure, Category="PHY|Character|Animation")
|
||||
UPHYCharacterMeshBridgeComponent* GetMeshBridgeComponent() const { return MeshBridgeComponent; }
|
||||
|
||||
/** @brief 获取 PHY SLS 运动桥接组件。 */
|
||||
UFUNCTION(BlueprintCallable, BlueprintPure, Category="PHY|Character|Locomotion")
|
||||
UPHYSLSMovementBridgeComponent* GetSLSMovementBridgeComponent() const { return SLSMovementBridgeComponent; }
|
||||
|
||||
/** @brief 获取 SLS 角色运动组件。 */
|
||||
/** @brief 获取 SLS 角色移动组件。 */
|
||||
UFUNCTION(BlueprintCallable, BlueprintPure, Category="PHY|Character|Locomotion")
|
||||
USLSCharacterMovementComponent* GetSLSCharacterMovementComponent() const;
|
||||
|
||||
@@ -146,7 +168,7 @@ public:
|
||||
UFUNCTION(BlueprintCallable, BlueprintPure, Category="PHY|Character|Locomotion")
|
||||
UActorComponent* GetGenericIntegrationComponent() const { return GenericIntegrationComponent; }
|
||||
|
||||
/** @brief 获取显示层 Mesh 组件,职业和外观 Mesh 应应用到该组件。 */
|
||||
/** @brief 获取显示层 Mesh 组件,职业和外观 Mesh 应当应用到该组件。 */
|
||||
UFUNCTION(BlueprintCallable, BlueprintPure, Category="PHY|Character|Animation")
|
||||
USkeletalMeshComponent* GetDisplayMeshComponent() const { return DisplayMeshComponent; }
|
||||
|
||||
@@ -159,13 +181,32 @@ public:
|
||||
/** @brief 获取当前战斗目标场景对象。 */
|
||||
virtual USceneComponent* GetCombatTargetObject_Implementation() const override;
|
||||
|
||||
/** @brief 查询能力动作,首期骨架默认返回空。 */
|
||||
/**
|
||||
* @brief 查询可用 Ability 动作,首期骨架默认返回空。
|
||||
* @param AbilityTags 能力标签集合。
|
||||
* @param SourceTags 来源标签集合。
|
||||
* @param TargetTags 目标标签集合。
|
||||
* @param AbilityActions 输出的能力动作列表。
|
||||
* @return 找到可用动作时返回 true。
|
||||
*/
|
||||
virtual bool QueryAbilityActions_Implementation(FGameplayTagContainer AbilityTags, FGameplayTagContainer SourceTags, FGameplayTagContainer TargetTags, TArray<FGCS_AbilityAction>& AbilityActions) override;
|
||||
|
||||
/** @brief 按上下文查询能力动作,首期骨架默认返回空。 */
|
||||
/**
|
||||
* @brief 按上下文查询可用 Ability 动作,首期骨架默认返回空。
|
||||
* @param Context 查询上下文对象。
|
||||
* @param AbilityTags 能力标签集合。
|
||||
* @param SourceTags 来源标签集合。
|
||||
* @param TargetTags 目标标签集合。
|
||||
* @param AbilityActions 输出的能力动作列表。
|
||||
* @return 找到可用动作时返回 true。
|
||||
*/
|
||||
virtual bool QueryAbilityActionsByContext_Implementation(UObject* Context, FGameplayTagContainer AbilityTags, FGameplayTagContainer SourceTags, FGameplayTagContainer TargetTags, TArray<FGCS_AbilityAction>& AbilityActions) override;
|
||||
|
||||
/** @brief 按 Tag 查询当前武器。 */
|
||||
/**
|
||||
* @brief 按 Tag 查询当前武器。
|
||||
* @param Query 用于匹配武器标签的查询。
|
||||
* @return 匹配到的武器对象。
|
||||
*/
|
||||
virtual UObject* QueryWeapon_Implementation(const FGameplayTagQuery& Query) const override;
|
||||
|
||||
/** @brief 设置旋转模式。 */
|
||||
@@ -198,17 +239,33 @@ public:
|
||||
/** @brief 获取移动意图。 */
|
||||
virtual FVector GetMovementIntent_Implementation() const override;
|
||||
|
||||
/** @brief 获取当前武器。 */
|
||||
/**
|
||||
* @brief 获取当前武器。
|
||||
* @param Context 可选查询上下文。
|
||||
* @return 当前武器对象。
|
||||
*/
|
||||
virtual UObject* GetCurrentWeapon_Implementation(UObject* Context = nullptr) const override;
|
||||
|
||||
/** @brief 设置当前武器。 */
|
||||
virtual void SetCurrentWeapon_Implementation(UObject* Weapon) override;
|
||||
|
||||
/** @brief 获取指定 Socket 的相对变换。 */
|
||||
/**
|
||||
* @brief 获取指定 Socket 的相对变换。
|
||||
* @param InSkeletalMeshComponent 用于查找 Socket 的骨骼网格组件。
|
||||
* @param StaticMesh 静态网格参考。
|
||||
* @param SkeletalMesh 骨骼网格参考。
|
||||
* @param SocketName Socket 名称。
|
||||
* @param OutTransform 输出相对变换。
|
||||
* @return 成功取得变换时返回 true。
|
||||
*/
|
||||
virtual bool GetRelativeTransformToSocket_Implementation(const USkeletalMeshComponent* InSkeletalMeshComponent, const UStaticMesh* StaticMesh, const USkeletalMesh* SkeletalMesh, FName SocketName, FTransform& OutTransform) const override;
|
||||
|
||||
protected:
|
||||
/** @brief 将输入 Tag 映射为 Ability Tag。 */
|
||||
/**
|
||||
* @brief 将输入 Tag 映射为 Ability Tag。
|
||||
* @param InputTag 输入系统提供的 Tag。
|
||||
* @return 对应的 Ability Tag。
|
||||
*/
|
||||
virtual FGameplayTag GetAbilityTagForInputTag(FGameplayTag InputTag) const;
|
||||
|
||||
/** @brief 应用 Character 配置默认值。 */
|
||||
@@ -270,10 +327,6 @@ protected:
|
||||
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category="PHY|Character|Animation")
|
||||
TObjectPtr<UPHYCharacterMeshBridgeComponent> MeshBridgeComponent;
|
||||
|
||||
/** @brief PHY 对 SLS 运动系统的桥接组件。 */
|
||||
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category="PHY|Character|Locomotion")
|
||||
TObjectPtr<UPHYSLSMovementBridgeComponent> SLSMovementBridgeComponent;
|
||||
|
||||
/** @brief SLS 插件集成入口组件,由运动层集中接入。 */
|
||||
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category="PHY|Character|Locomotion")
|
||||
TObjectPtr<USLSIntegrationComponent> SLSIntegrationComponent;
|
||||
|
||||
@@ -6,9 +6,10 @@
|
||||
#include "Characters/PHYCharacterBase.h"
|
||||
#include "PHYPlayerCharacter.generated.h"
|
||||
|
||||
class USpringArmComponent;
|
||||
class UCameraComponent;
|
||||
class UGIPS_InputSystemComponent;
|
||||
class UPHYUGCSpringArmComponent;
|
||||
|
||||
|
||||
/**
|
||||
* @brief PHY 玩家角色。
|
||||
@@ -32,7 +33,7 @@ public:
|
||||
|
||||
/** @brief 获取 UGC 相机臂。 */
|
||||
UFUNCTION(BlueprintCallable, BlueprintPure, Category="PHY|Camera")
|
||||
UPHYUGCSpringArmComponent* GetCameraBoom() const { return CameraBoom; }
|
||||
USpringArmComponent* GetCameraBoom() const { return CameraBoom; }
|
||||
|
||||
/** @brief 获取跟随相机。 */
|
||||
UFUNCTION(BlueprintCallable, BlueprintPure, Category="PHY|Camera")
|
||||
@@ -52,7 +53,7 @@ protected:
|
||||
|
||||
/** @brief UGC 项目相机臂。 */
|
||||
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category="PHY|Camera")
|
||||
TObjectPtr<UPHYUGCSpringArmComponent> CameraBoom;
|
||||
TObjectPtr<USpringArmComponent> CameraBoom;
|
||||
|
||||
/** @brief 玩家跟随相机。 */
|
||||
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category="PHY|Camera")
|
||||
|
||||
@@ -4,8 +4,12 @@
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
#include "GameFramework/HUD.h"
|
||||
#include "UI/GUIS_GameUIStructLibrary.h"
|
||||
#include "PHYHUD.generated.h"
|
||||
|
||||
class UCommonActivatableWidget;
|
||||
class UPHYPlayerUIContext;
|
||||
|
||||
/**
|
||||
* @brief PHY HUD 根入口。
|
||||
*
|
||||
@@ -32,7 +36,31 @@ 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);
|
||||
|
||||
/** @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;
|
||||
|
||||
/** @brief 当前本地玩家 UI 上下文注册句柄。 */
|
||||
UPROPERTY(Transient)
|
||||
FGUIS_UIContextBindingHandle PlayerUIContextBindingHandle;
|
||||
|
||||
/** @brief 默认 HUD 控件实例。 */
|
||||
UPROPERTY(Transient, BlueprintReadOnly, Category="PHY|UI")
|
||||
TObjectPtr<UCommonActivatableWidget> DefaultHUDWidget;
|
||||
/** @brief HUD 是否已经完成基础初始化。 */
|
||||
UPROPERTY(Transient, BlueprintReadOnly, Category="PHY|UI")
|
||||
bool bHUDInitialized = false;
|
||||
|
||||
@@ -38,4 +38,7 @@ namespace PHYGameplayTags
|
||||
|
||||
/** @brief 背包开关输入。 */
|
||||
UE_DECLARE_GAMEPLAY_TAG_EXTERN(Input_Inventory_Toggle);
|
||||
|
||||
/** @brief 角色属性菜单开关输入。 */
|
||||
UE_DECLARE_GAMEPLAY_TAG_EXTERN(Input_Menu_Attribute);
|
||||
}
|
||||
|
||||
@@ -1,53 +1,56 @@
|
||||
// Copyright Epic Games, Inc. All Rights Reserved.
|
||||
// Copyright Epic Games, Inc. All Rights Reserved.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "NativeGameplayTags.h"
|
||||
|
||||
/**
|
||||
* @brief PHY 状态相关原生 Gameplay Tag。
|
||||
* @brief PHY Gameplay Tag definitions.
|
||||
*/
|
||||
namespace PHYGameplayTags
|
||||
{
|
||||
/** @brief 角色处于战斗状态。 */
|
||||
/** @brief Character combat state tags. */
|
||||
UE_DECLARE_GAMEPLAY_TAG_EXTERN(State_Combat);
|
||||
|
||||
/** @brief 角色处于瞄准状态。 */
|
||||
/** @brief Character aiming state tag. */
|
||||
UE_DECLARE_GAMEPLAY_TAG_EXTERN(State_Aiming);
|
||||
|
||||
/** @brief 角色处于锁定目标状态。 */
|
||||
/** @brief Character lock-on state tag. */
|
||||
UE_DECLARE_GAMEPLAY_TAG_EXTERN(State_LockedOn);
|
||||
|
||||
/** @brief 角色正在执行交互。 */
|
||||
/** @brief In-combat action tag. */
|
||||
UE_DECLARE_GAMEPLAY_TAG_EXTERN(State_Interacting);
|
||||
|
||||
/** @brief UI 占用主要输入。 */
|
||||
/** @brief UI open input state tag. */
|
||||
UE_DECLARE_GAMEPLAY_TAG_EXTERN(State_UI_Open);
|
||||
|
||||
/** @brief 角色已经进入死亡流程。 */
|
||||
/** @brief Character dead state tag. */
|
||||
UE_DECLARE_GAMEPLAY_TAG_EXTERN(State_Dead);
|
||||
|
||||
/** @brief 角色处于行走移动状态。 */
|
||||
/** @brief Movement walk state tag. */
|
||||
UE_DECLARE_GAMEPLAY_TAG_EXTERN(State_Movement_Walk);
|
||||
|
||||
/** @brief 角色处于常规跑动移动状态。 */
|
||||
/** @brief Movement run state tag. */
|
||||
UE_DECLARE_GAMEPLAY_TAG_EXTERN(State_Movement_Run);
|
||||
|
||||
/** @brief 角色处于冲刺移动状态。 */
|
||||
/** @brief Movement sprint state tag. */
|
||||
UE_DECLARE_GAMEPLAY_TAG_EXTERN(State_Movement_Sprint);
|
||||
|
||||
/** @brief 角色处于下落移动状态。 */
|
||||
/** @brief Character falling movement state tag. */
|
||||
UE_DECLARE_GAMEPLAY_TAG_EXTERN(State_Movement_Falling);
|
||||
|
||||
/** @brief 角色使用默认移动集合。 */
|
||||
/** @brief Default movement set tag. */
|
||||
UE_DECLARE_GAMEPLAY_TAG_EXTERN(State_MovementSet_Default);
|
||||
|
||||
/** @brief 角色使用瞄准移动集合。 */
|
||||
/** @brief Aiming movement set tag. */
|
||||
UE_DECLARE_GAMEPLAY_TAG_EXTERN(State_MovementSet_Aiming);
|
||||
|
||||
/** @brief 角色旋转朝向移动方向。 */
|
||||
/** @brief Movement rotation orient-to-movement mode tag. */
|
||||
UE_DECLARE_GAMEPLAY_TAG_EXTERN(State_Rotation_OrientToMovement);
|
||||
|
||||
/** @brief 角色旋转使用战斗横移模式。 */
|
||||
UE_DECLARE_GAMEPLAY_TAG_EXTERN(State_Rotation_Strafe);
|
||||
/** @brief Looking-direction rotation mode tag. */
|
||||
UE_DECLARE_GAMEPLAY_TAG_EXTERN(State_Rotation_Looking);
|
||||
|
||||
/** @brief Aiming-direction rotation mode tag. */
|
||||
UE_DECLARE_GAMEPLAY_TAG_EXTERN(State_Rotation_Aiming);
|
||||
}
|
||||
|
||||
23
Source/PHY/Public/GameplayTags/PHYGameplayTags_UI.h
Normal file
23
Source/PHY/Public/GameplayTags/PHYGameplayTags_UI.h
Normal file
@@ -0,0 +1,23 @@
|
||||
// Copyright Epic Games, Inc. All Rights Reserved.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "NativeGameplayTags.h"
|
||||
|
||||
/**
|
||||
* @brief PHY UI 层级 Gameplay Tag 定义。
|
||||
*/
|
||||
namespace PHYGameplayTags
|
||||
{
|
||||
/** @brief 非阻塞 gameplay 承载层,用于后续准星、交互提示等常驻玩法 UI。 */
|
||||
UE_DECLARE_GAMEPLAY_TAG_EXTERN(UI_Layer_Game);
|
||||
|
||||
/** @brief HUD 承载层,用于血条、资源、快捷栏等常驻玩家界面。 */
|
||||
UE_DECLARE_GAMEPLAY_TAG_EXTERN(UI_Layer_HUD);
|
||||
|
||||
/** @brief 菜单承载层,用于背包、角色、设置等可交互界面。 */
|
||||
UE_DECLARE_GAMEPLAY_TAG_EXTERN(UI_Layer_Menu);
|
||||
|
||||
/** @brief 模态承载层,用于确认框、错误提示等最高优先级界面。 */
|
||||
UE_DECLARE_GAMEPLAY_TAG_EXTERN(UI_Layer_Modal);
|
||||
}
|
||||
@@ -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;
|
||||
};
|
||||
@@ -1,97 +0,0 @@
|
||||
// Copyright Epic Games, Inc. All Rights Reserved.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
#include "Components/ActorComponent.h"
|
||||
#include "PHYSLSMovementBridgeComponent.generated.h"
|
||||
|
||||
class ACharacter;
|
||||
class USLSCharacterMovementComponent;
|
||||
class USLSIntegrationComponent;
|
||||
|
||||
/**
|
||||
* @brief PHY 对 Smooth Locomotion System 的首期运动桥接组件。
|
||||
*
|
||||
* 该组件缓存并初始化明确接入的 SLS 运动组件和 SLSIntegrationComponent,
|
||||
* 但不承担相机系统职责。
|
||||
*/
|
||||
UCLASS(BlueprintType, Blueprintable)
|
||||
class PHY_API UPHYSLSMovementBridgeComponent : public UActorComponent
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
public:
|
||||
/** @brief 创建运动桥接组件。 */
|
||||
UPHYSLSMovementBridgeComponent();
|
||||
|
||||
/**
|
||||
* @brief 初始化 SLS 运动桥接。
|
||||
* @param InCharacter 拥有该桥接组件的角色。
|
||||
* @param InGenericIntegrationComponent 可选的 Generic 集成入口组件。
|
||||
* @param InSLSIntegrationComponent SLS 插件集成入口组件。
|
||||
* @return 初始化是否成功。
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, Category="PHY|Locomotion|SLS")
|
||||
bool InitializeSLSMovementBridge(ACharacter* InCharacter, UActorComponent* InGenericIntegrationComponent, USLSIntegrationComponent* InSLSIntegrationComponent = nullptr);
|
||||
|
||||
/**
|
||||
* @brief 通过 SLS 原生移动输入接口处理二维移动输入。
|
||||
* @param MoveInput X 为右移输入,Y 为前进输入。
|
||||
* @return 成功路由到 SLS 运动组件时返回 true。
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, Category="PHY|Locomotion|SLS|Input")
|
||||
bool HandleMoveInput(FVector2D MoveInput);
|
||||
|
||||
/**
|
||||
* @brief 通过 SLS 原生视角输入接口处理二维视角输入。
|
||||
* @param LookInput X 为 Yaw 输入,Y 为 Pitch 输入。
|
||||
* @return 成功路由到 SLS 运动组件时返回 true。
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, Category="PHY|Locomotion|SLS|Input")
|
||||
bool HandleLookInput(FVector2D LookInput);
|
||||
|
||||
/** @brief 获取桥接到的角色。 */
|
||||
UFUNCTION(BlueprintCallable, BlueprintPure, Category="PHY|Locomotion|SLS")
|
||||
ACharacter* GetOwnerCharacter() const { return OwnerCharacter; }
|
||||
|
||||
/** @brief 获取 SLS 角色运动组件。 */
|
||||
UFUNCTION(BlueprintCallable, BlueprintPure, Category="PHY|Locomotion|SLS")
|
||||
USLSCharacterMovementComponent* GetSLSCharacterMovementComponent() const { return SLSCharacterMovementComponent; }
|
||||
|
||||
/** @brief 获取 SLS 插件集成入口组件。 */
|
||||
UFUNCTION(BlueprintCallable, BlueprintPure, Category="PHY|Locomotion|SLS")
|
||||
USLSIntegrationComponent* GetSLSIntegrationComponent() const { return SLSIntegrationComponent; }
|
||||
|
||||
/** @brief 获取可选的 Generic 集成入口组件。 */
|
||||
UFUNCTION(BlueprintCallable, BlueprintPure, Category="PHY|Locomotion|SLS")
|
||||
UActorComponent* GetGenericIntegrationComponent() const { return GenericIntegrationComponent; }
|
||||
|
||||
/** @brief 查询桥接是否已完成初始化。 */
|
||||
UFUNCTION(BlueprintCallable, BlueprintPure, Category="PHY|Locomotion|SLS")
|
||||
bool IsSLSMovementBridgeInitialized() const { return bInitialized; }
|
||||
|
||||
protected:
|
||||
/** @brief 确保桥接组件已经缓存角色和 SLS 运动组件。 */
|
||||
bool EnsureSLSMovementBridgeReady();
|
||||
|
||||
/** @brief 拥有 SLS 运动能力的角色。 */
|
||||
UPROPERTY(Transient, BlueprintReadOnly, Category="PHY|Locomotion|SLS")
|
||||
TObjectPtr<ACharacter> OwnerCharacter;
|
||||
|
||||
/** @brief SLS 核心角色运动组件。 */
|
||||
UPROPERTY(Transient, BlueprintReadOnly, Category="PHY|Locomotion|SLS")
|
||||
TObjectPtr<USLSCharacterMovementComponent> SLSCharacterMovementComponent;
|
||||
|
||||
/** @brief SLS 插件集成入口组件。 */
|
||||
UPROPERTY(Transient, BlueprintReadOnly, Category="PHY|Locomotion|SLS")
|
||||
TObjectPtr<USLSIntegrationComponent> SLSIntegrationComponent;
|
||||
|
||||
/** @brief GenericGameSystem 提供的可选集成入口。 */
|
||||
UPROPERTY(Transient, BlueprintReadOnly, Category="PHY|Locomotion|SLS")
|
||||
TObjectPtr<UActorComponent> GenericIntegrationComponent;
|
||||
|
||||
/** @brief 桥接初始化状态。 */
|
||||
UPROPERTY(Transient, BlueprintReadOnly, Category="PHY|Locomotion|SLS")
|
||||
bool bInitialized = false;
|
||||
};
|
||||
@@ -3,11 +3,14 @@
|
||||
#pragma once
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
#include "GameplayTagContainer.h"
|
||||
#include "UObject/Object.h"
|
||||
#include "UObject/SoftObjectPtr.h"
|
||||
#include "PHYConfigSettings.generated.h"
|
||||
|
||||
class UCommonActivatableWidget;
|
||||
class UGIPS_InputProcessor;
|
||||
class UGUIS_GameUILayout;
|
||||
|
||||
/**
|
||||
* @brief PHY 项目核心配置。
|
||||
@@ -63,6 +66,10 @@ public:
|
||||
/** @brief 默认视角输入处理器类,用于创建 GenericInputSystem 控制配置。 */
|
||||
UPROPERTY(Config)
|
||||
TSoftClassPtr<UGIPS_InputProcessor> DefaultLookInputProcessorClass;
|
||||
|
||||
/** @brief 默认角色属性菜单输入处理器类,用于推入 UI Menu 层。 */
|
||||
UPROPERTY(Config)
|
||||
TSoftClassPtr<UGIPS_InputProcessor> DefaultAttributeMenuInputProcessorClass;
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -148,10 +155,42 @@ public:
|
||||
UPROPERTY(Config)
|
||||
bool bUseCommonUI = true;
|
||||
|
||||
/** @brief 是否启用 GenericUISystem 作为项目 UI 根层级和推送系统。 */
|
||||
UPROPERTY(Config)
|
||||
bool bUseGenericUISystem = true;
|
||||
|
||||
/** @brief 打开菜单时是否暂停 Gameplay。 */
|
||||
UPROPERTY(Config)
|
||||
bool bOpenMenusWithGameplayPause = false;
|
||||
|
||||
/** @brief 原生根布局类,默认使用项目 C++ 布局并在其中注册 Generic UI 层。 */
|
||||
UPROPERTY(Config)
|
||||
TSoftClassPtr<UGUIS_GameUILayout> RootLayoutClass;
|
||||
|
||||
/** @brief 默认 HUD 控件类,本地玩家 HUD 初始化时推入 HUD 层。 */
|
||||
UPROPERTY(Config)
|
||||
TSoftClassPtr<UCommonActivatableWidget> DefaultHUDWidgetClass;
|
||||
|
||||
/** @brief 默认角色属性菜单控件类,用于推入 Menu 层。 */
|
||||
UPROPERTY(Config)
|
||||
TSoftClassPtr<UCommonActivatableWidget> AttributeMenuWidgetClass;
|
||||
|
||||
/** @brief 默认 gameplay 层 Tag。 */
|
||||
UPROPERTY(Config)
|
||||
FGameplayTag GameplayLayerTag;
|
||||
|
||||
/** @brief 默认 HUD 层 Tag。 */
|
||||
UPROPERTY(Config)
|
||||
FGameplayTag HUDLayerTag;
|
||||
|
||||
/** @brief 默认菜单层 Tag。 */
|
||||
UPROPERTY(Config)
|
||||
FGameplayTag MenuLayerTag;
|
||||
|
||||
/** @brief 默认模态层 Tag。 */
|
||||
UPROPERTY(Config)
|
||||
FGameplayTag ModalLayerTag;
|
||||
|
||||
/** @brief 默认 HUD 层名称。 */
|
||||
UPROPERTY(Config)
|
||||
FName DefaultHUDLayerName = TEXT("HUD");
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
#include "GameplayTags/PHYGameplayTags_Event.h"
|
||||
#include "GameplayTags/PHYGameplayTags_Input.h"
|
||||
#include "GameplayTags/PHYGameplayTags_State.h"
|
||||
#include "GameplayTags/PHYGameplayTags_UI.h"
|
||||
|
||||
/**
|
||||
* @brief PHY 项目的原生 Gameplay Tag 聚合入口。
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
63
Source/PHY/Public/UI/PHYAttributeGroupWidget.h
Normal file
63
Source/PHY/Public/UI/PHYAttributeGroupWidget.h
Normal 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;
|
||||
};
|
||||
177
Source/PHY/Public/UI/PHYAttributeMenuWidget.h
Normal file
177
Source/PHY/Public/UI/PHYAttributeMenuWidget.h
Normal 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;
|
||||
};
|
||||
25
Source/PHY/Public/UI/PHYAttributeOverviewPageWidget.h
Normal file
25
Source/PHY/Public/UI/PHYAttributeOverviewPageWidget.h
Normal 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);
|
||||
};
|
||||
72
Source/PHY/Public/UI/PHYAttributePageWidget.h
Normal file
72
Source/PHY/Public/UI/PHYAttributePageWidget.h
Normal 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;
|
||||
};
|
||||
70
Source/PHY/Public/UI/PHYAttributeResourceBarWidget.h
Normal file
70
Source/PHY/Public/UI/PHYAttributeResourceBarWidget.h
Normal 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;
|
||||
};
|
||||
80
Source/PHY/Public/UI/PHYAttributeRowWidget.h
Normal file
80
Source/PHY/Public/UI/PHYAttributeRowWidget.h
Normal 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;
|
||||
};
|
||||
106
Source/PHY/Public/UI/PHYAttributeSummaryWidget.h
Normal file
106
Source/PHY/Public/UI/PHYAttributeSummaryWidget.h
Normal 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;
|
||||
};
|
||||
25
Source/PHY/Public/UI/PHYCombatDerivedAttributePageWidget.h
Normal file
25
Source/PHY/Public/UI/PHYCombatDerivedAttributePageWidget.h
Normal 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);
|
||||
};
|
||||
25
Source/PHY/Public/UI/PHYElementDerivedAttributePageWidget.h
Normal file
25
Source/PHY/Public/UI/PHYElementDerivedAttributePageWidget.h
Normal 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);
|
||||
};
|
||||
58
Source/PHY/Public/UI/PHYGameUILayout.h
Normal file
58
Source/PHY/Public/UI/PHYGameUILayout.h
Normal file
@@ -0,0 +1,58 @@
|
||||
// Copyright Epic Games, Inc. All Rights Reserved.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
#include "UI/GUIS_GameUILayout.h"
|
||||
#include "PHYGameUILayout.generated.h"
|
||||
|
||||
class UCommonActivatableWidgetStack;
|
||||
class UOverlay;
|
||||
|
||||
/**
|
||||
* @brief PHY 原生游戏 UI 根布局。
|
||||
*
|
||||
* 以 GenericUISystem 的 UGUIS_GameUILayout 为基类,在 C++ 中创建并注册 Game、HUD、Menu、Modal 四个层级。
|
||||
*/
|
||||
UCLASS(BlueprintType, Blueprintable)
|
||||
class PHY_API UPHYGameUILayout : public UGUIS_GameUILayout
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
public:
|
||||
/** @brief 构造根布局。 */
|
||||
UPHYGameUILayout(const FObjectInitializer& ObjectInitializer = FObjectInitializer::Get());
|
||||
|
||||
/** @brief 构建原生 WidgetTree。 */
|
||||
virtual TSharedRef<SWidget> RebuildWidget() override;
|
||||
|
||||
protected:
|
||||
/** @brief 构造根 Overlay 和各层 Stack。 */
|
||||
void BuildNativeLayoutTree();
|
||||
|
||||
/** @brief 向根 Overlay 添加一个全屏层 Stack。 */
|
||||
UCommonActivatableWidgetStack* AddLayerStack(UOverlay* RootOverlay, FName StackName) const;
|
||||
|
||||
/** @brief 注册 Native GameplayTag 到 GenericUISystem 层级表。 */
|
||||
void RegisterNativeLayers();
|
||||
|
||||
/** @brief Gameplay 常驻层。 */
|
||||
UPROPERTY(Transient)
|
||||
TObjectPtr<UCommonActivatableWidgetStack> GameLayerStack;
|
||||
|
||||
/** @brief HUD 常驻层。 */
|
||||
UPROPERTY(Transient)
|
||||
TObjectPtr<UCommonActivatableWidgetStack> HUDLayerStack;
|
||||
|
||||
/** @brief 菜单交互层。 */
|
||||
UPROPERTY(Transient)
|
||||
TObjectPtr<UCommonActivatableWidgetStack> MenuLayerStack;
|
||||
|
||||
/** @brief 模态最高优先级层。 */
|
||||
UPROPERTY(Transient)
|
||||
TObjectPtr<UCommonActivatableWidgetStack> ModalLayerStack;
|
||||
|
||||
/** @brief 是否已将当前层 Stack 注册到 GenericUISystem。 */
|
||||
UPROPERTY(Transient)
|
||||
bool bNativeLayersRegistered = false;
|
||||
};
|
||||
26
Source/PHY/Public/UI/PHYGameUIPolicy.h
Normal file
26
Source/PHY/Public/UI/PHYGameUIPolicy.h
Normal file
@@ -0,0 +1,26 @@
|
||||
// Copyright Epic Games, Inc. All Rights Reserved.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
#include "UI/GUIS_GameUIPolicy.h"
|
||||
#include "PHYGameUIPolicy.generated.h"
|
||||
|
||||
/**
|
||||
* @brief PHY GenericUISystem 策略。
|
||||
*
|
||||
* 负责把 GenericUISystem 的根布局指向项目 C++ 布局类,避免在 GameMode 或 HUD 中直接装配 UMG 层级。
|
||||
*/
|
||||
UCLASS(BlueprintType, Blueprintable)
|
||||
class PHY_API UPHYGameUIPolicy : public UGUIS_GameUIPolicy
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
public:
|
||||
/** @brief 构造策略并写入项目根布局类。 */
|
||||
UPHYGameUIPolicy(const FObjectInitializer& ObjectInitializer = FObjectInitializer::Get());
|
||||
|
||||
protected:
|
||||
/** @brief 将项目配置中的 RootLayoutClass 写入 GenericUISystem 策略基类。 */
|
||||
void ApplyConfiguredLayoutClass();
|
||||
};
|
||||
192
Source/PHY/Public/UI/PHYHUDStatusWidget.h
Normal file
192
Source/PHY/Public/UI/PHYHUDStatusWidget.h
Normal file
@@ -0,0 +1,192 @@
|
||||
// Copyright Epic Games, Inc. All Rights Reserved.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
#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++ 可见占位,绑定 PlayerState 上的 GAS 资源属性和成长状态。
|
||||
*/
|
||||
UCLASS(BlueprintType, Blueprintable)
|
||||
class PHY_API UPHYHUDStatusWidget : public UGUIS_ActivatableWidget
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
public:
|
||||
/** @brief 构造 HUD 状态控件。 */
|
||||
UPHYHUDStatusWidget(const FObjectInitializer& ObjectInitializer = FObjectInitializer::Get());
|
||||
|
||||
/** @brief 构建原生 HUD WidgetTree。 */
|
||||
virtual TSharedRef<SWidget> RebuildWidget() override;
|
||||
|
||||
/** @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 绑定新的 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 当前生命值。 */
|
||||
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category="PHY|UI")
|
||||
float Health = 100.0f;
|
||||
|
||||
/** @brief 当前最大生命值。 */
|
||||
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;
|
||||
|
||||
/** @brief 当前最大耐力值。 */
|
||||
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;
|
||||
|
||||
/** @brief 生命进度条。 */
|
||||
UPROPERTY(Transient)
|
||||
TObjectPtr<UProgressBar> HealthBar;
|
||||
|
||||
/** @brief 法力文本。 */
|
||||
UPROPERTY(Transient)
|
||||
TObjectPtr<UTextBlock> ManaText;
|
||||
|
||||
/** @brief 法力进度条。 */
|
||||
UPROPERTY(Transient)
|
||||
TObjectPtr<UProgressBar> ManaBar;
|
||||
|
||||
/** @brief 耐力文本。 */
|
||||
UPROPERTY(Transient)
|
||||
TObjectPtr<UTextBlock> StaminaText;
|
||||
|
||||
/** @brief 耐力进度条。 */
|
||||
UPROPERTY(Transient)
|
||||
TObjectPtr<UProgressBar> StaminaBar;
|
||||
|
||||
/** @brief 经验文本。 */
|
||||
UPROPERTY(Transient)
|
||||
TObjectPtr<UTextBlock> ExperienceText;
|
||||
|
||||
/** @brief 经验进度条。 */
|
||||
UPROPERTY(Transient)
|
||||
TObjectPtr<UProgressBar> ExperienceBar;
|
||||
};
|
||||
47
Source/PHY/Public/UI/PHYPlayerUIContext.h
Normal file
47
Source/PHY/Public/UI/PHYPlayerUIContext.h
Normal file
@@ -0,0 +1,47 @@
|
||||
// Copyright Epic Games, Inc. All Rights Reserved.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
#include "UI/GUIS_GameUIContext.h"
|
||||
#include "PHYPlayerUIContext.generated.h"
|
||||
|
||||
class APlayerController;
|
||||
class APawn;
|
||||
|
||||
/**
|
||||
* @brief 本地玩家 UI 上下文。
|
||||
*
|
||||
* 用于把 PlayerController、Pawn 等本地只读引用提供给 HUD、菜单和后续扩展界面。
|
||||
*/
|
||||
UCLASS(BlueprintType)
|
||||
class PHY_API UPHYPlayerUIContext : public UGUIS_GameUIContext
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
public:
|
||||
/** @brief 设置本地玩家控制器并刷新 Pawn 引用。 */
|
||||
UFUNCTION(BlueprintCallable, Category="PHY|UI")
|
||||
void InitializeForPlayer(APlayerController* InPlayerController);
|
||||
|
||||
/** @brief 刷新当前 Pawn 引用。 */
|
||||
UFUNCTION(BlueprintCallable, Category="PHY|UI")
|
||||
void RefreshPawn();
|
||||
|
||||
/** @brief 获取本地玩家控制器。 */
|
||||
UFUNCTION(BlueprintCallable, BlueprintPure, Category="PHY|UI")
|
||||
APlayerController* GetPlayerController() const { return PlayerController.Get(); }
|
||||
|
||||
/** @brief 获取当前 Pawn。 */
|
||||
UFUNCTION(BlueprintCallable, BlueprintPure, Category="PHY|UI")
|
||||
APawn* GetPawn() const { return Pawn.Get(); }
|
||||
|
||||
private:
|
||||
/** @brief 本地玩家控制器弱引用。 */
|
||||
UPROPERTY(Transient)
|
||||
TWeakObjectPtr<APlayerController> PlayerController;
|
||||
|
||||
/** @brief 当前 Pawn 弱引用。 */
|
||||
UPROPERTY(Transient)
|
||||
TWeakObjectPtr<APawn> Pawn;
|
||||
};
|
||||
Reference in New Issue
Block a user