Compare commits

...

24 Commits

Author SHA1 Message Date
63d845c910 Tune attribute menu reference palette 2026-04-26 21:55:24 +08:00
6b19fe2d81 Use attribute menu design backplate 2026-04-26 21:51:08 +08:00
8a29b6eeb7 Improve attribute menu visual style 2026-04-26 21:29:17 +08:00
baeb4098c9 Add attribute menu input processor 2026-04-26 21:02:35 +08:00
57363d9e7f Add paged character attribute menu 2026-04-26 20:53:55 +08:00
c4f185736f Bind HUD status to GAS and player state 2026-04-26 20:20:32 +08:00
7f3d9cd989 Update git workflow to commit only 2026-04-26 20:07:43 +08:00
ee12f1ad32 Add Generic UI foundation 2026-04-26 20:05:25 +08:00
2ae207533c Fix character base header comments 2026-04-26 19:10:08 +08:00
6ca0f21163 修复bug 2026-04-26 18:49:19 +08:00
88d01f588b Remove redundant SLS movement bridge 2026-04-26 18:13:19 +08:00
3499cf363c Use blueprint retarget pose graph 2026-04-26 17:28:37 +08:00
cd7444f4d2 Fix retarget display mesh initialization 2026-04-26 17:11:57 +08:00
8df5fc29e9 Point camera manager config at blueprint asset 2026-04-26 16:38:25 +08:00
662184001d Move player camera manager implementation to blueprint 2026-04-26 16:34:47 +08:00
163092858c Route player input through SLS movement 2026-04-26 16:28:55 +08:00
4d2186c43c Configure character mesh transform defaults 2026-04-26 16:20:29 +08:00
d9faa65a27 Fix class visual mesh initialization 2026-04-26 16:15:14 +08:00
21865dcc09 Keep input system on player character 2026-04-26 15:58:42 +08:00
960280c9a7 Add gameplay framework blueprints 2026-04-26 15:53:25 +08:00
4677d04b74 Add core game mode framework 2026-04-26 15:45:03 +08:00
e9d818e999 Move animation retarget assets under AGame 2026-04-26 15:40:13 +08:00
7a04253843 Ignore local UE MCP bridge plugin 2026-04-26 15:26:33 +08:00
a52a960347 Document UE MCP usage 2026-04-26 15:23:47 +08:00
113 changed files with 4533 additions and 477 deletions

7
.gitignore vendored
View File

@@ -29,6 +29,13 @@ Content/*
*.sublime-workspace *.sublime-workspace
*.code-workspace *.code-workspace
# Local MCP bridge plugin
Plugins/UEBridgeMCP-main/
Plugins/UE_MCP_Bridge/
.claude/
.ue-mcp.json
ue-mcp.yml
# OS files # OS files
Thumbs.db Thumbs.db
Desktop.ini Desktop.ini

View File

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

View File

@@ -159,3 +159,15 @@ ManualIPAddress=
+CollisionChannelRedirects=(OldName="VehicleMovement",NewName="Vehicle") +CollisionChannelRedirects=(OldName="VehicleMovement",NewName="Vehicle")
+CollisionChannelRedirects=(OldName="PawnMovement",NewName="Pawn") +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

View File

@@ -2,7 +2,14 @@
[/Script/CommonUI.CommonUISettings] [/Script/CommonUI.CommonUISettings]
CommonButtonAcceptKeyHandling=TriggerClick CommonButtonAcceptKeyHandling=TriggerClick
[/Script/GenericUISystem.GUIS_GenericUISystemSettings]
GameUIPolicyClass="/Script/PHY.PHYGameUIPolicy"
[/Script/EngineSettings.GeneralProjectSettings] [/Script/EngineSettings.GeneralProjectSettings]
ProjectID=E7C26E1F4D195F0DBE49C2A3E5017988 ProjectID=E7C26E1F4D195F0DBE49C2A3E5017988
CopyrightNotice= CopyrightNotice=
[/Script/EngineSettings.GameMapsSettings]
GlobalDefaultGameMode=/Game/AGame/Gameplay/Framework/BP_PHYGameMode.BP_PHYGameMode_C
GlobalDefaultServerGameMode=/Game/AGame/Gameplay/Framework/BP_PHYGameMode.BP_PHYGameMode_C

View File

@@ -1,8 +1,8 @@
[/Script/PHY.PHYAnimationSettings] [/Script/PHY.PHYAnimationSettings]
DefaultSourceLocomotionMesh="/Game/AGame/Character/SKM_Manny_Invis.SKM_Manny_Invis" DefaultSourceLocomotionMesh="/Game/AGame/Character/SKM_Manny_Invis.SKM_Manny_Invis"
DefaultSourceAnimClass="/Game/SmoothLocomotionSystem/SLS_UE5/Blueprints/AnimBP/ABP_UE5_Main.ABP_UE5_Main_C" DefaultSourceAnimClass="/Game/AGame/Animation/Locomotion/ABP_UE5_Main.ABP_UE5_Main_C"
DefaultDisplayAnimClass=None DefaultDisplayAnimClass="/Game/AGame/Animation/Retargeters/ABP_Retargeter.ABP_Retargeter_C"
DefaultIKRetargeter= DefaultIKRetargeter="/Game/AGame/Animation/Retargeters/RTG_Manny_To_Ida.RTG_Manny_To_Ida"
bHideSourceMeshInGame=False bHideSourceMeshInGame=False
bPreferLeaderPoseForSameSkeleton=True bPreferLeaderPoseForSameSkeleton=True
bAllowRuntimeVisualMeshSwitch=True bAllowRuntimeVisualMeshSwitch=True

View File

@@ -4,4 +4,12 @@ bEnableContextEffects=True
DefaultMaxWalkSpeed=500.000000 DefaultMaxWalkSpeed=500.000000
DefaultSprintSpeed=650.000000 DefaultSprintSpeed=650.000000
DefaultMaxAcceleration=2048.000000 DefaultMaxAcceleration=2048.000000
DefaultMeshRelativeLocation=(X=0.000000,Y=0.000000,Z=-90.000000)
DefaultMeshRelativeRotation=(Pitch=0.000000,Yaw=-90.000000,Roll=0.000000)
DefaultInteractionOption=0 DefaultInteractionOption=0
DefaultPlayerCharacterClass="/Game/AGame/Character/Player/BP_PlayerBase.BP_PlayerBase_C"
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/Camera/BP_CameraManager_Default.BP_CameraManager_Default_C"
DefaultUGCCameraDataAsset="/Game/AGame/Gameplay/Camera/DA_Camera_Default.DA_Camera_Default"

View File

@@ -4,7 +4,7 @@ DefaultAIClassTag=(TagName="Class.Saber")
bApplyPlayerClassMeshToPlayers=True bApplyPlayerClassMeshToPlayers=True
bApplyPlayerClassMeshToAI=False bApplyPlayerClassMeshToAI=False
!PlayerClassMeshes=ClearArray !PlayerClassMeshes=ClearArray
+PlayerClassMeshes=(ClassTag=(TagName="Class.Saber"),PlayerMesh=None,PlayerRetargeter=) +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.Lancer"),PlayerMesh=None,PlayerRetargeter=)
+PlayerClassMeshes=(ClassTag=(TagName="Class.Archer"),PlayerMesh=None,PlayerRetargeter=) +PlayerClassMeshes=(ClassTag=(TagName="Class.Archer"),PlayerMesh=None,PlayerRetargeter=)
+PlayerClassMeshes=(ClassTag=(TagName="Class.Rider"),PlayerMesh=None,PlayerRetargeter=) +PlayerClassMeshes=(ClassTag=(TagName="Class.Rider"),PlayerMesh=None,PlayerRetargeter=)

View File

@@ -3,3 +3,13 @@ ConfigVersion=1
bMultiplayerFirst=True bMultiplayerFirst=True
bPreferCodeAndConfig=True bPreferCodeAndConfig=True
TargetPlatformName=Win64 TargetPlatformName=Win64
[/Script/PHY.PHYGameFrameworkSettings]
DefaultPawnClass="/Game/AGame/Character/Player/BP_PlayerBase.BP_PlayerBase_C"
PlayerControllerClass="/Game/AGame/Gameplay/Player/BP_PHYPlayerController.BP_PHYPlayerController_C"
PlayerStateClass="/Game/AGame/Gameplay/Player/BP_PHYPlayerState.BP_PHYPlayerState_C"
GameStateClass="/Game/AGame/Gameplay/Framework/BP_PHYGameState.BP_PHYGameState_C"
HUDClass="/Game/AGame/Gameplay/Framework/BP_PHYHUD.BP_PHYHUD_C"
SpectatorClass="/Game/AGame/Gameplay/Framework/BP_PHYSpectatorPawn.BP_PHYSpectatorPawn_C"
bUseSeamlessTravel=True
bStartPlayersAsSpectators=False

View File

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

View File

@@ -1,4 +1,12 @@
[/Script/PHY.PHYUISettings] [/Script/PHY.PHYUISettings]
bUseCommonUI=True bUseCommonUI=True
bUseGenericUISystem=True
bOpenMenusWithGameplayPause=False 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 DefaultHUDLayerName=HUD

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@@ -11,6 +11,10 @@
} }
], ],
"Plugins": [ "Plugins": [
{
"Name": "PythonScriptPlugin",
"Enabled": true
},
{ {
"Name": "ModelingToolsEditorMode", "Name": "ModelingToolsEditorMode",
"Enabled": true, "Enabled": true,
@@ -57,6 +61,26 @@
"SupportedTargetPlatforms": [ "SupportedTargetPlatforms": [
"Win64" "Win64"
] ]
},
{
"Name": "UE_MCP_Bridge",
"Enabled": true
},
{
"Name": "Niagara",
"Enabled": true
},
{
"Name": "PCG",
"Enabled": true
},
{
"Name": "EnhancedInput",
"Enabled": true
},
{
"Name": "CommonUI",
"Enabled": true
} }
] ]
} }

View File

@@ -19,10 +19,16 @@ public class PHY : ModuleRules
"GameplayAbilities", "GameplayAbilities",
"GameplayTasks", "GameplayTasks",
"ModularGameplay", "ModularGameplay",
"CommonUI",
"CommonInput",
"UMG",
"Slate",
"SlateCore",
"GenericGameplayAbilities", "GenericGameplayAbilities",
"GenericCombatSystem", "GenericCombatSystem",
"GenericInputSystem", "GenericInputSystem",
"GenericGameSystem", "GenericGameSystem",
"GenericUISystem",
"GenericEffectsSystem", "GenericEffectsSystem",
"SLSCore", "SLSCore",
"SmoothLocomotionSystem", "SmoothLocomotionSystem",

View File

@@ -18,6 +18,14 @@ UPHYCharacterMeshBridgeComponent::UPHYCharacterMeshBridgeComponent(const FObject
PrimaryComponentTick.bCanEverTick = false; PrimaryComponentTick.bCanEverTick = false;
} }
void UPHYCharacterMeshBridgeComponent::BeginPlay()
{
Super::BeginPlay();
// 构造期或 PlayerState 职业同步可能早于显示 Mesh 注册,运行时再确认一次。
RefreshFollowPoseMode();
}
void UPHYCharacterMeshBridgeComponent::InitializeMeshBridge(ACharacter* Character, USkeletalMeshComponent* InDisplayMeshComponent) void UPHYCharacterMeshBridgeComponent::InitializeMeshBridge(ACharacter* Character, USkeletalMeshComponent* InDisplayMeshComponent)
{ {
SourceMeshComponent = Character ? Character->GetMesh() : nullptr; SourceMeshComponent = Character ? Character->GetMesh() : nullptr;
@@ -36,7 +44,10 @@ void UPHYCharacterMeshBridgeComponent::InitializeMeshBridge(ACharacter* Characte
ApplySourceDefaults(); ApplySourceDefaults();
const UPHYAnimationSettings* Settings = GetDefault<UPHYAnimationSettings>(); const UPHYAnimationSettings* Settings = GetDefault<UPHYAnimationSettings>();
if (!CurrentRetargeter.IsValid())
{
CurrentRetargeter = Settings ? Settings->DefaultIKRetargeter : FSoftObjectPath(); CurrentRetargeter = Settings ? Settings->DefaultIKRetargeter : FSoftObjectPath();
}
if (Settings) if (Settings)
{ {
@@ -182,6 +193,16 @@ void UPHYCharacterMeshBridgeComponent::ConfigureRetargetFollowPose()
return; return;
} }
if (!DisplayMeshComponent->GetSkeletalMeshAsset())
{
return;
}
if (!SourceMeshComponent->IsRegistered() || !DisplayMeshComponent->IsRegistered())
{
return;
}
TSubclassOf<UAnimInstance> RetargetAnimClass = ResolveRetargetDisplayAnimClass(); TSubclassOf<UAnimInstance> RetargetAnimClass = ResolveRetargetDisplayAnimClass();
if (!RetargetAnimClass) if (!RetargetAnimClass)
{ {
@@ -201,6 +222,8 @@ void UPHYCharacterMeshBridgeComponent::ConfigureRetargetFollowPose()
DisplayMeshComponent->SetAnimInstanceClass(RetargetAnimClass); DisplayMeshComponent->SetAnimInstanceClass(RetargetAnimClass);
} }
DisplayMeshComponent->InitAnim(true);
if (!CurrentRetargeter.IsValid()) if (!CurrentRetargeter.IsValid())
{ {
UE_LOG(LogTemp, Warning, TEXT("PHYCharacterMeshBridgeComponent: DisplayMesh %s needs an IK Retargeter for cross-skeleton follow pose."), *GetNameSafe(DisplayMeshComponent)); UE_LOG(LogTemp, Warning, TEXT("PHYCharacterMeshBridgeComponent: DisplayMesh %s needs an IK Retargeter for cross-skeleton follow pose."), *GetNameSafe(DisplayMeshComponent));
@@ -210,7 +233,10 @@ void UPHYCharacterMeshBridgeComponent::ConfigureRetargetFollowPose()
UPHYRetargetPoseAnimInstance* RetargetAnimInstance = Cast<UPHYRetargetPoseAnimInstance>(DisplayMeshComponent->GetAnimInstance()); UPHYRetargetPoseAnimInstance* RetargetAnimInstance = Cast<UPHYRetargetPoseAnimInstance>(DisplayMeshComponent->GetAnimInstance());
if (!RetargetAnimInstance) 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; return;
} }
@@ -225,9 +251,12 @@ TSubclassOf<UAnimInstance> UPHYCharacterMeshBridgeComponent::ResolveRetargetDisp
} }
if (UClass* CurrentAnimClass = DisplayMeshComponent->GetAnimClass()) if (UClass* CurrentAnimClass = DisplayMeshComponent->GetAnimClass())
{
if (CurrentAnimClass != UPHYRetargetPoseAnimInstance::StaticClass())
{ {
return CurrentAnimClass; return CurrentAnimClass;
} }
}
const UPHYAnimationSettings* Settings = GetDefault<UPHYAnimationSettings>(); const UPHYAnimationSettings* Settings = GetDefault<UPHYAnimationSettings>();
if (Settings && !Settings->DefaultDisplayAnimClass.IsNull()) if (Settings && !Settings->DefaultDisplayAnimClass.IsNull())

View File

@@ -7,84 +7,7 @@
#include "Components/SkeletalMeshComponent.h" #include "Components/SkeletalMeshComponent.h"
#include "Retargeter/IKRetargeter.h" #include "Retargeter/IKRetargeter.h"
FPHYRetargetPoseAnimInstanceProxy::FPHYRetargetPoseAnimInstanceProxy(UAnimInstance* InAnimInstance, FAnimNode_RetargetPoseFromMesh* InRetargetNode) bool UPHYRetargetPoseAnimInstance::ConfigureRetargetPoseFromMesh(USkeletalMeshComponent* InSourceMeshComponent, const FSoftObjectPath InRetargeterPath)
: 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)
{ {
RetargetSourceMeshComponent = InSourceMeshComponent; RetargetSourceMeshComponent = InSourceMeshComponent;
RetargeterPath = InRetargeterPath; RetargeterPath = InRetargeterPath;
@@ -110,21 +33,6 @@ bool UPHYRetargetPoseAnimInstance::ConfigureRetargetPoseFromMesh(USkeletalMeshCo
return false; return false;
} }
FPHYRetargetPoseAnimInstanceProxy& Proxy = GetProxyOnGameThread<FPHYRetargetPoseAnimInstanceProxy>(); // AnimGraph 中的 Retarget Pose From Mesh 节点通过属性绑定读取 LoadedRetargeter。
Proxy.ConfigureRetargetPose(LoadedRetargeter, RetargetSourceMeshComponent);
return true; 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);
}

View File

@@ -1,7 +0,0 @@
// Copyright Epic Games, Inc. All Rights Reserved.
#include "Camera/PHYUGCSpringArmComponent.h"
#include UE_INLINE_GENERATED_CPP_BY_NAME(PHYUGCSpringArmComponent)
// 当前包装层只解除抽象限制,后续相机专家可在这里集中扩展 UGC 项目逻辑。

View File

@@ -10,10 +10,14 @@
#include "Class/PHYClassComponent.h" #include "Class/PHYClassComponent.h"
#include "Class/PHYClassSettings.h" #include "Class/PHYClassSettings.h"
#include "GGA_AbilitySystemComponent.h" #include "GGA_AbilitySystemComponent.h"
#include "AI/PHYAIController.h"
APHYAICharacter::APHYAICharacter(const FObjectInitializer& ObjectInitializer) APHYAICharacter::APHYAICharacter(const FObjectInitializer& ObjectInitializer)
: Super(ObjectInitializer) : Super(ObjectInitializer)
{ {
AIControllerClass = APHYAIController::StaticClass();
AutoPossessAI = EAutoPossessAI::PlacedInWorldOrSpawned;
AbilitySystemComponent = CreateDefaultSubobject<UGGA_AbilitySystemComponent>(TEXT("AbilitySystemComponent")); AbilitySystemComponent = CreateDefaultSubobject<UGGA_AbilitySystemComponent>(TEXT("AbilitySystemComponent"));
AbilitySystemComponent->SetIsReplicated(true); AbilitySystemComponent->SetIsReplicated(true);
AbilitySystemComponent->SetReplicationMode(EGameplayEffectReplicationMode::Mixed); AbilitySystemComponent->SetReplicationMode(EGameplayEffectReplicationMode::Mixed);

View File

@@ -17,8 +17,8 @@
#include "GGA_AbilitySystemComponent.h" #include "GGA_AbilitySystemComponent.h"
#include "GameFramework/CharacterMovementComponent.h" #include "GameFramework/CharacterMovementComponent.h"
#include "Interaction/GGS_InteractionSystemComponent.h" #include "Interaction/GGS_InteractionSystemComponent.h"
#include "Locomotion/PHYSLSMovementBridgeComponent.h"
#include "Net/UnrealNetwork.h" #include "Net/UnrealNetwork.h"
#include "PHYConfigSettings.h"
#include "PHYGameplayTags.h" #include "PHYGameplayTags.h"
#include "Ragdoll/GGS_RagdollComponent.h" #include "Ragdoll/GGS_RagdollComponent.h"
#include "Components/SLSCharacterMovementComponent.h" #include "Components/SLSCharacterMovementComponent.h"
@@ -27,11 +27,36 @@
#include "Targeting/GCS_TargetingSystemComponent.h" #include "Targeting/GCS_TargetingSystemComponent.h"
#include "Team/GCS_CombatTeamAgentComponent.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) APHYCharacterBase::APHYCharacterBase(const FObjectInitializer& ObjectInitializer)
: Super(ObjectInitializer.SetDefaultSubobjectClass<USLSCharacterMovementComponent>(ACharacter::CharacterMovementComponentName)) : Super(ObjectInitializer.SetDefaultSubobjectClass<USLSCharacterMovementComponent>(ACharacter::CharacterMovementComponentName))
{ {
bReplicates = true; bReplicates = true;
const UPHYCharacterSettings* CharacterSettings = GetDefault<UPHYCharacterSettings>();
if (USkeletalMeshComponent* SourceMeshComponent = GetMesh())
{
SourceMeshComponent->SetRelativeLocation(CharacterSettings ? CharacterSettings->DefaultMeshRelativeLocation : FVector(0.0f, 0.0f, -90.0f));
SourceMeshComponent->SetRelativeRotation(CharacterSettings ? CharacterSettings->DefaultMeshRelativeRotation : FRotator(0.0f, -90.0f, 0.0f));
}
CharacterStateComponent = CreateDefaultSubobject<UPHYCharacterStateComponent>(TEXT("CharacterStateComponent")); CharacterStateComponent = CreateDefaultSubobject<UPHYCharacterStateComponent>(TEXT("CharacterStateComponent"));
CombatSystemComponent = CreateDefaultSubobject<UGCS_CombatSystemComponent>(TEXT("CombatSystemComponent")); CombatSystemComponent = CreateDefaultSubobject<UGCS_CombatSystemComponent>(TEXT("CombatSystemComponent"));
TargetingSystemComponent = CreateDefaultSubobject<UGCS_TargetingSystemComponent>(TEXT("TargetingSystemComponent")); TargetingSystemComponent = CreateDefaultSubobject<UGCS_TargetingSystemComponent>(TEXT("TargetingSystemComponent"));
@@ -40,7 +65,6 @@ APHYCharacterBase::APHYCharacterBase(const FObjectInitializer& ObjectInitializer
RagdollComponent = CreateDefaultSubobject<UGGS_RagdollComponent>(TEXT("RagdollComponent")); RagdollComponent = CreateDefaultSubobject<UGGS_RagdollComponent>(TEXT("RagdollComponent"));
ContextEffectComponent = CreateDefaultSubobject<UGES_ContextEffectComponent>(TEXT("ContextEffectComponent")); ContextEffectComponent = CreateDefaultSubobject<UGES_ContextEffectComponent>(TEXT("ContextEffectComponent"));
MeshBridgeComponent = CreateDefaultSubobject<UPHYCharacterMeshBridgeComponent>(TEXT("MeshBridgeComponent")); MeshBridgeComponent = CreateDefaultSubobject<UPHYCharacterMeshBridgeComponent>(TEXT("MeshBridgeComponent"));
SLSMovementBridgeComponent = CreateDefaultSubobject<UPHYSLSMovementBridgeComponent>(TEXT("SLSMovementBridgeComponent"));
SLSIntegrationComponent = CreateDefaultSubobject<USLSIntegrationComponent>(TEXT("SLSIntegrationComponent")); SLSIntegrationComponent = CreateDefaultSubobject<USLSIntegrationComponent>(TEXT("SLSIntegrationComponent"));
USLSIntegrationDataAsset* SLSIntegrationRuntimeDataAsset = CreateDefaultSubobject<USLSIntegrationDataAsset>(TEXT("SLSIntegrationRuntimeDataAsset")); USLSIntegrationDataAsset* SLSIntegrationRuntimeDataAsset = CreateDefaultSubobject<USLSIntegrationDataAsset>(TEXT("SLSIntegrationRuntimeDataAsset"));
@@ -80,6 +104,8 @@ APHYCharacterBase::APHYCharacterBase(const FObjectInitializer& ObjectInitializer
DisplayMeshComponent = CreateDefaultSubobject<USkeletalMeshComponent>(TEXT("DisplayMeshComponent")); DisplayMeshComponent = CreateDefaultSubobject<USkeletalMeshComponent>(TEXT("DisplayMeshComponent"));
DisplayMeshComponent->SetupAttachment(GetMesh()); DisplayMeshComponent->SetupAttachment(GetMesh());
DisplayMeshComponent->SetRelativeLocation(FVector::ZeroVector);
DisplayMeshComponent->SetRelativeRotation(FRotator::ZeroRotator);
DisplayMeshComponent->SetCollisionEnabled(ECollisionEnabled::NoCollision); DisplayMeshComponent->SetCollisionEnabled(ECollisionEnabled::NoCollision);
DisplayMeshComponent->SetGenerateOverlapEvents(false); DisplayMeshComponent->SetGenerateOverlapEvents(false);
} }
@@ -91,16 +117,21 @@ void APHYCharacterBase::PreInitializeComponents()
UGameFrameworkComponentManager::AddGameFrameworkComponentReceiver(this); UGameFrameworkComponentManager::AddGameFrameworkComponentReceiver(this);
} }
void APHYCharacterBase::BeginPlay() void APHYCharacterBase::PostInitializeComponents()
{ {
Super::BeginPlay(); Super::PostInitializeComponents();
ApplyCharacterSettings();
if (MeshBridgeComponent) if (MeshBridgeComponent)
{ {
MeshBridgeComponent->InitializeMeshBridge(this, DisplayMeshComponent); MeshBridgeComponent->InitializeMeshBridge(this, DisplayMeshComponent);
} }
}
void APHYCharacterBase::BeginPlay()
{
Super::BeginPlay();
ApplyCharacterSettings();
if (RagdollComponent) if (RagdollComponent)
{ {
@@ -112,9 +143,20 @@ void APHYCharacterBase::BeginPlay()
ContextEffectComponent->SetGameplayTagsProvider(this); 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); UGameFrameworkComponentManager::SendGameFrameworkComponentExtensionEvent(this, UGameFrameworkComponentManager::NAME_GameActorReady);
@@ -249,15 +291,10 @@ bool APHYCharacterBase::HandleInputTag(const FGameplayTag InputTag, const ETrigg
if (InputTag == PHYGameplayTags::Input_Aim) if (InputTag == PHYGameplayTags::Input_Aim)
{ {
if (USLSCharacterMovementComponent* SLSMovementComponent = GetSLSCharacterMovementComponent()) SetRotationMode_Implementation(bPressed ? PHYGameplayTags::State_Rotation_Aiming : PHYGameplayTags::State_Rotation_OrientToMovement);
{
SLSMovementComponent->UpdateRotationMode(bPressed ? ESLSRotationMode::AimingDirection : ESLSRotationMode::VelocityDirection);
}
if (CharacterStateComponent) if (CharacterStateComponent)
{ {
CharacterStateComponent->SetMovementSet(bPressed ? PHYGameplayTags::State_MovementSet_Aiming : PHYGameplayTags::State_MovementSet_Default); CharacterStateComponent->SetMovementSet(bPressed ? PHYGameplayTags::State_MovementSet_Aiming : PHYGameplayTags::State_MovementSet_Default);
CharacterStateComponent->SetRotationMode(bPressed ? PHYGameplayTags::State_Rotation_Strafe : PHYGameplayTags::State_Rotation_OrientToMovement);
} }
return true; return true;
@@ -470,6 +507,11 @@ void APHYCharacterBase::SetRotationMode_Implementation(const FGameplayTag NewRot
{ {
CharacterStateComponent->SetRotationMode(NewRotationMode); CharacterStateComponent->SetRotationMode(NewRotationMode);
} }
if (USLSCharacterMovementComponent* SLSMovementComponent = GetSLSCharacterMovementComponent())
{
SLSMovementComponent->UpdateRotationMode(GetSLSRotationMode(NewRotationMode));
}
} }
FGameplayTag APHYCharacterBase::GetRotationMode_Implementation() const FGameplayTag APHYCharacterBase::GetRotationMode_Implementation() const
@@ -613,6 +655,12 @@ void APHYCharacterBase::ApplyCharacterSettings()
MoveComponent->MaxAcceleration = Settings->DefaultMaxAcceleration; MoveComponent->MaxAcceleration = Settings->DefaultMaxAcceleration;
} }
if (USkeletalMeshComponent* SourceMeshComponent = GetMesh())
{
SourceMeshComponent->SetRelativeLocation(Settings->DefaultMeshRelativeLocation);
SourceMeshComponent->SetRelativeRotation(Settings->DefaultMeshRelativeRotation);
}
if (CharacterStateComponent && GetCharacterMovement() && GetCharacterMovement()->IsFalling()) if (CharacterStateComponent && GetCharacterMovement() && GetCharacterMovement()->IsFalling())
{ {
CharacterStateComponent->SetMovementState(PHYGameplayTags::State_Movement_Falling); CharacterStateComponent->SetMovementState(PHYGameplayTags::State_Movement_Falling);

View File

@@ -5,7 +5,6 @@
#include UE_INLINE_GENERATED_CPP_BY_NAME(PHYPlayerCharacter) #include UE_INLINE_GENERATED_CPP_BY_NAME(PHYPlayerCharacter)
#include "Camera/CameraComponent.h" #include "Camera/CameraComponent.h"
#include "Camera/PHYUGCSpringArmComponent.h"
#include "Class/PHYClassComponent.h" #include "Class/PHYClassComponent.h"
#include "GameFramework/SpringArmComponent.h" #include "GameFramework/SpringArmComponent.h"
#include "GIPS_InputSystemComponent.h" #include "GIPS_InputSystemComponent.h"
@@ -16,7 +15,7 @@ APHYPlayerCharacter::APHYPlayerCharacter(const FObjectInitializer& ObjectInitial
{ {
InputSystemComponent = CreateDefaultSubobject<UGIPS_InputSystemComponent>(TEXT("InputSystemComponent")); InputSystemComponent = CreateDefaultSubobject<UGIPS_InputSystemComponent>(TEXT("InputSystemComponent"));
CameraBoom = CreateDefaultSubobject<UPHYUGCSpringArmComponent>(TEXT("CameraBoom")); CameraBoom = CreateDefaultSubobject<USpringArmComponent>(TEXT("CameraBoom"));
CameraBoom->SetupAttachment(GetRootComponent()); CameraBoom->SetupAttachment(GetRootComponent());
CameraBoom->TargetArmLength = 320.0f; CameraBoom->TargetArmLength = 320.0f;
CameraBoom->bUsePawnControlRotation = true; CameraBoom->bUsePawnControlRotation = true;

View File

@@ -5,6 +5,7 @@
#include UE_INLINE_GENERATED_CPP_BY_NAME(PHYClassComponent) #include UE_INLINE_GENERATED_CPP_BY_NAME(PHYClassComponent)
#include "AbilitySystemComponent.h" #include "AbilitySystemComponent.h"
#include "Animation/AnimInstance.h"
#include "AbilitySystem/Attributes/PHYAttributeCalculationLibrary.h" #include "AbilitySystem/Attributes/PHYAttributeCalculationLibrary.h"
#include "AbilitySystem/Attributes/PHYCombatAttributeSet.h" #include "AbilitySystem/Attributes/PHYCombatAttributeSet.h"
#include "AbilitySystem/Attributes/PHYCoreAttributeSet.h" #include "AbilitySystem/Attributes/PHYCoreAttributeSet.h"
@@ -92,6 +93,25 @@ USkeletalMesh* UPHYClassComponent::GetConfiguredPlayerMesh() const
return nullptr; 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 FSoftObjectPath UPHYClassComponent::GetConfiguredPlayerRetargeter() const
{ {
const UPHYClassSettings* ClassSettings = GetDefault<UPHYClassSettings>(); const UPHYClassSettings* ClassSettings = GetDefault<UPHYClassSettings>();
@@ -133,7 +153,13 @@ bool UPHYClassComponent::ApplyConfiguredPlayerMeshIfAllowed(AActor* AvatarActor)
const FSoftObjectPath PlayerRetargeter = GetConfiguredPlayerRetargeter(); const FSoftObjectPath PlayerRetargeter = GetConfiguredPlayerRetargeter();
UPHYCharacterMeshBridgeComponent* MeshBridge = Character->GetMeshBridgeComponent(); UPHYCharacterMeshBridgeComponent* MeshBridge = Character->GetMeshBridgeComponent();
return MeshBridge ? MeshBridge->ApplyDisplayMesh(PlayerMesh, nullptr, PlayerRetargeter) : false; if (!MeshBridge)
{
return false;
}
MeshBridge->InitializeMeshBridge(Character, Character->GetDisplayMeshComponent());
return MeshBridge->ApplyDisplayMesh(PlayerMesh, GetConfiguredPlayerDisplayAnimClass(), PlayerRetargeter);
} }
bool UPHYClassComponent::ApplyClassAttributesToAbilitySystem(UAbilitySystemComponent* AbilitySystemComponent) const bool UPHYClassComponent::ApplyClassAttributesToAbilitySystem(UAbilitySystemComponent* AbilitySystemComponent) const

View File

@@ -0,0 +1,22 @@
// Copyright Epic Games, Inc. All Rights Reserved.
#include "Game/PHYGameFrameworkSettings.h"
#include UE_INLINE_GENERATED_CPP_BY_NAME(PHYGameFrameworkSettings)
#include "Characters/PHYPlayerCharacter.h"
#include "Game/PHYGameState.h"
#include "Game/PHYHUD.h"
#include "GameFramework/SpectatorPawn.h"
#include "Player/PHYPlayerController.h"
#include "Player/PHYPlayerState.h"
UPHYGameFrameworkSettings::UPHYGameFrameworkSettings()
{
DefaultPawnClass = APHYPlayerCharacter::StaticClass();
PlayerControllerClass = APHYPlayerController::StaticClass();
PlayerStateClass = APHYPlayerState::StaticClass();
GameStateClass = APHYGameState::StaticClass();
HUDClass = APHYHUD::StaticClass();
SpectatorClass = ASpectatorPawn::StaticClass();
}

View File

@@ -0,0 +1,116 @@
// Copyright Epic Games, Inc. All Rights Reserved.
#include "Game/PHYGameModeBase.h"
#include UE_INLINE_GENERATED_CPP_BY_NAME(PHYGameModeBase)
#include "Characters/PHYPlayerCharacter.h"
#include "Game/PHYGameFrameworkSettings.h"
#include "Game/PHYGameState.h"
#include "Game/PHYHUD.h"
#include "GameFramework/SpectatorPawn.h"
#include "Player/PHYPlayerController.h"
#include "Player/PHYPlayerState.h"
namespace
{
template <typename TBase>
void ApplyConfiguredClass(const TSoftClassPtr<TBase>& ConfiguredClass, TSubclassOf<TBase>& TargetClass)
{
if (ConfiguredClass.IsNull())
{
return;
}
if (UClass* LoadedClass = ConfiguredClass.LoadSynchronous())
{
TargetClass = LoadedClass;
}
}
}
APHYGameModeBase::APHYGameModeBase(const FObjectInitializer& ObjectInitializer)
: Super(ObjectInitializer)
{
DefaultPawnClass = APHYPlayerCharacter::StaticClass();
PlayerControllerClass = APHYPlayerController::StaticClass();
PlayerStateClass = APHYPlayerState::StaticClass();
GameStateClass = APHYGameState::StaticClass();
HUDClass = APHYHUD::StaticClass();
SpectatorClass = ASpectatorPawn::StaticClass();
bUseSeamlessTravel = true;
bStartPlayersAsSpectators = false;
}
void APHYGameModeBase::InitGame(const FString& MapName, const FString& Options, FString& ErrorMessage)
{
ApplyGameFrameworkSettings();
Super::InitGame(MapName, Options, ErrorMessage);
}
void APHYGameModeBase::InitGameState()
{
Super::InitGameState();
SetGameFrameworkReady(false);
}
void APHYGameModeBase::StartPlay()
{
Super::StartPlay();
SetGameFrameworkReady(true);
}
void APHYGameModeBase::PostLogin(APlayerController* NewPlayer)
{
Super::PostLogin(NewPlayer);
// 玩家登录后的队伍、职业、存档恢复由后续对应系统在这里挂接。
}
void APHYGameModeBase::Logout(AController* Exiting)
{
// 玩家退出前的持久化和队伍清理由后续对应系统在这里挂接。
Super::Logout(Exiting);
}
UClass* APHYGameModeBase::GetDefaultPawnClassForController_Implementation(AController* InController)
{
(void)InController;
return DefaultPawnClass ? DefaultPawnClass.Get() : Super::GetDefaultPawnClassForController_Implementation(InController);
}
APHYGameState* APHYGameModeBase::GetPHYGameState() const
{
return GetGameState<APHYGameState>();
}
void APHYGameModeBase::ApplyGameFrameworkSettings()
{
const UPHYGameFrameworkSettings* Settings = GetDefault<UPHYGameFrameworkSettings>();
if (!Settings)
{
return;
}
ApplyConfiguredClass(Settings->DefaultPawnClass, DefaultPawnClass);
ApplyConfiguredClass(Settings->PlayerControllerClass, PlayerControllerClass);
ApplyConfiguredClass(Settings->PlayerStateClass, PlayerStateClass);
ApplyConfiguredClass(Settings->GameStateClass, GameStateClass);
ApplyConfiguredClass(Settings->HUDClass, HUDClass);
ApplyConfiguredClass(Settings->SpectatorClass, SpectatorClass);
bUseSeamlessTravel = Settings->bUseSeamlessTravel;
bStartPlayersAsSpectators = Settings->bStartPlayersAsSpectators;
}
void APHYGameModeBase::SetGameFrameworkReady(const bool bNewReady)
{
if (APHYGameState* PHYGameState = GetPHYGameState())
{
PHYGameState->SetGameFrameworkReady(bNewReady);
}
}

View File

@@ -0,0 +1,36 @@
// Copyright Epic Games, Inc. All Rights Reserved.
#include "Game/PHYGameState.h"
#include UE_INLINE_GENERATED_CPP_BY_NAME(PHYGameState)
#include "Net/UnrealNetwork.h"
APHYGameState::APHYGameState(const FObjectInitializer& ObjectInitializer)
: Super(ObjectInitializer)
{
bReplicates = true;
}
void APHYGameState::GetLifetimeReplicatedProps(TArray<FLifetimeProperty>& OutLifetimeProps) const
{
Super::GetLifetimeReplicatedProps(OutLifetimeProps);
DOREPLIFETIME(ThisClass, bGameFrameworkReady);
}
void APHYGameState::SetGameFrameworkReady(const bool bNewReady)
{
if (!HasAuthority())
{
return;
}
bGameFrameworkReady = bNewReady;
OnRep_GameFrameworkReady();
}
void APHYGameState::OnRep_GameFrameworkReady()
{
// 预留给 HUD、UI 或战局阶段系统监听框架就绪状态。
}

View File

@@ -0,0 +1,177 @@
// Copyright Epic Games, Inc. All Rights Reserved.
#include "Game/PHYHUD.h"
#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)
{
}
void APHYHUD::BeginPlay()
{
Super::BeginPlay();
InitializeHUDForPlayer(GetOwningPlayerController());
}
void APHYHUD::InitializeHUDForPlayer(APlayerController* OwningPlayerController)
{
if (!OwningPlayerController || bHUDInitialized)
{
return;
}
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);
}

View File

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

View File

@@ -18,5 +18,6 @@ namespace PHYGameplayTags
UE_DEFINE_GAMEPLAY_TAG(State_MovementSet_Default, "State.MovementSet.Default"); 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_MovementSet_Aiming, "State.MovementSet.Aiming");
UE_DEFINE_GAMEPLAY_TAG(State_Rotation_OrientToMovement, "State.Rotation.OrientToMovement"); 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");
} }

View 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");
}

View File

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

View File

@@ -1,63 +0,0 @@
// Copyright Epic Games, Inc. All Rights Reserved.
#include "Locomotion/PHYSLSMovementBridgeComponent.h"
#include UE_INLINE_GENERATED_CPP_BY_NAME(PHYSLSMovementBridgeComponent)
#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;
}

View File

@@ -1,19 +0,0 @@
// Copyright Epic Games, Inc. All Rights Reserved.
#include "Player/PHYPlayerCameraManager.h"
#include UE_INLINE_GENERATED_CPP_BY_NAME(PHYPlayerCameraManager)
#include "Player/PHYPlayerController.h"
FRotator APHYPlayerCameraManager::GetRotationInput_Implementation() const
{
const APHYPlayerController* PHYController = Cast<APHYPlayerController>(PCOwner);
return PHYController ? PHYController->GetCachedRotationInput() : FRotator::ZeroRotator;
}
FVector APHYPlayerCameraManager::GetMovementControlInput_Implementation() const
{
const APHYPlayerController* PHYController = Cast<APHYPlayerController>(PCOwner);
return PHYController ? PHYController->GetCachedMovementControlInput() : FVector::ZeroVector;
}

View File

@@ -5,9 +5,11 @@
#include UE_INLINE_GENERATED_CPP_BY_NAME(PHYPlayerController) #include UE_INLINE_GENERATED_CPP_BY_NAME(PHYPlayerController)
#include "Characters/PHYPlayerCharacter.h" #include "Characters/PHYPlayerCharacter.h"
#include "Characters/PHYCharacterSettings.h"
#include "Components/SLSCharacterMovementComponent.h"
#include "Game/PHYHUD.h"
#include "GIPS_InputSystemComponent.h" #include "GIPS_InputSystemComponent.h"
#include "InputActionValue.h" #include "InputActionValue.h"
#include "Player/PHYPlayerCameraManager.h"
#include "PHYGameplayTags.h" #include "PHYGameplayTags.h"
namespace namespace
@@ -40,9 +42,14 @@ namespace
APHYPlayerController::APHYPlayerController(const FObjectInitializer& ObjectInitializer) APHYPlayerController::APHYPlayerController(const FObjectInitializer& ObjectInitializer)
: Super(ObjectInitializer) : Super(ObjectInitializer)
{ {
PlayerCameraManagerClass = APHYPlayerCameraManager::StaticClass(); const UPHYCharacterSettings* CharacterSettings = GetDefault<UPHYCharacterSettings>();
if (CharacterSettings && !CharacterSettings->DefaultPlayerCameraManagerClass.IsNull())
InputSystemComponent = CreateDefaultSubobject<UGIPS_InputSystemComponent>(TEXT("InputSystemComponent")); {
if (UClass* CameraManagerClass = CharacterSettings->DefaultPlayerCameraManagerClass.LoadSynchronous())
{
PlayerCameraManagerClass = CameraManagerClass;
}
}
} }
void APHYPlayerController::BeginPlay() void APHYPlayerController::BeginPlay()
@@ -68,13 +75,10 @@ UGIPS_InputSystemComponent* APHYPlayerController::GetInputSystemComponent() cons
{ {
if (const APHYPlayerCharacter* PHYCharacter = GetPHYPlayerCharacter()) if (const APHYPlayerCharacter* PHYCharacter = GetPHYPlayerCharacter())
{ {
if (UGIPS_InputSystemComponent* CharacterInputSystemComponent = PHYCharacter->GetInputSystemComponent()) return PHYCharacter->GetInputSystemComponent();
{
return CharacterInputSystemComponent;
}
} }
return InputSystemComponent; return nullptr;
} }
void APHYPlayerController::BindInputEvents() void APHYPlayerController::BindInputEvents()
@@ -112,6 +116,15 @@ void APHYPlayerController::OnReceivedInput(const FInputActionInstance& ActionDat
return; return;
} }
if (InputTag == PHYGameplayTags::Input_Menu_Attribute)
{
if (!ConsumeAttributeMenuProcessorGuard(TriggerEvent))
{
RouteOpenAttributeMenuInput(ActionData, TriggerEvent);
}
return;
}
if (APHYPlayerCharacter* PHYCharacter = GetPHYPlayerCharacter()) if (APHYPlayerCharacter* PHYCharacter = GetPHYPlayerCharacter())
{ {
PHYCharacter->HandleInputTag(InputTag, TriggerEvent); PHYCharacter->HandleInputTag(InputTag, TriggerEvent);
@@ -142,6 +155,18 @@ bool APHYPlayerController::RouteLookInputFromProcessor(const FInputActionInstanc
return RouteLookInput(ActionData, TriggerEvent); 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) bool APHYPlayerController::HandleMoveInput(const FInputActionInstance& ActionData, const ETriggerEvent TriggerEvent)
{ {
APHYPlayerCharacter* PHYCharacter = GetPHYPlayerCharacter(); APHYPlayerCharacter* PHYCharacter = GetPHYPlayerCharacter();
@@ -153,21 +178,19 @@ bool APHYPlayerController::HandleMoveInput(const FInputActionInstance& ActionDat
CachedMovementInput = IsInputReleased(TriggerEvent) ? FVector2D::ZeroVector : ExtractAxis2D(ActionData.GetValue()); CachedMovementInput = IsInputReleased(TriggerEvent) ? FVector2D::ZeroVector : ExtractAxis2D(ActionData.GetValue());
const FRotator CurrentControlRotation = GetControlRotation(); const FRotator YawRotation(0.0f, PHYCharacter->GetControlRotation().Yaw, 0.0f);
const FRotator YawRotation(0.0f, CurrentControlRotation.Yaw, 0.0f);
const FVector ForwardDirection = FRotationMatrix(YawRotation).GetUnitAxis(EAxis::X); const FVector ForwardDirection = FRotationMatrix(YawRotation).GetUnitAxis(EAxis::X);
const FVector RightDirection = FRotationMatrix(YawRotation).GetUnitAxis(EAxis::Y); const FVector RightDirection = FRotationMatrix(YawRotation).GetUnitAxis(EAxis::Y);
const FVector WorldIntent = (ForwardDirection * CachedMovementInput.Y) + (RightDirection * CachedMovementInput.X); const FVector WorldIntent = (ForwardDirection * CachedMovementInput.Y) + (RightDirection * CachedMovementInput.X);
PHYCharacter->SetMovementIntent(WorldIntent); PHYCharacter->SetMovementIntent(WorldIntent);
if (!CachedMovementInput.IsNearlyZero()) if (USLSCharacterMovementComponent* SLSMovementComponent = PHYCharacter->GetSLSCharacterMovementComponent())
{ {
PHYCharacter->AddMovementInput(ForwardDirection, CachedMovementInput.Y); SLSMovementComponent->Input_OnMove(CachedMovementInput);
PHYCharacter->AddMovementInput(RightDirection, CachedMovementInput.X); return true;
} }
return true; return false;
} }
bool APHYPlayerController::ConsumeMoveProcessorGuard(const ETriggerEvent TriggerEvent) bool APHYPlayerController::ConsumeMoveProcessorGuard(const ETriggerEvent TriggerEvent)
@@ -198,16 +221,49 @@ bool APHYPlayerController::ConsumeLookProcessorGuard(const ETriggerEvent Trigger
return false; 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) bool APHYPlayerController::HandleLookInput(const FInputActionInstance& ActionData, const ETriggerEvent TriggerEvent)
{ {
CachedLookInput = IsInputReleased(TriggerEvent) ? FVector2D::ZeroVector : ExtractAxis2D(ActionData.GetValue()); CachedLookInput = IsInputReleased(TriggerEvent) ? FVector2D::ZeroVector : ExtractAxis2D(ActionData.GetValue());
CachedRotationInput = FRotator(CachedLookInput.Y, CachedLookInput.X, 0.0f); CachedRotationInput = FRotator(CachedLookInput.Y, CachedLookInput.X, 0.0f);
if (!CachedLookInput.IsNearlyZero()) APHYPlayerCharacter* PHYCharacter = GetPHYPlayerCharacter();
if (!PHYCharacter)
{ {
AddYawInput(CachedLookInput.X); return false;
AddPitchInput(CachedLookInput.Y);
} }
if (USLSCharacterMovementComponent* SLSMovementComponent = PHYCharacter->GetSLSCharacterMovementComponent())
{
SLSMovementComponent->Input_OnLook(CachedLookInput);
return true; return true;
}
return false;
} }

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -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;
}

View 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()));
}
}

View 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));
}
}

View 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;
}

View File

@@ -72,6 +72,9 @@ public:
FSoftObjectPath GetCurrentRetargeter() const { return CurrentRetargeter; } FSoftObjectPath GetCurrentRetargeter() const { return CurrentRetargeter; }
protected: protected:
/** @brief 组件进入运行时后重新确认 Follow Pose 模式,覆盖构造期组件未注册的情况。 */
virtual void BeginPlay() override;
/** @brief 应用配置中的源运动 Mesh 和 AnimClass 默认值。 */ /** @brief 应用配置中的源运动 Mesh 和 AnimClass 默认值。 */
void ApplySourceDefaults(); void ApplySourceDefaults();

View File

@@ -3,8 +3,6 @@
#pragma once #pragma once
#include "Animation/AnimInstance.h" #include "Animation/AnimInstance.h"
#include "Animation/AnimInstanceProxy.h"
#include "AnimNodes/AnimNode_RetargetPoseFromMesh.h"
#include "UObject/SoftObjectPath.h" #include "UObject/SoftObjectPath.h"
#include "PHYRetargetPoseAnimInstance.generated.h" #include "PHYRetargetPoseAnimInstance.generated.h"
@@ -12,46 +10,19 @@ class UIKRetargeter;
class USkeletalMeshComponent; 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() UCLASS(Transient, BlueprintType, Blueprintable)
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)
class PHY_API UPHYRetargetPoseAnimInstance : public UAnimInstance class PHY_API UPHYRetargetPoseAnimInstance : public UAnimInstance
{ {
GENERATED_BODY() GENERATED_BODY()
public: public:
/** /**
* @brief 设置 Retarget Pose From Mesh 的运行时输入。 * @brief 设置 Retarget AnimBlueprint 读取的运行时输入。
* @param InSourceMeshComponent 提供运动姿态的源 Mesh 组件。 * @param InSourceMeshComponent 提供运动姿态的源 Mesh 组件。
* @param InRetargeterPath IK Retargeter 资产软路径。 * @param InRetargeterPath IK Retargeter 资产软路径。
* @return SourceMesh 与 Retargeter 均有效时返回 true。 * @return SourceMesh 与 Retargeter 均有效时返回 true。
@@ -63,29 +34,24 @@ public:
UFUNCTION(BlueprintCallable, BlueprintPure, Category="PHY|Animation") UFUNCTION(BlueprintCallable, BlueprintPure, Category="PHY|Animation")
USkeletalMeshComponent* GetRetargetSourceMeshComponent() const { return RetargetSourceMeshComponent; } USkeletalMeshComponent* GetRetargetSourceMeshComponent() const { return RetargetSourceMeshComponent; }
/** @brief 获取当前已加载的 IK Retargeter 资产,供 AnimBlueprint 节点绑定。 */
UFUNCTION(BlueprintCallable, BlueprintPure, Category="PHY|Animation")
UIKRetargeter* GetLoadedRetargeter() const { return LoadedRetargeter; }
/** @brief 获取当前 IK Retargeter 资产路径。 */ /** @brief 获取当前 IK Retargeter 资产路径。 */
UFUNCTION(BlueprintCallable, BlueprintPure, Category="PHY|Animation") UFUNCTION(BlueprintCallable, BlueprintPure, Category="PHY|Animation")
FSoftObjectPath GetRetargeterPath() const { return RetargeterPath; } FSoftObjectPath GetRetargeterPath() const { return RetargeterPath; }
virtual void NativeInitializeAnimation() override;
protected: protected:
virtual FAnimInstanceProxy* CreateAnimInstanceProxy() override;
private:
/** @brief Retarget Pose From Mesh 运行时节点。 */
UPROPERTY()
FAnimNode_RetargetPoseFromMesh RetargetNode;
/** @brief 当前用于重定向的源 Mesh 组件。 */ /** @brief 当前用于重定向的源 Mesh 组件。 */
UPROPERTY(Transient) UPROPERTY(Transient, BlueprintReadOnly, Category="PHY|Animation")
TObjectPtr<USkeletalMeshComponent> RetargetSourceMeshComponent; TObjectPtr<USkeletalMeshComponent> RetargetSourceMeshComponent;
/** @brief 当前使用的 IK Retargeter 资产。 */ /** @brief 当前使用的 IK Retargeter 资产AnimBlueprint 的节点应绑定到该属性或 Getter。 */
UPROPERTY(Transient) UPROPERTY(Transient, BlueprintReadOnly, Category="PHY|Animation")
TObjectPtr<UIKRetargeter> LoadedRetargeter; TObjectPtr<UIKRetargeter> LoadedRetargeter;
/** @brief 当前 IK Retargeter 资产软路径。 */ /** @brief 当前 IK Retargeter 资产软路径。 */
UPROPERTY(Transient) UPROPERTY(Transient, BlueprintReadOnly, Category="PHY|Animation")
FSoftObjectPath RetargeterPath; FSoftObjectPath RetargeterPath;
}; };

View File

@@ -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()
};

View File

@@ -19,15 +19,15 @@ class UGGS_InteractionSystemComponent;
class UGGS_RagdollComponent; class UGGS_RagdollComponent;
class UPHYCharacterMeshBridgeComponent; class UPHYCharacterMeshBridgeComponent;
class UPHYCharacterStateComponent; class UPHYCharacterStateComponent;
class UPHYSLSMovementBridgeComponent;
class USkeletalMeshComponent; class USkeletalMeshComponent;
class USLSCharacterMovementComponent; class USLSCharacterMovementComponent;
class USLSIntegrationComponent; class USLSIntegrationComponent;
/** /**
* @brief PHY 玩家和 AI 共用角色基类。 * @brief PHY 玩家和 AI 共用角色基类。
* *
* 基类直接继承 ACharacter并通过项目级组件和接口适配 GAS、GCS、GGS、UGC 与后续运动系统。 * 基类直接继承 ACharacter并通过项目级组件和接口适配 GAS、GCS、GGS、
* UGC 与 Smooth Locomotion 等系统。
*/ */
UCLASS(Abstract, BlueprintType, Blueprintable) UCLASS(Abstract, BlueprintType, Blueprintable)
class PHY_API APHYCharacterBase : public ACharacter, public IAbilitySystemInterface, public IGameplayTagAssetInterface, public IGCS_CombatEntityInterface class PHY_API APHYCharacterBase : public ACharacter, public IAbilitySystemInterface, public IGameplayTagAssetInterface, public IGCS_CombatEntityInterface
@@ -35,19 +35,22 @@ class PHY_API APHYCharacterBase : public ACharacter, public IAbilitySystemInterf
GENERATED_BODY() GENERATED_BODY()
public: public:
/** @brief 构造基础角色组件。 */ /** @brief 创建角色基类并挂载项目核心组件。 */
APHYCharacterBase(const FObjectInitializer& ObjectInitializer = FObjectInitializer::Get()); APHYCharacterBase(const FObjectInitializer& ObjectInitializer = FObjectInitializer::Get());
/** @brief 注册 ModularGameplay 组件接收者。 */ /** @brief 注册 ModularGameplay 组件接收者。 */
virtual void PreInitializeComponents() override; virtual void PreInitializeComponents() override;
/** @brief 初始化角色运行时组件。 */ /** @brief 在 Possess 和 PlayerState 初始化前绑定显示 Mesh 桥接组件。 */
virtual void PostInitializeComponents() override;
/** @brief 初始化角色运行时状态和组件。 */
virtual void BeginPlay() override; virtual void BeginPlay() override;
/** @brief 移除 ModularGameplay 组件接收者。 */ /** @brief 移除 ModularGameplay 组件接收者。 */
virtual void EndPlay(const EEndPlayReason::Type EndPlayReason) override; virtual void EndPlay(const EEndPlayReason::Type EndPlayReason) override;
/** @brief 控制器接管时刷新项目状态。 */ /** @brief 控制器接管角色时刷新项目状态。 */
virtual void PossessedBy(AController* NewController) override; virtual void PossessedBy(AController* NewController) override;
/** @brief 控制器复制到客户端后刷新项目状态。 */ /** @brief 控制器复制到客户端后刷新项目状态。 */
@@ -59,7 +62,11 @@ public:
/** @brief 收集 ASC 与角色状态组件提供的 GameplayTag。 */ /** @brief 收集 ASC 与角色状态组件提供的 GameplayTag。 */
virtual void GetOwnedGameplayTags(FGameplayTagContainer& TagContainer) const override; virtual void GetOwnedGameplayTags(FGameplayTagContainer& TagContainer) const override;
/** @brief 初始化当前 Avatar 对应的 ASC。 */ /**
* @brief 初始化当前 Avatar 对应的 ASC。
* @param NewAbilitySystemComponent 要绑定到该角色的 ASC。
* @param OwnerActor ASC 的 OwnerActor玩家通常为 PlayerStateAI 通常为自身。
*/
UFUNCTION(BlueprintCallable, Category="PHY|Character|Ability") UFUNCTION(BlueprintCallable, Category="PHY|Character|Ability")
virtual void InitializeAbilitySystem(UAbilitySystemComponent* NewAbilitySystemComponent, AActor* OwnerActor); virtual void InitializeAbilitySystem(UAbilitySystemComponent* NewAbilitySystemComponent, AActor* OwnerActor);
@@ -71,23 +78,42 @@ public:
UFUNCTION(BlueprintCallable, BlueprintPure, Category="PHY|Character|Ability") UFUNCTION(BlueprintCallable, BlueprintPure, Category="PHY|Character|Ability")
bool IsAbilitySystemReady() const { return bAbilitySystemReady; } 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") UFUNCTION(BlueprintCallable, Category="PHY|Character|Input")
virtual bool HandleInputTag(FGameplayTag InputTag, ETriggerEvent TriggerEvent); virtual bool HandleInputTag(FGameplayTag InputTag, ETriggerEvent TriggerEvent);
/** @brief 设置移动意图,并在客户端请求服务端同步。 */ /**
* @brief 设置角色移动意图,并在客户端请求服务端同步。
* @param NewMovementIntent 世界空间移动意图。
*/
UFUNCTION(BlueprintCallable, Category="PHY|Character|Movement") UFUNCTION(BlueprintCallable, Category="PHY|Character|Movement")
virtual void SetMovementIntent(FVector NewMovementIntent); virtual void SetMovementIntent(FVector NewMovementIntent);
/** @brief 设置项目级战斗目标。 */ /**
* @brief 设置项目级战斗目标。
* @param NewCombatTargetActor 新的战斗目标 Actor。
*/
UFUNCTION(BlueprintCallable, Category="PHY|Character|Combat") UFUNCTION(BlueprintCallable, Category="PHY|Character|Combat")
virtual void SetCombatTargetActor(AActor* NewCombatTargetActor); virtual void SetCombatTargetActor(AActor* NewCombatTargetActor);
/** @brief 请求开始交互。 */ /**
* @brief 请求开始交互。
* @param OptionIndex 交互选项索引。
* @return 交互请求被接受时返回 true。
*/
UFUNCTION(BlueprintCallable, Category="PHY|Character|Interaction") UFUNCTION(BlueprintCallable, Category="PHY|Character|Interaction")
virtual bool RequestInteraction(int32 OptionIndex = 0); virtual bool RequestInteraction(int32 OptionIndex = 0);
/** @brief 请求一次性即时交互。 */ /**
* @brief 请求执行一次即时交互。
* @param OptionIndex 交互选项索引。
* @return 交互请求被接受时返回 true。
*/
UFUNCTION(BlueprintCallable, Category="PHY|Character|Interaction") UFUNCTION(BlueprintCallable, Category="PHY|Character|Interaction")
virtual bool RequestInstantInteraction(int32 OptionIndex = 0); virtual bool RequestInstantInteraction(int32 OptionIndex = 0);
@@ -95,23 +121,26 @@ public:
UFUNCTION(BlueprintCallable, Category="PHY|Character|Interaction") UFUNCTION(BlueprintCallable, Category="PHY|Character|Interaction")
virtual void EndInteraction(); virtual void EndInteraction();
/** @brief 请求切换当前可交互对象。 */ /**
* @brief 请求切换当前可交互目标。
* @param bNext 为 true 时切到下一个目标,否则切到上一个目标。
*/
UFUNCTION(BlueprintCallable, Category="PHY|Character|Interaction") UFUNCTION(BlueprintCallable, Category="PHY|Character|Interaction")
virtual void CycleInteractionTarget(bool bNext); virtual void CycleInteractionTarget(bool bNext);
/** @brief 获取项目角色状态组件。 */ /** @brief 获取角色状态组件。 */
UFUNCTION(BlueprintCallable, BlueprintPure, Category="PHY|Character") UFUNCTION(BlueprintCallable, BlueprintPure, Category="PHY|Character")
UPHYCharacterStateComponent* GetCharacterStateComponent() const { return CharacterStateComponent; } UPHYCharacterStateComponent* GetCharacterStateComponent() const { return CharacterStateComponent; }
/** @brief 获取 GCS 战斗组件。 */ /** @brief 获取 GCS 战斗系统组件。 */
UFUNCTION(BlueprintCallable, BlueprintPure, Category="PHY|Character") UFUNCTION(BlueprintCallable, BlueprintPure, Category="PHY|Character")
UGCS_CombatSystemComponent* GetCombatSystemComponent() const { return CombatSystemComponent; } UGCS_CombatSystemComponent* GetCombatSystemComponent() const { return CombatSystemComponent; }
/** @brief 获取 GCS 目标组件。 */ /** @brief 获取 GCS 目标系统组件。 */
UFUNCTION(BlueprintCallable, BlueprintPure, Category="PHY|Character") UFUNCTION(BlueprintCallable, BlueprintPure, Category="PHY|Character")
UGCS_TargetingSystemComponent* GetTargetingSystemComponent() const { return TargetingSystemComponent; } UGCS_TargetingSystemComponent* GetTargetingSystemComponent() const { return TargetingSystemComponent; }
/** @brief 获取 GGS 交互组件。 */ /** @brief 获取 GGS 交互系统组件。 */
UFUNCTION(BlueprintCallable, BlueprintPure, Category="PHY|Character") UFUNCTION(BlueprintCallable, BlueprintPure, Category="PHY|Character")
UGGS_InteractionSystemComponent* GetInteractionSystemComponent() const { return InteractionSystemComponent; } UGGS_InteractionSystemComponent* GetInteractionSystemComponent() const { return InteractionSystemComponent; }
@@ -119,7 +148,7 @@ public:
UFUNCTION(BlueprintCallable, BlueprintPure, Category="PHY|Character") UFUNCTION(BlueprintCallable, BlueprintPure, Category="PHY|Character")
UGGS_RagdollComponent* GetRagdollComponent() const { return RagdollComponent; } UGGS_RagdollComponent* GetRagdollComponent() const { return RagdollComponent; }
/** @brief 获取上下文效果组件。 */ /** @brief 获取 GES 上下文效果组件。 */
UFUNCTION(BlueprintCallable, BlueprintPure, Category="PHY|Character") UFUNCTION(BlueprintCallable, BlueprintPure, Category="PHY|Character")
UGES_ContextEffectComponent* GetContextEffectComponent() const { return ContextEffectComponent; } UGES_ContextEffectComponent* GetContextEffectComponent() const { return ContextEffectComponent; }
@@ -127,11 +156,7 @@ public:
UFUNCTION(BlueprintCallable, BlueprintPure, Category="PHY|Character|Animation") UFUNCTION(BlueprintCallable, BlueprintPure, Category="PHY|Character|Animation")
UPHYCharacterMeshBridgeComponent* GetMeshBridgeComponent() const { return MeshBridgeComponent; } UPHYCharacterMeshBridgeComponent* GetMeshBridgeComponent() const { return MeshBridgeComponent; }
/** @brief 获取 PHY SLS 运动桥接组件。 */ /** @brief 获取 SLS 角色移动组件。 */
UFUNCTION(BlueprintCallable, BlueprintPure, Category="PHY|Character|Locomotion")
UPHYSLSMovementBridgeComponent* GetSLSMovementBridgeComponent() const { return SLSMovementBridgeComponent; }
/** @brief 获取 SLS 角色运动组件。 */
UFUNCTION(BlueprintCallable, BlueprintPure, Category="PHY|Character|Locomotion") UFUNCTION(BlueprintCallable, BlueprintPure, Category="PHY|Character|Locomotion")
USLSCharacterMovementComponent* GetSLSCharacterMovementComponent() const; USLSCharacterMovementComponent* GetSLSCharacterMovementComponent() const;
@@ -143,7 +168,7 @@ public:
UFUNCTION(BlueprintCallable, BlueprintPure, Category="PHY|Character|Locomotion") UFUNCTION(BlueprintCallable, BlueprintPure, Category="PHY|Character|Locomotion")
UActorComponent* GetGenericIntegrationComponent() const { return GenericIntegrationComponent; } UActorComponent* GetGenericIntegrationComponent() const { return GenericIntegrationComponent; }
/** @brief 获取显示层 Mesh 组件,职业和外观 Mesh 应应用到该组件。 */ /** @brief 获取显示层 Mesh 组件,职业和外观 Mesh 应应用到该组件。 */
UFUNCTION(BlueprintCallable, BlueprintPure, Category="PHY|Character|Animation") UFUNCTION(BlueprintCallable, BlueprintPure, Category="PHY|Character|Animation")
USkeletalMeshComponent* GetDisplayMeshComponent() const { return DisplayMeshComponent; } USkeletalMeshComponent* GetDisplayMeshComponent() const { return DisplayMeshComponent; }
@@ -156,13 +181,32 @@ public:
/** @brief 获取当前战斗目标场景对象。 */ /** @brief 获取当前战斗目标场景对象。 */
virtual USceneComponent* GetCombatTargetObject_Implementation() const override; 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; 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; 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; virtual UObject* QueryWeapon_Implementation(const FGameplayTagQuery& Query) const override;
/** @brief 设置旋转模式。 */ /** @brief 设置旋转模式。 */
@@ -195,17 +239,33 @@ public:
/** @brief 获取移动意图。 */ /** @brief 获取移动意图。 */
virtual FVector GetMovementIntent_Implementation() const override; virtual FVector GetMovementIntent_Implementation() const override;
/** @brief 获取当前武器。 */ /**
* @brief 获取当前武器。
* @param Context 可选查询上下文。
* @return 当前武器对象。
*/
virtual UObject* GetCurrentWeapon_Implementation(UObject* Context = nullptr) const override; virtual UObject* GetCurrentWeapon_Implementation(UObject* Context = nullptr) const override;
/** @brief 设置当前武器。 */ /** @brief 设置当前武器。 */
virtual void SetCurrentWeapon_Implementation(UObject* Weapon) override; 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; virtual bool GetRelativeTransformToSocket_Implementation(const USkeletalMeshComponent* InSkeletalMeshComponent, const UStaticMesh* StaticMesh, const USkeletalMesh* SkeletalMesh, FName SocketName, FTransform& OutTransform) const override;
protected: protected:
/** @brief 将输入 Tag 映射为 Ability Tag。 */ /**
* @brief 将输入 Tag 映射为 Ability Tag。
* @param InputTag 输入系统提供的 Tag。
* @return 对应的 Ability Tag。
*/
virtual FGameplayTag GetAbilityTagForInputTag(FGameplayTag InputTag) const; virtual FGameplayTag GetAbilityTagForInputTag(FGameplayTag InputTag) const;
/** @brief 应用 Character 配置默认值。 */ /** @brief 应用 Character 配置默认值。 */
@@ -267,10 +327,6 @@ protected:
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category="PHY|Character|Animation") UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category="PHY|Character|Animation")
TObjectPtr<UPHYCharacterMeshBridgeComponent> MeshBridgeComponent; TObjectPtr<UPHYCharacterMeshBridgeComponent> MeshBridgeComponent;
/** @brief PHY 对 SLS 运动系统的桥接组件。 */
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category="PHY|Character|Locomotion")
TObjectPtr<UPHYSLSMovementBridgeComponent> SLSMovementBridgeComponent;
/** @brief SLS 插件集成入口组件,由运动层集中接入。 */ /** @brief SLS 插件集成入口组件,由运动层集中接入。 */
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category="PHY|Character|Locomotion") UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category="PHY|Character|Locomotion")
TObjectPtr<USLSIntegrationComponent> SLSIntegrationComponent; TObjectPtr<USLSIntegrationComponent> SLSIntegrationComponent;

View File

@@ -9,9 +9,9 @@
class APHYAICharacter; class APHYAICharacter;
class APHYAIController; class APHYAIController;
class APHYPlayerCameraManager;
class APHYPlayerCharacter; class APHYPlayerCharacter;
class APHYPlayerController; class APHYPlayerController;
class APlayerCameraManager;
class UUGC_CameraDataAssetBase; class UUGC_CameraDataAssetBase;
/** /**
@@ -45,6 +45,14 @@ public:
UPROPERTY(Config, EditDefaultsOnly, Category="PHY|Character") UPROPERTY(Config, EditDefaultsOnly, Category="PHY|Character")
float DefaultMaxAcceleration = 2048.0f; float DefaultMaxAcceleration = 2048.0f;
/** @brief 角色源 Mesh 相对 Capsule 的默认位置。 */
UPROPERTY(Config, EditDefaultsOnly, Category="PHY|Character|Mesh")
FVector DefaultMeshRelativeLocation = FVector(0.0f, 0.0f, -90.0f);
/** @brief 角色源 Mesh 相对 Capsule 的默认旋转。 */
UPROPERTY(Config, EditDefaultsOnly, Category="PHY|Character|Mesh")
FRotator DefaultMeshRelativeRotation = FRotator(0.0f, -90.0f, 0.0f);
/** @brief 默认交互选项索引。 */ /** @brief 默认交互选项索引。 */
UPROPERTY(Config, EditDefaultsOnly, Category="PHY|Character") UPROPERTY(Config, EditDefaultsOnly, Category="PHY|Character")
int32 DefaultInteractionOption = 0; int32 DefaultInteractionOption = 0;
@@ -67,7 +75,7 @@ public:
/** @brief 玩家相机管理器默认软类引用。 */ /** @brief 玩家相机管理器默认软类引用。 */
UPROPERTY(Config, EditDefaultsOnly, Category="PHY|Character") UPROPERTY(Config, EditDefaultsOnly, Category="PHY|Character")
TSoftClassPtr<APHYPlayerCameraManager> DefaultPlayerCameraManagerClass; TSoftClassPtr<APlayerCameraManager> DefaultPlayerCameraManagerClass;
/** @brief UGC 默认 CameraData 软引用,首期不强制制作资产。 */ /** @brief UGC 默认 CameraData 软引用,首期不强制制作资产。 */
UPROPERTY(Config, EditDefaultsOnly, Category="PHY|Character") UPROPERTY(Config, EditDefaultsOnly, Category="PHY|Character")

View File

@@ -6,9 +6,10 @@
#include "Characters/PHYCharacterBase.h" #include "Characters/PHYCharacterBase.h"
#include "PHYPlayerCharacter.generated.h" #include "PHYPlayerCharacter.generated.h"
class USpringArmComponent;
class UCameraComponent; class UCameraComponent;
class UGIPS_InputSystemComponent; class UGIPS_InputSystemComponent;
class UPHYUGCSpringArmComponent;
/** /**
* @brief PHY 玩家角色。 * @brief PHY 玩家角色。
@@ -32,7 +33,7 @@ public:
/** @brief 获取 UGC 相机臂。 */ /** @brief 获取 UGC 相机臂。 */
UFUNCTION(BlueprintCallable, BlueprintPure, Category="PHY|Camera") UFUNCTION(BlueprintCallable, BlueprintPure, Category="PHY|Camera")
UPHYUGCSpringArmComponent* GetCameraBoom() const { return CameraBoom; } USpringArmComponent* GetCameraBoom() const { return CameraBoom; }
/** @brief 获取跟随相机。 */ /** @brief 获取跟随相机。 */
UFUNCTION(BlueprintCallable, BlueprintPure, Category="PHY|Camera") UFUNCTION(BlueprintCallable, BlueprintPure, Category="PHY|Camera")
@@ -52,7 +53,7 @@ protected:
/** @brief UGC 项目相机臂。 */ /** @brief UGC 项目相机臂。 */
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category="PHY|Camera") UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category="PHY|Camera")
TObjectPtr<UPHYUGCSpringArmComponent> CameraBoom; TObjectPtr<USpringArmComponent> CameraBoom;
/** @brief 玩家跟随相机。 */ /** @brief 玩家跟随相机。 */
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category="PHY|Camera") UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category="PHY|Camera")

View File

@@ -8,8 +8,9 @@
#include "UObject/SoftObjectPath.h" #include "UObject/SoftObjectPath.h"
#include "PHYClassComponent.generated.h" #include "PHYClassComponent.generated.h"
class USkeletalMesh;
class UAbilitySystemComponent; class UAbilitySystemComponent;
class UAnimInstance;
class USkeletalMesh;
/** /**
* @brief PHY 职业组件。 * @brief PHY 职业组件。
@@ -44,6 +45,10 @@ public:
UFUNCTION(BlueprintCallable, Category="PHY|Class") UFUNCTION(BlueprintCallable, Category="PHY|Class")
USkeletalMesh* GetConfiguredPlayerMesh() const; USkeletalMesh* GetConfiguredPlayerMesh() const;
/** @brief 按当前职业读取配置的显示层 AnimClass未配置时返回空并交给动画默认配置。 */
UFUNCTION(BlueprintCallable, Category="PHY|Class")
TSubclassOf<UAnimInstance> GetConfiguredPlayerDisplayAnimClass() const;
/** @brief 按当前职业读取配置的玩家 IK Retargeter 软路径,未配置时返回空路径。 */ /** @brief 按当前职业读取配置的玩家 IK Retargeter 软路径,未配置时返回空路径。 */
UFUNCTION(BlueprintCallable, BlueprintPure, Category="PHY|Class") UFUNCTION(BlueprintCallable, BlueprintPure, Category="PHY|Class")
FSoftObjectPath GetConfiguredPlayerRetargeter() const; FSoftObjectPath GetConfiguredPlayerRetargeter() const;

View File

@@ -9,6 +9,7 @@
#include "UObject/SoftObjectPtr.h" #include "UObject/SoftObjectPtr.h"
#include "PHYClassSettings.generated.h" #include "PHYClassSettings.generated.h"
class UAnimInstance;
class USkeletalMesh; class USkeletalMesh;
/** /**
@@ -74,6 +75,10 @@ public:
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category="PHY|Class") UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category="PHY|Class")
TSoftObjectPtr<USkeletalMesh> PlayerMesh; TSoftObjectPtr<USkeletalMesh> PlayerMesh;
/** @brief 玩家使用该职业时优先使用的显示层 AnimClass留空时回退动画默认配置。 */
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category="PHY|Class")
TSoftClassPtr<UAnimInstance> PlayerDisplayAnimClass;
/** @brief 玩家使用该职业时优先使用的 IK Retargeter 软路径,留空时回退动画默认配置。 */ /** @brief 玩家使用该职业时优先使用的 IK Retargeter 软路径,留空时回退动画默认配置。 */
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category="PHY|Class") UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category="PHY|Class")
FSoftObjectPath PlayerRetargeter; FSoftObjectPath PlayerRetargeter;

View File

@@ -0,0 +1,63 @@
// Copyright Epic Games, Inc. All Rights Reserved.
#pragma once
#include "CoreMinimal.h"
#include "UObject/Object.h"
#include "UObject/SoftObjectPtr.h"
#include "PHYGameFrameworkSettings.generated.h"
class AGameStateBase;
class AHUD;
class APawn;
class APlayerController;
class APlayerState;
class ASpectatorPawn;
/**
* @brief PHY 游戏框架默认类配置。
*
* GameMode 会优先使用这里配置的类,未配置时回退到项目 C++ 原生骨架。
* 这样后续需要替换为蓝图子类时,只需要改配置,不需要改业务代码。
*/
UCLASS(Config=PHYCore, DefaultConfig)
class PHY_API UPHYGameFrameworkSettings : public UObject
{
GENERATED_BODY()
public:
/** @brief 构造默认 C++ 框架类引用。 */
UPHYGameFrameworkSettings();
/** @brief 默认玩家 Pawn 类。 */
UPROPERTY(Config, EditDefaultsOnly, Category="PHY|Game")
TSoftClassPtr<APawn> DefaultPawnClass;
/** @brief 默认玩家控制器类。 */
UPROPERTY(Config, EditDefaultsOnly, Category="PHY|Game")
TSoftClassPtr<APlayerController> PlayerControllerClass;
/** @brief 默认玩家状态类。 */
UPROPERTY(Config, EditDefaultsOnly, Category="PHY|Game")
TSoftClassPtr<APlayerState> PlayerStateClass;
/** @brief 默认游戏状态类。 */
UPROPERTY(Config, EditDefaultsOnly, Category="PHY|Game")
TSoftClassPtr<AGameStateBase> GameStateClass;
/** @brief 默认 HUD 类。 */
UPROPERTY(Config, EditDefaultsOnly, Category="PHY|Game")
TSoftClassPtr<AHUD> HUDClass;
/** @brief 默认旁观者 Pawn 类。 */
UPROPERTY(Config, EditDefaultsOnly, Category="PHY|Game")
TSoftClassPtr<ASpectatorPawn> SpectatorClass;
/** @brief 是否启用无缝旅行,默认按多人优先启用。 */
UPROPERTY(Config, EditDefaultsOnly, Category="PHY|Game")
bool bUseSeamlessTravel = true;
/** @brief 是否让玩家默认以旁观者身份开始。 */
UPROPERTY(Config, EditDefaultsOnly, Category="PHY|Game")
bool bStartPlayersAsSpectators = false;
};

View File

@@ -0,0 +1,54 @@
// Copyright Epic Games, Inc. All Rights Reserved.
#pragma once
#include "CoreMinimal.h"
#include "GameFramework/GameModeBase.h"
#include "PHYGameModeBase.generated.h"
class APHYGameState;
/**
* @brief PHY 游戏模式根类。
*
* GameMode 是服务端玩法框架入口,负责绑定默认 Pawn、Controller、PlayerState、
* GameState 和 HUD。具体玩法系统仍通过 Character、ASC、输入和组件完成。
*/
UCLASS(BlueprintType, Blueprintable)
class PHY_API APHYGameModeBase : public AGameModeBase
{
GENERATED_BODY()
public:
/** @brief 构造默认项目框架类。 */
APHYGameModeBase(const FObjectInitializer& ObjectInitializer = FObjectInitializer::Get());
/** @brief 初始化地图参数并应用项目框架配置。 */
virtual void InitGame(const FString& MapName, const FString& Options, FString& ErrorMessage) override;
/** @brief GameState 初始化后同步项目框架状态。 */
virtual void InitGameState() override;
/** @brief 游戏正式开始时标记框架就绪。 */
virtual void StartPlay() override;
/** @brief 玩家登录后执行项目级初始化扩展点。 */
virtual void PostLogin(APlayerController* NewPlayer) override;
/** @brief 玩家退出时执行项目级清理扩展点。 */
virtual void Logout(AController* Exiting) override;
/** @brief 按控制器返回默认 Pawn 类,后续可扩展职业或模式差异。 */
virtual UClass* GetDefaultPawnClassForController_Implementation(AController* InController) override;
/** @brief 获取强类型 PHY GameState。 */
UFUNCTION(BlueprintCallable, BlueprintPure, Category="PHY|Game")
APHYGameState* GetPHYGameState() const;
protected:
/** @brief 从 UPHYGameFrameworkSettings 应用可配置框架类。 */
virtual void ApplyGameFrameworkSettings();
/** @brief 标记 GameState 框架是否就绪。 */
virtual void SetGameFrameworkReady(bool bNewReady);
};

View File

@@ -0,0 +1,43 @@
// Copyright Epic Games, Inc. All Rights Reserved.
#pragma once
#include "CoreMinimal.h"
#include "GameFramework/GameStateBase.h"
#include "PHYGameState.generated.h"
/**
* @brief PHY 游戏状态。
*
* 当前只承载项目框架是否完成初始化的复制状态,后续战局阶段、队伍比分、
* 复活队列等多人状态都应优先汇总到这里,而不是散落在关卡蓝图。
*/
UCLASS(BlueprintType, Blueprintable)
class PHY_API APHYGameState : public AGameStateBase
{
GENERATED_BODY()
public:
/** @brief 构造游戏状态并启用复制。 */
APHYGameState(const FObjectInitializer& ObjectInitializer = FObjectInitializer::Get());
/** @brief 声明需要复制的属性。 */
virtual void GetLifetimeReplicatedProps(TArray<FLifetimeProperty>& OutLifetimeProps) const override;
/** @brief 查询游戏框架是否已由服务端标记为就绪。 */
UFUNCTION(BlueprintCallable, BlueprintPure, Category="PHY|Game")
bool IsGameFrameworkReady() const { return bGameFrameworkReady; }
/** @brief 服务端设置游戏框架就绪状态。 */
UFUNCTION(BlueprintCallable, BlueprintAuthorityOnly, Category="PHY|Game")
void SetGameFrameworkReady(bool bNewReady);
protected:
/** @brief 客户端接收框架就绪状态后的扩展点。 */
UFUNCTION()
virtual void OnRep_GameFrameworkReady();
/** @brief 服务端确认 GameMode 已完成基础类和玩家流程初始化。 */
UPROPERTY(ReplicatedUsing=OnRep_GameFrameworkReady, BlueprintReadOnly, Category="PHY|Game")
bool bGameFrameworkReady = false;
};

View File

@@ -0,0 +1,67 @@
// Copyright Epic Games, Inc. All Rights Reserved.
#pragma once
#include "CoreMinimal.h"
#include "GameFramework/HUD.h"
#include "UI/GUIS_GameUIStructLibrary.h"
#include "PHYHUD.generated.h"
class UCommonActivatableWidget;
class UPHYPlayerUIContext;
/**
* @brief PHY HUD 根入口。
*
* 首期只提供 C++ HUD 生命周期入口。真正 UI 由后续 GenericUISystem/CommonUI
* 专家接入,避免在 GameMode 或 PlayerController 中直接堆 UMG 装配逻辑。
*/
UCLASS(BlueprintType, Blueprintable)
class PHY_API APHYHUD : public AHUD
{
GENERATED_BODY()
public:
/** @brief 构造 HUD 根入口。 */
APHYHUD(const FObjectInitializer& ObjectInitializer = FObjectInitializer::Get());
/** @brief HUD 开始运行时初始化玩家 UI 入口。 */
virtual void BeginPlay() override;
/** @brief 初始化当前玩家的 HUD 逻辑。 */
UFUNCTION(BlueprintCallable, Category="PHY|UI")
virtual void InitializeHUDForPlayer(APlayerController* OwningPlayerController);
/** @brief 查询 HUD 是否已经完成初始化。 */
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;
};

View File

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

View File

@@ -1,53 +1,56 @@
// Copyright Epic Games, Inc. All Rights Reserved. // Copyright Epic Games, Inc. All Rights Reserved.
#pragma once #pragma once
#include "NativeGameplayTags.h" #include "NativeGameplayTags.h"
/** /**
* @brief PHY 状态相关原生 Gameplay Tag * @brief PHY Gameplay Tag definitions.
*/ */
namespace PHYGameplayTags namespace PHYGameplayTags
{ {
/** @brief 角色处于战斗状态。 */ /** @brief Character combat state tags. */
UE_DECLARE_GAMEPLAY_TAG_EXTERN(State_Combat); UE_DECLARE_GAMEPLAY_TAG_EXTERN(State_Combat);
/** @brief 角色处于瞄准状态。 */ /** @brief Character aiming state tag. */
UE_DECLARE_GAMEPLAY_TAG_EXTERN(State_Aiming); UE_DECLARE_GAMEPLAY_TAG_EXTERN(State_Aiming);
/** @brief 角色处于锁定目标状态。 */ /** @brief Character lock-on state tag. */
UE_DECLARE_GAMEPLAY_TAG_EXTERN(State_LockedOn); UE_DECLARE_GAMEPLAY_TAG_EXTERN(State_LockedOn);
/** @brief 角色正在执行交互。 */ /** @brief In-combat action tag. */
UE_DECLARE_GAMEPLAY_TAG_EXTERN(State_Interacting); UE_DECLARE_GAMEPLAY_TAG_EXTERN(State_Interacting);
/** @brief UI 占用主要输入。 */ /** @brief UI open input state tag. */
UE_DECLARE_GAMEPLAY_TAG_EXTERN(State_UI_Open); UE_DECLARE_GAMEPLAY_TAG_EXTERN(State_UI_Open);
/** @brief 角色已经进入死亡流程。 */ /** @brief Character dead state tag. */
UE_DECLARE_GAMEPLAY_TAG_EXTERN(State_Dead); UE_DECLARE_GAMEPLAY_TAG_EXTERN(State_Dead);
/** @brief 角色处于行走移动状态。 */ /** @brief Movement walk state tag. */
UE_DECLARE_GAMEPLAY_TAG_EXTERN(State_Movement_Walk); UE_DECLARE_GAMEPLAY_TAG_EXTERN(State_Movement_Walk);
/** @brief 角色处于常规跑动移动状态。 */ /** @brief Movement run state tag. */
UE_DECLARE_GAMEPLAY_TAG_EXTERN(State_Movement_Run); UE_DECLARE_GAMEPLAY_TAG_EXTERN(State_Movement_Run);
/** @brief 角色处于冲刺移动状态。 */ /** @brief Movement sprint state tag. */
UE_DECLARE_GAMEPLAY_TAG_EXTERN(State_Movement_Sprint); UE_DECLARE_GAMEPLAY_TAG_EXTERN(State_Movement_Sprint);
/** @brief 角色处于下落移动状态。 */ /** @brief Character falling movement state tag. */
UE_DECLARE_GAMEPLAY_TAG_EXTERN(State_Movement_Falling); UE_DECLARE_GAMEPLAY_TAG_EXTERN(State_Movement_Falling);
/** @brief 角色使用默认移动集合。 */ /** @brief Default movement set tag. */
UE_DECLARE_GAMEPLAY_TAG_EXTERN(State_MovementSet_Default); UE_DECLARE_GAMEPLAY_TAG_EXTERN(State_MovementSet_Default);
/** @brief 角色使用瞄准移动集合。 */ /** @brief Aiming movement set tag. */
UE_DECLARE_GAMEPLAY_TAG_EXTERN(State_MovementSet_Aiming); 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); UE_DECLARE_GAMEPLAY_TAG_EXTERN(State_Rotation_OrientToMovement);
/** @brief 角色旋转使用战斗横移模式。 */ /** @brief Looking-direction rotation mode tag. */
UE_DECLARE_GAMEPLAY_TAG_EXTERN(State_Rotation_Strafe); UE_DECLARE_GAMEPLAY_TAG_EXTERN(State_Rotation_Looking);
/** @brief Aiming-direction rotation mode tag. */
UE_DECLARE_GAMEPLAY_TAG_EXTERN(State_Rotation_Aiming);
} }

View 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);
}

View File

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

View File

@@ -1,78 +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 获取桥接到的角色。 */
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 运动能力的角色。 */
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;
};

View File

@@ -3,11 +3,14 @@
#pragma once #pragma once
#include "CoreMinimal.h" #include "CoreMinimal.h"
#include "GameplayTagContainer.h"
#include "UObject/Object.h" #include "UObject/Object.h"
#include "UObject/SoftObjectPtr.h" #include "UObject/SoftObjectPtr.h"
#include "PHYConfigSettings.generated.h" #include "PHYConfigSettings.generated.h"
class UCommonActivatableWidget;
class UGIPS_InputProcessor; class UGIPS_InputProcessor;
class UGUIS_GameUILayout;
/** /**
* @brief PHY 项目核心配置。 * @brief PHY 项目核心配置。
@@ -63,6 +66,10 @@ public:
/** @brief 默认视角输入处理器类,用于创建 GenericInputSystem 控制配置。 */ /** @brief 默认视角输入处理器类,用于创建 GenericInputSystem 控制配置。 */
UPROPERTY(Config) UPROPERTY(Config)
TSoftClassPtr<UGIPS_InputProcessor> DefaultLookInputProcessorClass; TSoftClassPtr<UGIPS_InputProcessor> DefaultLookInputProcessorClass;
/** @brief 默认角色属性菜单输入处理器类,用于推入 UI Menu 层。 */
UPROPERTY(Config)
TSoftClassPtr<UGIPS_InputProcessor> DefaultAttributeMenuInputProcessorClass;
}; };
/** /**
@@ -148,10 +155,42 @@ public:
UPROPERTY(Config) UPROPERTY(Config)
bool bUseCommonUI = true; bool bUseCommonUI = true;
/** @brief 是否启用 GenericUISystem 作为项目 UI 根层级和推送系统。 */
UPROPERTY(Config)
bool bUseGenericUISystem = true;
/** @brief 打开菜单时是否暂停 Gameplay。 */ /** @brief 打开菜单时是否暂停 Gameplay。 */
UPROPERTY(Config) UPROPERTY(Config)
bool bOpenMenusWithGameplayPause = false; 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 层名称。 */ /** @brief 默认 HUD 层名称。 */
UPROPERTY(Config) UPROPERTY(Config)
FName DefaultHUDLayerName = TEXT("HUD"); FName DefaultHUDLayerName = TEXT("HUD");

View File

@@ -9,6 +9,7 @@
#include "GameplayTags/PHYGameplayTags_Event.h" #include "GameplayTags/PHYGameplayTags_Event.h"
#include "GameplayTags/PHYGameplayTags_Input.h" #include "GameplayTags/PHYGameplayTags_Input.h"
#include "GameplayTags/PHYGameplayTags_State.h" #include "GameplayTags/PHYGameplayTags_State.h"
#include "GameplayTags/PHYGameplayTags_UI.h"
/** /**
* @brief PHY 项目的原生 Gameplay Tag 聚合入口。 * @brief PHY 项目的原生 Gameplay Tag 聚合入口。

View File

@@ -1,25 +0,0 @@
// Copyright Epic Games, Inc. All Rights Reserved.
#pragma once
#include "CoreMinimal.h"
#include "Camera/UGC_PlayerCameraManager.h"
#include "PHYPlayerCameraManager.generated.h"
/**
* @brief PHY 玩家相机管理器。
*
* 该类型从 PlayerController 的 C++ 缓存读取输入,不把 UGC 蓝图 Pawn Interface 作为主路径。
*/
UCLASS(BlueprintType, Blueprintable)
class PHY_API APHYPlayerCameraManager : public AUGC_PlayerCameraManager
{
GENERATED_BODY()
public:
/** @brief 返回玩家视角输入。 */
virtual FRotator GetRotationInput_Implementation() const override;
/** @brief 返回玩家移动控制输入。 */
virtual FVector GetMovementControlInput_Implementation() const override;
};

View File

@@ -13,7 +13,8 @@ class UGIPS_InputSystemComponent;
/** /**
* @brief PHY 玩家控制器。 * @brief PHY 玩家控制器。
* *
* 控制器优先绑定当前玩家角色的 GenericInputSystem 组件,并将 Input GameplayTag 路由到角色、ASC、移动和相机输入缓存。 * 控制器不持有 GenericInputSystem 组件,只绑定当前玩家角色身上的输入组件,
* 并把 Input GameplayTag 路由到角色、ASC、移动和相机输入缓存。
*/ */
UCLASS(BlueprintType, Blueprintable) UCLASS(BlueprintType, Blueprintable)
class PHY_API APHYPlayerController : public APlayerController class PHY_API APHYPlayerController : public APlayerController
@@ -21,7 +22,7 @@ class PHY_API APHYPlayerController : public APlayerController
GENERATED_BODY() GENERATED_BODY()
public: public:
/** @brief 构造输入组件并设置 UGC 相机管理器。 */ /** @brief 构造控制器并设置 UGC 相机管理器。 */
APHYPlayerController(const FObjectInitializer& ObjectInitializer = FObjectInitializer::Get()); APHYPlayerController(const FObjectInitializer& ObjectInitializer = FObjectInitializer::Get());
/** @brief 初始化输入委托。 */ /** @brief 初始化输入委托。 */
@@ -30,14 +31,10 @@ public:
/** @brief 接管 Pawn 时刷新输入委托。 */ /** @brief 接管 Pawn 时刷新输入委托。 */
virtual void OnPossess(APawn* InPawn) override; virtual void OnPossess(APawn* InPawn) override;
/** @brief 获取当前实际绑定的 GenericInputSystem 组件,优先返回玩家角色上的组件。 */ /** @brief 获取当前玩家角色身上的 GenericInputSystem 组件。 */
UFUNCTION(BlueprintCallable, BlueprintPure, Category="PHY|Input") UFUNCTION(BlueprintCallable, BlueprintPure, Category="PHY|Input")
UGIPS_InputSystemComponent* GetInputSystemComponent() const; UGIPS_InputSystemComponent* GetInputSystemComponent() const;
/** @brief 获取控制器自身保留的 GenericInputSystem 组件。 */
UFUNCTION(BlueprintCallable, BlueprintPure, Category="PHY|Input")
UGIPS_InputSystemComponent* GetControllerInputSystemComponent() const { return InputSystemComponent; }
/** @brief 获取当前 PHY 玩家角色。 */ /** @brief 获取当前 PHY 玩家角色。 */
UFUNCTION(BlueprintCallable, BlueprintPure, Category="PHY|Input") UFUNCTION(BlueprintCallable, BlueprintPure, Category="PHY|Input")
APHYPlayerCharacter* GetPHYPlayerCharacter() const; APHYPlayerCharacter* GetPHYPlayerCharacter() const;
@@ -53,17 +50,23 @@ public:
/** @brief 从输入处理器或控制器 fallback 路由移动输入。 */ /** @brief 从输入处理器或控制器 fallback 路由移动输入。 */
bool RouteMoveInput(const FInputActionInstance& ActionData, ETriggerEvent TriggerEvent); bool RouteMoveInput(const FInputActionInstance& ActionData, ETriggerEvent TriggerEvent);
/** @brief 从 GenericInputSystem 处理器路由移动输入,并阻止同一事件被广播 fallback 重复执行。 */ /** @brief 从 GenericInputSystem 处理器路由移动输入,并阻止同一事件被 fallback 重复执行。 */
bool RouteMoveInputFromProcessor(const FInputActionInstance& ActionData, ETriggerEvent TriggerEvent); bool RouteMoveInputFromProcessor(const FInputActionInstance& ActionData, ETriggerEvent TriggerEvent);
/** @brief 从输入处理器或控制器 fallback 路由视角输入。 */ /** @brief 从输入处理器或控制器 fallback 路由视角输入。 */
bool RouteLookInput(const FInputActionInstance& ActionData, ETriggerEvent TriggerEvent); bool RouteLookInput(const FInputActionInstance& ActionData, ETriggerEvent TriggerEvent);
/** @brief 从 GenericInputSystem 处理器路由视角输入,并阻止同一事件被广播 fallback 重复执行。 */ /** @brief 从 GenericInputSystem 处理器路由视角输入,并阻止同一事件被 fallback 重复执行。 */
bool RouteLookInputFromProcessor(const FInputActionInstance& ActionData, ETriggerEvent TriggerEvent); 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: protected:
/** @brief 绑定 GenericInputSystem 输入委托。 */ /** @brief 绑定玩家角色身上的 GenericInputSystem 输入委托。 */
virtual void BindInputEvents(); virtual void BindInputEvents();
/** @brief 处理 GenericInputSystem 发出的输入事件。 */ /** @brief 处理 GenericInputSystem 发出的输入事件。 */
@@ -76,17 +79,19 @@ protected:
/** @brief 处理视角输入。 */ /** @brief 处理视角输入。 */
virtual bool HandleLookInput(const FInputActionInstance& ActionData, ETriggerEvent TriggerEvent); virtual bool HandleLookInput(const FInputActionInstance& ActionData, ETriggerEvent TriggerEvent);
/** @brief 处理角色属性菜单输入,本地推入 Generic UI Menu 层。 */
virtual bool HandleOpenAttributeMenuInput(const FInputActionInstance& ActionData, ETriggerEvent TriggerEvent);
/** @brief 查询并消费移动处理器留下的重复执行保护。 */ /** @brief 查询并消费移动处理器留下的重复执行保护。 */
virtual bool ConsumeMoveProcessorGuard(ETriggerEvent TriggerEvent); virtual bool ConsumeMoveProcessorGuard(ETriggerEvent TriggerEvent);
/** @brief 查询并消费视角处理器留下的重复执行保护。 */ /** @brief 查询并消费视角处理器留下的重复执行保护。 */
virtual bool ConsumeLookProcessorGuard(ETriggerEvent TriggerEvent); virtual bool ConsumeLookProcessorGuard(ETriggerEvent TriggerEvent);
/** @brief GenericInputSystem 路由组件。 */ /** @brief 查询并消费属性菜单处理器留下的重复执行保护。 */
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category="PHY|Input") virtual bool ConsumeAttributeMenuProcessorGuard(ETriggerEvent TriggerEvent);
TObjectPtr<UGIPS_InputSystemComponent> InputSystemComponent;
/** @brief 当前已经绑定输入委托的组件。 */ /** @brief 当前已经绑定输入委托的角色输入组件。 */
UPROPERTY(Transient) UPROPERTY(Transient)
TObjectPtr<UGIPS_InputSystemComponent> BoundInputSystemComponent; TObjectPtr<UGIPS_InputSystemComponent> BoundInputSystemComponent;
@@ -117,4 +122,12 @@ protected:
/** @brief 最近一次由输入处理器处理的 Look 触发事件。 */ /** @brief 最近一次由输入处理器处理的 Look 触发事件。 */
UPROPERTY(Transient) UPROPERTY(Transient)
ETriggerEvent LastLookInputProcessorTriggerEvent = ETriggerEvent::None; ETriggerEvent LastLookInputProcessorTriggerEvent = ETriggerEvent::None;
/** @brief 输入处理器是否已经处理了最近一次属性菜单输入。 */
UPROPERTY(Transient)
bool bAttributeMenuInputHandledByProcessor = false;
/** @brief 最近一次由输入处理器处理的属性菜单触发事件。 */
UPROPERTY(Transient)
ETriggerEvent LastAttributeMenuInputProcessorTriggerEvent = ETriggerEvent::None;
}; };

View File

@@ -13,6 +13,8 @@ class UPHYCoreAttributeSet;
class UPHYElementAttributeSet; class UPHYElementAttributeSet;
class UPHYClassComponent; class UPHYClassComponent;
DECLARE_DYNAMIC_MULTICAST_DELEGATE_ThreeParams(FPHYPlayerProgressionChangedSignature, int32, NewLevel, int32, NewExperience, int32, NewExperienceForNextLevel);
/** /**
* @brief PHY 玩家状态。 * @brief PHY 玩家状态。
* *
@@ -27,6 +29,9 @@ public:
/** @brief 构造玩家状态与 ASC。 */ /** @brief 构造玩家状态与 ASC。 */
APHYPlayerState(const FObjectInitializer& ObjectInitializer = FObjectInitializer::Get()); APHYPlayerState(const FObjectInitializer& ObjectInitializer = FObjectInitializer::Get());
/** @brief 注册 PlayerState 自身复制字段。 */
virtual void GetLifetimeReplicatedProps(TArray<FLifetimeProperty>& OutLifetimeProps) const override;
/** @brief 获取玩家持久 AbilitySystemComponent。 */ /** @brief 获取玩家持久 AbilitySystemComponent。 */
virtual UAbilitySystemComponent* GetAbilitySystemComponent() const override; virtual UAbilitySystemComponent* GetAbilitySystemComponent() const override;
@@ -50,7 +55,50 @@ public:
UFUNCTION(BlueprintCallable, BlueprintPure, Category="PHY|Class") UFUNCTION(BlueprintCallable, BlueprintPure, Category="PHY|Class")
UPHYClassComponent* GetClassComponent() const { return ClassComponent; } 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: protected:
/** @brief 复制后通知本地 UI 刷新成长信息。 */
UFUNCTION()
void OnRep_Progression();
/** @brief 广播当前成长状态。 */
void BroadcastProgressionChanged();
/** @brief 玩家持久 ASC。 */ /** @brief 玩家持久 ASC。 */
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category="PHY|PlayerState") UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category="PHY|PlayerState")
TObjectPtr<UGGA_AbilitySystemComponent> AbilitySystemComponent; TObjectPtr<UGGA_AbilitySystemComponent> AbilitySystemComponent;
@@ -70,4 +118,16 @@ protected:
/** @brief 玩家持久职业组件,职业不保存在 PlayerCharacter 上。 */ /** @brief 玩家持久职业组件,职业不保存在 PlayerCharacter 上。 */
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category="PHY|Class") UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category="PHY|Class")
TObjectPtr<UPHYClassComponent> ClassComponent; 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;
}; };

Some files were not shown because too many files have changed in this diff Show More