Compare commits

..

36 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
a2a2e16c93 Add SLS integration component 2026-04-26 14:35:32 +08:00
2ed2e91381 Add SLS movement bridge 2026-04-26 12:59:41 +08:00
08e998324c Add class-specific retargeter config 2026-04-26 12:32:03 +08:00
08182fcc15 添加蓝图资产 2026-04-26 12:08:59 +08:00
f6fda856bf Add native retarget pose anim instance 2026-04-26 11:59:04 +08:00
f8169a63e9 Add character mesh bridge 2026-04-26 10:35:30 +08:00
349d603775 Apply class attribute allocations 2026-04-26 01:44:04 +08:00
408a764560 Add PHY gameplay attributes 2026-04-26 01:30:00 +08:00
ddb5f599f9 Add PHY class framework 2026-04-26 01:06:46 +08:00
d392c103ca Add look input processor 2026-04-26 00:53:53 +08:00
281db9a375 Add move input processor 2026-04-26 00:49:21 +08:00
794fcbb8a9 Add input system to player character 2026-04-26 00:46:03 +08:00
140 changed files with 7657 additions and 155 deletions

7
.gitignore vendored
View File

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

View File

@@ -8,7 +8,7 @@
- Marketplace 插件源码只允许读取和适配,不直接修改 Engine 下的插件文件。
- Git 远程仓库固定为 `https://git.codable.cn/cit110/PHY.git`
- `Content` 目录只管理 `Content/AGame` 及其子文件;`Content/Collections``Content/Developers` 等其他目录默认不纳入版本控制。
- 每次完成修改并通过对应验证后,必须提交并推送到 `origin`;如果远程认证或冲突阻塞推送,必须在最终汇报中说明
- 每次完成修改并通过对应验证后,只提交到本地 Git默认不推送到 `origin`,除非用户在当前任务中明确要求推送
## 插件源码位置
- Generic Combat System`D:\ue\UE_5.7\Engine\Plugins\Marketplace\GenericCd51b9e2c8181V4`
@@ -27,7 +27,7 @@
- 相机专家向总架构汇报。主责 `AuroraDevs_UGC`、战斗锁定/瞄准/移动相机状态;不得主动引入 `GenericCameraSystem``SLSCameraModeSystem` 作为玩法层依赖。
- 战斗/GAS 专家向总架构汇报。主责 ASC、Ability、Attribute、GameplayTag、GameplayCue、伤害、复制、预测。
- 输入专家向总架构汇报。主责 `GenericInputSystem``EnhancedInput`、输入到 GAS/UGC/运动/UI 的路由。
- 运动专家向总架构汇报。主责 `SmoothLocomotionSystem``SLSCore``TraversalSystem``SLSIntegration``FootStepSystem`;运动状态只通过项目接口通知相机。
- 运动专家向总架构汇报。主责 `SmoothLocomotionSystem``SLSCore``TraversalSystem``SLSIntegration``FootStepSystem``USLSIntegrationComponent` 允许由运动专家集中接入,禁止其它系统散落依赖;运动状态只通过项目接口通知相机。
- UI 专家向总架构汇报。主责 `GenericUISystem``CommonUI`、UMG、HUD、输入模式切换。
- 背包专家向总架构汇报。主责 `GenericInventorySystem`、物品、装备、拾取、战斗/UI 联动。
- 交互/效果专家向总架构汇报。主责 `GenericGameSystem``GenericEffectsSystem``SmartObjects``TargetingSystem`、Niagara。
@@ -42,12 +42,21 @@
- 专家交付必须包含:`改动范围``新增/修改接口``配置文件``蓝图/DataAsset 使用原因``验证结果``遗留风险`
- 验证 Agent 通过构建和必要测试后Git/仓库 Agent 才能提交;总架构最终向用户汇报结果。
- Git/仓库 Agent 初始化仓库时必须设置 `origin=https://git.codable.cn/cit110/PHY.git`,启用 Git LFS并确认 `Content` 跟踪范围仅限 `Content/AGame/**`
- Git/仓库 Agent 在每次修改完成后负责提交并推送当前改动,不保留已完成但未提交的工作树。
- Git/仓库 Agent 在每次修改完成后负责提交当前改动,不保留已完成但未提交的工作树;默认不推送,除非用户在当前任务中明确要求
- 相机/输入/运动/战斗之间的接口冲突、是否使用蓝图/DataAsset、是否引入插件模块依赖统一由总架构裁决。
## UE-MCP 使用约定
- 本项目已配置 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`
- `GenericCameraSystem``SLSCameraModeSystem` 只允许作为插件内部依赖、示例资产参考或薄适配层依赖;项目玩法层尽可能不用。
- `USLSIntegrationComponent` 由运动层集中持有和暴露,不等于允许业务层直接依赖 `SLSCameraModeSystem`
- 相机状态由项目级接口接收输入、运动和战斗状态,不允许业务代码到处直接耦合多个相机系统。
## C++ 和配置规则

File diff suppressed because one or more lines are too long

View File

@@ -159,3 +159,15 @@ ManualIPAddress=
+CollisionChannelRedirects=(OldName="VehicleMovement",NewName="Vehicle")
+CollisionChannelRedirects=(OldName="PawnMovement",NewName="Pawn")
[/Script/GameplayDebugger.GameplayDebuggerConfig]
CategorySlot0=F10
CategorySlot1=F1
CategorySlot2=F2
CategorySlot3=F3
CategorySlot4=F4
CategorySlot5=F5
CategorySlot6=F6
CategorySlot7=F7
CategorySlot8=F8
CategorySlot9=F9

View File

@@ -2,7 +2,14 @@
[/Script/CommonUI.CommonUISettings]
CommonButtonAcceptKeyHandling=TriggerClick
[/Script/GenericUISystem.GUIS_GenericUISystemSettings]
GameUIPolicyClass="/Script/PHY.PHYGameUIPolicy"
[/Script/EngineSettings.GeneralProjectSettings]
ProjectID=E7C26E1F4D195F0DBE49C2A3E5017988
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

@@ -0,0 +1,9 @@
[/Script/PHY.PHYAnimationSettings]
DefaultSourceLocomotionMesh="/Game/AGame/Character/SKM_Manny_Invis.SKM_Manny_Invis"
DefaultSourceAnimClass="/Game/AGame/Animation/Locomotion/ABP_UE5_Main.ABP_UE5_Main_C"
DefaultDisplayAnimClass="/Game/AGame/Animation/Retargeters/ABP_Retargeter.ABP_Retargeter_C"
DefaultIKRetargeter="/Game/AGame/Animation/Retargeters/RTG_Manny_To_Ida.RTG_Manny_To_Ida"
bHideSourceMeshInGame=False
bPreferLeaderPoseForSameSkeleton=True
bAllowRuntimeVisualMeshSwitch=True
bApplyPlayerClassMeshToAI=False

View File

@@ -4,4 +4,12 @@ bEnableContextEffects=True
DefaultMaxWalkSpeed=500.000000
DefaultSprintSpeed=650.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
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

@@ -0,0 +1,21 @@
[/Script/PHY.PHYClassSettings]
DefaultPlayerClassTag=(TagName="Class.Saber")
DefaultAIClassTag=(TagName="Class.Saber")
bApplyPlayerClassMeshToPlayers=True
bApplyPlayerClassMeshToAI=False
!PlayerClassMeshes=ClearArray
+PlayerClassMeshes=(ClassTag=(TagName="Class.Saber"),PlayerMesh="/Game/AGame/Character/Ida/SK_MMO_ADVANCED_F_01_AGame.SK_MMO_ADVANCED_F_01_AGame",PlayerDisplayAnimClass="/Game/AGame/Animation/Retargeters/ABP_Retargeter.ABP_Retargeter_C",PlayerRetargeter="/Game/AGame/Animation/Retargeters/RTG_Manny_To_Ida.RTG_Manny_To_Ida")
+PlayerClassMeshes=(ClassTag=(TagName="Class.Lancer"),PlayerMesh=None,PlayerRetargeter=)
+PlayerClassMeshes=(ClassTag=(TagName="Class.Archer"),PlayerMesh=None,PlayerRetargeter=)
+PlayerClassMeshes=(ClassTag=(TagName="Class.Rider"),PlayerMesh=None,PlayerRetargeter=)
+PlayerClassMeshes=(ClassTag=(TagName="Class.Caster"),PlayerMesh=None,PlayerRetargeter=)
+PlayerClassMeshes=(ClassTag=(TagName="Class.Assassin"),PlayerMesh=None,PlayerRetargeter=)
+PlayerClassMeshes=(ClassTag=(TagName="Class.Berserker"),PlayerMesh=None,PlayerRetargeter=)
!CoreAttributeAllocations=ClearArray
+CoreAttributeAllocations=(ClassTag=(TagName="Class.Saber"),Strength=12,Dexterity=10,Vitality=12,Intelligence=8,Spirit=8,Perception=10)
+CoreAttributeAllocations=(ClassTag=(TagName="Class.Lancer"),Strength=10,Dexterity=13,Vitality=10,Intelligence=7,Spirit=8,Perception=12)
+CoreAttributeAllocations=(ClassTag=(TagName="Class.Archer"),Strength=8,Dexterity=12,Vitality=8,Intelligence=8,Spirit=10,Perception=14)
+CoreAttributeAllocations=(ClassTag=(TagName="Class.Rider"),Strength=10,Dexterity=11,Vitality=11,Intelligence=8,Spirit=10,Perception=10)
+CoreAttributeAllocations=(ClassTag=(TagName="Class.Caster"),Strength=5,Dexterity=7,Vitality=8,Intelligence=16,Spirit=15,Perception=9)
+CoreAttributeAllocations=(ClassTag=(TagName="Class.Assassin"),Strength=7,Dexterity=15,Vitality=7,Intelligence=8,Spirit=8,Perception=15)
+CoreAttributeAllocations=(ClassTag=(TagName="Class.Berserker"),Strength=16,Dexterity=8,Vitality=15,Intelligence=4,Spirit=5,Perception=12)

View File

@@ -2,3 +2,8 @@
bRequireServerAuthority=True
bEnableClientPrediction=True
DefaultHitConfirmWindow=0.200000
[/Script/PHY.PHYAttributeSettings]
DefaultCoreAttributes=(Strength=10.000000,Dexterity=10.000000,Vitality=10.000000,Intelligence=10.000000,Spirit=10.000000,Perception=10.000000)
CombatFormula=(MaxHealthBase=100.000000,MaxHealthVitality=18.000000,MaxHealthStrength=3.000000,MaxManaBase=60.000000,MaxManaIntelligence=12.000000,MaxManaSpirit=6.000000,MaxStaminaBase=80.000000,MaxStaminaVitality=5.000000,MaxStaminaDexterity=4.000000,PhysicalAttackPowerStrength=2.000000,PhysicalAttackPowerDexterity=0.500000,SpellPowerIntelligence=2.000000,SpellPowerSpirit=0.400000,ArmorVitality=1.200000,ArmorStrength=0.400000,MagicResistanceSpirit=1.100000,MagicResistanceIntelligence=0.300000,AccuracyBase=0.800000,AccuracyPerception=0.006000,AccuracyDexterity=0.002000,EvasionBase=0.030000,EvasionDexterity=0.003000,EvasionPerception=0.001000,CriticalChanceBase=0.050000,CriticalChanceDexterity=0.002000,CriticalChancePerception=0.001500,CriticalDamageBase=1.500000,CriticalDamageStrength=0.004000,CriticalDamagePerception=0.003000,AttackSpeedBase=1.000000,AttackSpeedDexterity=0.010000,CooldownReductionBase=0.000000,CooldownReductionIntelligence=0.001500,CooldownReductionSpirit=0.001000,BlockPowerBase=5.000000,BlockPowerStrength=1.200000,BlockPowerVitality=0.600000,GuardBreakPowerBase=5.000000,GuardBreakPowerStrength=1.100000,GuardBreakPowerPerception=0.400000,PoiseBase=20.000000,PoiseVitality=1.500000,PoiseStrength=0.500000,PoiseDamageBase=5.000000,PoiseDamageStrength=0.800000,PoiseDamageDexterity=0.200000)
ElementFormula=(DamageBonusBase=0.000000,DamageBonusIntelligence=0.001500,DamageBonusPerception=0.000500,ResistanceBase=0.020000,ResistanceSpirit=0.002000,ResistanceVitality=0.000500,PenetrationBase=0.000000,PenetrationPerception=0.001000,PenetrationIntelligence=0.000500)

View File

@@ -3,3 +3,13 @@ ConfigVersion=1
bMultiplayerFirst=True
bPreferCodeAndConfig=True
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

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

View File

@@ -1,4 +1,12 @@
[/Script/PHY.PHYUISettings]
bUseCommonUI=True
bUseGenericUISystem=True
bOpenMenusWithGameplayPause=False
RootLayoutClass="/Script/PHY.PHYGameUILayout"
DefaultHUDWidgetClass="/Script/PHY.PHYHUDStatusWidget"
AttributeMenuWidgetClass="/Script/PHY.PHYAttributeMenuWidget"
GameplayLayerTag=(TagName="UI.Layer.Game")
HUDLayerTag=(TagName="UI.Layer.HUD")
MenuLayerTag=(TagName="UI.Layer.Menu")
ModalLayerTag=(TagName="UI.Layer.Modal")
DefaultHUDLayerName=HUD

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.

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": [
{
"Name": "PythonScriptPlugin",
"Enabled": true
},
{
"Name": "ModelingToolsEditorMode",
"Enabled": true,
@@ -26,6 +30,10 @@
"Name": "ModularGameplay",
"Enabled": true
},
{
"Name": "IKRig",
"Enabled": true
},
{
"Name": "GenericCombatSystem",
"Enabled": true,
@@ -53,6 +61,26 @@
"SupportedTargetPlatforms": [
"Win64"
]
},
{
"Name": "UE_MCP_Bridge",
"Enabled": true
},
{
"Name": "Niagara",
"Enabled": true
},
{
"Name": "PCG",
"Enabled": true
},
{
"Name": "EnhancedInput",
"Enabled": true
},
{
"Name": "CommonUI",
"Enabled": true
}
]
}

View File

@@ -19,12 +19,22 @@ public class PHY : ModuleRules
"GameplayAbilities",
"GameplayTasks",
"ModularGameplay",
"CommonUI",
"CommonInput",
"UMG",
"Slate",
"SlateCore",
"GenericGameplayAbilities",
"GenericCombatSystem",
"GenericInputSystem",
"GenericGameSystem",
"GenericUISystem",
"GenericEffectsSystem",
"SLSCore",
"SmoothLocomotionSystem",
"SLSIntegration",
"AuroraDevs_UGC",
"IKRig",
"AIModule"
});

View File

@@ -0,0 +1,74 @@
// Copyright Epic Games, Inc. All Rights Reserved.
#include "AbilitySystem/Attributes/PHYAttributeCalculationLibrary.h"
#include UE_INLINE_GENERATED_CPP_BY_NAME(PHYAttributeCalculationLibrary)
#include "AbilitySystem/Attributes/PHYCoreAttributeSet.h"
FPHYCoreAttributeSnapshot UPHYAttributeCalculationLibrary::MakeCoreSnapshotFromDefaults()
{
const FPHYCoreAttributeDefaults& Defaults = GetDefault<UPHYAttributeSettings>()->DefaultCoreAttributes;
FPHYCoreAttributeSnapshot Snapshot;
Snapshot.Strength = Defaults.Strength;
Snapshot.Dexterity = Defaults.Dexterity;
Snapshot.Vitality = Defaults.Vitality;
Snapshot.Intelligence = Defaults.Intelligence;
Snapshot.Spirit = Defaults.Spirit;
Snapshot.Perception = Defaults.Perception;
return Snapshot;
}
FPHYCoreAttributeSnapshot UPHYAttributeCalculationLibrary::MakeCoreSnapshotFromAttributeSet(const UPHYCoreAttributeSet* CoreAttributes)
{
if (!CoreAttributes)
{
return MakeCoreSnapshotFromDefaults();
}
FPHYCoreAttributeSnapshot Snapshot;
Snapshot.Strength = CoreAttributes->GetStrength();
Snapshot.Dexterity = CoreAttributes->GetDexterity();
Snapshot.Vitality = CoreAttributes->GetVitality();
Snapshot.Intelligence = CoreAttributes->GetIntelligence();
Snapshot.Spirit = CoreAttributes->GetSpirit();
Snapshot.Perception = CoreAttributes->GetPerception();
return Snapshot;
}
FPHYDerivedCombatAttributes UPHYAttributeCalculationLibrary::CalculateCombatAttributes(const FPHYCoreAttributeSnapshot& CoreAttributes)
{
const FPHYCombatAttributeFormula& Formula = GetDefault<UPHYAttributeSettings>()->CombatFormula;
FPHYDerivedCombatAttributes Result;
Result.MaxHealth = Formula.MaxHealthBase + CoreAttributes.Vitality * Formula.MaxHealthVitality + CoreAttributes.Strength * Formula.MaxHealthStrength;
Result.MaxMana = Formula.MaxManaBase + CoreAttributes.Intelligence * Formula.MaxManaIntelligence + CoreAttributes.Spirit * Formula.MaxManaSpirit;
Result.MaxStamina = Formula.MaxStaminaBase + CoreAttributes.Vitality * Formula.MaxStaminaVitality + CoreAttributes.Dexterity * Formula.MaxStaminaDexterity;
Result.PhysicalAttackPower = CoreAttributes.Strength * Formula.PhysicalAttackPowerStrength + CoreAttributes.Dexterity * Formula.PhysicalAttackPowerDexterity;
Result.SpellPower = CoreAttributes.Intelligence * Formula.SpellPowerIntelligence + CoreAttributes.Spirit * Formula.SpellPowerSpirit;
Result.Armor = CoreAttributes.Vitality * Formula.ArmorVitality + CoreAttributes.Strength * Formula.ArmorStrength;
Result.MagicResistance = CoreAttributes.Spirit * Formula.MagicResistanceSpirit + CoreAttributes.Intelligence * Formula.MagicResistanceIntelligence;
Result.Accuracy = FMath::Clamp(Formula.AccuracyBase + CoreAttributes.Perception * Formula.AccuracyPerception + CoreAttributes.Dexterity * Formula.AccuracyDexterity, 0.0f, 1.0f);
Result.Evasion = FMath::Clamp(Formula.EvasionBase + CoreAttributes.Dexterity * Formula.EvasionDexterity + CoreAttributes.Perception * Formula.EvasionPerception, 0.0f, 0.6f);
Result.CriticalChance = FMath::Clamp(Formula.CriticalChanceBase + CoreAttributes.Dexterity * Formula.CriticalChanceDexterity + CoreAttributes.Perception * Formula.CriticalChancePerception, 0.0f, 0.6f);
Result.CriticalDamage = FMath::Clamp(Formula.CriticalDamageBase + CoreAttributes.Strength * Formula.CriticalDamageStrength + CoreAttributes.Perception * Formula.CriticalDamagePerception, 1.0f, 3.0f);
Result.AttackSpeed = FMath::Clamp(Formula.AttackSpeedBase + CoreAttributes.Dexterity * Formula.AttackSpeedDexterity, 0.2f, 3.0f);
Result.CooldownReduction = FMath::Clamp(Formula.CooldownReductionBase + CoreAttributes.Intelligence * Formula.CooldownReductionIntelligence + CoreAttributes.Spirit * Formula.CooldownReductionSpirit, 0.0f, 0.6f);
Result.BlockPower = Formula.BlockPowerBase + CoreAttributes.Strength * Formula.BlockPowerStrength + CoreAttributes.Vitality * Formula.BlockPowerVitality;
Result.GuardBreakPower = Formula.GuardBreakPowerBase + CoreAttributes.Strength * Formula.GuardBreakPowerStrength + CoreAttributes.Perception * Formula.GuardBreakPowerPerception;
Result.Poise = Formula.PoiseBase + CoreAttributes.Vitality * Formula.PoiseVitality + CoreAttributes.Strength * Formula.PoiseStrength;
Result.PoiseDamage = Formula.PoiseDamageBase + CoreAttributes.Strength * Formula.PoiseDamageStrength + CoreAttributes.Dexterity * Formula.PoiseDamageDexterity;
return Result;
}
FPHYDerivedElementAttributes UPHYAttributeCalculationLibrary::CalculateElementAttributes(const FPHYCoreAttributeSnapshot& CoreAttributes)
{
const FPHYElementAttributeFormula& Formula = GetDefault<UPHYAttributeSettings>()->ElementFormula;
FPHYDerivedElementAttributes Result;
Result.DamageBonus = FMath::Clamp(Formula.DamageBonusBase + CoreAttributes.Intelligence * Formula.DamageBonusIntelligence + CoreAttributes.Perception * Formula.DamageBonusPerception, 0.0f, 2.0f);
Result.Resistance = FMath::Clamp(Formula.ResistanceBase + CoreAttributes.Spirit * Formula.ResistanceSpirit + CoreAttributes.Vitality * Formula.ResistanceVitality, 0.0f, 0.75f);
Result.Penetration = FMath::Clamp(Formula.PenetrationBase + CoreAttributes.Perception * Formula.PenetrationPerception + CoreAttributes.Intelligence * Formula.PenetrationIntelligence, 0.0f, 0.75f);
return Result;
}

View File

@@ -0,0 +1,5 @@
// Copyright Epic Games, Inc. All Rights Reserved.
#include "AbilitySystem/Attributes/PHYAttributeSettings.h"
#include UE_INLINE_GENERATED_CPP_BY_NAME(PHYAttributeSettings)

View File

@@ -0,0 +1,202 @@
// Copyright Epic Games, Inc. All Rights Reserved.
#include "AbilitySystem/Attributes/PHYCombatAttributeSet.h"
#include UE_INLINE_GENERATED_CPP_BY_NAME(PHYCombatAttributeSet)
#include "AbilitySystem/Attributes/PHYAttributeCalculationLibrary.h"
#include "AbilitySystem/Attributes/PHYCoreAttributeSet.h"
#include "GameplayEffectExtension.h"
#include "Net/UnrealNetwork.h"
UPHYCombatAttributeSet::UPHYCombatAttributeSet()
{
InitializeFromConfiguredCoreDefaults();
}
void UPHYCombatAttributeSet::GetLifetimeReplicatedProps(TArray<FLifetimeProperty>& OutLifetimeProps) const
{
Super::GetLifetimeReplicatedProps(OutLifetimeProps);
DOREPLIFETIME_CONDITION_NOTIFY(UPHYCombatAttributeSet, Health, COND_None, REPNOTIFY_Always);
DOREPLIFETIME_CONDITION_NOTIFY(UPHYCombatAttributeSet, MaxHealth, COND_None, REPNOTIFY_Always);
DOREPLIFETIME_CONDITION_NOTIFY(UPHYCombatAttributeSet, Mana, COND_None, REPNOTIFY_Always);
DOREPLIFETIME_CONDITION_NOTIFY(UPHYCombatAttributeSet, MaxMana, COND_None, REPNOTIFY_Always);
DOREPLIFETIME_CONDITION_NOTIFY(UPHYCombatAttributeSet, Stamina, COND_None, REPNOTIFY_Always);
DOREPLIFETIME_CONDITION_NOTIFY(UPHYCombatAttributeSet, MaxStamina, COND_None, REPNOTIFY_Always);
DOREPLIFETIME_CONDITION_NOTIFY(UPHYCombatAttributeSet, PhysicalAttackPower, COND_None, REPNOTIFY_Always);
DOREPLIFETIME_CONDITION_NOTIFY(UPHYCombatAttributeSet, SpellPower, COND_None, REPNOTIFY_Always);
DOREPLIFETIME_CONDITION_NOTIFY(UPHYCombatAttributeSet, Armor, COND_None, REPNOTIFY_Always);
DOREPLIFETIME_CONDITION_NOTIFY(UPHYCombatAttributeSet, MagicResistance, COND_None, REPNOTIFY_Always);
DOREPLIFETIME_CONDITION_NOTIFY(UPHYCombatAttributeSet, Accuracy, COND_None, REPNOTIFY_Always);
DOREPLIFETIME_CONDITION_NOTIFY(UPHYCombatAttributeSet, Evasion, COND_None, REPNOTIFY_Always);
DOREPLIFETIME_CONDITION_NOTIFY(UPHYCombatAttributeSet, CriticalChance, COND_None, REPNOTIFY_Always);
DOREPLIFETIME_CONDITION_NOTIFY(UPHYCombatAttributeSet, CriticalDamage, COND_None, REPNOTIFY_Always);
DOREPLIFETIME_CONDITION_NOTIFY(UPHYCombatAttributeSet, AttackSpeed, COND_None, REPNOTIFY_Always);
DOREPLIFETIME_CONDITION_NOTIFY(UPHYCombatAttributeSet, CooldownReduction, COND_None, REPNOTIFY_Always);
DOREPLIFETIME_CONDITION_NOTIFY(UPHYCombatAttributeSet, BlockPower, COND_None, REPNOTIFY_Always);
DOREPLIFETIME_CONDITION_NOTIFY(UPHYCombatAttributeSet, GuardBreakPower, COND_None, REPNOTIFY_Always);
DOREPLIFETIME_CONDITION_NOTIFY(UPHYCombatAttributeSet, Poise, COND_None, REPNOTIFY_Always);
DOREPLIFETIME_CONDITION_NOTIFY(UPHYCombatAttributeSet, PoiseDamage, COND_None, REPNOTIFY_Always);
}
void UPHYCombatAttributeSet::PreAttributeChange(const FGameplayAttribute& Attribute, float& NewValue)
{
Super::PreAttributeChange(Attribute, NewValue);
if (Attribute == GetHealthAttribute())
{
NewValue = FMath::Clamp(NewValue, 0.0f, GetMaxHealth());
}
else if (Attribute == GetManaAttribute())
{
NewValue = FMath::Clamp(NewValue, 0.0f, GetMaxMana());
}
else if (Attribute == GetStaminaAttribute())
{
NewValue = FMath::Clamp(NewValue, 0.0f, GetMaxStamina());
}
else if (Attribute == GetMaxHealthAttribute() || Attribute == GetMaxManaAttribute() || Attribute == GetMaxStaminaAttribute())
{
NewValue = FMath::Max(NewValue, 1.0f);
}
else if (Attribute == GetAccuracyAttribute())
{
NewValue = FMath::Clamp(NewValue, 0.0f, 1.0f);
}
else if (Attribute == GetEvasionAttribute() || Attribute == GetCriticalChanceAttribute() || Attribute == GetCooldownReductionAttribute())
{
NewValue = FMath::Clamp(NewValue, 0.0f, 0.6f);
}
else if (Attribute == GetCriticalDamageAttribute())
{
NewValue = FMath::Clamp(NewValue, 1.0f, 5.0f);
}
else if (Attribute == GetAttackSpeedAttribute())
{
NewValue = FMath::Clamp(NewValue, 0.1f, 5.0f);
}
else
{
NewValue = FMath::Max(NewValue, 0.0f);
}
}
void UPHYCombatAttributeSet::PostGameplayEffectExecute(const FGameplayEffectModCallbackData& Data)
{
Super::PostGameplayEffectExecute(Data);
if (Data.EvaluatedData.Attribute == GetHealthAttribute())
{
SetHealth(FMath::Clamp(GetHealth(), 0.0f, GetMaxHealth()));
}
else if (Data.EvaluatedData.Attribute == GetManaAttribute())
{
SetMana(FMath::Clamp(GetMana(), 0.0f, GetMaxMana()));
}
else if (Data.EvaluatedData.Attribute == GetStaminaAttribute())
{
SetStamina(FMath::Clamp(GetStamina(), 0.0f, GetMaxStamina()));
}
}
void UPHYCombatAttributeSet::PostAttributeChange(const FGameplayAttribute& Attribute, float OldValue, float NewValue)
{
Super::PostAttributeChange(Attribute, OldValue, NewValue);
if (Attribute == GetMaxHealthAttribute() && GetHealth() > NewValue)
{
SetHealth(NewValue);
}
else if (Attribute == GetMaxManaAttribute() && GetMana() > NewValue)
{
SetMana(NewValue);
}
else if (Attribute == GetMaxStaminaAttribute() && GetStamina() > NewValue)
{
SetStamina(NewValue);
}
}
void UPHYCombatAttributeSet::InitializeFromConfiguredCoreDefaults()
{
const FPHYCoreAttributeSnapshot Snapshot = UPHYAttributeCalculationLibrary::MakeCoreSnapshotFromDefaults();
const FPHYDerivedCombatAttributes Derived = UPHYAttributeCalculationLibrary::CalculateCombatAttributes(Snapshot);
InitMaxHealth(Derived.MaxHealth);
InitHealth(Derived.MaxHealth);
InitMaxMana(Derived.MaxMana);
InitMana(Derived.MaxMana);
InitMaxStamina(Derived.MaxStamina);
InitStamina(Derived.MaxStamina);
InitPhysicalAttackPower(Derived.PhysicalAttackPower);
InitSpellPower(Derived.SpellPower);
InitArmor(Derived.Armor);
InitMagicResistance(Derived.MagicResistance);
InitAccuracy(Derived.Accuracy);
InitEvasion(Derived.Evasion);
InitCriticalChance(Derived.CriticalChance);
InitCriticalDamage(Derived.CriticalDamage);
InitAttackSpeed(Derived.AttackSpeed);
InitCooldownReduction(Derived.CooldownReduction);
InitBlockPower(Derived.BlockPower);
InitGuardBreakPower(Derived.GuardBreakPower);
InitPoise(Derived.Poise);
InitPoiseDamage(Derived.PoiseDamage);
}
void UPHYCombatAttributeSet::InitializeFromCoreAttributes(const UPHYCoreAttributeSet* CoreAttributes)
{
const FPHYCoreAttributeSnapshot Snapshot = UPHYAttributeCalculationLibrary::MakeCoreSnapshotFromAttributeSet(CoreAttributes);
const FPHYDerivedCombatAttributes Derived = UPHYAttributeCalculationLibrary::CalculateCombatAttributes(Snapshot);
SetMaxHealth(Derived.MaxHealth);
SetHealth(FMath::Clamp(GetHealth(), 0.0f, Derived.MaxHealth));
SetMaxMana(Derived.MaxMana);
SetMana(FMath::Clamp(GetMana(), 0.0f, Derived.MaxMana));
SetMaxStamina(Derived.MaxStamina);
SetStamina(FMath::Clamp(GetStamina(), 0.0f, Derived.MaxStamina));
SetPhysicalAttackPower(Derived.PhysicalAttackPower);
SetSpellPower(Derived.SpellPower);
SetArmor(Derived.Armor);
SetMagicResistance(Derived.MagicResistance);
SetAccuracy(Derived.Accuracy);
SetEvasion(Derived.Evasion);
SetCriticalChance(Derived.CriticalChance);
SetCriticalDamage(Derived.CriticalDamage);
SetAttackSpeed(Derived.AttackSpeed);
SetCooldownReduction(Derived.CooldownReduction);
SetBlockPower(Derived.BlockPower);
SetGuardBreakPower(Derived.GuardBreakPower);
SetPoise(Derived.Poise);
SetPoiseDamage(Derived.PoiseDamage);
}
#define PHY_COMBAT_REPNOTIFY(PropertyName) \
void UPHYCombatAttributeSet::OnRep_##PropertyName(const FGameplayAttributeData& OldValue) \
{ \
GAMEPLAYATTRIBUTE_REPNOTIFY(UPHYCombatAttributeSet, PropertyName, OldValue); \
}
PHY_COMBAT_REPNOTIFY(Health)
PHY_COMBAT_REPNOTIFY(MaxHealth)
PHY_COMBAT_REPNOTIFY(Mana)
PHY_COMBAT_REPNOTIFY(MaxMana)
PHY_COMBAT_REPNOTIFY(Stamina)
PHY_COMBAT_REPNOTIFY(MaxStamina)
PHY_COMBAT_REPNOTIFY(PhysicalAttackPower)
PHY_COMBAT_REPNOTIFY(SpellPower)
PHY_COMBAT_REPNOTIFY(Armor)
PHY_COMBAT_REPNOTIFY(MagicResistance)
PHY_COMBAT_REPNOTIFY(Accuracy)
PHY_COMBAT_REPNOTIFY(Evasion)
PHY_COMBAT_REPNOTIFY(CriticalChance)
PHY_COMBAT_REPNOTIFY(CriticalDamage)
PHY_COMBAT_REPNOTIFY(AttackSpeed)
PHY_COMBAT_REPNOTIFY(CooldownReduction)
PHY_COMBAT_REPNOTIFY(BlockPower)
PHY_COMBAT_REPNOTIFY(GuardBreakPower)
PHY_COMBAT_REPNOTIFY(Poise)
PHY_COMBAT_REPNOTIFY(PoiseDamage)
#undef PHY_COMBAT_REPNOTIFY

View File

@@ -0,0 +1,62 @@
// Copyright Epic Games, Inc. All Rights Reserved.
#include "AbilitySystem/Attributes/PHYCoreAttributeSet.h"
#include UE_INLINE_GENERATED_CPP_BY_NAME(PHYCoreAttributeSet)
#include "AbilitySystem/Attributes/PHYAttributeSettings.h"
#include "Net/UnrealNetwork.h"
UPHYCoreAttributeSet::UPHYCoreAttributeSet()
{
const FPHYCoreAttributeDefaults& Defaults = GetDefault<UPHYAttributeSettings>()->DefaultCoreAttributes;
InitStrength(Defaults.Strength);
InitDexterity(Defaults.Dexterity);
InitVitality(Defaults.Vitality);
InitIntelligence(Defaults.Intelligence);
InitSpirit(Defaults.Spirit);
InitPerception(Defaults.Perception);
}
void UPHYCoreAttributeSet::GetLifetimeReplicatedProps(TArray<FLifetimeProperty>& OutLifetimeProps) const
{
Super::GetLifetimeReplicatedProps(OutLifetimeProps);
DOREPLIFETIME_CONDITION_NOTIFY(UPHYCoreAttributeSet, Strength, COND_None, REPNOTIFY_Always);
DOREPLIFETIME_CONDITION_NOTIFY(UPHYCoreAttributeSet, Dexterity, COND_None, REPNOTIFY_Always);
DOREPLIFETIME_CONDITION_NOTIFY(UPHYCoreAttributeSet, Vitality, COND_None, REPNOTIFY_Always);
DOREPLIFETIME_CONDITION_NOTIFY(UPHYCoreAttributeSet, Intelligence, COND_None, REPNOTIFY_Always);
DOREPLIFETIME_CONDITION_NOTIFY(UPHYCoreAttributeSet, Spirit, COND_None, REPNOTIFY_Always);
DOREPLIFETIME_CONDITION_NOTIFY(UPHYCoreAttributeSet, Perception, COND_None, REPNOTIFY_Always);
}
void UPHYCoreAttributeSet::OnRep_Strength(const FGameplayAttributeData& OldValue)
{
GAMEPLAYATTRIBUTE_REPNOTIFY(UPHYCoreAttributeSet, Strength, OldValue);
}
void UPHYCoreAttributeSet::OnRep_Dexterity(const FGameplayAttributeData& OldValue)
{
GAMEPLAYATTRIBUTE_REPNOTIFY(UPHYCoreAttributeSet, Dexterity, OldValue);
}
void UPHYCoreAttributeSet::OnRep_Vitality(const FGameplayAttributeData& OldValue)
{
GAMEPLAYATTRIBUTE_REPNOTIFY(UPHYCoreAttributeSet, Vitality, OldValue);
}
void UPHYCoreAttributeSet::OnRep_Intelligence(const FGameplayAttributeData& OldValue)
{
GAMEPLAYATTRIBUTE_REPNOTIFY(UPHYCoreAttributeSet, Intelligence, OldValue);
}
void UPHYCoreAttributeSet::OnRep_Spirit(const FGameplayAttributeData& OldValue)
{
GAMEPLAYATTRIBUTE_REPNOTIFY(UPHYCoreAttributeSet, Spirit, OldValue);
}
void UPHYCoreAttributeSet::OnRep_Perception(const FGameplayAttributeData& OldValue)
{
GAMEPLAYATTRIBUTE_REPNOTIFY(UPHYCoreAttributeSet, Perception, OldValue);
}

View File

@@ -0,0 +1,128 @@
// Copyright Epic Games, Inc. All Rights Reserved.
#include "AbilitySystem/Attributes/PHYElementAttributeSet.h"
#include UE_INLINE_GENERATED_CPP_BY_NAME(PHYElementAttributeSet)
#include "AbilitySystem/Attributes/PHYAttributeCalculationLibrary.h"
#include "AbilitySystem/Attributes/PHYCoreAttributeSet.h"
#include "Net/UnrealNetwork.h"
UPHYElementAttributeSet::UPHYElementAttributeSet()
{
InitializeFromConfiguredCoreDefaults();
}
void UPHYElementAttributeSet::GetLifetimeReplicatedProps(TArray<FLifetimeProperty>& OutLifetimeProps) const
{
Super::GetLifetimeReplicatedProps(OutLifetimeProps);
DOREPLIFETIME_CONDITION_NOTIFY(UPHYElementAttributeSet, FireDamageBonus, COND_None, REPNOTIFY_Always);
DOREPLIFETIME_CONDITION_NOTIFY(UPHYElementAttributeSet, WaterDamageBonus, COND_None, REPNOTIFY_Always);
DOREPLIFETIME_CONDITION_NOTIFY(UPHYElementAttributeSet, IceDamageBonus, COND_None, REPNOTIFY_Always);
DOREPLIFETIME_CONDITION_NOTIFY(UPHYElementAttributeSet, LightningDamageBonus, COND_None, REPNOTIFY_Always);
DOREPLIFETIME_CONDITION_NOTIFY(UPHYElementAttributeSet, EarthDamageBonus, COND_None, REPNOTIFY_Always);
DOREPLIFETIME_CONDITION_NOTIFY(UPHYElementAttributeSet, WindDamageBonus, COND_None, REPNOTIFY_Always);
DOREPLIFETIME_CONDITION_NOTIFY(UPHYElementAttributeSet, LightDamageBonus, COND_None, REPNOTIFY_Always);
DOREPLIFETIME_CONDITION_NOTIFY(UPHYElementAttributeSet, DarkDamageBonus, COND_None, REPNOTIFY_Always);
DOREPLIFETIME_CONDITION_NOTIFY(UPHYElementAttributeSet, FireResistance, COND_None, REPNOTIFY_Always);
DOREPLIFETIME_CONDITION_NOTIFY(UPHYElementAttributeSet, WaterResistance, COND_None, REPNOTIFY_Always);
DOREPLIFETIME_CONDITION_NOTIFY(UPHYElementAttributeSet, IceResistance, COND_None, REPNOTIFY_Always);
DOREPLIFETIME_CONDITION_NOTIFY(UPHYElementAttributeSet, LightningResistance, COND_None, REPNOTIFY_Always);
DOREPLIFETIME_CONDITION_NOTIFY(UPHYElementAttributeSet, EarthResistance, COND_None, REPNOTIFY_Always);
DOREPLIFETIME_CONDITION_NOTIFY(UPHYElementAttributeSet, WindResistance, COND_None, REPNOTIFY_Always);
DOREPLIFETIME_CONDITION_NOTIFY(UPHYElementAttributeSet, LightResistance, COND_None, REPNOTIFY_Always);
DOREPLIFETIME_CONDITION_NOTIFY(UPHYElementAttributeSet, DarkResistance, COND_None, REPNOTIFY_Always);
DOREPLIFETIME_CONDITION_NOTIFY(UPHYElementAttributeSet, ElementPenetration, COND_None, REPNOTIFY_Always);
}
void UPHYElementAttributeSet::PreAttributeChange(const FGameplayAttribute& Attribute, float& NewValue)
{
Super::PreAttributeChange(Attribute, NewValue);
if (Attribute == GetFireResistanceAttribute() || Attribute == GetWaterResistanceAttribute() ||
Attribute == GetIceResistanceAttribute() || Attribute == GetLightningResistanceAttribute() ||
Attribute == GetEarthResistanceAttribute() || Attribute == GetWindResistanceAttribute() ||
Attribute == GetLightResistanceAttribute() || Attribute == GetDarkResistanceAttribute() ||
Attribute == GetElementPenetrationAttribute())
{
NewValue = FMath::Clamp(NewValue, 0.0f, 0.75f);
return;
}
NewValue = FMath::Clamp(NewValue, 0.0f, 2.0f);
}
void UPHYElementAttributeSet::InitializeFromConfiguredCoreDefaults()
{
const FPHYCoreAttributeSnapshot Snapshot = UPHYAttributeCalculationLibrary::MakeCoreSnapshotFromDefaults();
const FPHYDerivedElementAttributes Derived = UPHYAttributeCalculationLibrary::CalculateElementAttributes(Snapshot);
InitFireDamageBonus(Derived.DamageBonus);
InitWaterDamageBonus(Derived.DamageBonus);
InitIceDamageBonus(Derived.DamageBonus);
InitLightningDamageBonus(Derived.DamageBonus);
InitEarthDamageBonus(Derived.DamageBonus);
InitWindDamageBonus(Derived.DamageBonus);
InitLightDamageBonus(Derived.DamageBonus);
InitDarkDamageBonus(Derived.DamageBonus);
InitFireResistance(Derived.Resistance);
InitWaterResistance(Derived.Resistance);
InitIceResistance(Derived.Resistance);
InitLightningResistance(Derived.Resistance);
InitEarthResistance(Derived.Resistance);
InitWindResistance(Derived.Resistance);
InitLightResistance(Derived.Resistance);
InitDarkResistance(Derived.Resistance);
InitElementPenetration(Derived.Penetration);
}
void UPHYElementAttributeSet::InitializeFromCoreAttributes(const UPHYCoreAttributeSet* CoreAttributes)
{
const FPHYCoreAttributeSnapshot Snapshot = UPHYAttributeCalculationLibrary::MakeCoreSnapshotFromAttributeSet(CoreAttributes);
const FPHYDerivedElementAttributes Derived = UPHYAttributeCalculationLibrary::CalculateElementAttributes(Snapshot);
SetFireDamageBonus(Derived.DamageBonus);
SetWaterDamageBonus(Derived.DamageBonus);
SetIceDamageBonus(Derived.DamageBonus);
SetLightningDamageBonus(Derived.DamageBonus);
SetEarthDamageBonus(Derived.DamageBonus);
SetWindDamageBonus(Derived.DamageBonus);
SetLightDamageBonus(Derived.DamageBonus);
SetDarkDamageBonus(Derived.DamageBonus);
SetFireResistance(Derived.Resistance);
SetWaterResistance(Derived.Resistance);
SetIceResistance(Derived.Resistance);
SetLightningResistance(Derived.Resistance);
SetEarthResistance(Derived.Resistance);
SetWindResistance(Derived.Resistance);
SetLightResistance(Derived.Resistance);
SetDarkResistance(Derived.Resistance);
SetElementPenetration(Derived.Penetration);
}
#define PHY_ELEMENT_REPNOTIFY(PropertyName) \
void UPHYElementAttributeSet::OnRep_##PropertyName(const FGameplayAttributeData& OldValue) \
{ \
GAMEPLAYATTRIBUTE_REPNOTIFY(UPHYElementAttributeSet, PropertyName, OldValue); \
}
PHY_ELEMENT_REPNOTIFY(FireDamageBonus)
PHY_ELEMENT_REPNOTIFY(WaterDamageBonus)
PHY_ELEMENT_REPNOTIFY(IceDamageBonus)
PHY_ELEMENT_REPNOTIFY(LightningDamageBonus)
PHY_ELEMENT_REPNOTIFY(EarthDamageBonus)
PHY_ELEMENT_REPNOTIFY(WindDamageBonus)
PHY_ELEMENT_REPNOTIFY(LightDamageBonus)
PHY_ELEMENT_REPNOTIFY(DarkDamageBonus)
PHY_ELEMENT_REPNOTIFY(FireResistance)
PHY_ELEMENT_REPNOTIFY(WaterResistance)
PHY_ELEMENT_REPNOTIFY(IceResistance)
PHY_ELEMENT_REPNOTIFY(LightningResistance)
PHY_ELEMENT_REPNOTIFY(EarthResistance)
PHY_ELEMENT_REPNOTIFY(WindResistance)
PHY_ELEMENT_REPNOTIFY(LightResistance)
PHY_ELEMENT_REPNOTIFY(DarkResistance)
PHY_ELEMENT_REPNOTIFY(ElementPenetration)
#undef PHY_ELEMENT_REPNOTIFY

View File

@@ -0,0 +1,7 @@
// Copyright Epic Games, Inc. All Rights Reserved.
#include "Animation/PHYAnimationSettings.h"
#include UE_INLINE_GENERATED_CPP_BY_NAME(PHYAnimationSettings)
// 动画配置仅承载默认值,实际应用由 Mesh Bridge 在角色初始化时读取。

View File

@@ -0,0 +1,271 @@
// Copyright Epic Games, Inc. All Rights Reserved.
#include "Animation/PHYCharacterMeshBridgeComponent.h"
#include UE_INLINE_GENERATED_CPP_BY_NAME(PHYCharacterMeshBridgeComponent)
#include "Animation/AnimInstance.h"
#include "Animation/PHYAnimationSettings.h"
#include "Class/PHYClassComponent.h"
#include "Animation/PHYRetargetPoseAnimInstance.h"
#include "Components/SkeletalMeshComponent.h"
#include "Engine/SkeletalMesh.h"
#include "GameFramework/Character.h"
UPHYCharacterMeshBridgeComponent::UPHYCharacterMeshBridgeComponent(const FObjectInitializer& ObjectInitializer)
: Super(ObjectInitializer)
{
PrimaryComponentTick.bCanEverTick = false;
}
void UPHYCharacterMeshBridgeComponent::BeginPlay()
{
Super::BeginPlay();
// 构造期或 PlayerState 职业同步可能早于显示 Mesh 注册,运行时再确认一次。
RefreshFollowPoseMode();
}
void UPHYCharacterMeshBridgeComponent::InitializeMeshBridge(ACharacter* Character, USkeletalMeshComponent* InDisplayMeshComponent)
{
SourceMeshComponent = Character ? Character->GetMesh() : nullptr;
DisplayMeshComponent = InDisplayMeshComponent;
if (!SourceMeshComponent || !DisplayMeshComponent)
{
return;
}
DisplayMeshComponent->SetCollisionEnabled(ECollisionEnabled::NoCollision);
DisplayMeshComponent->SetGenerateOverlapEvents(false);
DisplayMeshComponent->bReceivesDecals = SourceMeshComponent->bReceivesDecals;
DisplayMeshComponent->VisibilityBasedAnimTickOption = SourceMeshComponent->VisibilityBasedAnimTickOption;
ApplySourceDefaults();
const UPHYAnimationSettings* Settings = GetDefault<UPHYAnimationSettings>();
if (!CurrentRetargeter.IsValid())
{
CurrentRetargeter = Settings ? Settings->DefaultIKRetargeter : FSoftObjectPath();
}
if (Settings)
{
SourceMeshComponent->SetHiddenInGame(Settings->bHideSourceMeshInGame);
}
RefreshFollowPoseMode();
}
bool UPHYCharacterMeshBridgeComponent::ApplyDisplayMesh(USkeletalMesh* NewDisplayMesh, TSubclassOf<UAnimInstance> NewDisplayAnimClass, FSoftObjectPath NewRetargeter)
{
if (!DisplayMeshComponent || !NewDisplayMesh)
{
return false;
}
const UPHYAnimationSettings* Settings = GetDefault<UPHYAnimationSettings>();
if (DisplayMeshComponent->GetSkeletalMeshAsset() && Settings && !Settings->bAllowRuntimeVisualMeshSwitch)
{
return false;
}
DisplayMeshComponent->SetSkeletalMesh(NewDisplayMesh);
if (NewDisplayAnimClass)
{
DisplayMeshComponent->SetAnimationMode(EAnimationMode::AnimationBlueprint);
DisplayMeshComponent->SetAnimInstanceClass(NewDisplayAnimClass);
}
else if (Settings && !Settings->DefaultDisplayAnimClass.IsNull())
{
if (UClass* DefaultDisplayAnimClass = Settings->DefaultDisplayAnimClass.LoadSynchronous())
{
DisplayMeshComponent->SetAnimationMode(EAnimationMode::AnimationBlueprint);
DisplayMeshComponent->SetAnimInstanceClass(DefaultDisplayAnimClass);
}
}
CurrentRetargeter = NewRetargeter.IsValid() ? NewRetargeter : (Settings ? Settings->DefaultIKRetargeter : FSoftObjectPath());
RefreshFollowPoseMode();
return true;
}
bool UPHYCharacterMeshBridgeComponent::ApplyPlayerClassVisualFromClassComponent(const UPHYClassComponent* ClassComponent)
{
if (!ClassComponent)
{
return false;
}
if (!ClassComponent->IsPlayerClassMeshAllowed())
{
return false;
}
USkeletalMesh* PlayerMesh = ClassComponent->GetConfiguredPlayerMesh();
const FSoftObjectPath PlayerRetargeter = ClassComponent->GetConfiguredPlayerRetargeter();
return PlayerMesh ? ApplyDisplayMesh(PlayerMesh, nullptr, PlayerRetargeter) : false;
}
void UPHYCharacterMeshBridgeComponent::RefreshFollowPoseMode()
{
if (!SourceMeshComponent || !DisplayMeshComponent)
{
return;
}
if (CanUseLeaderPose())
{
DisplayMeshComponent->SetLeaderPoseComponent(SourceMeshComponent);
DisplayMeshComponent->SetAnimationMode(EAnimationMode::AnimationBlueprint);
return;
}
DisplayMeshComponent->SetLeaderPoseComponent(nullptr);
ConfigureRetargetFollowPose();
}
USkeletalMeshComponent* UPHYCharacterMeshBridgeComponent::GetVisualMeshComponentForSocket(const FName SocketName) const
{
if (!SocketName.IsNone() && DisplayMeshComponent && DisplayMeshComponent->DoesSocketExist(SocketName))
{
return DisplayMeshComponent;
}
if (!SocketName.IsNone() && SourceMeshComponent && SourceMeshComponent->DoesSocketExist(SocketName))
{
return SourceMeshComponent;
}
return DisplayMeshComponent ? DisplayMeshComponent : SourceMeshComponent;
}
void UPHYCharacterMeshBridgeComponent::ApplySourceDefaults()
{
if (!SourceMeshComponent)
{
return;
}
const UPHYAnimationSettings* Settings = GetDefault<UPHYAnimationSettings>();
if (!Settings)
{
return;
}
if (!SourceMeshComponent->GetSkeletalMeshAsset() && !Settings->DefaultSourceLocomotionMesh.IsNull())
{
if (USkeletalMesh* DefaultSourceMesh = Settings->DefaultSourceLocomotionMesh.LoadSynchronous())
{
SourceMeshComponent->SetSkeletalMesh(DefaultSourceMesh);
}
}
if (!Settings->DefaultSourceAnimClass.IsNull())
{
if (UClass* DefaultSourceAnimClass = Settings->DefaultSourceAnimClass.LoadSynchronous())
{
SourceMeshComponent->SetAnimationMode(EAnimationMode::AnimationBlueprint);
SourceMeshComponent->SetAnimInstanceClass(DefaultSourceAnimClass);
}
}
}
bool UPHYCharacterMeshBridgeComponent::CanUseLeaderPose() const
{
const UPHYAnimationSettings* Settings = GetDefault<UPHYAnimationSettings>();
if (Settings && !Settings->bPreferLeaderPoseForSameSkeleton)
{
return false;
}
const USkeletalMesh* SourceMesh = SourceMeshComponent ? SourceMeshComponent->GetSkeletalMeshAsset() : nullptr;
const USkeletalMesh* DisplayMesh = DisplayMeshComponent ? DisplayMeshComponent->GetSkeletalMeshAsset() : nullptr;
return SourceMesh && DisplayMesh && SourceMesh->GetSkeleton() && SourceMesh->GetSkeleton() == DisplayMesh->GetSkeleton();
}
void UPHYCharacterMeshBridgeComponent::ConfigureRetargetFollowPose()
{
if (!SourceMeshComponent || !DisplayMeshComponent)
{
return;
}
if (!DisplayMeshComponent->GetSkeletalMeshAsset())
{
return;
}
if (!SourceMeshComponent->IsRegistered() || !DisplayMeshComponent->IsRegistered())
{
return;
}
TSubclassOf<UAnimInstance> RetargetAnimClass = ResolveRetargetDisplayAnimClass();
if (!RetargetAnimClass)
{
UE_LOG(LogTemp, Warning, TEXT("PHYCharacterMeshBridgeComponent: DisplayMesh %s has no retarget AnimClass."), *GetNameSafe(DisplayMeshComponent));
return;
}
if (!RetargetAnimClass->IsChildOf(UPHYRetargetPoseAnimInstance::StaticClass()))
{
UE_LOG(LogTemp, Warning, TEXT("PHYCharacterMeshBridgeComponent: Display AnimClass %s is not based on UPHYRetargetPoseAnimInstance; falling back to native retarget instance."), *GetNameSafe(RetargetAnimClass.Get()));
RetargetAnimClass = UPHYRetargetPoseAnimInstance::StaticClass();
}
DisplayMeshComponent->SetAnimationMode(EAnimationMode::AnimationBlueprint);
if (DisplayMeshComponent->GetAnimClass() != RetargetAnimClass.Get())
{
DisplayMeshComponent->SetAnimInstanceClass(RetargetAnimClass);
}
DisplayMeshComponent->InitAnim(true);
if (!CurrentRetargeter.IsValid())
{
UE_LOG(LogTemp, Warning, TEXT("PHYCharacterMeshBridgeComponent: DisplayMesh %s needs an IK Retargeter for cross-skeleton follow pose."), *GetNameSafe(DisplayMeshComponent));
return;
}
UPHYRetargetPoseAnimInstance* RetargetAnimInstance = Cast<UPHYRetargetPoseAnimInstance>(DisplayMeshComponent->GetAnimInstance());
if (!RetargetAnimInstance)
{
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;
}
RetargetAnimInstance->ConfigureRetargetPoseFromMesh(SourceMeshComponent, CurrentRetargeter);
}
TSubclassOf<UAnimInstance> UPHYCharacterMeshBridgeComponent::ResolveRetargetDisplayAnimClass() const
{
if (!DisplayMeshComponent)
{
return nullptr;
}
if (UClass* CurrentAnimClass = DisplayMeshComponent->GetAnimClass())
{
if (CurrentAnimClass != UPHYRetargetPoseAnimInstance::StaticClass())
{
return CurrentAnimClass;
}
}
const UPHYAnimationSettings* Settings = GetDefault<UPHYAnimationSettings>();
if (Settings && !Settings->DefaultDisplayAnimClass.IsNull())
{
if (UClass* DefaultDisplayAnimClass = Settings->DefaultDisplayAnimClass.LoadSynchronous())
{
return DefaultDisplayAnimClass;
}
}
return UPHYRetargetPoseAnimInstance::StaticClass();
}

View File

@@ -0,0 +1,38 @@
// Copyright Epic Games, Inc. All Rights Reserved.
#include "Animation/PHYRetargetPoseAnimInstance.h"
#include UE_INLINE_GENERATED_CPP_BY_NAME(PHYRetargetPoseAnimInstance)
#include "Components/SkeletalMeshComponent.h"
#include "Retargeter/IKRetargeter.h"
bool UPHYRetargetPoseAnimInstance::ConfigureRetargetPoseFromMesh(USkeletalMeshComponent* InSourceMeshComponent, const FSoftObjectPath InRetargeterPath)
{
RetargetSourceMeshComponent = InSourceMeshComponent;
RetargeterPath = InRetargeterPath;
LoadedRetargeter = nullptr;
if (!RetargetSourceMeshComponent)
{
UE_LOG(LogTemp, Warning, TEXT("PHYRetargetPoseAnimInstance: SourceMeshComponent is missing."));
return false;
}
if (!RetargeterPath.IsValid())
{
UE_LOG(LogTemp, Warning, TEXT("PHYRetargetPoseAnimInstance: IK Retargeter path is missing for %s."), *GetNameSafe(GetOwningComponent()));
return false;
}
UObject* LoadedObject = RetargeterPath.TryLoad();
LoadedRetargeter = Cast<UIKRetargeter>(LoadedObject);
if (!LoadedRetargeter)
{
UE_LOG(LogTemp, Warning, TEXT("PHYRetargetPoseAnimInstance: %s is not a valid UIKRetargeter asset."), *RetargeterPath.ToString());
return false;
}
// AnimGraph 中的 Retarget Pose From Mesh 节点通过属性绑定读取 LoadedRetargeter。
return true;
}

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

@@ -4,14 +4,30 @@
#include UE_INLINE_GENERATED_CPP_BY_NAME(PHYAICharacter)
#include "AbilitySystem/Attributes/PHYCombatAttributeSet.h"
#include "AbilitySystem/Attributes/PHYCoreAttributeSet.h"
#include "AbilitySystem/Attributes/PHYElementAttributeSet.h"
#include "Class/PHYClassComponent.h"
#include "Class/PHYClassSettings.h"
#include "GGA_AbilitySystemComponent.h"
#include "AI/PHYAIController.h"
APHYAICharacter::APHYAICharacter(const FObjectInitializer& ObjectInitializer)
: Super(ObjectInitializer)
{
AIControllerClass = APHYAIController::StaticClass();
AutoPossessAI = EAutoPossessAI::PlacedInWorldOrSpawned;
AbilitySystemComponent = CreateDefaultSubobject<UGGA_AbilitySystemComponent>(TEXT("AbilitySystemComponent"));
AbilitySystemComponent->SetIsReplicated(true);
AbilitySystemComponent->SetReplicationMode(EGameplayEffectReplicationMode::Mixed);
CoreAttributeSet = CreateDefaultSubobject<UPHYCoreAttributeSet>(TEXT("CoreAttributeSet"));
CombatAttributeSet = CreateDefaultSubobject<UPHYCombatAttributeSet>(TEXT("CombatAttributeSet"));
ElementAttributeSet = CreateDefaultSubobject<UPHYElementAttributeSet>(TEXT("ElementAttributeSet"));
ClassComponent = CreateDefaultSubobject<UPHYClassComponent>(TEXT("ClassComponent"));
ClassComponent->SetClassTag(GetDefault<UPHYClassSettings>()->DefaultAIClassTag);
}
void APHYAICharacter::BeginPlay()
@@ -33,5 +49,11 @@ void APHYAICharacter::InitializeAIAbilitySystem()
if (AbilitySystemComponent)
{
InitializeAbilitySystem(AbilitySystemComponent, this);
if (ClassComponent)
{
ClassComponent->ApplyClassAttributesToAbilitySystem(AbilitySystemComponent);
ClassComponent->ApplyConfiguredPlayerMeshIfAllowed(this);
}
}
}

View File

@@ -5,6 +5,7 @@
#include UE_INLINE_GENERATED_CPP_BY_NAME(PHYCharacterBase)
#include "AbilitySystemComponent.h"
#include "Animation/PHYCharacterMeshBridgeComponent.h"
#include "Camera/CameraComponent.h"
#include "Characters/PHYCharacterSettings.h"
#include "Characters/PHYCharacterStateComponent.h"
@@ -17,16 +18,45 @@
#include "GameFramework/CharacterMovementComponent.h"
#include "Interaction/GGS_InteractionSystemComponent.h"
#include "Net/UnrealNetwork.h"
#include "PHYConfigSettings.h"
#include "PHYGameplayTags.h"
#include "Ragdoll/GGS_RagdollComponent.h"
#include "Components/SLSCharacterMovementComponent.h"
#include "SLSIntegrationComponent.h"
#include "SLSIntegrationDataAsset.h"
#include "Targeting/GCS_TargetingSystemComponent.h"
#include "Team/GCS_CombatTeamAgentComponent.h"
namespace
{
ESLSRotationMode GetSLSRotationMode(const FGameplayTag NewRotationMode)
{
if (NewRotationMode == PHYGameplayTags::State_Rotation_Looking)
{
return ESLSRotationMode::LookingDirection;
}
if (NewRotationMode == PHYGameplayTags::State_Rotation_Aiming)
{
return ESLSRotationMode::AimingDirection;
}
return ESLSRotationMode::VelocityDirection;
}
}
APHYCharacterBase::APHYCharacterBase(const FObjectInitializer& ObjectInitializer)
: Super(ObjectInitializer)
: Super(ObjectInitializer.SetDefaultSubobjectClass<USLSCharacterMovementComponent>(ACharacter::CharacterMovementComponentName))
{
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"));
CombatSystemComponent = CreateDefaultSubobject<UGCS_CombatSystemComponent>(TEXT("CombatSystemComponent"));
TargetingSystemComponent = CreateDefaultSubobject<UGCS_TargetingSystemComponent>(TEXT("TargetingSystemComponent"));
@@ -34,6 +64,50 @@ APHYCharacterBase::APHYCharacterBase(const FObjectInitializer& ObjectInitializer
InteractionSystemComponent = CreateDefaultSubobject<UGGS_InteractionSystemComponent>(TEXT("InteractionSystemComponent"));
RagdollComponent = CreateDefaultSubobject<UGGS_RagdollComponent>(TEXT("RagdollComponent"));
ContextEffectComponent = CreateDefaultSubobject<UGES_ContextEffectComponent>(TEXT("ContextEffectComponent"));
MeshBridgeComponent = CreateDefaultSubobject<UPHYCharacterMeshBridgeComponent>(TEXT("MeshBridgeComponent"));
SLSIntegrationComponent = CreateDefaultSubobject<USLSIntegrationComponent>(TEXT("SLSIntegrationComponent"));
USLSIntegrationDataAsset* SLSIntegrationRuntimeDataAsset = CreateDefaultSubobject<USLSIntegrationDataAsset>(TEXT("SLSIntegrationRuntimeDataAsset"));
if (SLSIntegrationComponent && SLSIntegrationRuntimeDataAsset)
{
FSLSBaseCharacterMovementSettingsOp* MovementSettingsOp = SLSIntegrationRuntimeDataAsset->GetFirstSLSDataOpOfType<FSLSBaseCharacterMovementSettingsOp>();
if (!MovementSettingsOp)
{
MovementSettingsOp = SLSIntegrationRuntimeDataAsset->AddAndGetSLSDataOp<FSLSBaseCharacterMovementSettingsOp>();
}
if (MovementSettingsOp)
{
if (FSLSBaseCharacterMovementSettings* MovementSettings = static_cast<FSLSBaseCharacterMovementSettings*>(MovementSettingsOp->GetSettings()))
{
MovementSettings->bAddSkeletalBoneSyncComponent = false;
MovementSettings->Config.InitComponentType = ESLSComponentInitType::ManualInit;
MovementSettings->Config.MovementType = ESLSMovementType::Jog;
MovementSettings->Config.RotationMode = ESLSRotationMode::VelocityDirection;
MovementSettings->Config.bEnableSprintInLookingDirectionRotationMode = true;
}
}
SLSIntegrationRuntimeDataAsset->InitializeData();
SLSIntegrationComponent->SLSIntegrationDataAsset = SLSIntegrationRuntimeDataAsset;
}
if (UClass* GenericIntegrationClass = FindObject<UClass>(nullptr, TEXT("/Script/GenericGameSystem.GenericIntegrationComponent")))
{
GenericIntegrationComponent = Cast<UActorComponent>(CreateDefaultSubobject(
TEXT("GenericIntegrationComponent"),
UActorComponent::StaticClass(),
GenericIntegrationClass,
false,
false));
}
DisplayMeshComponent = CreateDefaultSubobject<USkeletalMeshComponent>(TEXT("DisplayMeshComponent"));
DisplayMeshComponent->SetupAttachment(GetMesh());
DisplayMeshComponent->SetRelativeLocation(FVector::ZeroVector);
DisplayMeshComponent->SetRelativeRotation(FRotator::ZeroRotator);
DisplayMeshComponent->SetCollisionEnabled(ECollisionEnabled::NoCollision);
DisplayMeshComponent->SetGenerateOverlapEvents(false);
}
void APHYCharacterBase::PreInitializeComponents()
@@ -43,6 +117,16 @@ void APHYCharacterBase::PreInitializeComponents()
UGameFrameworkComponentManager::AddGameFrameworkComponentReceiver(this);
}
void APHYCharacterBase::PostInitializeComponents()
{
Super::PostInitializeComponents();
if (MeshBridgeComponent)
{
MeshBridgeComponent->InitializeMeshBridge(this, DisplayMeshComponent);
}
}
void APHYCharacterBase::BeginPlay()
{
Super::BeginPlay();
@@ -59,6 +143,22 @@ void APHYCharacterBase::BeginPlay()
ContextEffectComponent->SetGameplayTagsProvider(this);
}
if (const UPHYLocomotionSettings* LocomotionSettings = GetDefault<UPHYLocomotionSettings>(); LocomotionSettings && LocomotionSettings->bUseSmoothLocomotionSystem)
{
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);
}
@@ -170,6 +270,11 @@ bool APHYCharacterBase::HandleInputTag(const FGameplayTag InputTag, const ETrigg
if (InputTag == PHYGameplayTags::Input_Sprint)
{
if (USLSCharacterMovementComponent* SLSMovementComponent = GetSLSCharacterMovementComponent())
{
SLSMovementComponent->UpdateMovementType(bPressed ? ESLSMovementType::Sprint : ESLSMovementType::Jog);
}
if (CharacterStateComponent)
{
CharacterStateComponent->SetMovementState(bPressed ? PHYGameplayTags::State_Movement_Sprint : PHYGameplayTags::State_Movement_Run);
@@ -186,10 +291,10 @@ bool APHYCharacterBase::HandleInputTag(const FGameplayTag InputTag, const ETrigg
if (InputTag == PHYGameplayTags::Input_Aim)
{
SetRotationMode_Implementation(bPressed ? PHYGameplayTags::State_Rotation_Aiming : PHYGameplayTags::State_Rotation_OrientToMovement);
if (CharacterStateComponent)
{
CharacterStateComponent->SetMovementSet(bPressed ? PHYGameplayTags::State_MovementSet_Aiming : PHYGameplayTags::State_MovementSet_Default);
CharacterStateComponent->SetRotationMode(bPressed ? PHYGameplayTags::State_Rotation_Strafe : PHYGameplayTags::State_Rotation_OrientToMovement);
}
return true;
@@ -230,6 +335,11 @@ bool APHYCharacterBase::HandleInputTag(const FGameplayTag InputTag, const ETrigg
return false;
}
USLSCharacterMovementComponent* APHYCharacterBase::GetSLSCharacterMovementComponent() const
{
return Cast<USLSCharacterMovementComponent>(GetCharacterMovement());
}
void APHYCharacterBase::SetMovementIntent(const FVector NewMovementIntent)
{
const FVector ClampedIntent = NewMovementIntent.GetClampedToMaxSize(1.0f);
@@ -397,6 +507,11 @@ void APHYCharacterBase::SetRotationMode_Implementation(const FGameplayTag NewRot
{
CharacterStateComponent->SetRotationMode(NewRotationMode);
}
if (USLSCharacterMovementComponent* SLSMovementComponent = GetSLSCharacterMovementComponent())
{
SLSMovementComponent->UpdateRotationMode(GetSLSRotationMode(NewRotationMode));
}
}
FGameplayTag APHYCharacterBase::GetRotationMode_Implementation() const
@@ -540,6 +655,12 @@ void APHYCharacterBase::ApplyCharacterSettings()
MoveComponent->MaxAcceleration = Settings->DefaultMaxAcceleration;
}
if (USkeletalMeshComponent* SourceMeshComponent = GetMesh())
{
SourceMeshComponent->SetRelativeLocation(Settings->DefaultMeshRelativeLocation);
SourceMeshComponent->SetRelativeRotation(Settings->DefaultMeshRelativeRotation);
}
if (CharacterStateComponent && GetCharacterMovement() && GetCharacterMovement()->IsFalling())
{
CharacterStateComponent->SetMovementState(PHYGameplayTags::State_Movement_Falling);

View File

@@ -5,14 +5,17 @@
#include UE_INLINE_GENERATED_CPP_BY_NAME(PHYPlayerCharacter)
#include "Camera/CameraComponent.h"
#include "Camera/PHYUGCSpringArmComponent.h"
#include "Class/PHYClassComponent.h"
#include "GameFramework/SpringArmComponent.h"
#include "GIPS_InputSystemComponent.h"
#include "Player/PHYPlayerState.h"
APHYPlayerCharacter::APHYPlayerCharacter(const FObjectInitializer& ObjectInitializer)
: Super(ObjectInitializer)
{
CameraBoom = CreateDefaultSubobject<UPHYUGCSpringArmComponent>(TEXT("CameraBoom"));
InputSystemComponent = CreateDefaultSubobject<UGIPS_InputSystemComponent>(TEXT("InputSystemComponent"));
CameraBoom = CreateDefaultSubobject<USpringArmComponent>(TEXT("CameraBoom"));
CameraBoom->SetupAttachment(GetRootComponent());
CameraBoom->TargetArmLength = 320.0f;
CameraBoom->bUsePawnControlRotation = true;
@@ -47,5 +50,11 @@ void APHYPlayerCharacter::InitializeAbilitySystemFromPlayerState()
if (UAbilitySystemComponent* PlayerASC = PHYPlayerState->GetAbilitySystemComponent())
{
InitializeAbilitySystem(PlayerASC, PHYPlayerState);
if (UPHYClassComponent* ClassComponent = PHYPlayerState->GetClassComponent())
{
ClassComponent->ApplyClassAttributesToAbilitySystem(PlayerASC);
ClassComponent->ApplyConfiguredPlayerMeshIfAllowed(this);
}
}
}

View File

@@ -0,0 +1,264 @@
// Copyright Epic Games, Inc. All Rights Reserved.
#include "Class/PHYClassComponent.h"
#include UE_INLINE_GENERATED_CPP_BY_NAME(PHYClassComponent)
#include "AbilitySystemComponent.h"
#include "Animation/AnimInstance.h"
#include "AbilitySystem/Attributes/PHYAttributeCalculationLibrary.h"
#include "AbilitySystem/Attributes/PHYCombatAttributeSet.h"
#include "AbilitySystem/Attributes/PHYCoreAttributeSet.h"
#include "AbilitySystem/Attributes/PHYElementAttributeSet.h"
#include "Animation/PHYCharacterMeshBridgeComponent.h"
#include "Characters/PHYAICharacter.h"
#include "Characters/PHYCharacterBase.h"
#include "Characters/PHYPlayerCharacter.h"
#include "Class/PHYClassSettings.h"
#include "Net/UnrealNetwork.h"
#include "PHYGameplayTags.h"
#include "Player/PHYPlayerState.h"
UPHYClassComponent::UPHYClassComponent(const FObjectInitializer& ObjectInitializer)
: Super(ObjectInitializer)
{
SetIsReplicatedByDefault(true);
CurrentClassTag = PHYGameplayTags::Class_Saber;
}
void UPHYClassComponent::GetLifetimeReplicatedProps(TArray<FLifetimeProperty>& OutLifetimeProps) const
{
Super::GetLifetimeReplicatedProps(OutLifetimeProps);
DOREPLIFETIME(ThisClass, CurrentClassTag);
}
void UPHYClassComponent::SetClassTag(const FGameplayTag NewClassTag)
{
if (!NewClassTag.IsValid())
{
return;
}
if (AActor* OwnerActor = GetOwner())
{
if (!OwnerActor->HasAuthority())
{
return;
}
}
CurrentClassTag = NewClassTag;
}
bool UPHYClassComponent::IsPlayerClassMeshAllowed() const
{
const UPHYClassSettings* ClassSettings = GetDefault<UPHYClassSettings>();
if (!ClassSettings)
{
return false;
}
const AActor* OwnerActor = GetOwner();
if (OwnerActor && OwnerActor->IsA<APHYAICharacter>())
{
return ClassSettings->bApplyPlayerClassMeshToAI;
}
if (OwnerActor && (OwnerActor->IsA<APHYPlayerState>() || OwnerActor->IsA<APHYPlayerCharacter>()))
{
return ClassSettings->bApplyPlayerClassMeshToPlayers;
}
return ClassSettings->bApplyPlayerClassMeshToPlayers;
}
USkeletalMesh* UPHYClassComponent::GetConfiguredPlayerMesh() const
{
const UPHYClassSettings* ClassSettings = GetDefault<UPHYClassSettings>();
if (!ClassSettings || !CurrentClassTag.IsValid())
{
return nullptr;
}
for (const FPHYPlayerClassMeshConfig& MeshConfig : ClassSettings->PlayerClassMeshes)
{
if (MeshConfig.ClassTag == CurrentClassTag)
{
return MeshConfig.PlayerMesh.LoadSynchronous();
}
}
return nullptr;
}
TSubclassOf<UAnimInstance> UPHYClassComponent::GetConfiguredPlayerDisplayAnimClass() const
{
const UPHYClassSettings* ClassSettings = GetDefault<UPHYClassSettings>();
if (!ClassSettings || !CurrentClassTag.IsValid())
{
return nullptr;
}
for (const FPHYPlayerClassMeshConfig& MeshConfig : ClassSettings->PlayerClassMeshes)
{
if (MeshConfig.ClassTag == CurrentClassTag)
{
return MeshConfig.PlayerDisplayAnimClass.LoadSynchronous();
}
}
return nullptr;
}
FSoftObjectPath UPHYClassComponent::GetConfiguredPlayerRetargeter() const
{
const UPHYClassSettings* ClassSettings = GetDefault<UPHYClassSettings>();
if (!ClassSettings || !CurrentClassTag.IsValid())
{
return FSoftObjectPath();
}
for (const FPHYPlayerClassMeshConfig& MeshConfig : ClassSettings->PlayerClassMeshes)
{
if (MeshConfig.ClassTag == CurrentClassTag)
{
return MeshConfig.PlayerRetargeter;
}
}
return FSoftObjectPath();
}
bool UPHYClassComponent::ApplyConfiguredPlayerMeshIfAllowed(AActor* AvatarActor) const
{
if (!IsPlayerClassMeshAllowed())
{
return false;
}
AActor* MeshOwner = AvatarActor ? AvatarActor : GetOwner();
APHYCharacterBase* Character = Cast<APHYCharacterBase>(MeshOwner);
if (!Character)
{
return false;
}
USkeletalMesh* PlayerMesh = GetConfiguredPlayerMesh();
if (!PlayerMesh)
{
return false;
}
const FSoftObjectPath PlayerRetargeter = GetConfiguredPlayerRetargeter();
UPHYCharacterMeshBridgeComponent* MeshBridge = Character->GetMeshBridgeComponent();
if (!MeshBridge)
{
return false;
}
MeshBridge->InitializeMeshBridge(Character, Character->GetDisplayMeshComponent());
return MeshBridge->ApplyDisplayMesh(PlayerMesh, GetConfiguredPlayerDisplayAnimClass(), PlayerRetargeter);
}
bool UPHYClassComponent::ApplyClassAttributesToAbilitySystem(UAbilitySystemComponent* AbilitySystemComponent) const
{
if (!AbilitySystemComponent)
{
return false;
}
const AActor* AuthorityActor = GetOwner() ? GetOwner() : AbilitySystemComponent->GetOwnerActor();
if (!AuthorityActor || !AuthorityActor->HasAuthority())
{
return false;
}
const UPHYClassSettings* ClassSettings = GetDefault<UPHYClassSettings>();
if (!ClassSettings)
{
return false;
}
constexpr int32 RequiredTotalPoints = 60;
const FPHYClassCoreAttributeAllocation* Allocation = ClassSettings->FindCoreAttributeAllocation(CurrentClassTag);
if (!Allocation || !Allocation->IsValidTotal(RequiredTotalPoints))
{
UE_LOG(LogTemp, Warning, TEXT("PHYClassComponent: Class %s has no valid %d-point core attribute allocation. Falling back to DefaultPlayerClassTag."),
*CurrentClassTag.ToString(), RequiredTotalPoints);
Allocation = ClassSettings->FindCoreAttributeAllocation(ClassSettings->DefaultPlayerClassTag);
}
if (!Allocation || !Allocation->IsValidTotal(RequiredTotalPoints))
{
UE_LOG(LogTemp, Warning, TEXT("PHYClassComponent: DefaultPlayerClassTag allocation is missing or invalid. Falling back to Class.Saber."));
Allocation = ClassSettings->FindCoreAttributeAllocation(PHYGameplayTags::Class_Saber);
}
if (!Allocation || !Allocation->IsValidTotal(RequiredTotalPoints))
{
UE_LOG(LogTemp, Warning, TEXT("PHYClassComponent: Unable to apply class attributes because no valid fallback allocation exists."));
return false;
}
FPHYCoreAttributeSnapshot Snapshot;
Snapshot.Strength = Allocation->Strength;
Snapshot.Dexterity = Allocation->Dexterity;
Snapshot.Vitality = Allocation->Vitality;
Snapshot.Intelligence = Allocation->Intelligence;
Snapshot.Spirit = Allocation->Spirit;
Snapshot.Perception = Allocation->Perception;
const FPHYDerivedCombatAttributes CombatAttributes = UPHYAttributeCalculationLibrary::CalculateCombatAttributes(Snapshot);
const FPHYDerivedElementAttributes ElementAttributes = UPHYAttributeCalculationLibrary::CalculateElementAttributes(Snapshot);
// 直接设置 ASC 属性基值,重复调用只会覆盖当前职业结果,不会叠加。
AbilitySystemComponent->SetNumericAttributeBase(UPHYCoreAttributeSet::GetStrengthAttribute(), Snapshot.Strength);
AbilitySystemComponent->SetNumericAttributeBase(UPHYCoreAttributeSet::GetDexterityAttribute(), Snapshot.Dexterity);
AbilitySystemComponent->SetNumericAttributeBase(UPHYCoreAttributeSet::GetVitalityAttribute(), Snapshot.Vitality);
AbilitySystemComponent->SetNumericAttributeBase(UPHYCoreAttributeSet::GetIntelligenceAttribute(), Snapshot.Intelligence);
AbilitySystemComponent->SetNumericAttributeBase(UPHYCoreAttributeSet::GetSpiritAttribute(), Snapshot.Spirit);
AbilitySystemComponent->SetNumericAttributeBase(UPHYCoreAttributeSet::GetPerceptionAttribute(), Snapshot.Perception);
AbilitySystemComponent->SetNumericAttributeBase(UPHYCombatAttributeSet::GetMaxHealthAttribute(), CombatAttributes.MaxHealth);
AbilitySystemComponent->SetNumericAttributeBase(UPHYCombatAttributeSet::GetHealthAttribute(), CombatAttributes.MaxHealth);
AbilitySystemComponent->SetNumericAttributeBase(UPHYCombatAttributeSet::GetMaxManaAttribute(), CombatAttributes.MaxMana);
AbilitySystemComponent->SetNumericAttributeBase(UPHYCombatAttributeSet::GetManaAttribute(), CombatAttributes.MaxMana);
AbilitySystemComponent->SetNumericAttributeBase(UPHYCombatAttributeSet::GetMaxStaminaAttribute(), CombatAttributes.MaxStamina);
AbilitySystemComponent->SetNumericAttributeBase(UPHYCombatAttributeSet::GetStaminaAttribute(), CombatAttributes.MaxStamina);
AbilitySystemComponent->SetNumericAttributeBase(UPHYCombatAttributeSet::GetPhysicalAttackPowerAttribute(), CombatAttributes.PhysicalAttackPower);
AbilitySystemComponent->SetNumericAttributeBase(UPHYCombatAttributeSet::GetSpellPowerAttribute(), CombatAttributes.SpellPower);
AbilitySystemComponent->SetNumericAttributeBase(UPHYCombatAttributeSet::GetArmorAttribute(), CombatAttributes.Armor);
AbilitySystemComponent->SetNumericAttributeBase(UPHYCombatAttributeSet::GetMagicResistanceAttribute(), CombatAttributes.MagicResistance);
AbilitySystemComponent->SetNumericAttributeBase(UPHYCombatAttributeSet::GetAccuracyAttribute(), CombatAttributes.Accuracy);
AbilitySystemComponent->SetNumericAttributeBase(UPHYCombatAttributeSet::GetEvasionAttribute(), CombatAttributes.Evasion);
AbilitySystemComponent->SetNumericAttributeBase(UPHYCombatAttributeSet::GetCriticalChanceAttribute(), CombatAttributes.CriticalChance);
AbilitySystemComponent->SetNumericAttributeBase(UPHYCombatAttributeSet::GetCriticalDamageAttribute(), CombatAttributes.CriticalDamage);
AbilitySystemComponent->SetNumericAttributeBase(UPHYCombatAttributeSet::GetAttackSpeedAttribute(), CombatAttributes.AttackSpeed);
AbilitySystemComponent->SetNumericAttributeBase(UPHYCombatAttributeSet::GetCooldownReductionAttribute(), CombatAttributes.CooldownReduction);
AbilitySystemComponent->SetNumericAttributeBase(UPHYCombatAttributeSet::GetBlockPowerAttribute(), CombatAttributes.BlockPower);
AbilitySystemComponent->SetNumericAttributeBase(UPHYCombatAttributeSet::GetGuardBreakPowerAttribute(), CombatAttributes.GuardBreakPower);
AbilitySystemComponent->SetNumericAttributeBase(UPHYCombatAttributeSet::GetPoiseAttribute(), CombatAttributes.Poise);
AbilitySystemComponent->SetNumericAttributeBase(UPHYCombatAttributeSet::GetPoiseDamageAttribute(), CombatAttributes.PoiseDamage);
AbilitySystemComponent->SetNumericAttributeBase(UPHYElementAttributeSet::GetFireDamageBonusAttribute(), ElementAttributes.DamageBonus);
AbilitySystemComponent->SetNumericAttributeBase(UPHYElementAttributeSet::GetWaterDamageBonusAttribute(), ElementAttributes.DamageBonus);
AbilitySystemComponent->SetNumericAttributeBase(UPHYElementAttributeSet::GetIceDamageBonusAttribute(), ElementAttributes.DamageBonus);
AbilitySystemComponent->SetNumericAttributeBase(UPHYElementAttributeSet::GetLightningDamageBonusAttribute(), ElementAttributes.DamageBonus);
AbilitySystemComponent->SetNumericAttributeBase(UPHYElementAttributeSet::GetEarthDamageBonusAttribute(), ElementAttributes.DamageBonus);
AbilitySystemComponent->SetNumericAttributeBase(UPHYElementAttributeSet::GetWindDamageBonusAttribute(), ElementAttributes.DamageBonus);
AbilitySystemComponent->SetNumericAttributeBase(UPHYElementAttributeSet::GetLightDamageBonusAttribute(), ElementAttributes.DamageBonus);
AbilitySystemComponent->SetNumericAttributeBase(UPHYElementAttributeSet::GetDarkDamageBonusAttribute(), ElementAttributes.DamageBonus);
AbilitySystemComponent->SetNumericAttributeBase(UPHYElementAttributeSet::GetFireResistanceAttribute(), ElementAttributes.Resistance);
AbilitySystemComponent->SetNumericAttributeBase(UPHYElementAttributeSet::GetWaterResistanceAttribute(), ElementAttributes.Resistance);
AbilitySystemComponent->SetNumericAttributeBase(UPHYElementAttributeSet::GetIceResistanceAttribute(), ElementAttributes.Resistance);
AbilitySystemComponent->SetNumericAttributeBase(UPHYElementAttributeSet::GetLightningResistanceAttribute(), ElementAttributes.Resistance);
AbilitySystemComponent->SetNumericAttributeBase(UPHYElementAttributeSet::GetEarthResistanceAttribute(), ElementAttributes.Resistance);
AbilitySystemComponent->SetNumericAttributeBase(UPHYElementAttributeSet::GetWindResistanceAttribute(), ElementAttributes.Resistance);
AbilitySystemComponent->SetNumericAttributeBase(UPHYElementAttributeSet::GetLightResistanceAttribute(), ElementAttributes.Resistance);
AbilitySystemComponent->SetNumericAttributeBase(UPHYElementAttributeSet::GetDarkResistanceAttribute(), ElementAttributes.Resistance);
AbilitySystemComponent->SetNumericAttributeBase(UPHYElementAttributeSet::GetElementPenetrationAttribute(), ElementAttributes.Penetration);
return true;
}

View File

@@ -0,0 +1,63 @@
// Copyright Epic Games, Inc. All Rights Reserved.
#include "Class/PHYClassSettings.h"
#include UE_INLINE_GENERATED_CPP_BY_NAME(PHYClassSettings)
#include "PHYGameplayTags.h"
int32 FPHYClassCoreAttributeAllocation::GetTotalPoints() const
{
return Strength + Dexterity + Vitality + Intelligence + Spirit + Perception;
}
bool FPHYClassCoreAttributeAllocation::IsValidTotal(const int32 RequiredTotal) const
{
return GetTotalPoints() == RequiredTotal;
}
UPHYClassSettings::UPHYClassSettings()
{
DefaultPlayerClassTag = PHYGameplayTags::Class_Saber;
DefaultAIClassTag = PHYGameplayTags::Class_Saber;
PlayerClassMeshes =
{
{ PHYGameplayTags::Class_Saber, nullptr },
{ PHYGameplayTags::Class_Lancer, nullptr },
{ PHYGameplayTags::Class_Archer, nullptr },
{ PHYGameplayTags::Class_Rider, nullptr },
{ PHYGameplayTags::Class_Caster, nullptr },
{ PHYGameplayTags::Class_Assassin, nullptr },
{ PHYGameplayTags::Class_Berserker, nullptr }
};
CoreAttributeAllocations =
{
{ PHYGameplayTags::Class_Saber, 12, 10, 12, 8, 8, 10 },
{ PHYGameplayTags::Class_Lancer, 10, 13, 10, 7, 8, 12 },
{ PHYGameplayTags::Class_Archer, 8, 12, 8, 8, 10, 14 },
{ PHYGameplayTags::Class_Rider, 10, 11, 11, 8, 10, 10 },
{ PHYGameplayTags::Class_Caster, 5, 7, 8, 16, 15, 9 },
{ PHYGameplayTags::Class_Assassin, 7, 15, 7, 8, 8, 15 },
{ PHYGameplayTags::Class_Berserker, 16, 8, 15, 4, 5, 12 }
};
}
const FPHYClassCoreAttributeAllocation* UPHYClassSettings::FindCoreAttributeAllocation(const FGameplayTag ClassTag) const
{
if (!ClassTag.IsValid())
{
return nullptr;
}
for (const FPHYClassCoreAttributeAllocation& Allocation : CoreAttributeAllocations)
{
if (Allocation.ClassTag == ClassTag)
{
return &Allocation;
}
}
return nullptr;
}

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

@@ -0,0 +1,35 @@
// Copyright Epic Games, Inc. All Rights Reserved.
#include "GameplayTags/PHYGameplayTags_Attribute.h"
// 属性 Tag 只表达领域和分类,具体属性读写仍通过 GAS AttributeSet 完成。
namespace PHYGameplayTags
{
UE_DEFINE_GAMEPLAY_TAG(Attribute_Core, "Attribute.Core");
UE_DEFINE_GAMEPLAY_TAG(Attribute_Core_Strength, "Attribute.Core.Strength");
UE_DEFINE_GAMEPLAY_TAG(Attribute_Core_Dexterity, "Attribute.Core.Dexterity");
UE_DEFINE_GAMEPLAY_TAG(Attribute_Core_Vitality, "Attribute.Core.Vitality");
UE_DEFINE_GAMEPLAY_TAG(Attribute_Core_Intelligence, "Attribute.Core.Intelligence");
UE_DEFINE_GAMEPLAY_TAG(Attribute_Core_Spirit, "Attribute.Core.Spirit");
UE_DEFINE_GAMEPLAY_TAG(Attribute_Core_Perception, "Attribute.Core.Perception");
UE_DEFINE_GAMEPLAY_TAG(Attribute_Combat, "Attribute.Combat");
UE_DEFINE_GAMEPLAY_TAG(Attribute_Combat_Health, "Attribute.Combat.Health");
UE_DEFINE_GAMEPLAY_TAG(Attribute_Combat_Mana, "Attribute.Combat.Mana");
UE_DEFINE_GAMEPLAY_TAG(Attribute_Combat_Stamina, "Attribute.Combat.Stamina");
UE_DEFINE_GAMEPLAY_TAG(Attribute_Combat_Offense, "Attribute.Combat.Offense");
UE_DEFINE_GAMEPLAY_TAG(Attribute_Combat_Defense, "Attribute.Combat.Defense");
UE_DEFINE_GAMEPLAY_TAG(Attribute_Element, "Attribute.Element");
UE_DEFINE_GAMEPLAY_TAG(Attribute_Element_Fire, "Attribute.Element.Fire");
UE_DEFINE_GAMEPLAY_TAG(Attribute_Element_Water, "Attribute.Element.Water");
UE_DEFINE_GAMEPLAY_TAG(Attribute_Element_Ice, "Attribute.Element.Ice");
UE_DEFINE_GAMEPLAY_TAG(Attribute_Element_Lightning, "Attribute.Element.Lightning");
UE_DEFINE_GAMEPLAY_TAG(Attribute_Element_Earth, "Attribute.Element.Earth");
UE_DEFINE_GAMEPLAY_TAG(Attribute_Element_Wind, "Attribute.Element.Wind");
UE_DEFINE_GAMEPLAY_TAG(Attribute_Element_Light, "Attribute.Element.Light");
UE_DEFINE_GAMEPLAY_TAG(Attribute_Element_Dark, "Attribute.Element.Dark");
UE_DEFINE_GAMEPLAY_TAG(Attribute_Element_DamageBonus, "Attribute.Element.DamageBonus");
UE_DEFINE_GAMEPLAY_TAG(Attribute_Element_Resistance, "Attribute.Element.Resistance");
UE_DEFINE_GAMEPLAY_TAG(Attribute_Element_Penetration, "Attribute.Element.Penetration");
}

View File

@@ -0,0 +1,15 @@
// Copyright Epic Games, Inc. All Rights Reserved.
#include "GameplayTags/PHYGameplayTags_Class.h"
// 职业 Tag 由职业系统维护,玩家和 AI 共享分类语义,但 Mesh 应用策略由职业配置控制。
namespace PHYGameplayTags
{
UE_DEFINE_GAMEPLAY_TAG(Class_Saber, "Class.Saber");
UE_DEFINE_GAMEPLAY_TAG(Class_Lancer, "Class.Lancer");
UE_DEFINE_GAMEPLAY_TAG(Class_Archer, "Class.Archer");
UE_DEFINE_GAMEPLAY_TAG(Class_Rider, "Class.Rider");
UE_DEFINE_GAMEPLAY_TAG(Class_Caster, "Class.Caster");
UE_DEFINE_GAMEPLAY_TAG(Class_Assassin, "Class.Assassin");
UE_DEFINE_GAMEPLAY_TAG(Class_Berserker, "Class.Berserker");
}

View File

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

View File

@@ -18,5 +18,6 @@ namespace PHYGameplayTags
UE_DEFINE_GAMEPLAY_TAG(State_MovementSet_Default, "State.MovementSet.Default");
UE_DEFINE_GAMEPLAY_TAG(State_MovementSet_Aiming, "State.MovementSet.Aiming");
UE_DEFINE_GAMEPLAY_TAG(State_Rotation_OrientToMovement, "State.Rotation.OrientToMovement");
UE_DEFINE_GAMEPLAY_TAG(State_Rotation_Strafe, "State.Rotation.Strafe");
UE_DEFINE_GAMEPLAY_TAG(State_Rotation_Looking, "State.Rotation.Looking");
UE_DEFINE_GAMEPLAY_TAG(State_Rotation_Aiming, "State.Rotation.Aiming");
}

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,83 @@
// Copyright Epic Games, Inc. All Rights Reserved.
#include "Input/PHYInputProcessor_Look.h"
#include UE_INLINE_GENERATED_CPP_BY_NAME(PHYInputProcessor_Look)
#include "Characters/PHYPlayerCharacter.h"
#include "GIPS_InputSystemComponent.h"
#include "PHYGameplayTags.h"
#include "Player/PHYPlayerController.h"
UPHYInputProcessor_Look::UPHYInputProcessor_Look()
{
InputTags.AddTag(PHYGameplayTags::Input_Look);
TriggerEvents = {
ETriggerEvent::Started,
ETriggerEvent::Triggered,
ETriggerEvent::Ongoing,
ETriggerEvent::Completed,
ETriggerEvent::Canceled
};
}
bool UPHYInputProcessor_Look::CheckCanHandleInput_Implementation(UGIPS_InputSystemComponent* IC, const FInputActionInstance& ActionData, const FGameplayTag InputTag, const ETriggerEvent TriggerEvent) const
{
(void)ActionData;
(void)TriggerEvent;
if (!IC || InputTag != PHYGameplayTags::Input_Look)
{
return false;
}
const APHYPlayerCharacter* PlayerCharacter = Cast<APHYPlayerCharacter>(IC->GetControlledPawn());
return PlayerCharacter && PlayerCharacter->GetController<APHYPlayerController>();
}
void UPHYInputProcessor_Look::HandleInputTriggered_Implementation(UGIPS_InputSystemComponent* IC, const FInputActionInstance& ActionData, const FGameplayTag InputTag) const
{
(void)InputTag;
RouteLookInput(IC, ActionData, ETriggerEvent::Triggered);
}
void UPHYInputProcessor_Look::HandleInputStarted_Implementation(UGIPS_InputSystemComponent* IC, const FInputActionInstance& ActionData, const FGameplayTag InputTag) const
{
(void)InputTag;
RouteLookInput(IC, ActionData, ETriggerEvent::Started);
}
void UPHYInputProcessor_Look::HandleInputOngoing_Implementation(UGIPS_InputSystemComponent* IC, const FInputActionInstance& ActionData, const FGameplayTag InputTag) const
{
(void)InputTag;
RouteLookInput(IC, ActionData, ETriggerEvent::Ongoing);
}
void UPHYInputProcessor_Look::HandleInputCanceled_Implementation(UGIPS_InputSystemComponent* IC, const FInputActionInstance& ActionData, const FGameplayTag InputTag) const
{
(void)InputTag;
RouteLookInput(IC, ActionData, ETriggerEvent::Canceled);
}
void UPHYInputProcessor_Look::HandleInputCompleted_Implementation(UGIPS_InputSystemComponent* IC, const FInputActionInstance& ActionData, const FGameplayTag InputTag) const
{
(void)InputTag;
RouteLookInput(IC, ActionData, ETriggerEvent::Completed);
}
FString UPHYInputProcessor_Look::GetEditorFriendlyName_Implementation() const
{
return TEXT("PHY Look");
}
bool UPHYInputProcessor_Look::RouteLookInput(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->RouteLookInputFromProcessor(ActionData, TriggerEvent) : false;
}

View File

@@ -0,0 +1,83 @@
// Copyright Epic Games, Inc. All Rights Reserved.
#include "Input/PHYInputProcessor_Move.h"
#include UE_INLINE_GENERATED_CPP_BY_NAME(PHYInputProcessor_Move)
#include "Characters/PHYPlayerCharacter.h"
#include "GIPS_InputSystemComponent.h"
#include "PHYGameplayTags.h"
#include "Player/PHYPlayerController.h"
UPHYInputProcessor_Move::UPHYInputProcessor_Move()
{
InputTags.AddTag(PHYGameplayTags::Input_Move);
TriggerEvents = {
ETriggerEvent::Started,
ETriggerEvent::Triggered,
ETriggerEvent::Ongoing,
ETriggerEvent::Completed,
ETriggerEvent::Canceled
};
}
bool UPHYInputProcessor_Move::CheckCanHandleInput_Implementation(UGIPS_InputSystemComponent* IC, const FInputActionInstance& ActionData, const FGameplayTag InputTag, const ETriggerEvent TriggerEvent) const
{
(void)ActionData;
(void)TriggerEvent;
if (!IC || InputTag != PHYGameplayTags::Input_Move)
{
return false;
}
const APHYPlayerCharacter* PlayerCharacter = Cast<APHYPlayerCharacter>(IC->GetControlledPawn());
return PlayerCharacter && PlayerCharacter->GetController<APHYPlayerController>();
}
void UPHYInputProcessor_Move::HandleInputTriggered_Implementation(UGIPS_InputSystemComponent* IC, const FInputActionInstance& ActionData, const FGameplayTag InputTag) const
{
(void)InputTag;
RouteMoveInput(IC, ActionData, ETriggerEvent::Triggered);
}
void UPHYInputProcessor_Move::HandleInputStarted_Implementation(UGIPS_InputSystemComponent* IC, const FInputActionInstance& ActionData, const FGameplayTag InputTag) const
{
(void)InputTag;
RouteMoveInput(IC, ActionData, ETriggerEvent::Started);
}
void UPHYInputProcessor_Move::HandleInputOngoing_Implementation(UGIPS_InputSystemComponent* IC, const FInputActionInstance& ActionData, const FGameplayTag InputTag) const
{
(void)InputTag;
RouteMoveInput(IC, ActionData, ETriggerEvent::Ongoing);
}
void UPHYInputProcessor_Move::HandleInputCanceled_Implementation(UGIPS_InputSystemComponent* IC, const FInputActionInstance& ActionData, const FGameplayTag InputTag) const
{
(void)InputTag;
RouteMoveInput(IC, ActionData, ETriggerEvent::Canceled);
}
void UPHYInputProcessor_Move::HandleInputCompleted_Implementation(UGIPS_InputSystemComponent* IC, const FInputActionInstance& ActionData, const FGameplayTag InputTag) const
{
(void)InputTag;
RouteMoveInput(IC, ActionData, ETriggerEvent::Completed);
}
FString UPHYInputProcessor_Move::GetEditorFriendlyName_Implementation() const
{
return TEXT("PHY Move");
}
bool UPHYInputProcessor_Move::RouteMoveInput(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->RouteMoveInputFromProcessor(ActionData, TriggerEvent) : false;
}

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,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 "Characters/PHYPlayerCharacter.h"
#include "Characters/PHYCharacterSettings.h"
#include "Components/SLSCharacterMovementComponent.h"
#include "Game/PHYHUD.h"
#include "GIPS_InputSystemComponent.h"
#include "InputActionValue.h"
#include "Player/PHYPlayerCameraManager.h"
#include "PHYGameplayTags.h"
namespace
@@ -40,9 +42,14 @@ namespace
APHYPlayerController::APHYPlayerController(const FObjectInitializer& ObjectInitializer)
: Super(ObjectInitializer)
{
PlayerCameraManagerClass = APHYPlayerCameraManager::StaticClass();
InputSystemComponent = CreateDefaultSubobject<UGIPS_InputSystemComponent>(TEXT("InputSystemComponent"));
const UPHYCharacterSettings* CharacterSettings = GetDefault<UPHYCharacterSettings>();
if (CharacterSettings && !CharacterSettings->DefaultPlayerCameraManagerClass.IsNull())
{
if (UClass* CameraManagerClass = CharacterSettings->DefaultPlayerCameraManagerClass.LoadSynchronous())
{
PlayerCameraManagerClass = CameraManagerClass;
}
}
}
void APHYPlayerController::BeginPlay()
@@ -64,28 +71,57 @@ APHYPlayerCharacter* APHYPlayerController::GetPHYPlayerCharacter() const
return Cast<APHYPlayerCharacter>(GetPawn());
}
void APHYPlayerController::BindInputEvents()
UGIPS_InputSystemComponent* APHYPlayerController::GetInputSystemComponent() const
{
if (!InputSystemComponent)
if (const APHYPlayerCharacter* PHYCharacter = GetPHYPlayerCharacter())
{
return;
return PHYCharacter->GetInputSystemComponent();
}
InputSystemComponent->OnReceivedInput.RemoveDynamic(this, &ThisClass::OnReceivedInput);
InputSystemComponent->OnReceivedInput.AddDynamic(this, &ThisClass::OnReceivedInput);
return nullptr;
}
void APHYPlayerController::BindInputEvents()
{
if (BoundInputSystemComponent)
{
BoundInputSystemComponent->OnReceivedInput.RemoveDynamic(this, &ThisClass::OnReceivedInput);
}
BoundInputSystemComponent = GetInputSystemComponent();
if (BoundInputSystemComponent)
{
BoundInputSystemComponent->OnReceivedInput.RemoveDynamic(this, &ThisClass::OnReceivedInput);
BoundInputSystemComponent->OnReceivedInput.AddDynamic(this, &ThisClass::OnReceivedInput);
}
}
void APHYPlayerController::OnReceivedInput(const FInputActionInstance& ActionData, const FGameplayTag& InputTag, const ETriggerEvent TriggerEvent)
{
if (InputTag == PHYGameplayTags::Input_Move)
{
HandleMoveInput(ActionData, TriggerEvent);
if (!ConsumeMoveProcessorGuard(TriggerEvent))
{
RouteMoveInput(ActionData, TriggerEvent);
}
return;
}
if (InputTag == PHYGameplayTags::Input_Look)
{
HandleLookInput(ActionData, TriggerEvent);
if (!ConsumeLookProcessorGuard(TriggerEvent))
{
RouteLookInput(ActionData, TriggerEvent);
}
return;
}
if (InputTag == PHYGameplayTags::Input_Menu_Attribute)
{
if (!ConsumeAttributeMenuProcessorGuard(TriggerEvent))
{
RouteOpenAttributeMenuInput(ActionData, TriggerEvent);
}
return;
}
@@ -95,6 +131,42 @@ void APHYPlayerController::OnReceivedInput(const FInputActionInstance& ActionDat
}
}
bool APHYPlayerController::RouteMoveInput(const FInputActionInstance& ActionData, const ETriggerEvent TriggerEvent)
{
return HandleMoveInput(ActionData, TriggerEvent);
}
bool APHYPlayerController::RouteMoveInputFromProcessor(const FInputActionInstance& ActionData, const ETriggerEvent TriggerEvent)
{
bMoveInputHandledByProcessor = true;
LastMoveInputProcessorTriggerEvent = TriggerEvent;
return RouteMoveInput(ActionData, TriggerEvent);
}
bool APHYPlayerController::RouteLookInput(const FInputActionInstance& ActionData, const ETriggerEvent TriggerEvent)
{
return HandleLookInput(ActionData, TriggerEvent);
}
bool APHYPlayerController::RouteLookInputFromProcessor(const FInputActionInstance& ActionData, const ETriggerEvent TriggerEvent)
{
bLookInputHandledByProcessor = true;
LastLookInputProcessorTriggerEvent = 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)
{
APHYPlayerCharacter* PHYCharacter = GetPHYPlayerCharacter();
@@ -106,33 +178,92 @@ bool APHYPlayerController::HandleMoveInput(const FInputActionInstance& ActionDat
CachedMovementInput = IsInputReleased(TriggerEvent) ? FVector2D::ZeroVector : ExtractAxis2D(ActionData.GetValue());
const FRotator CurrentControlRotation = GetControlRotation();
const FRotator YawRotation(0.0f, CurrentControlRotation.Yaw, 0.0f);
const FRotator YawRotation(0.0f, PHYCharacter->GetControlRotation().Yaw, 0.0f);
const FVector ForwardDirection = FRotationMatrix(YawRotation).GetUnitAxis(EAxis::X);
const FVector RightDirection = FRotationMatrix(YawRotation).GetUnitAxis(EAxis::Y);
const FVector WorldIntent = (ForwardDirection * CachedMovementInput.Y) + (RightDirection * CachedMovementInput.X);
PHYCharacter->SetMovementIntent(WorldIntent);
if (!CachedMovementInput.IsNearlyZero())
if (USLSCharacterMovementComponent* SLSMovementComponent = PHYCharacter->GetSLSCharacterMovementComponent())
{
PHYCharacter->AddMovementInput(ForwardDirection, CachedMovementInput.Y);
PHYCharacter->AddMovementInput(RightDirection, CachedMovementInput.X);
SLSMovementComponent->Input_OnMove(CachedMovementInput);
return true;
}
return false;
}
bool APHYPlayerController::ConsumeMoveProcessorGuard(const ETriggerEvent TriggerEvent)
{
if (bMoveInputHandledByProcessor && LastMoveInputProcessorTriggerEvent == TriggerEvent)
{
bMoveInputHandledByProcessor = false;
LastMoveInputProcessorTriggerEvent = ETriggerEvent::None;
return true;
}
bMoveInputHandledByProcessor = false;
LastMoveInputProcessorTriggerEvent = ETriggerEvent::None;
return false;
}
bool APHYPlayerController::ConsumeLookProcessorGuard(const ETriggerEvent TriggerEvent)
{
if (bLookInputHandledByProcessor && LastLookInputProcessorTriggerEvent == TriggerEvent)
{
bLookInputHandledByProcessor = false;
LastLookInputProcessorTriggerEvent = ETriggerEvent::None;
return true;
}
bLookInputHandledByProcessor = false;
LastLookInputProcessorTriggerEvent = ETriggerEvent::None;
return false;
}
bool APHYPlayerController::ConsumeAttributeMenuProcessorGuard(const ETriggerEvent TriggerEvent)
{
if (bAttributeMenuInputHandledByProcessor && LastAttributeMenuInputProcessorTriggerEvent == TriggerEvent)
{
bAttributeMenuInputHandledByProcessor = false;
LastAttributeMenuInputProcessorTriggerEvent = ETriggerEvent::None;
return true;
}
bAttributeMenuInputHandledByProcessor = false;
LastAttributeMenuInputProcessorTriggerEvent = ETriggerEvent::None;
return false;
}
bool APHYPlayerController::HandleOpenAttributeMenuInput(const FInputActionInstance& ActionData, const ETriggerEvent TriggerEvent)
{
(void)ActionData;
if (TriggerEvent != ETriggerEvent::Started || !IsLocalController())
{
return false;
}
APHYHUD* PHYHUD = GetHUD<APHYHUD>();
return PHYHUD && PHYHUD->OpenAttributeMenu(this);
}
bool APHYPlayerController::HandleLookInput(const FInputActionInstance& ActionData, const ETriggerEvent TriggerEvent)
{
CachedLookInput = IsInputReleased(TriggerEvent) ? FVector2D::ZeroVector : ExtractAxis2D(ActionData.GetValue());
CachedRotationInput = FRotator(CachedLookInput.Y, CachedLookInput.X, 0.0f);
if (!CachedLookInput.IsNearlyZero())
APHYPlayerCharacter* PHYCharacter = GetPHYPlayerCharacter();
if (!PHYCharacter)
{
AddYawInput(CachedLookInput.X);
AddPitchInput(CachedLookInput.Y);
return false;
}
if (USLSCharacterMovementComponent* SLSMovementComponent = PHYCharacter->GetSLSCharacterMovementComponent())
{
SLSMovementComponent->Input_OnLook(CachedLookInput);
return true;
}
return false;
}

View File

@@ -4,7 +4,13 @@
#include UE_INLINE_GENERATED_CPP_BY_NAME(PHYPlayerState)
#include "AbilitySystem/Attributes/PHYCombatAttributeSet.h"
#include "AbilitySystem/Attributes/PHYCoreAttributeSet.h"
#include "AbilitySystem/Attributes/PHYElementAttributeSet.h"
#include "Class/PHYClassComponent.h"
#include "Class/PHYClassSettings.h"
#include "GGA_AbilitySystemComponent.h"
#include "Net/UnrealNetwork.h"
APHYPlayerState::APHYPlayerState(const FObjectInitializer& ObjectInitializer)
: Super(ObjectInitializer)
@@ -14,9 +20,78 @@ APHYPlayerState::APHYPlayerState(const FObjectInitializer& ObjectInitializer)
AbilitySystemComponent = CreateDefaultSubobject<UGGA_AbilitySystemComponent>(TEXT("AbilitySystemComponent"));
AbilitySystemComponent->SetIsReplicated(true);
AbilitySystemComponent->SetReplicationMode(EGameplayEffectReplicationMode::Mixed);
CoreAttributeSet = CreateDefaultSubobject<UPHYCoreAttributeSet>(TEXT("CoreAttributeSet"));
CombatAttributeSet = CreateDefaultSubobject<UPHYCombatAttributeSet>(TEXT("CombatAttributeSet"));
ElementAttributeSet = CreateDefaultSubobject<UPHYElementAttributeSet>(TEXT("ElementAttributeSet"));
ClassComponent = CreateDefaultSubobject<UPHYClassComponent>(TEXT("ClassComponent"));
ClassComponent->SetClassTag(GetDefault<UPHYClassSettings>()->DefaultPlayerClassTag);
}
void APHYPlayerState::GetLifetimeReplicatedProps(TArray<FLifetimeProperty>& OutLifetimeProps) const
{
Super::GetLifetimeReplicatedProps(OutLifetimeProps);
DOREPLIFETIME(ThisClass, PlayerLevel);
DOREPLIFETIME(ThisClass, Experience);
DOREPLIFETIME(ThisClass, ExperienceForNextLevel);
}
UAbilitySystemComponent* APHYPlayerState::GetAbilitySystemComponent() const
{
return AbilitySystemComponent;
}
void APHYPlayerState::SetPlayerLevel(const int32 NewLevel)
{
SetProgression(NewLevel, Experience, ExperienceForNextLevel);
}
void APHYPlayerState::SetExperience(const int32 NewExperience)
{
SetProgression(PlayerLevel, NewExperience, ExperienceForNextLevel);
}
void APHYPlayerState::AddExperience(const int32 DeltaExperience)
{
SetExperience(Experience + DeltaExperience);
}
void APHYPlayerState::SetExperienceForNextLevel(const int32 NewExperienceForNextLevel)
{
SetProgression(PlayerLevel, Experience, NewExperienceForNextLevel);
}
void APHYPlayerState::SetProgression(const int32 NewLevel, const int32 NewExperience, const int32 NewExperienceForNextLevel)
{
if (!HasAuthority())
{
return;
}
const int32 ClampedLevel = FMath::Max(1, NewLevel);
const int32 ClampedExperience = FMath::Max(0, NewExperience);
const int32 ClampedExperienceForNextLevel = FMath::Max(1, NewExperienceForNextLevel);
if (PlayerLevel == ClampedLevel && Experience == ClampedExperience && ExperienceForNextLevel == ClampedExperienceForNextLevel)
{
return;
}
PlayerLevel = ClampedLevel;
Experience = ClampedExperience;
ExperienceForNextLevel = ClampedExperienceForNextLevel;
BroadcastProgressionChanged();
}
void APHYPlayerState::OnRep_Progression()
{
BroadcastProgressionChanged();
}
void APHYPlayerState::BroadcastProgressionChanged()
{
OnProgressionChanged.Broadcast(PlayerLevel, Experience, ExperienceForNextLevel);
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

@@ -0,0 +1,167 @@
// Copyright Epic Games, Inc. All Rights Reserved.
#pragma once
#include "CoreMinimal.h"
#include "Kismet/BlueprintFunctionLibrary.h"
#include "AbilitySystem/Attributes/PHYAttributeSettings.h"
#include "PHYAttributeCalculationLibrary.generated.h"
class UPHYCoreAttributeSet;
/**
* @brief 基础属性输入快照,用于 C++、GE 和后续 MMC 共享公式。
*/
USTRUCT(BlueprintType)
struct FPHYCoreAttributeSnapshot
{
GENERATED_BODY()
/** @brief 力量。 */
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="PHY|Attributes|Core")
float Strength = 0.0f;
/** @brief 灵巧。 */
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="PHY|Attributes|Core")
float Dexterity = 0.0f;
/** @brief 体魄。 */
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="PHY|Attributes|Core")
float Vitality = 0.0f;
/** @brief 智力。 */
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="PHY|Attributes|Core")
float Intelligence = 0.0f;
/** @brief 精神。 */
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="PHY|Attributes|Core")
float Spirit = 0.0f;
/** @brief 感知。 */
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="PHY|Attributes|Core")
float Perception = 0.0f;
};
/**
* @brief 次级战斗属性计算结果。
*/
USTRUCT(BlueprintType)
struct FPHYDerivedCombatAttributes
{
GENERATED_BODY()
/** @brief 推导后的生命上限。 */
UPROPERTY(BlueprintReadOnly, Category="PHY|Attributes|Combat")
float MaxHealth = 0.0f;
/** @brief 推导后的法力上限。 */
UPROPERTY(BlueprintReadOnly, Category="PHY|Attributes|Combat")
float MaxMana = 0.0f;
/** @brief 推导后的耐力上限。 */
UPROPERTY(BlueprintReadOnly, Category="PHY|Attributes|Combat")
float MaxStamina = 0.0f;
/** @brief 推导后的物理攻击强度。 */
UPROPERTY(BlueprintReadOnly, Category="PHY|Attributes|Combat")
float PhysicalAttackPower = 0.0f;
/** @brief 推导后的法术强度。 */
UPROPERTY(BlueprintReadOnly, Category="PHY|Attributes|Combat")
float SpellPower = 0.0f;
/** @brief 推导后的护甲。 */
UPROPERTY(BlueprintReadOnly, Category="PHY|Attributes|Combat")
float Armor = 0.0f;
/** @brief 推导后的魔法抗性。 */
UPROPERTY(BlueprintReadOnly, Category="PHY|Attributes|Combat")
float MagicResistance = 0.0f;
/** @brief 推导后的命中率。 */
UPROPERTY(BlueprintReadOnly, Category="PHY|Attributes|Combat")
float Accuracy = 0.0f;
/** @brief 推导后的闪避率。 */
UPROPERTY(BlueprintReadOnly, Category="PHY|Attributes|Combat")
float Evasion = 0.0f;
/** @brief 推导后的暴击率。 */
UPROPERTY(BlueprintReadOnly, Category="PHY|Attributes|Combat")
float CriticalChance = 0.0f;
/** @brief 推导后的暴击伤害倍率。 */
UPROPERTY(BlueprintReadOnly, Category="PHY|Attributes|Combat")
float CriticalDamage = 0.0f;
/** @brief 推导后的攻击速度倍率。 */
UPROPERTY(BlueprintReadOnly, Category="PHY|Attributes|Combat")
float AttackSpeed = 0.0f;
/** @brief 推导后的冷却缩减比例。 */
UPROPERTY(BlueprintReadOnly, Category="PHY|Attributes|Combat")
float CooldownReduction = 0.0f;
/** @brief 推导后的格挡强度。 */
UPROPERTY(BlueprintReadOnly, Category="PHY|Attributes|Combat")
float BlockPower = 0.0f;
/** @brief 推导后的破防强度。 */
UPROPERTY(BlueprintReadOnly, Category="PHY|Attributes|Combat")
float GuardBreakPower = 0.0f;
/** @brief 推导后的韧性。 */
UPROPERTY(BlueprintReadOnly, Category="PHY|Attributes|Combat")
float Poise = 0.0f;
/** @brief 推导后的韧性伤害。 */
UPROPERTY(BlueprintReadOnly, Category="PHY|Attributes|Combat")
float PoiseDamage = 0.0f;
};
/**
* @brief 元素属性计算结果。
*/
USTRUCT(BlueprintType)
struct FPHYDerivedElementAttributes
{
GENERATED_BODY()
/** @brief 推导后的通用元素伤害加成。 */
UPROPERTY(BlueprintReadOnly, Category="PHY|Attributes|Element")
float DamageBonus = 0.0f;
/** @brief 推导后的通用元素抗性。 */
UPROPERTY(BlueprintReadOnly, Category="PHY|Attributes|Element")
float Resistance = 0.0f;
/** @brief 推导后的通用元素穿透。 */
UPROPERTY(BlueprintReadOnly, Category="PHY|Attributes|Element")
float Penetration = 0.0f;
};
/**
* @brief 属性公式函数库,避免首期依赖复杂 MMC 资产。
*/
UCLASS()
class PHY_API UPHYAttributeCalculationLibrary : public UBlueprintFunctionLibrary
{
GENERATED_BODY()
public:
/** @brief 从配置默认值创建基础属性快照。 */
UFUNCTION(BlueprintCallable, BlueprintPure, Category="PHY|Attributes")
static FPHYCoreAttributeSnapshot MakeCoreSnapshotFromDefaults();
/** @brief 从基础属性集创建基础属性快照。 */
UFUNCTION(BlueprintCallable, BlueprintPure, Category="PHY|Attributes")
static FPHYCoreAttributeSnapshot MakeCoreSnapshotFromAttributeSet(const UPHYCoreAttributeSet* CoreAttributes);
/** @brief 根据基础属性和配置公式计算次级战斗属性。 */
UFUNCTION(BlueprintCallable, BlueprintPure, Category="PHY|Attributes")
static FPHYDerivedCombatAttributes CalculateCombatAttributes(const FPHYCoreAttributeSnapshot& CoreAttributes);
/** @brief 根据基础属性和配置公式计算通用元素属性。 */
UFUNCTION(BlueprintCallable, BlueprintPure, Category="PHY|Attributes")
static FPHYDerivedElementAttributes CalculateElementAttributes(const FPHYCoreAttributeSnapshot& CoreAttributes);
};

View File

@@ -0,0 +1,12 @@
// Copyright Epic Games, Inc. All Rights Reserved.
#pragma once
#include "AbilitySystemComponent.h"
#include "AttributeSet.h"
#define PHY_ATTRIBUTE_ACCESSORS(ClassName, PropertyName) \
GAMEPLAYATTRIBUTE_PROPERTY_GETTER(ClassName, PropertyName) \
GAMEPLAYATTRIBUTE_VALUE_GETTER(PropertyName) \
GAMEPLAYATTRIBUTE_VALUE_SETTER(PropertyName) \
GAMEPLAYATTRIBUTE_VALUE_INITTER(PropertyName)

View File

@@ -0,0 +1,301 @@
// Copyright Epic Games, Inc. All Rights Reserved.
#pragma once
#include "CoreMinimal.h"
#include "UObject/Object.h"
#include "PHYAttributeSettings.generated.h"
/**
* @brief 基础属性初始值。
*/
USTRUCT(BlueprintType)
struct FPHYCoreAttributeDefaults
{
GENERATED_BODY()
/** @brief 力量初始值。 */
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category="PHY|Attributes|Core")
float Strength = 10.0f;
/** @brief 灵巧初始值。 */
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category="PHY|Attributes|Core")
float Dexterity = 10.0f;
/** @brief 体魄初始值。 */
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category="PHY|Attributes|Core")
float Vitality = 10.0f;
/** @brief 智力初始值。 */
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category="PHY|Attributes|Core")
float Intelligence = 10.0f;
/** @brief 精神初始值。 */
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category="PHY|Attributes|Core")
float Spirit = 10.0f;
/** @brief 感知初始值。 */
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category="PHY|Attributes|Core")
float Perception = 10.0f;
};
/**
* @brief 次级战斗属性公式系数。
*/
USTRUCT(BlueprintType)
struct FPHYCombatAttributeFormula
{
GENERATED_BODY()
/** @brief 生命上限基础值。 */
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category="PHY|Attributes|Formula")
float MaxHealthBase = 100.0f;
/** @brief 每点体魄提供的生命上限。 */
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category="PHY|Attributes|Formula")
float MaxHealthVitality = 18.0f;
/** @brief 每点力量提供的生命上限。 */
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category="PHY|Attributes|Formula")
float MaxHealthStrength = 3.0f;
/** @brief 法力上限基础值。 */
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category="PHY|Attributes|Formula")
float MaxManaBase = 60.0f;
/** @brief 每点智力提供的法力上限。 */
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category="PHY|Attributes|Formula")
float MaxManaIntelligence = 12.0f;
/** @brief 每点精神提供的法力上限。 */
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category="PHY|Attributes|Formula")
float MaxManaSpirit = 6.0f;
/** @brief 耐力上限基础值。 */
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category="PHY|Attributes|Formula")
float MaxStaminaBase = 80.0f;
/** @brief 每点体魄提供的耐力上限。 */
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category="PHY|Attributes|Formula")
float MaxStaminaVitality = 5.0f;
/** @brief 每点灵巧提供的耐力上限。 */
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category="PHY|Attributes|Formula")
float MaxStaminaDexterity = 4.0f;
/** @brief 力量对物理攻击的系数。 */
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category="PHY|Attributes|Formula")
float PhysicalAttackPowerStrength = 2.0f;
/** @brief 灵巧对物理攻击的系数。 */
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category="PHY|Attributes|Formula")
float PhysicalAttackPowerDexterity = 0.5f;
/** @brief 智力对法术强度的系数。 */
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category="PHY|Attributes|Formula")
float SpellPowerIntelligence = 2.0f;
/** @brief 精神对法术强度的系数。 */
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category="PHY|Attributes|Formula")
float SpellPowerSpirit = 0.4f;
/** @brief 体魄对护甲的系数。 */
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category="PHY|Attributes|Formula")
float ArmorVitality = 1.2f;
/** @brief 力量对护甲的系数。 */
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category="PHY|Attributes|Formula")
float ArmorStrength = 0.4f;
/** @brief 精神对魔法抗性的系数。 */
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category="PHY|Attributes|Formula")
float MagicResistanceSpirit = 1.1f;
/** @brief 智力对魔法抗性的系数。 */
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category="PHY|Attributes|Formula")
float MagicResistanceIntelligence = 0.3f;
/** @brief 命中基础值。 */
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category="PHY|Attributes|Formula")
float AccuracyBase = 0.8f;
/** @brief 感知对命中的系数。 */
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category="PHY|Attributes|Formula")
float AccuracyPerception = 0.006f;
/** @brief 灵巧对命中的系数。 */
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category="PHY|Attributes|Formula")
float AccuracyDexterity = 0.002f;
/** @brief 闪避基础值。 */
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category="PHY|Attributes|Formula")
float EvasionBase = 0.03f;
/** @brief 灵巧对闪避的系数。 */
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category="PHY|Attributes|Formula")
float EvasionDexterity = 0.003f;
/** @brief 感知对闪避的系数。 */
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category="PHY|Attributes|Formula")
float EvasionPerception = 0.001f;
/** @brief 暴击率基础值。 */
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category="PHY|Attributes|Formula")
float CriticalChanceBase = 0.05f;
/** @brief 灵巧对暴击率的系数。 */
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category="PHY|Attributes|Formula")
float CriticalChanceDexterity = 0.002f;
/** @brief 感知对暴击率的系数。 */
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category="PHY|Attributes|Formula")
float CriticalChancePerception = 0.0015f;
/** @brief 暴击伤害基础倍率。 */
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category="PHY|Attributes|Formula")
float CriticalDamageBase = 1.5f;
/** @brief 力量对暴击伤害的系数。 */
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category="PHY|Attributes|Formula")
float CriticalDamageStrength = 0.004f;
/** @brief 感知对暴击伤害的系数。 */
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category="PHY|Attributes|Formula")
float CriticalDamagePerception = 0.003f;
/** @brief 攻击速度基础倍率。 */
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category="PHY|Attributes|Formula")
float AttackSpeedBase = 1.0f;
/** @brief 灵巧对攻击速度的系数。 */
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category="PHY|Attributes|Formula")
float AttackSpeedDexterity = 0.01f;
/** @brief 冷却缩减基础值。 */
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category="PHY|Attributes|Formula")
float CooldownReductionBase = 0.0f;
/** @brief 智力对冷却缩减的系数。 */
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category="PHY|Attributes|Formula")
float CooldownReductionIntelligence = 0.0015f;
/** @brief 精神对冷却缩减的系数。 */
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category="PHY|Attributes|Formula")
float CooldownReductionSpirit = 0.001f;
/** @brief 格挡强度基础值。 */
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category="PHY|Attributes|Formula")
float BlockPowerBase = 5.0f;
/** @brief 力量对格挡强度的系数。 */
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category="PHY|Attributes|Formula")
float BlockPowerStrength = 1.2f;
/** @brief 体魄对格挡强度的系数。 */
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category="PHY|Attributes|Formula")
float BlockPowerVitality = 0.6f;
/** @brief 破防强度基础值。 */
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category="PHY|Attributes|Formula")
float GuardBreakPowerBase = 5.0f;
/** @brief 力量对破防强度的系数。 */
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category="PHY|Attributes|Formula")
float GuardBreakPowerStrength = 1.1f;
/** @brief 感知对破防强度的系数。 */
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category="PHY|Attributes|Formula")
float GuardBreakPowerPerception = 0.4f;
/** @brief 韧性基础值。 */
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category="PHY|Attributes|Formula")
float PoiseBase = 20.0f;
/** @brief 体魄对韧性的系数。 */
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category="PHY|Attributes|Formula")
float PoiseVitality = 1.5f;
/** @brief 力量对韧性的系数。 */
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category="PHY|Attributes|Formula")
float PoiseStrength = 0.5f;
/** @brief 韧性伤害基础值。 */
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category="PHY|Attributes|Formula")
float PoiseDamageBase = 5.0f;
/** @brief 力量对韧性伤害的系数。 */
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category="PHY|Attributes|Formula")
float PoiseDamageStrength = 0.8f;
/** @brief 灵巧对韧性伤害的系数。 */
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category="PHY|Attributes|Formula")
float PoiseDamageDexterity = 0.2f;
};
/**
* @brief 元素属性公式系数。
*/
USTRUCT(BlueprintType)
struct FPHYElementAttributeFormula
{
GENERATED_BODY()
/** @brief 元素伤害加成基础值。 */
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category="PHY|Attributes|Element")
float DamageBonusBase = 0.0f;
/** @brief 智力对元素伤害加成的系数。 */
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category="PHY|Attributes|Element")
float DamageBonusIntelligence = 0.0015f;
/** @brief 感知对元素伤害加成的系数。 */
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category="PHY|Attributes|Element")
float DamageBonusPerception = 0.0005f;
/** @brief 元素抗性基础值。 */
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category="PHY|Attributes|Element")
float ResistanceBase = 0.02f;
/** @brief 精神对元素抗性的系数。 */
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category="PHY|Attributes|Element")
float ResistanceSpirit = 0.002f;
/** @brief 体魄对元素抗性的系数。 */
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category="PHY|Attributes|Element")
float ResistanceVitality = 0.0005f;
/** @brief 通用元素穿透基础值。 */
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category="PHY|Attributes|Element")
float PenetrationBase = 0.0f;
/** @brief 感知对通用元素穿透的系数。 */
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category="PHY|Attributes|Element")
float PenetrationPerception = 0.001f;
/** @brief 智力对通用元素穿透的系数。 */
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category="PHY|Attributes|Element")
float PenetrationIntelligence = 0.0005f;
};
/**
* @brief 属性体系配置,首期放入 PHYCombat 以便和战斗公式一起维护。
*/
UCLASS(Config=PHYCombat, DefaultConfig)
class PHY_API UPHYAttributeSettings : public UObject
{
GENERATED_BODY()
public:
/** @brief 基础属性默认初始值。 */
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category="PHY|Attributes")
FPHYCoreAttributeDefaults DefaultCoreAttributes;
/** @brief 次级战斗属性公式。 */
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category="PHY|Attributes")
FPHYCombatAttributeFormula CombatFormula;
/** @brief 元素属性公式。 */
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category="PHY|Attributes")
FPHYElementAttributeFormula ElementFormula;
};

View File

@@ -0,0 +1,183 @@
// Copyright Epic Games, Inc. All Rights Reserved.
#pragma once
#include "CoreMinimal.h"
#include "AbilitySystem/Attributes/PHYAttributeSet.h"
#include "PHYCombatAttributeSet.generated.h"
class UPHYCoreAttributeSet;
struct FGameplayEffectModCallbackData;
/**
* @brief PHY 次级战斗属性集,由基础属性和装备、效果共同驱动。
*/
UCLASS(BlueprintType)
class PHY_API UPHYCombatAttributeSet : public UAttributeSet
{
GENERATED_BODY()
public:
/** @brief 构造战斗属性并按默认基础属性推导首期数值。 */
UPHYCombatAttributeSet();
/** @brief 注册属性复制。 */
virtual void GetLifetimeReplicatedProps(TArray<FLifetimeProperty>& OutLifetimeProps) const override;
/** @brief 属性变更前限制百分比和资源范围。 */
virtual void PreAttributeChange(const FGameplayAttribute& Attribute, float& NewValue) override;
/** @brief GameplayEffect 执行后限制资源不超过上限。 */
virtual void PostGameplayEffectExecute(const FGameplayEffectModCallbackData& Data) override;
/** @brief 属性变更后同步当前资源与上限。 */
virtual void PostAttributeChange(const FGameplayAttribute& Attribute, float OldValue, float NewValue) override;
/** @brief 当前生命。 */
UPROPERTY(BlueprintReadOnly, ReplicatedUsing=OnRep_Health, Category="PHY|Attributes|Combat")
FGameplayAttributeData Health;
PHY_ATTRIBUTE_ACCESSORS(UPHYCombatAttributeSet, Health);
/** @brief 生命上限。 */
UPROPERTY(BlueprintReadOnly, ReplicatedUsing=OnRep_MaxHealth, Category="PHY|Attributes|Combat")
FGameplayAttributeData MaxHealth;
PHY_ATTRIBUTE_ACCESSORS(UPHYCombatAttributeSet, MaxHealth);
/** @brief 当前法力。 */
UPROPERTY(BlueprintReadOnly, ReplicatedUsing=OnRep_Mana, Category="PHY|Attributes|Combat")
FGameplayAttributeData Mana;
PHY_ATTRIBUTE_ACCESSORS(UPHYCombatAttributeSet, Mana);
/** @brief 法力上限。 */
UPROPERTY(BlueprintReadOnly, ReplicatedUsing=OnRep_MaxMana, Category="PHY|Attributes|Combat")
FGameplayAttributeData MaxMana;
PHY_ATTRIBUTE_ACCESSORS(UPHYCombatAttributeSet, MaxMana);
/** @brief 当前耐力。 */
UPROPERTY(BlueprintReadOnly, ReplicatedUsing=OnRep_Stamina, Category="PHY|Attributes|Combat")
FGameplayAttributeData Stamina;
PHY_ATTRIBUTE_ACCESSORS(UPHYCombatAttributeSet, Stamina);
/** @brief 耐力上限。 */
UPROPERTY(BlueprintReadOnly, ReplicatedUsing=OnRep_MaxStamina, Category="PHY|Attributes|Combat")
FGameplayAttributeData MaxStamina;
PHY_ATTRIBUTE_ACCESSORS(UPHYCombatAttributeSet, MaxStamina);
/** @brief 物理攻击强度。 */
UPROPERTY(BlueprintReadOnly, ReplicatedUsing=OnRep_PhysicalAttackPower, Category="PHY|Attributes|Combat")
FGameplayAttributeData PhysicalAttackPower;
PHY_ATTRIBUTE_ACCESSORS(UPHYCombatAttributeSet, PhysicalAttackPower);
/** @brief 法术强度。 */
UPROPERTY(BlueprintReadOnly, ReplicatedUsing=OnRep_SpellPower, Category="PHY|Attributes|Combat")
FGameplayAttributeData SpellPower;
PHY_ATTRIBUTE_ACCESSORS(UPHYCombatAttributeSet, SpellPower);
/** @brief 护甲。 */
UPROPERTY(BlueprintReadOnly, ReplicatedUsing=OnRep_Armor, Category="PHY|Attributes|Combat")
FGameplayAttributeData Armor;
PHY_ATTRIBUTE_ACCESSORS(UPHYCombatAttributeSet, Armor);
/** @brief 魔法抗性。 */
UPROPERTY(BlueprintReadOnly, ReplicatedUsing=OnRep_MagicResistance, Category="PHY|Attributes|Combat")
FGameplayAttributeData MagicResistance;
PHY_ATTRIBUTE_ACCESSORS(UPHYCombatAttributeSet, MagicResistance);
/** @brief 命中率,通常作为 0 到 1 的比例使用。 */
UPROPERTY(BlueprintReadOnly, ReplicatedUsing=OnRep_Accuracy, Category="PHY|Attributes|Combat")
FGameplayAttributeData Accuracy;
PHY_ATTRIBUTE_ACCESSORS(UPHYCombatAttributeSet, Accuracy);
/** @brief 闪避率,通常作为 0 到 1 的比例使用。 */
UPROPERTY(BlueprintReadOnly, ReplicatedUsing=OnRep_Evasion, Category="PHY|Attributes|Combat")
FGameplayAttributeData Evasion;
PHY_ATTRIBUTE_ACCESSORS(UPHYCombatAttributeSet, Evasion);
/** @brief 暴击率,通常作为 0 到 1 的比例使用。 */
UPROPERTY(BlueprintReadOnly, ReplicatedUsing=OnRep_CriticalChance, Category="PHY|Attributes|Combat")
FGameplayAttributeData CriticalChance;
PHY_ATTRIBUTE_ACCESSORS(UPHYCombatAttributeSet, CriticalChance);
/** @brief 暴击伤害倍率1.5 表示 150% 伤害。 */
UPROPERTY(BlueprintReadOnly, ReplicatedUsing=OnRep_CriticalDamage, Category="PHY|Attributes|Combat")
FGameplayAttributeData CriticalDamage;
PHY_ATTRIBUTE_ACCESSORS(UPHYCombatAttributeSet, CriticalDamage);
/** @brief 攻击速度倍率。 */
UPROPERTY(BlueprintReadOnly, ReplicatedUsing=OnRep_AttackSpeed, Category="PHY|Attributes|Combat")
FGameplayAttributeData AttackSpeed;
PHY_ATTRIBUTE_ACCESSORS(UPHYCombatAttributeSet, AttackSpeed);
/** @brief 冷却缩减比例。 */
UPROPERTY(BlueprintReadOnly, ReplicatedUsing=OnRep_CooldownReduction, Category="PHY|Attributes|Combat")
FGameplayAttributeData CooldownReduction;
PHY_ATTRIBUTE_ACCESSORS(UPHYCombatAttributeSet, CooldownReduction);
/** @brief 格挡强度。 */
UPROPERTY(BlueprintReadOnly, ReplicatedUsing=OnRep_BlockPower, Category="PHY|Attributes|Combat")
FGameplayAttributeData BlockPower;
PHY_ATTRIBUTE_ACCESSORS(UPHYCombatAttributeSet, BlockPower);
/** @brief 破防强度。 */
UPROPERTY(BlueprintReadOnly, ReplicatedUsing=OnRep_GuardBreakPower, Category="PHY|Attributes|Combat")
FGameplayAttributeData GuardBreakPower;
PHY_ATTRIBUTE_ACCESSORS(UPHYCombatAttributeSet, GuardBreakPower);
/** @brief 韧性。 */
UPROPERTY(BlueprintReadOnly, ReplicatedUsing=OnRep_Poise, Category="PHY|Attributes|Combat")
FGameplayAttributeData Poise;
PHY_ATTRIBUTE_ACCESSORS(UPHYCombatAttributeSet, Poise);
/** @brief 韧性伤害。 */
UPROPERTY(BlueprintReadOnly, ReplicatedUsing=OnRep_PoiseDamage, Category="PHY|Attributes|Combat")
FGameplayAttributeData PoiseDamage;
PHY_ATTRIBUTE_ACCESSORS(UPHYCombatAttributeSet, PoiseDamage);
/** @brief 按默认配置中的基础属性计算并写入次级属性。 */
void InitializeFromConfiguredCoreDefaults();
/** @brief 按指定基础属性集计算并写入次级属性。 */
void InitializeFromCoreAttributes(const UPHYCoreAttributeSet* CoreAttributes);
protected:
/** @brief 复制当前生命变化。 */
UFUNCTION() void OnRep_Health(const FGameplayAttributeData& OldValue);
/** @brief 复制生命上限变化。 */
UFUNCTION() void OnRep_MaxHealth(const FGameplayAttributeData& OldValue);
/** @brief 复制当前法力变化。 */
UFUNCTION() void OnRep_Mana(const FGameplayAttributeData& OldValue);
/** @brief 复制法力上限变化。 */
UFUNCTION() void OnRep_MaxMana(const FGameplayAttributeData& OldValue);
/** @brief 复制当前耐力变化。 */
UFUNCTION() void OnRep_Stamina(const FGameplayAttributeData& OldValue);
/** @brief 复制耐力上限变化。 */
UFUNCTION() void OnRep_MaxStamina(const FGameplayAttributeData& OldValue);
/** @brief 复制物理攻击强度变化。 */
UFUNCTION() void OnRep_PhysicalAttackPower(const FGameplayAttributeData& OldValue);
/** @brief 复制法术强度变化。 */
UFUNCTION() void OnRep_SpellPower(const FGameplayAttributeData& OldValue);
/** @brief 复制护甲变化。 */
UFUNCTION() void OnRep_Armor(const FGameplayAttributeData& OldValue);
/** @brief 复制魔法抗性变化。 */
UFUNCTION() void OnRep_MagicResistance(const FGameplayAttributeData& OldValue);
/** @brief 复制命中率变化。 */
UFUNCTION() void OnRep_Accuracy(const FGameplayAttributeData& OldValue);
/** @brief 复制闪避率变化。 */
UFUNCTION() void OnRep_Evasion(const FGameplayAttributeData& OldValue);
/** @brief 复制暴击率变化。 */
UFUNCTION() void OnRep_CriticalChance(const FGameplayAttributeData& OldValue);
/** @brief 复制暴击伤害倍率变化。 */
UFUNCTION() void OnRep_CriticalDamage(const FGameplayAttributeData& OldValue);
/** @brief 复制攻击速度倍率变化。 */
UFUNCTION() void OnRep_AttackSpeed(const FGameplayAttributeData& OldValue);
/** @brief 复制冷却缩减比例变化。 */
UFUNCTION() void OnRep_CooldownReduction(const FGameplayAttributeData& OldValue);
/** @brief 复制格挡强度变化。 */
UFUNCTION() void OnRep_BlockPower(const FGameplayAttributeData& OldValue);
/** @brief 复制破防强度变化。 */
UFUNCTION() void OnRep_GuardBreakPower(const FGameplayAttributeData& OldValue);
/** @brief 复制韧性变化。 */
UFUNCTION() void OnRep_Poise(const FGameplayAttributeData& OldValue);
/** @brief 复制韧性伤害变化。 */
UFUNCTION() void OnRep_PoiseDamage(const FGameplayAttributeData& OldValue);
};

View File

@@ -0,0 +1,78 @@
// Copyright Epic Games, Inc. All Rights Reserved.
#pragma once
#include "CoreMinimal.h"
#include "AbilitySystem/Attributes/PHYAttributeSet.h"
#include "PHYCoreAttributeSet.generated.h"
/**
* @brief PHY 基础属性集,作为所有玩家和 AI 的通用根属性。
*/
UCLASS(BlueprintType)
class PHY_API UPHYCoreAttributeSet : public UAttributeSet
{
GENERATED_BODY()
public:
/** @brief 构造基础属性并读取默认配置。 */
UPHYCoreAttributeSet();
/** @brief 注册属性复制。 */
virtual void GetLifetimeReplicatedProps(TArray<FLifetimeProperty>& OutLifetimeProps) const override;
/** @brief 力量,影响物理攻击、格挡、破防和负重倾向。 */
UPROPERTY(BlueprintReadOnly, ReplicatedUsing=OnRep_Strength, Category="PHY|Attributes|Core")
FGameplayAttributeData Strength;
PHY_ATTRIBUTE_ACCESSORS(UPHYCoreAttributeSet, Strength);
/** @brief 灵巧,影响暴击、攻速、命中和闪避倾向。 */
UPROPERTY(BlueprintReadOnly, ReplicatedUsing=OnRep_Dexterity, Category="PHY|Attributes|Core")
FGameplayAttributeData Dexterity;
PHY_ATTRIBUTE_ACCESSORS(UPHYCoreAttributeSet, Dexterity);
/** @brief 体魄,影响生命、护甲、耐力和韧性倾向。 */
UPROPERTY(BlueprintReadOnly, ReplicatedUsing=OnRep_Vitality, Category="PHY|Attributes|Core")
FGameplayAttributeData Vitality;
PHY_ATTRIBUTE_ACCESSORS(UPHYCoreAttributeSet, Vitality);
/** @brief 智力,影响法术强度、法力和冷却缩减倾向。 */
UPROPERTY(BlueprintReadOnly, ReplicatedUsing=OnRep_Intelligence, Category="PHY|Attributes|Core")
FGameplayAttributeData Intelligence;
PHY_ATTRIBUTE_ACCESSORS(UPHYCoreAttributeSet, Intelligence);
/** @brief 精神,影响法力、魔法抗性和元素抗性倾向。 */
UPROPERTY(BlueprintReadOnly, ReplicatedUsing=OnRep_Spirit, Category="PHY|Attributes|Core")
FGameplayAttributeData Spirit;
PHY_ATTRIBUTE_ACCESSORS(UPHYCoreAttributeSet, Spirit);
/** @brief 感知,影响命中、暴击、弱点和穿透倾向。 */
UPROPERTY(BlueprintReadOnly, ReplicatedUsing=OnRep_Perception, Category="PHY|Attributes|Core")
FGameplayAttributeData Perception;
PHY_ATTRIBUTE_ACCESSORS(UPHYCoreAttributeSet, Perception);
protected:
/** @brief 复制力量。 */
UFUNCTION()
void OnRep_Strength(const FGameplayAttributeData& OldValue);
/** @brief 复制灵巧。 */
UFUNCTION()
void OnRep_Dexterity(const FGameplayAttributeData& OldValue);
/** @brief 复制体魄。 */
UFUNCTION()
void OnRep_Vitality(const FGameplayAttributeData& OldValue);
/** @brief 复制智力。 */
UFUNCTION()
void OnRep_Intelligence(const FGameplayAttributeData& OldValue);
/** @brief 复制精神。 */
UFUNCTION()
void OnRep_Spirit(const FGameplayAttributeData& OldValue);
/** @brief 复制感知。 */
UFUNCTION()
void OnRep_Perception(const FGameplayAttributeData& OldValue);
};

View File

@@ -0,0 +1,155 @@
// Copyright Epic Games, Inc. All Rights Reserved.
#pragma once
#include "CoreMinimal.h"
#include "AbilitySystem/Attributes/PHYAttributeSet.h"
#include "PHYElementAttributeSet.generated.h"
class UPHYCoreAttributeSet;
/**
* @brief PHY 元素进攻与防御属性集。
*/
UCLASS(BlueprintType)
class PHY_API UPHYElementAttributeSet : public UAttributeSet
{
GENERATED_BODY()
public:
/** @brief 构造元素属性并按默认基础属性推导首期数值。 */
UPHYElementAttributeSet();
/** @brief 注册属性复制。 */
virtual void GetLifetimeReplicatedProps(TArray<FLifetimeProperty>& OutLifetimeProps) const override;
/** @brief 限制元素比例属性范围。 */
virtual void PreAttributeChange(const FGameplayAttribute& Attribute, float& NewValue) override;
/** @brief 按默认配置中的基础属性计算并写入元素属性。 */
void InitializeFromConfiguredCoreDefaults();
/** @brief 按指定基础属性集计算并写入元素属性。 */
void InitializeFromCoreAttributes(const UPHYCoreAttributeSet* CoreAttributes);
/** @brief 火元素伤害加成。 */
UPROPERTY(BlueprintReadOnly, ReplicatedUsing=OnRep_FireDamageBonus, Category="PHY|Attributes|Element")
FGameplayAttributeData FireDamageBonus;
PHY_ATTRIBUTE_ACCESSORS(UPHYElementAttributeSet, FireDamageBonus);
/** @brief 水元素伤害加成。 */
UPROPERTY(BlueprintReadOnly, ReplicatedUsing=OnRep_WaterDamageBonus, Category="PHY|Attributes|Element")
FGameplayAttributeData WaterDamageBonus;
PHY_ATTRIBUTE_ACCESSORS(UPHYElementAttributeSet, WaterDamageBonus);
/** @brief 冰元素伤害加成。 */
UPROPERTY(BlueprintReadOnly, ReplicatedUsing=OnRep_IceDamageBonus, Category="PHY|Attributes|Element")
FGameplayAttributeData IceDamageBonus;
PHY_ATTRIBUTE_ACCESSORS(UPHYElementAttributeSet, IceDamageBonus);
/** @brief 雷元素伤害加成。 */
UPROPERTY(BlueprintReadOnly, ReplicatedUsing=OnRep_LightningDamageBonus, Category="PHY|Attributes|Element")
FGameplayAttributeData LightningDamageBonus;
PHY_ATTRIBUTE_ACCESSORS(UPHYElementAttributeSet, LightningDamageBonus);
/** @brief 地元素伤害加成。 */
UPROPERTY(BlueprintReadOnly, ReplicatedUsing=OnRep_EarthDamageBonus, Category="PHY|Attributes|Element")
FGameplayAttributeData EarthDamageBonus;
PHY_ATTRIBUTE_ACCESSORS(UPHYElementAttributeSet, EarthDamageBonus);
/** @brief 风元素伤害加成。 */
UPROPERTY(BlueprintReadOnly, ReplicatedUsing=OnRep_WindDamageBonus, Category="PHY|Attributes|Element")
FGameplayAttributeData WindDamageBonus;
PHY_ATTRIBUTE_ACCESSORS(UPHYElementAttributeSet, WindDamageBonus);
/** @brief 光元素伤害加成。 */
UPROPERTY(BlueprintReadOnly, ReplicatedUsing=OnRep_LightDamageBonus, Category="PHY|Attributes|Element")
FGameplayAttributeData LightDamageBonus;
PHY_ATTRIBUTE_ACCESSORS(UPHYElementAttributeSet, LightDamageBonus);
/** @brief 暗元素伤害加成。 */
UPROPERTY(BlueprintReadOnly, ReplicatedUsing=OnRep_DarkDamageBonus, Category="PHY|Attributes|Element")
FGameplayAttributeData DarkDamageBonus;
PHY_ATTRIBUTE_ACCESSORS(UPHYElementAttributeSet, DarkDamageBonus);
/** @brief 火元素抗性。 */
UPROPERTY(BlueprintReadOnly, ReplicatedUsing=OnRep_FireResistance, Category="PHY|Attributes|Element")
FGameplayAttributeData FireResistance;
PHY_ATTRIBUTE_ACCESSORS(UPHYElementAttributeSet, FireResistance);
/** @brief 水元素抗性。 */
UPROPERTY(BlueprintReadOnly, ReplicatedUsing=OnRep_WaterResistance, Category="PHY|Attributes|Element")
FGameplayAttributeData WaterResistance;
PHY_ATTRIBUTE_ACCESSORS(UPHYElementAttributeSet, WaterResistance);
/** @brief 冰元素抗性。 */
UPROPERTY(BlueprintReadOnly, ReplicatedUsing=OnRep_IceResistance, Category="PHY|Attributes|Element")
FGameplayAttributeData IceResistance;
PHY_ATTRIBUTE_ACCESSORS(UPHYElementAttributeSet, IceResistance);
/** @brief 雷元素抗性。 */
UPROPERTY(BlueprintReadOnly, ReplicatedUsing=OnRep_LightningResistance, Category="PHY|Attributes|Element")
FGameplayAttributeData LightningResistance;
PHY_ATTRIBUTE_ACCESSORS(UPHYElementAttributeSet, LightningResistance);
/** @brief 地元素抗性。 */
UPROPERTY(BlueprintReadOnly, ReplicatedUsing=OnRep_EarthResistance, Category="PHY|Attributes|Element")
FGameplayAttributeData EarthResistance;
PHY_ATTRIBUTE_ACCESSORS(UPHYElementAttributeSet, EarthResistance);
/** @brief 风元素抗性。 */
UPROPERTY(BlueprintReadOnly, ReplicatedUsing=OnRep_WindResistance, Category="PHY|Attributes|Element")
FGameplayAttributeData WindResistance;
PHY_ATTRIBUTE_ACCESSORS(UPHYElementAttributeSet, WindResistance);
/** @brief 光元素抗性。 */
UPROPERTY(BlueprintReadOnly, ReplicatedUsing=OnRep_LightResistance, Category="PHY|Attributes|Element")
FGameplayAttributeData LightResistance;
PHY_ATTRIBUTE_ACCESSORS(UPHYElementAttributeSet, LightResistance);
/** @brief 暗元素抗性。 */
UPROPERTY(BlueprintReadOnly, ReplicatedUsing=OnRep_DarkResistance, Category="PHY|Attributes|Element")
FGameplayAttributeData DarkResistance;
PHY_ATTRIBUTE_ACCESSORS(UPHYElementAttributeSet, DarkResistance);
/** @brief 通用元素穿透,首期不按元素拆分。 */
UPROPERTY(BlueprintReadOnly, ReplicatedUsing=OnRep_ElementPenetration, Category="PHY|Attributes|Element")
FGameplayAttributeData ElementPenetration;
PHY_ATTRIBUTE_ACCESSORS(UPHYElementAttributeSet, ElementPenetration);
protected:
/** @brief 复制火元素伤害加成变化。 */
UFUNCTION() void OnRep_FireDamageBonus(const FGameplayAttributeData& OldValue);
/** @brief 复制水元素伤害加成变化。 */
UFUNCTION() void OnRep_WaterDamageBonus(const FGameplayAttributeData& OldValue);
/** @brief 复制冰元素伤害加成变化。 */
UFUNCTION() void OnRep_IceDamageBonus(const FGameplayAttributeData& OldValue);
/** @brief 复制雷元素伤害加成变化。 */
UFUNCTION() void OnRep_LightningDamageBonus(const FGameplayAttributeData& OldValue);
/** @brief 复制地元素伤害加成变化。 */
UFUNCTION() void OnRep_EarthDamageBonus(const FGameplayAttributeData& OldValue);
/** @brief 复制风元素伤害加成变化。 */
UFUNCTION() void OnRep_WindDamageBonus(const FGameplayAttributeData& OldValue);
/** @brief 复制光元素伤害加成变化。 */
UFUNCTION() void OnRep_LightDamageBonus(const FGameplayAttributeData& OldValue);
/** @brief 复制暗元素伤害加成变化。 */
UFUNCTION() void OnRep_DarkDamageBonus(const FGameplayAttributeData& OldValue);
/** @brief 复制火元素抗性变化。 */
UFUNCTION() void OnRep_FireResistance(const FGameplayAttributeData& OldValue);
/** @brief 复制水元素抗性变化。 */
UFUNCTION() void OnRep_WaterResistance(const FGameplayAttributeData& OldValue);
/** @brief 复制冰元素抗性变化。 */
UFUNCTION() void OnRep_IceResistance(const FGameplayAttributeData& OldValue);
/** @brief 复制雷元素抗性变化。 */
UFUNCTION() void OnRep_LightningResistance(const FGameplayAttributeData& OldValue);
/** @brief 复制地元素抗性变化。 */
UFUNCTION() void OnRep_EarthResistance(const FGameplayAttributeData& OldValue);
/** @brief 复制风元素抗性变化。 */
UFUNCTION() void OnRep_WindResistance(const FGameplayAttributeData& OldValue);
/** @brief 复制光元素抗性变化。 */
UFUNCTION() void OnRep_LightResistance(const FGameplayAttributeData& OldValue);
/** @brief 复制暗元素抗性变化。 */
UFUNCTION() void OnRep_DarkResistance(const FGameplayAttributeData& OldValue);
/** @brief 复制通用元素穿透变化。 */
UFUNCTION() void OnRep_ElementPenetration(const FGameplayAttributeData& OldValue);
};

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