Compare commits
14 Commits
662184001d
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
| 63d845c910 | |||
| 6b19fe2d81 | |||
| 8a29b6eeb7 | |||
| baeb4098c9 | |||
| 57363d9e7f | |||
| c4f185736f | |||
| 7f3d9cd989 | |||
| ee12f1ad32 | |||
| 2ae207533c | |||
| 6ca0f21163 | |||
| 88d01f588b | |||
| 3499cf363c | |||
| cd7444f4d2 | |||
| 8df5fc29e9 |
@@ -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
|
||||
|
||||
@@ -11,4 +11,5 @@ DefaultPlayerCharacterClass="/Game/AGame/Character/Player/BP_PlayerBase.BP_Playe
|
||||
DefaultAICharacterClass="/Game/AGame/Character/AI/BP_AIBase.BP_AIBase_C"
|
||||
DefaultPlayerControllerClass="/Game/AGame/Gameplay/Player/BP_PHYPlayerController.BP_PHYPlayerController_C"
|
||||
DefaultAIControllerClass="/Game/AGame/Gameplay/AI/BP_PHYAIController.BP_PHYAIController_C"
|
||||
DefaultPlayerCameraManagerClass="/Game/AGame/Gameplay/Player/BP_PHYPlayerCameraManager.BP_PHYPlayerCameraManager_C"
|
||||
DefaultPlayerCameraManagerClass="/Game/AGame/Gameplay/Camera/BP_CameraManager_Default.BP_CameraManager_Default_C"
|
||||
DefaultUGCCameraDataAsset="/Game/AGame/Gameplay/Camera/DA_Camera_Default.DA_Camera_Default"
|
||||
|
||||
@@ -4,7 +4,7 @@ DefaultAIClassTag=(TagName="Class.Saber")
|
||||
bApplyPlayerClassMeshToPlayers=True
|
||||
bApplyPlayerClassMeshToAI=False
|
||||
!PlayerClassMeshes=ClearArray
|
||||
+PlayerClassMeshes=(ClassTag=(TagName="Class.Saber"),PlayerMesh="/Game/AGame/Character/Ida/SK_MMO_ADVANCED_F_01_AGame.SK_MMO_ADVANCED_F_01_AGame",PlayerRetargeter="/Game/AGame/Animation/Retargeters/RTG_Manny_To_Ida.RTG_Manny_To_Ida")
|
||||
+PlayerClassMeshes=(ClassTag=(TagName="Class.Saber"),PlayerMesh="/Game/AGame/Character/Ida/SK_MMO_ADVANCED_F_01_AGame.SK_MMO_ADVANCED_F_01_AGame",PlayerDisplayAnimClass="/Game/AGame/Animation/Retargeters/ABP_Retargeter.ABP_Retargeter_C",PlayerRetargeter="/Game/AGame/Animation/Retargeters/RTG_Manny_To_Ida.RTG_Manny_To_Ida")
|
||||
+PlayerClassMeshes=(ClassTag=(TagName="Class.Lancer"),PlayerMesh=None,PlayerRetargeter=)
|
||||
+PlayerClassMeshes=(ClassTag=(TagName="Class.Archer"),PlayerMesh=None,PlayerRetargeter=)
|
||||
+PlayerClassMeshes=(ClassTag=(TagName="Class.Rider"),PlayerMesh=None,PlayerRetargeter=)
|
||||
|
||||
@@ -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.
BIN
Content/AGame/Gameplay/Camera/BP_CameraManager_Default.uasset
LFS
Normal file
BIN
Content/AGame/Gameplay/Camera/BP_CameraManager_Default.uasset
LFS
Normal file
Binary file not shown.
BIN
Content/AGame/Gameplay/Camera/DA_Camera_Default.uasset
LFS
Normal file
BIN
Content/AGame/Gameplay/Camera/DA_Camera_Default.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.
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",
|
||||
|
||||
@@ -18,6 +18,14 @@ UPHYCharacterMeshBridgeComponent::UPHYCharacterMeshBridgeComponent(const FObject
|
||||
PrimaryComponentTick.bCanEverTick = false;
|
||||
}
|
||||
|
||||
void UPHYCharacterMeshBridgeComponent::BeginPlay()
|
||||
{
|
||||
Super::BeginPlay();
|
||||
|
||||
// 构造期或 PlayerState 职业同步可能早于显示 Mesh 注册,运行时再确认一次。
|
||||
RefreshFollowPoseMode();
|
||||
}
|
||||
|
||||
void UPHYCharacterMeshBridgeComponent::InitializeMeshBridge(ACharacter* Character, USkeletalMeshComponent* InDisplayMeshComponent)
|
||||
{
|
||||
SourceMeshComponent = Character ? Character->GetMesh() : nullptr;
|
||||
@@ -185,6 +193,16 @@ void UPHYCharacterMeshBridgeComponent::ConfigureRetargetFollowPose()
|
||||
return;
|
||||
}
|
||||
|
||||
if (!DisplayMeshComponent->GetSkeletalMeshAsset())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (!SourceMeshComponent->IsRegistered() || !DisplayMeshComponent->IsRegistered())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
TSubclassOf<UAnimInstance> RetargetAnimClass = ResolveRetargetDisplayAnimClass();
|
||||
if (!RetargetAnimClass)
|
||||
{
|
||||
@@ -204,6 +222,8 @@ void UPHYCharacterMeshBridgeComponent::ConfigureRetargetFollowPose()
|
||||
DisplayMeshComponent->SetAnimInstanceClass(RetargetAnimClass);
|
||||
}
|
||||
|
||||
DisplayMeshComponent->InitAnim(true);
|
||||
|
||||
if (!CurrentRetargeter.IsValid())
|
||||
{
|
||||
UE_LOG(LogTemp, Warning, TEXT("PHYCharacterMeshBridgeComponent: DisplayMesh %s needs an IK Retargeter for cross-skeleton follow pose."), *GetNameSafe(DisplayMeshComponent));
|
||||
@@ -213,7 +233,10 @@ void UPHYCharacterMeshBridgeComponent::ConfigureRetargetFollowPose()
|
||||
UPHYRetargetPoseAnimInstance* RetargetAnimInstance = Cast<UPHYRetargetPoseAnimInstance>(DisplayMeshComponent->GetAnimInstance());
|
||||
if (!RetargetAnimInstance)
|
||||
{
|
||||
UE_LOG(LogTemp, Warning, TEXT("PHYCharacterMeshBridgeComponent: DisplayMesh %s did not create a UPHYRetargetPoseAnimInstance."), *GetNameSafe(DisplayMeshComponent));
|
||||
UE_LOG(LogTemp, Warning, TEXT("PHYCharacterMeshBridgeComponent: DisplayMesh %s did not create a UPHYRetargetPoseAnimInstance. AnimClass=%s AnimInstance=%s"),
|
||||
*GetNameSafe(DisplayMeshComponent),
|
||||
*GetNameSafe(DisplayMeshComponent->GetAnimClass()),
|
||||
*GetNameSafe(DisplayMeshComponent->GetAnimInstance()));
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -229,7 +252,10 @@ TSubclassOf<UAnimInstance> UPHYCharacterMeshBridgeComponent::ResolveRetargetDisp
|
||||
|
||||
if (UClass* CurrentAnimClass = DisplayMeshComponent->GetAnimClass())
|
||||
{
|
||||
return CurrentAnimClass;
|
||||
if (CurrentAnimClass != UPHYRetargetPoseAnimInstance::StaticClass())
|
||||
{
|
||||
return CurrentAnimClass;
|
||||
}
|
||||
}
|
||||
|
||||
const UPHYAnimationSettings* Settings = GetDefault<UPHYAnimationSettings>();
|
||||
|
||||
@@ -7,84 +7,7 @@
|
||||
#include "Components/SkeletalMeshComponent.h"
|
||||
#include "Retargeter/IKRetargeter.h"
|
||||
|
||||
FPHYRetargetPoseAnimInstanceProxy::FPHYRetargetPoseAnimInstanceProxy(UAnimInstance* InAnimInstance, FAnimNode_RetargetPoseFromMesh* InRetargetNode)
|
||||
: FAnimInstanceProxy(InAnimInstance)
|
||||
, RetargetNode(InRetargetNode)
|
||||
{
|
||||
}
|
||||
|
||||
void FPHYRetargetPoseAnimInstanceProxy::Initialize(UAnimInstance* InAnimInstance)
|
||||
{
|
||||
FAnimInstanceProxy::Initialize(InAnimInstance);
|
||||
|
||||
if (RetargetNode)
|
||||
{
|
||||
FAnimationInitializeContext InitContext(this);
|
||||
RetargetNode->Initialize_AnyThread(InitContext);
|
||||
}
|
||||
}
|
||||
|
||||
void FPHYRetargetPoseAnimInstanceProxy::PreUpdate(UAnimInstance* InAnimInstance, float DeltaSeconds)
|
||||
{
|
||||
FAnimInstanceProxy::PreUpdate(InAnimInstance, DeltaSeconds);
|
||||
|
||||
if (RetargetNode && RetargetNode->HasPreUpdate())
|
||||
{
|
||||
RetargetNode->PreUpdate(InAnimInstance);
|
||||
}
|
||||
}
|
||||
|
||||
void FPHYRetargetPoseAnimInstanceProxy::CacheBones()
|
||||
{
|
||||
if (bBoneCachesInvalidated && RetargetNode)
|
||||
{
|
||||
FAnimationCacheBonesContext Context(this);
|
||||
RetargetNode->CacheBones_AnyThread(Context);
|
||||
bBoneCachesInvalidated = false;
|
||||
}
|
||||
}
|
||||
|
||||
bool FPHYRetargetPoseAnimInstanceProxy::Evaluate(FPoseContext& Output)
|
||||
{
|
||||
if (!RetargetNode)
|
||||
{
|
||||
Output.ResetToRefPose();
|
||||
return true;
|
||||
}
|
||||
|
||||
RetargetNode->Evaluate_AnyThread(Output);
|
||||
return true;
|
||||
}
|
||||
|
||||
void FPHYRetargetPoseAnimInstanceProxy::UpdateAnimationNode(const FAnimationUpdateContext& InContext)
|
||||
{
|
||||
UpdateCounter.Increment();
|
||||
|
||||
if (RetargetNode)
|
||||
{
|
||||
RetargetNode->Update_AnyThread(InContext);
|
||||
}
|
||||
}
|
||||
|
||||
void FPHYRetargetPoseAnimInstanceProxy::ConfigureRetargetPose(UIKRetargeter* InRetargeter, USkeletalMeshComponent* InSourceMeshComponent)
|
||||
{
|
||||
if (!RetargetNode)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
RetargetNode->IKRetargeterAsset = InRetargeter;
|
||||
RetargetNode->RetargetFrom = ERetargetSourceMode::CustomSkeletalMeshComponent;
|
||||
RetargetNode->SourceMeshComponent = InSourceMeshComponent;
|
||||
RetargetNode->bSuppressWarnings = false;
|
||||
|
||||
if (FIKRetargetProcessor* Processor = RetargetNode->GetRetargetProcessor())
|
||||
{
|
||||
Processor->SetNeedsInitialized();
|
||||
}
|
||||
}
|
||||
|
||||
bool UPHYRetargetPoseAnimInstance::ConfigureRetargetPoseFromMesh(USkeletalMeshComponent* InSourceMeshComponent, FSoftObjectPath InRetargeterPath)
|
||||
bool UPHYRetargetPoseAnimInstance::ConfigureRetargetPoseFromMesh(USkeletalMeshComponent* InSourceMeshComponent, const FSoftObjectPath InRetargeterPath)
|
||||
{
|
||||
RetargetSourceMeshComponent = InSourceMeshComponent;
|
||||
RetargeterPath = InRetargeterPath;
|
||||
@@ -110,21 +33,6 @@ bool UPHYRetargetPoseAnimInstance::ConfigureRetargetPoseFromMesh(USkeletalMeshCo
|
||||
return false;
|
||||
}
|
||||
|
||||
FPHYRetargetPoseAnimInstanceProxy& Proxy = GetProxyOnGameThread<FPHYRetargetPoseAnimInstanceProxy>();
|
||||
Proxy.ConfigureRetargetPose(LoadedRetargeter, RetargetSourceMeshComponent);
|
||||
// AnimGraph 中的 Retarget Pose From Mesh 节点通过属性绑定读取 LoadedRetargeter。
|
||||
return true;
|
||||
}
|
||||
|
||||
void UPHYRetargetPoseAnimInstance::NativeInitializeAnimation()
|
||||
{
|
||||
Super::NativeInitializeAnimation();
|
||||
|
||||
FPHYRetargetPoseAnimInstanceProxy& Proxy = GetProxyOnGameThread<FPHYRetargetPoseAnimInstanceProxy>();
|
||||
Proxy.Initialize(this);
|
||||
Proxy.ConfigureRetargetPose(LoadedRetargeter, RetargetSourceMeshComponent);
|
||||
}
|
||||
|
||||
FAnimInstanceProxy* UPHYRetargetPoseAnimInstance::CreateAnimInstanceProxy()
|
||||
{
|
||||
return new FPHYRetargetPoseAnimInstanceProxy(this, &RetargetNode);
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
#include UE_INLINE_GENERATED_CPP_BY_NAME(PHYClassComponent)
|
||||
|
||||
#include "AbilitySystemComponent.h"
|
||||
#include "Animation/AnimInstance.h"
|
||||
#include "AbilitySystem/Attributes/PHYAttributeCalculationLibrary.h"
|
||||
#include "AbilitySystem/Attributes/PHYCombatAttributeSet.h"
|
||||
#include "AbilitySystem/Attributes/PHYCoreAttributeSet.h"
|
||||
@@ -92,6 +93,25 @@ USkeletalMesh* UPHYClassComponent::GetConfiguredPlayerMesh() const
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
TSubclassOf<UAnimInstance> UPHYClassComponent::GetConfiguredPlayerDisplayAnimClass() const
|
||||
{
|
||||
const UPHYClassSettings* ClassSettings = GetDefault<UPHYClassSettings>();
|
||||
if (!ClassSettings || !CurrentClassTag.IsValid())
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
for (const FPHYPlayerClassMeshConfig& MeshConfig : ClassSettings->PlayerClassMeshes)
|
||||
{
|
||||
if (MeshConfig.ClassTag == CurrentClassTag)
|
||||
{
|
||||
return MeshConfig.PlayerDisplayAnimClass.LoadSynchronous();
|
||||
}
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
FSoftObjectPath UPHYClassComponent::GetConfiguredPlayerRetargeter() const
|
||||
{
|
||||
const UPHYClassSettings* ClassSettings = GetDefault<UPHYClassSettings>();
|
||||
@@ -139,7 +159,7 @@ bool UPHYClassComponent::ApplyConfiguredPlayerMeshIfAllowed(AActor* AvatarActor)
|
||||
}
|
||||
|
||||
MeshBridge->InitializeMeshBridge(Character, Character->GetDisplayMeshComponent());
|
||||
return MeshBridge->ApplyDisplayMesh(PlayerMesh, nullptr, PlayerRetargeter);
|
||||
return MeshBridge->ApplyDisplayMesh(PlayerMesh, GetConfiguredPlayerDisplayAnimClass(), PlayerRetargeter);
|
||||
}
|
||||
|
||||
bool UPHYClassComponent::ApplyClassAttributesToAbilitySystem(UAbilitySystemComponent* AbilitySystemComponent) const
|
||||
|
||||
@@ -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 的根层级。
|
||||
bHUDInitialized = true;
|
||||
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;
|
||||
}
|
||||
@@ -72,6 +72,9 @@ public:
|
||||
FSoftObjectPath GetCurrentRetargeter() const { return CurrentRetargeter; }
|
||||
|
||||
protected:
|
||||
/** @brief 组件进入运行时后重新确认 Follow Pose 模式,覆盖构造期组件未注册的情况。 */
|
||||
virtual void BeginPlay() override;
|
||||
|
||||
/** @brief 应用配置中的源运动 Mesh 和 AnimClass 默认值。 */
|
||||
void ApplySourceDefaults();
|
||||
|
||||
|
||||
@@ -3,8 +3,6 @@
|
||||
#pragma once
|
||||
|
||||
#include "Animation/AnimInstance.h"
|
||||
#include "Animation/AnimInstanceProxy.h"
|
||||
#include "AnimNodes/AnimNode_RetargetPoseFromMesh.h"
|
||||
#include "UObject/SoftObjectPath.h"
|
||||
#include "PHYRetargetPoseAnimInstance.generated.h"
|
||||
|
||||
@@ -12,46 +10,19 @@ class UIKRetargeter;
|
||||
class USkeletalMeshComponent;
|
||||
|
||||
/**
|
||||
* @brief 显示层 Retarget Pose From Mesh 动画实例代理。
|
||||
* @brief PHY 显示层 Retarget 动画实例基类。
|
||||
*
|
||||
* 代理直接驱动 UE IKRig 的 FAnimNode_RetargetPoseFromMesh,用于把 SourceMesh 的运动姿态重定向到显示层 Mesh。
|
||||
* 该类只保存 SourceMesh 和 IK Retargeter 运行时参数,不再由 C++ 自动执行 AnimGraph 节点;
|
||||
* 具体的 Retarget Pose From Mesh 节点必须在派生 AnimBlueprint 中显式连接。
|
||||
*/
|
||||
USTRUCT()
|
||||
struct PHY_API FPHYRetargetPoseAnimInstanceProxy : public FAnimInstanceProxy
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
friend class UPHYRetargetPoseAnimInstance;
|
||||
|
||||
FPHYRetargetPoseAnimInstanceProxy() = default;
|
||||
FPHYRetargetPoseAnimInstanceProxy(UAnimInstance* InAnimInstance, FAnimNode_RetargetPoseFromMesh* InRetargetNode);
|
||||
protected:
|
||||
virtual void Initialize(UAnimInstance* InAnimInstance) override;
|
||||
virtual void PreUpdate(UAnimInstance* InAnimInstance, float DeltaSeconds) override;
|
||||
virtual void CacheBones() override;
|
||||
virtual bool Evaluate(FPoseContext& Output) override;
|
||||
virtual void UpdateAnimationNode(const FAnimationUpdateContext& InContext) override;
|
||||
|
||||
/** @brief 配置 Retarget 节点使用的源 Mesh 与 IK Retargeter。 */
|
||||
void ConfigureRetargetPose(UIKRetargeter* InRetargeter, USkeletalMeshComponent* InSourceMeshComponent);
|
||||
|
||||
private:
|
||||
FAnimNode_RetargetPoseFromMesh* RetargetNode = nullptr;
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief PHY 显示层原生 Retarget Pose 动画实例。
|
||||
*
|
||||
* MeshBridge 在异骨架显示层路径中使用该类,把 ACharacter::GetMesh() 的运动姿态通过 IK Retargeter 输出到 DisplayMesh。
|
||||
*/
|
||||
UCLASS(Transient, BlueprintType)
|
||||
UCLASS(Transient, BlueprintType, Blueprintable)
|
||||
class PHY_API UPHYRetargetPoseAnimInstance : public UAnimInstance
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
public:
|
||||
/**
|
||||
* @brief 设置 Retarget Pose From Mesh 的运行时输入。
|
||||
* @brief 设置 Retarget AnimBlueprint 读取的运行时输入。
|
||||
* @param InSourceMeshComponent 提供运动姿态的源 Mesh 组件。
|
||||
* @param InRetargeterPath IK Retargeter 资产软路径。
|
||||
* @return SourceMesh 与 Retargeter 均有效时返回 true。
|
||||
@@ -63,29 +34,24 @@ public:
|
||||
UFUNCTION(BlueprintCallable, BlueprintPure, Category="PHY|Animation")
|
||||
USkeletalMeshComponent* GetRetargetSourceMeshComponent() const { return RetargetSourceMeshComponent; }
|
||||
|
||||
/** @brief 获取当前已加载的 IK Retargeter 资产,供 AnimBlueprint 节点绑定。 */
|
||||
UFUNCTION(BlueprintCallable, BlueprintPure, Category="PHY|Animation")
|
||||
UIKRetargeter* GetLoadedRetargeter() const { return LoadedRetargeter; }
|
||||
|
||||
/** @brief 获取当前 IK Retargeter 资产路径。 */
|
||||
UFUNCTION(BlueprintCallable, BlueprintPure, Category="PHY|Animation")
|
||||
FSoftObjectPath GetRetargeterPath() const { return RetargeterPath; }
|
||||
|
||||
|
||||
virtual void NativeInitializeAnimation() override;
|
||||
protected:
|
||||
virtual FAnimInstanceProxy* CreateAnimInstanceProxy() override;
|
||||
|
||||
private:
|
||||
/** @brief Retarget Pose From Mesh 运行时节点。 */
|
||||
UPROPERTY()
|
||||
FAnimNode_RetargetPoseFromMesh RetargetNode;
|
||||
|
||||
/** @brief 当前用于重定向的源 Mesh 组件。 */
|
||||
UPROPERTY(Transient)
|
||||
UPROPERTY(Transient, BlueprintReadOnly, Category="PHY|Animation")
|
||||
TObjectPtr<USkeletalMeshComponent> RetargetSourceMeshComponent;
|
||||
|
||||
/** @brief 当前使用的 IK Retargeter 资产。 */
|
||||
UPROPERTY(Transient)
|
||||
/** @brief 当前使用的 IK Retargeter 资产,AnimBlueprint 的节点应绑定到该属性或 Getter。 */
|
||||
UPROPERTY(Transient, BlueprintReadOnly, Category="PHY|Animation")
|
||||
TObjectPtr<UIKRetargeter> LoadedRetargeter;
|
||||
|
||||
/** @brief 当前 IK Retargeter 资产软路径。 */
|
||||
UPROPERTY(Transient)
|
||||
UPROPERTY(Transient, BlueprintReadOnly, Category="PHY|Animation")
|
||||
FSoftObjectPath RetargeterPath;
|
||||
};
|
||||
|
||||
@@ -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")
|
||||
|
||||
@@ -8,8 +8,9 @@
|
||||
#include "UObject/SoftObjectPath.h"
|
||||
#include "PHYClassComponent.generated.h"
|
||||
|
||||
class USkeletalMesh;
|
||||
class UAbilitySystemComponent;
|
||||
class UAnimInstance;
|
||||
class USkeletalMesh;
|
||||
|
||||
/**
|
||||
* @brief PHY 职业组件。
|
||||
@@ -44,6 +45,10 @@ public:
|
||||
UFUNCTION(BlueprintCallable, Category="PHY|Class")
|
||||
USkeletalMesh* GetConfiguredPlayerMesh() const;
|
||||
|
||||
/** @brief 按当前职业读取配置的显示层 AnimClass,未配置时返回空并交给动画默认配置。 */
|
||||
UFUNCTION(BlueprintCallable, Category="PHY|Class")
|
||||
TSubclassOf<UAnimInstance> GetConfiguredPlayerDisplayAnimClass() const;
|
||||
|
||||
/** @brief 按当前职业读取配置的玩家 IK Retargeter 软路径,未配置时返回空路径。 */
|
||||
UFUNCTION(BlueprintCallable, BlueprintPure, Category="PHY|Class")
|
||||
FSoftObjectPath GetConfiguredPlayerRetargeter() const;
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
#include "UObject/SoftObjectPtr.h"
|
||||
#include "PHYClassSettings.generated.h"
|
||||
|
||||
class UAnimInstance;
|
||||
class USkeletalMesh;
|
||||
|
||||
/**
|
||||
@@ -74,6 +75,10 @@ public:
|
||||
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category="PHY|Class")
|
||||
TSoftObjectPtr<USkeletalMesh> PlayerMesh;
|
||||
|
||||
/** @brief 玩家使用该职业时优先使用的显示层 AnimClass,留空时回退动画默认配置。 */
|
||||
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category="PHY|Class")
|
||||
TSoftClassPtr<UAnimInstance> PlayerDisplayAnimClass;
|
||||
|
||||
/** @brief 玩家使用该职业时优先使用的 IK Retargeter 软路径,留空时回退动画默认配置。 */
|
||||
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category="PHY|Class")
|
||||
FSoftObjectPath PlayerRetargeter;
|
||||
|
||||
@@ -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