第一次提交

This commit is contained in:
不明不惑
2026-03-03 01:23:02 +08:00
commit 3e434877e8
1053 changed files with 102411 additions and 0 deletions

21
Source/PHY.Target.cs Normal file
View File

@@ -0,0 +1,21 @@
// Copyright Epic Games, Inc. All Rights Reserved.
using UnrealBuildTool;
using System.Collections.Generic;
public class PHYTarget : TargetRules
{
public PHYTarget(TargetInfo Target) : base(Target)
{
Type = TargetType.Game;
DefaultBuildSettings = BuildSettingsVersion.V6;
IncludeOrderVersion = EngineIncludeOrderVersion.Unreal5_7;
ExtraModuleNames.Add("PHY");
RegisterModulesCreatedByRider();
}
private void RegisterModulesCreatedByRider()
{
ExtraModuleNames.AddRange(new string[] { "PHYInventory" });
}
}

51
Source/PHY/PHY.Build.cs Normal file
View File

@@ -0,0 +1,51 @@
// Copyright Epic Games, Inc. All Rights Reserved.
using UnrealBuildTool;
public class PHY : ModuleRules
{
public PHY(ReadOnlyTargetRules Target) : base(Target)
{
PCHUsage = PCHUsageMode.UseExplicitOrSharedPCHs;
CppStandard = CppStandardVersion.Cpp20;
PrivateIncludePaths.AddRange(new string[]
{
System.IO.Path.Combine(ModuleDirectory, "Private"),
});
PublicDependencyModuleNames.AddRange(new string[] {
"Core",
"CoreUObject",
"Engine",
"InputCore",
"EnhancedInput"
});
PrivateDependencyModuleNames.AddRange(new string[]
{
"GameplayAbilities",
"GameplayTags",
"GameplayTasks",
"GenericInputSystem",
"GenericUISystem",
"GenericInventorySystem",
"GenericMovementSystem",
"Slate",
"SlateCore",
"AuroraDevs_UGC",
"IKRig",
"NetCore",
"UMG",
"CommonUI"
});
// Uncomment if you are using Slate UI
// PrivateDependencyModuleNames.AddRange(new string[] { "Slate", "SlateCore" });
// Uncomment if you are using online features
// PrivateDependencyModuleNames.Add("OnlineSubsystem");
// To include OnlineSubsystemSteam, add it to the plugins section in your uproject file with the Enabled attribute set to true
}
}

6
Source/PHY/PHY.cpp Normal file
View File

@@ -0,0 +1,6 @@
// Copyright Epic Games, Inc. All Rights Reserved.
#include "PHY.h"
#include "Modules/ModuleManager.h"
IMPLEMENT_PRIMARY_GAME_MODULE( FDefaultGameModuleImpl, PHY, "PHY" );

6
Source/PHY/PHY.h Normal file
View File

@@ -0,0 +1,6 @@
// Copyright Epic Games, Inc. All Rights Reserved.
#pragma once
#include "CoreMinimal.h"

View File

@@ -0,0 +1,246 @@
// Copyright 2025 PHY. All Rights Reserved.
#include "AbilitySystem/Attributes/PHYAttributeSet.h"
// (If your include paths don't pick up the module Public folder, use the short include below.)
// #include "PHYAttributeSet.h"
#include "GameplayEffectExtension.h"
#include "Net/UnrealNetwork.h"
UPHYAttributeSet::UPHYAttributeSet()
{
}
void UPHYAttributeSet::PreAttributeChange(const FGameplayAttribute& Attribute, float& NewValue)
{
Super::PreAttributeChange(Attribute, NewValue);
// Basic clamps
if (Attribute == GetHealthAttribute())
{
NewValue = ClampNonNegative(NewValue);
NewValue = FMath::Min(NewValue, GetMaxHealth());
}
else if (Attribute == GetMaxHealthAttribute())
{
NewValue = ClampNonNegative(NewValue);
}
else if (Attribute == GetInnerPowerAttribute())
{
NewValue = ClampNonNegative(NewValue);
NewValue = FMath::Min(NewValue, GetMaxInnerPower());
}
else if (Attribute == GetMaxInnerPowerAttribute())
{
NewValue = ClampNonNegative(NewValue);
}
else if (Attribute == GetMoveSpeedAttribute())
{
// Allow designers to decide exact caps later; keep reasonable defaults.
NewValue = FMath::Clamp(NewValue, 0.f, 2000.f);
}
else if (Attribute == GetCritChanceAttribute() || Attribute == GetDodgeChanceAttribute() || Attribute == GetHitChanceAttribute()
|| Attribute == GetParryChanceAttribute() || Attribute == GetCounterChanceAttribute() || Attribute == GetArmorPenetrationAttribute()
|| Attribute == GetDamageReductionAttribute() || Attribute == GetLifeStealAttribute())
{
NewValue = FMath::Clamp(NewValue, 0.f, 1.f);
}
else if (Attribute == GetCritDamageAttribute())
{
// Crit damage multiplier should be >= 1.0
NewValue = FMath::Max(1.f, NewValue);
}
}
void UPHYAttributeSet::PostGameplayEffectExecute(const FGameplayEffectModCallbackData& Data)
{
Super::PostGameplayEffectExecute(Data);
const FGameplayAttribute& Attribute = Data.EvaluatedData.Attribute;
if (Attribute == GetHealthAttribute())
{
SetHealth(FMath::Clamp(GetHealth(), 0.f, GetMaxHealth()));
}
else if (Attribute == GetMaxHealthAttribute())
{
// When MaxHealth is reduced, clamp Health to new MaxHealth.
SetMaxHealth(ClampNonNegative(GetMaxHealth()));
SetHealth(FMath::Clamp(GetHealth(), 0.f, GetMaxHealth()));
}
else if (Attribute == GetInnerPowerAttribute())
{
SetInnerPower(FMath::Clamp(GetInnerPower(), 0.f, GetMaxInnerPower()));
}
else if (Attribute == GetMaxInnerPowerAttribute())
{
SetMaxInnerPower(ClampNonNegative(GetMaxInnerPower()));
SetInnerPower(FMath::Clamp(GetInnerPower(), 0.f, GetMaxInnerPower()));
}
else if (Attribute == GetStrengthAttribute()) { SetStrength(ClampNonNegative(GetStrength())); }
else if (Attribute == GetConstitutionAttribute()) { SetConstitution(ClampNonNegative(GetConstitution())); }
else if (Attribute == GetInnerBreathAttribute()) { SetInnerBreath(ClampNonNegative(GetInnerBreath())); }
else if (Attribute == GetAgilityAttribute()) { SetAgility(ClampNonNegative(GetAgility())); }
else if (Attribute == GetPhysicalAttackAttribute()){ SetPhysicalAttack(ClampNonNegative(GetPhysicalAttack())); }
else if (Attribute == GetPhysicalDefenseAttribute()){ SetPhysicalDefense(ClampNonNegative(GetPhysicalDefense())); }
else if (Attribute == GetMoveSpeedAttribute()) { SetMoveSpeed(FMath::Clamp(GetMoveSpeed(), 0.f, 2000.f)); }
else if (Attribute == GetTenacityAttribute()) { SetTenacity(ClampNonNegative(GetTenacity())); }
else if (Attribute == GetCritChanceAttribute()) { SetCritChance(FMath::Clamp(GetCritChance(), 0.f, 1.f)); }
else if (Attribute == GetCritDamageAttribute()) { SetCritDamage(FMath::Max(1.f, GetCritDamage())); }
else if (Attribute == GetDodgeChanceAttribute()) { SetDodgeChance(FMath::Clamp(GetDodgeChance(), 0.f, 1.f)); }
else if (Attribute == GetHitChanceAttribute()) { SetHitChance(FMath::Clamp(GetHitChance(), 0.f, 1.f)); }
else if (Attribute == GetParryChanceAttribute()) { SetParryChance(FMath::Clamp(GetParryChance(), 0.f, 1.f)); }
else if (Attribute == GetCounterChanceAttribute()){ SetCounterChance(FMath::Clamp(GetCounterChance(), 0.f, 1.f)); }
else if (Attribute == GetArmorPenetrationAttribute()) { SetArmorPenetration(FMath::Clamp(GetArmorPenetration(), 0.f, 1.f)); }
else if (Attribute == GetDamageReductionAttribute()) { SetDamageReduction(FMath::Clamp(GetDamageReduction(), 0.f, 1.f)); }
else if (Attribute == GetLifeStealAttribute()) { SetLifeSteal(FMath::Clamp(GetLifeSteal(), 0.f, 1.f)); }
else if (Attribute == GetHealthRegenRateAttribute()) { SetHealthRegenRate(ClampNonNegative(GetHealthRegenRate())); }
else if (Attribute == GetInnerPowerRegenRateAttribute()) { SetInnerPowerRegenRate(ClampNonNegative(GetInnerPowerRegenRate())); }
}
void UPHYAttributeSet::OnRep_Strength(const FGameplayAttributeData& OldValue)
{
GAMEPLAYATTRIBUTE_REPNOTIFY(UPHYAttributeSet, Strength, OldValue);
}
void UPHYAttributeSet::OnRep_Constitution(const FGameplayAttributeData& OldValue)
{
GAMEPLAYATTRIBUTE_REPNOTIFY(UPHYAttributeSet, Constitution, OldValue);
}
void UPHYAttributeSet::OnRep_InnerBreath(const FGameplayAttributeData& OldValue)
{
GAMEPLAYATTRIBUTE_REPNOTIFY(UPHYAttributeSet, InnerBreath, OldValue);
}
void UPHYAttributeSet::OnRep_Agility(const FGameplayAttributeData& OldValue)
{
GAMEPLAYATTRIBUTE_REPNOTIFY(UPHYAttributeSet, Agility, OldValue);
}
void UPHYAttributeSet::OnRep_Health(const FGameplayAttributeData& OldValue)
{
GAMEPLAYATTRIBUTE_REPNOTIFY(UPHYAttributeSet, Health, OldValue);
}
void UPHYAttributeSet::OnRep_MaxHealth(const FGameplayAttributeData& OldValue)
{
GAMEPLAYATTRIBUTE_REPNOTIFY(UPHYAttributeSet, MaxHealth, OldValue);
}
void UPHYAttributeSet::OnRep_MoveSpeed(const FGameplayAttributeData& OldValue)
{
GAMEPLAYATTRIBUTE_REPNOTIFY(UPHYAttributeSet, MoveSpeed, OldValue);
}
void UPHYAttributeSet::OnRep_PhysicalAttack(const FGameplayAttributeData& OldValue)
{
GAMEPLAYATTRIBUTE_REPNOTIFY(UPHYAttributeSet, PhysicalAttack, OldValue);
}
void UPHYAttributeSet::OnRep_PhysicalDefense(const FGameplayAttributeData& OldValue)
{
GAMEPLAYATTRIBUTE_REPNOTIFY(UPHYAttributeSet, PhysicalDefense, OldValue);
}
void UPHYAttributeSet::OnRep_Tenacity(const FGameplayAttributeData& OldValue)
{
GAMEPLAYATTRIBUTE_REPNOTIFY(UPHYAttributeSet, Tenacity, OldValue);
}
void UPHYAttributeSet::OnRep_CritChance(const FGameplayAttributeData& OldValue)
{
GAMEPLAYATTRIBUTE_REPNOTIFY(UPHYAttributeSet, CritChance, OldValue);
}
void UPHYAttributeSet::OnRep_CritDamage(const FGameplayAttributeData& OldValue)
{
GAMEPLAYATTRIBUTE_REPNOTIFY(UPHYAttributeSet, CritDamage, OldValue);
}
void UPHYAttributeSet::OnRep_DodgeChance(const FGameplayAttributeData& OldValue)
{
GAMEPLAYATTRIBUTE_REPNOTIFY(UPHYAttributeSet, DodgeChance, OldValue);
}
void UPHYAttributeSet::OnRep_HitChance(const FGameplayAttributeData& OldValue)
{
GAMEPLAYATTRIBUTE_REPNOTIFY(UPHYAttributeSet, HitChance, OldValue);
}
void UPHYAttributeSet::OnRep_ParryChance(const FGameplayAttributeData& OldValue)
{
GAMEPLAYATTRIBUTE_REPNOTIFY(UPHYAttributeSet, ParryChance, OldValue);
}
void UPHYAttributeSet::OnRep_CounterChance(const FGameplayAttributeData& OldValue)
{
GAMEPLAYATTRIBUTE_REPNOTIFY(UPHYAttributeSet, CounterChance, OldValue);
}
void UPHYAttributeSet::OnRep_ArmorPenetration(const FGameplayAttributeData& OldValue)
{
GAMEPLAYATTRIBUTE_REPNOTIFY(UPHYAttributeSet, ArmorPenetration, OldValue);
}
void UPHYAttributeSet::OnRep_DamageReduction(const FGameplayAttributeData& OldValue)
{
GAMEPLAYATTRIBUTE_REPNOTIFY(UPHYAttributeSet, DamageReduction, OldValue);
}
void UPHYAttributeSet::OnRep_LifeSteal(const FGameplayAttributeData& OldValue)
{
GAMEPLAYATTRIBUTE_REPNOTIFY(UPHYAttributeSet, LifeSteal, OldValue);
}
void UPHYAttributeSet::OnRep_HealthRegenRate(const FGameplayAttributeData& OldValue)
{
GAMEPLAYATTRIBUTE_REPNOTIFY(UPHYAttributeSet, HealthRegenRate, OldValue);
}
void UPHYAttributeSet::OnRep_InnerPower(const FGameplayAttributeData& OldValue)
{
GAMEPLAYATTRIBUTE_REPNOTIFY(UPHYAttributeSet, InnerPower, OldValue);
}
void UPHYAttributeSet::OnRep_MaxInnerPower(const FGameplayAttributeData& OldValue)
{
GAMEPLAYATTRIBUTE_REPNOTIFY(UPHYAttributeSet, MaxInnerPower, OldValue);
}
void UPHYAttributeSet::OnRep_InnerPowerRegenRate(const FGameplayAttributeData& OldValue)
{
GAMEPLAYATTRIBUTE_REPNOTIFY(UPHYAttributeSet, InnerPowerRegenRate, OldValue);
}
void UPHYAttributeSet::GetLifetimeReplicatedProps(TArray<FLifetimeProperty>& OutLifetimeProps) const
{
Super::GetLifetimeReplicatedProps(OutLifetimeProps);
DOREPLIFETIME_CONDITION_NOTIFY(UPHYAttributeSet, Strength, COND_None, REPNOTIFY_Always);
DOREPLIFETIME_CONDITION_NOTIFY(UPHYAttributeSet, Constitution, COND_None, REPNOTIFY_Always);
DOREPLIFETIME_CONDITION_NOTIFY(UPHYAttributeSet, InnerBreath, COND_None, REPNOTIFY_Always);
DOREPLIFETIME_CONDITION_NOTIFY(UPHYAttributeSet, Agility, COND_None, REPNOTIFY_Always);
DOREPLIFETIME_CONDITION_NOTIFY(UPHYAttributeSet, Health, COND_None, REPNOTIFY_Always);
DOREPLIFETIME_CONDITION_NOTIFY(UPHYAttributeSet, MaxHealth, COND_None, REPNOTIFY_Always);
DOREPLIFETIME_CONDITION_NOTIFY(UPHYAttributeSet, InnerPower, COND_None, REPNOTIFY_Always);
DOREPLIFETIME_CONDITION_NOTIFY(UPHYAttributeSet, MaxInnerPower, COND_None, REPNOTIFY_Always);
DOREPLIFETIME_CONDITION_NOTIFY(UPHYAttributeSet, MoveSpeed, COND_None, REPNOTIFY_Always);
DOREPLIFETIME_CONDITION_NOTIFY(UPHYAttributeSet, PhysicalAttack, COND_None, REPNOTIFY_Always);
DOREPLIFETIME_CONDITION_NOTIFY(UPHYAttributeSet, PhysicalDefense, COND_None, REPNOTIFY_Always);
DOREPLIFETIME_CONDITION_NOTIFY(UPHYAttributeSet, Tenacity, COND_None, REPNOTIFY_Always);
DOREPLIFETIME_CONDITION_NOTIFY(UPHYAttributeSet, CritChance, COND_None, REPNOTIFY_Always);
DOREPLIFETIME_CONDITION_NOTIFY(UPHYAttributeSet, CritDamage, COND_None, REPNOTIFY_Always);
DOREPLIFETIME_CONDITION_NOTIFY(UPHYAttributeSet, DodgeChance, COND_None, REPNOTIFY_Always);
DOREPLIFETIME_CONDITION_NOTIFY(UPHYAttributeSet, HitChance, COND_None, REPNOTIFY_Always);
DOREPLIFETIME_CONDITION_NOTIFY(UPHYAttributeSet, ParryChance, COND_None, REPNOTIFY_Always);
DOREPLIFETIME_CONDITION_NOTIFY(UPHYAttributeSet, CounterChance, COND_None, REPNOTIFY_Always);
DOREPLIFETIME_CONDITION_NOTIFY(UPHYAttributeSet, ArmorPenetration, COND_None, REPNOTIFY_Always);
DOREPLIFETIME_CONDITION_NOTIFY(UPHYAttributeSet, DamageReduction, COND_None, REPNOTIFY_Always);
DOREPLIFETIME_CONDITION_NOTIFY(UPHYAttributeSet, LifeSteal, COND_None, REPNOTIFY_Always);
DOREPLIFETIME_CONDITION_NOTIFY(UPHYAttributeSet, HealthRegenRate, COND_None, REPNOTIFY_Always);
DOREPLIFETIME_CONDITION_NOTIFY(UPHYAttributeSet, InnerPowerRegenRate, COND_None, REPNOTIFY_Always);
}

View File

@@ -0,0 +1,163 @@
// Copyright 2025 PHY. All Rights Reserved.
#pragma once
#include "CoreMinimal.h"
#include "AttributeSet.h"
#include "AbilitySystemComponent.h"
#include "PHYAttributeSet.generated.h"
#define ATTRIBUTE_ACCESSORS(ClassName, PropertyName) \
GAMEPLAYATTRIBUTE_PROPERTY_GETTER(ClassName, PropertyName) \
GAMEPLAYATTRIBUTE_VALUE_GETTER(PropertyName) \
GAMEPLAYATTRIBUTE_VALUE_SETTER(PropertyName) \
GAMEPLAYATTRIBUTE_VALUE_INITTER(PropertyName)
UCLASS()
class PHY_API UPHYAttributeSet : public UAttributeSet
{
GENERATED_BODY()
public:
UPHYAttributeSet();
virtual void PreAttributeChange(const FGameplayAttribute& Attribute, float& NewValue) override;
virtual void PostGameplayEffectExecute(const FGameplayEffectModCallbackData& Data) override;
virtual void GetLifetimeReplicatedProps(TArray<FLifetimeProperty>& OutLifetimeProps) const override;
UPROPERTY(BlueprintReadOnly, Category = "Attributes|Primary", ReplicatedUsing = OnRep_Strength)
FGameplayAttributeData Strength;
ATTRIBUTE_ACCESSORS(UPHYAttributeSet, Strength)
UPROPERTY(BlueprintReadOnly, Category = "Attributes|Primary", ReplicatedUsing = OnRep_Constitution)
FGameplayAttributeData Constitution;
ATTRIBUTE_ACCESSORS(UPHYAttributeSet, Constitution)
UPROPERTY(BlueprintReadOnly, Category = "Attributes|Primary", ReplicatedUsing = OnRep_InnerBreath)
FGameplayAttributeData InnerBreath;
ATTRIBUTE_ACCESSORS(UPHYAttributeSet, InnerBreath)
UPROPERTY(BlueprintReadOnly, Category = "Attributes|Primary", ReplicatedUsing = OnRep_Agility)
FGameplayAttributeData Agility;
ATTRIBUTE_ACCESSORS(UPHYAttributeSet, Agility)
UPROPERTY(BlueprintReadOnly, Category = "Attributes|Vitals", ReplicatedUsing = OnRep_Health)
FGameplayAttributeData Health;
ATTRIBUTE_ACCESSORS(UPHYAttributeSet, Health)
UPROPERTY(BlueprintReadOnly, Category = "Attributes|Vitals", ReplicatedUsing = OnRep_MaxHealth)
FGameplayAttributeData MaxHealth;
ATTRIBUTE_ACCESSORS(UPHYAttributeSet, MaxHealth)
UPROPERTY(BlueprintReadOnly, Category = "Attributes|Derived", ReplicatedUsing = OnRep_MoveSpeed)
FGameplayAttributeData MoveSpeed;
ATTRIBUTE_ACCESSORS(UPHYAttributeSet, MoveSpeed)
UPROPERTY(BlueprintReadOnly, Category = "Attributes|Derived", ReplicatedUsing = OnRep_PhysicalAttack)
FGameplayAttributeData PhysicalAttack;
ATTRIBUTE_ACCESSORS(UPHYAttributeSet, PhysicalAttack)
UPROPERTY(BlueprintReadOnly, Category = "Attributes|Derived", ReplicatedUsing = OnRep_PhysicalDefense)
FGameplayAttributeData PhysicalDefense;
ATTRIBUTE_ACCESSORS(UPHYAttributeSet, PhysicalDefense)
/** Secondary - 韧性(负面效果持续缩短/抵抗) */
UPROPERTY(BlueprintReadOnly, Category = "Attributes|Secondary", ReplicatedUsing = OnRep_Tenacity)
FGameplayAttributeData Tenacity;
ATTRIBUTE_ACCESSORS(UPHYAttributeSet, Tenacity)
/** Secondary - 暴击率(0-1) */
UPROPERTY(BlueprintReadOnly, Category = "Attributes|Secondary", ReplicatedUsing = OnRep_CritChance)
FGameplayAttributeData CritChance;
ATTRIBUTE_ACCESSORS(UPHYAttributeSet, CritChance)
/** Secondary - 暴击伤害倍率(例如 1.5 表示 +50%) */
UPROPERTY(BlueprintReadOnly, Category = "Attributes|Secondary", ReplicatedUsing = OnRep_CritDamage)
FGameplayAttributeData CritDamage;
ATTRIBUTE_ACCESSORS(UPHYAttributeSet, CritDamage)
/** Secondary - 闪避率(0-1) */
UPROPERTY(BlueprintReadOnly, Category = "Attributes|Secondary", ReplicatedUsing = OnRep_DodgeChance)
FGameplayAttributeData DodgeChance;
ATTRIBUTE_ACCESSORS(UPHYAttributeSet, DodgeChance)
/** Secondary - 命中(可做命中率或命中值) */
UPROPERTY(BlueprintReadOnly, Category = "Attributes|Secondary", ReplicatedUsing = OnRep_HitChance)
FGameplayAttributeData HitChance;
ATTRIBUTE_ACCESSORS(UPHYAttributeSet, HitChance)
/** Secondary - 招架率(0-1) */
UPROPERTY(BlueprintReadOnly, Category = "Attributes|Secondary", ReplicatedUsing = OnRep_ParryChance)
FGameplayAttributeData ParryChance;
ATTRIBUTE_ACCESSORS(UPHYAttributeSet, ParryChance)
/** Secondary - 反击几率(0-1),一般在成功闪避/招架后判定 */
UPROPERTY(BlueprintReadOnly, Category = "Attributes|Secondary", ReplicatedUsing = OnRep_CounterChance)
FGameplayAttributeData CounterChance;
ATTRIBUTE_ACCESSORS(UPHYAttributeSet, CounterChance)
/** Secondary - 穿甲(百分比 0-1 或值,先按 0-1 做) */
UPROPERTY(BlueprintReadOnly, Category = "Attributes|Secondary", ReplicatedUsing = OnRep_ArmorPenetration)
FGameplayAttributeData ArmorPenetration;
ATTRIBUTE_ACCESSORS(UPHYAttributeSet, ArmorPenetration)
/** Secondary - 免伤(0-1) */
UPROPERTY(BlueprintReadOnly, Category = "Attributes|Secondary", ReplicatedUsing = OnRep_DamageReduction)
FGameplayAttributeData DamageReduction;
ATTRIBUTE_ACCESSORS(UPHYAttributeSet, DamageReduction)
/** Secondary - 吸血(0-1) */
UPROPERTY(BlueprintReadOnly, Category = "Attributes|Secondary", ReplicatedUsing = OnRep_LifeSteal)
FGameplayAttributeData LifeSteal;
ATTRIBUTE_ACCESSORS(UPHYAttributeSet, LifeSteal)
/** Secondary - 生命回复/秒 */
UPROPERTY(BlueprintReadOnly, Category = "Attributes|Secondary", ReplicatedUsing = OnRep_HealthRegenRate)
FGameplayAttributeData HealthRegenRate;
ATTRIBUTE_ACCESSORS(UPHYAttributeSet, HealthRegenRate)
/** Resource - 内力(当前) */
UPROPERTY(BlueprintReadOnly, Category = "Attributes|Resource", ReplicatedUsing = OnRep_InnerPower)
FGameplayAttributeData InnerPower;
ATTRIBUTE_ACCESSORS(UPHYAttributeSet, InnerPower)
/** Resource - 内力上限 */
UPROPERTY(BlueprintReadOnly, Category = "Attributes|Resource", ReplicatedUsing = OnRep_MaxInnerPower)
FGameplayAttributeData MaxInnerPower;
ATTRIBUTE_ACCESSORS(UPHYAttributeSet, MaxInnerPower)
/** Secondary - 内力回复/秒 */
UPROPERTY(BlueprintReadOnly, Category = "Attributes|Secondary", ReplicatedUsing = OnRep_InnerPowerRegenRate)
FGameplayAttributeData InnerPowerRegenRate;
ATTRIBUTE_ACCESSORS(UPHYAttributeSet, InnerPowerRegenRate)
protected:
UFUNCTION() void OnRep_Strength(const FGameplayAttributeData& OldValue);
UFUNCTION() void OnRep_Constitution(const FGameplayAttributeData& OldValue);
UFUNCTION() void OnRep_InnerBreath(const FGameplayAttributeData& OldValue);
UFUNCTION() void OnRep_Agility(const FGameplayAttributeData& OldValue);
UFUNCTION() void OnRep_Health(const FGameplayAttributeData& OldValue);
UFUNCTION() void OnRep_MaxHealth(const FGameplayAttributeData& OldValue);
UFUNCTION() void OnRep_MoveSpeed(const FGameplayAttributeData& OldValue);
UFUNCTION() void OnRep_PhysicalAttack(const FGameplayAttributeData& OldValue);
UFUNCTION() void OnRep_PhysicalDefense(const FGameplayAttributeData& OldValue);
UFUNCTION() void OnRep_Tenacity(const FGameplayAttributeData& OldValue);
UFUNCTION() void OnRep_CritChance(const FGameplayAttributeData& OldValue);
UFUNCTION() void OnRep_CritDamage(const FGameplayAttributeData& OldValue);
UFUNCTION() void OnRep_DodgeChance(const FGameplayAttributeData& OldValue);
UFUNCTION() void OnRep_HitChance(const FGameplayAttributeData& OldValue);
UFUNCTION() void OnRep_ParryChance(const FGameplayAttributeData& OldValue);
UFUNCTION() void OnRep_CounterChance(const FGameplayAttributeData& OldValue);
UFUNCTION() void OnRep_ArmorPenetration(const FGameplayAttributeData& OldValue);
UFUNCTION() void OnRep_DamageReduction(const FGameplayAttributeData& OldValue);
UFUNCTION() void OnRep_LifeSteal(const FGameplayAttributeData& OldValue);
UFUNCTION() void OnRep_HealthRegenRate(const FGameplayAttributeData& OldValue);
UFUNCTION() void OnRep_InnerPower(const FGameplayAttributeData& OldValue);
UFUNCTION() void OnRep_MaxInnerPower(const FGameplayAttributeData& OldValue);
UFUNCTION() void OnRep_InnerPowerRegenRate(const FGameplayAttributeData& OldValue);
static float ClampNonNegative(float Value) { return FMath::Max(0.f, Value); }
};
#undef ATTRIBUTE_ACCESSORS

View File

@@ -0,0 +1,191 @@
// Copyright 2025 PHY. All Rights Reserved.
#include "AbilitySystem/Effects/PHYGE_DerivedAttributes.h"
#include "AbilitySystem/Attributes/PHYAttributeSet.h"
#include "AbilitySystem/MMC/PHY_MMC_MaxHealth.h"
#include "AbilitySystem/MMC/PHY_MMC_MoveSpeed.h"
#include "AbilitySystem/MMC/PHY_MMC_PhysicalAttack.h"
#include "AbilitySystem/MMC/PHY_MMC_PhysicalDefense.h"
#include "AbilitySystem/MMC/PHY_MMC_MaxInnerPower.h"
#include "AbilitySystem/MMC/PHY_MMC_Tenacity.h"
#include "AbilitySystem/MMC/PHY_MMC_CritChance.h"
#include "AbilitySystem/MMC/PHY_MMC_CritDamage.h"
#include "AbilitySystem/MMC/PHY_MMC_DodgeChance.h"
#include "AbilitySystem/MMC/PHY_MMC_HitChance.h"
#include "AbilitySystem/MMC/PHY_MMC_ParryChance.h"
#include "AbilitySystem/MMC/PHY_MMC_CounterChance.h"
#include "AbilitySystem/MMC/PHY_MMC_DamageReduction.h"
#include "AbilitySystem/MMC/PHY_MMC_HealthRegenRate.h"
#include "AbilitySystem/MMC/PHY_MMC_InnerPowerRegenRate.h"
UPHYGE_DerivedAttributes::UPHYGE_DerivedAttributes()
{
DurationPolicy = EGameplayEffectDurationType::Infinite;
// MaxHealth
{
FGameplayModifierInfo Info;
Info.Attribute = UPHYAttributeSet::GetMaxHealthAttribute();
Info.ModifierOp = EGameplayModOp::Override;
FCustomCalculationBasedFloat Calc;
Calc.CalculationClassMagnitude = UPHY_MMC_MaxHealth::StaticClass();
Info.ModifierMagnitude = FGameplayEffectModifierMagnitude(Calc);
Modifiers.Add(Info);
}
// MoveSpeed
{
FGameplayModifierInfo Info;
Info.Attribute = UPHYAttributeSet::GetMoveSpeedAttribute();
Info.ModifierOp = EGameplayModOp::Override;
FCustomCalculationBasedFloat Calc;
Calc.CalculationClassMagnitude = UPHY_MMC_MoveSpeed::StaticClass();
Info.ModifierMagnitude = FGameplayEffectModifierMagnitude(Calc);
Modifiers.Add(Info);
}
// PhysicalAttack
{
FGameplayModifierInfo Info;
Info.Attribute = UPHYAttributeSet::GetPhysicalAttackAttribute();
Info.ModifierOp = EGameplayModOp::Override;
FCustomCalculationBasedFloat Calc;
Calc.CalculationClassMagnitude = UPHY_MMC_PhysicalAttack::StaticClass();
Info.ModifierMagnitude = FGameplayEffectModifierMagnitude(Calc);
Modifiers.Add(Info);
}
// PhysicalDefense
{
FGameplayModifierInfo Info;
Info.Attribute = UPHYAttributeSet::GetPhysicalDefenseAttribute();
Info.ModifierOp = EGameplayModOp::Override;
FCustomCalculationBasedFloat Calc;
Calc.CalculationClassMagnitude = UPHY_MMC_PhysicalDefense::StaticClass();
Info.ModifierMagnitude = FGameplayEffectModifierMagnitude(Calc);
Modifiers.Add(Info);
}
// MaxInnerPower
{
FGameplayModifierInfo Info;
Info.Attribute = UPHYAttributeSet::GetMaxInnerPowerAttribute();
Info.ModifierOp = EGameplayModOp::Override;
FCustomCalculationBasedFloat Calc;
Calc.CalculationClassMagnitude = UPHY_MMC_MaxInnerPower::StaticClass();
Info.ModifierMagnitude = FGameplayEffectModifierMagnitude(Calc);
Modifiers.Add(Info);
}
// Tenacity
{
FGameplayModifierInfo Info;
Info.Attribute = UPHYAttributeSet::GetTenacityAttribute();
Info.ModifierOp = EGameplayModOp::Override;
FCustomCalculationBasedFloat Calc;
Calc.CalculationClassMagnitude = UPHY_MMC_Tenacity::StaticClass();
Info.ModifierMagnitude = FGameplayEffectModifierMagnitude(Calc);
Modifiers.Add(Info);
}
// CritChance
{
FGameplayModifierInfo Info;
Info.Attribute = UPHYAttributeSet::GetCritChanceAttribute();
Info.ModifierOp = EGameplayModOp::Override;
FCustomCalculationBasedFloat Calc;
Calc.CalculationClassMagnitude = UPHY_MMC_CritChance::StaticClass();
Info.ModifierMagnitude = FGameplayEffectModifierMagnitude(Calc);
Modifiers.Add(Info);
}
// CritDamage
{
FGameplayModifierInfo Info;
Info.Attribute = UPHYAttributeSet::GetCritDamageAttribute();
Info.ModifierOp = EGameplayModOp::Override;
FCustomCalculationBasedFloat Calc;
Calc.CalculationClassMagnitude = UPHY_MMC_CritDamage::StaticClass();
Info.ModifierMagnitude = FGameplayEffectModifierMagnitude(Calc);
Modifiers.Add(Info);
}
// DodgeChance
{
FGameplayModifierInfo Info;
Info.Attribute = UPHYAttributeSet::GetDodgeChanceAttribute();
Info.ModifierOp = EGameplayModOp::Override;
FCustomCalculationBasedFloat Calc;
Calc.CalculationClassMagnitude = UPHY_MMC_DodgeChance::StaticClass();
Info.ModifierMagnitude = FGameplayEffectModifierMagnitude(Calc);
Modifiers.Add(Info);
}
// HitChance
{
FGameplayModifierInfo Info;
Info.Attribute = UPHYAttributeSet::GetHitChanceAttribute();
Info.ModifierOp = EGameplayModOp::Override;
FCustomCalculationBasedFloat Calc;
Calc.CalculationClassMagnitude = UPHY_MMC_HitChance::StaticClass();
Info.ModifierMagnitude = FGameplayEffectModifierMagnitude(Calc);
Modifiers.Add(Info);
}
// ParryChance
{
FGameplayModifierInfo Info;
Info.Attribute = UPHYAttributeSet::GetParryChanceAttribute();
Info.ModifierOp = EGameplayModOp::Override;
FCustomCalculationBasedFloat Calc;
Calc.CalculationClassMagnitude = UPHY_MMC_ParryChance::StaticClass();
Info.ModifierMagnitude = FGameplayEffectModifierMagnitude(Calc);
Modifiers.Add(Info);
}
// CounterChance
{
FGameplayModifierInfo Info;
Info.Attribute = UPHYAttributeSet::GetCounterChanceAttribute();
Info.ModifierOp = EGameplayModOp::Override;
FCustomCalculationBasedFloat Calc;
Calc.CalculationClassMagnitude = UPHY_MMC_CounterChance::StaticClass();
Info.ModifierMagnitude = FGameplayEffectModifierMagnitude(Calc);
Modifiers.Add(Info);
}
// DamageReduction
{
FGameplayModifierInfo Info;
Info.Attribute = UPHYAttributeSet::GetDamageReductionAttribute();
Info.ModifierOp = EGameplayModOp::Override;
FCustomCalculationBasedFloat Calc;
Calc.CalculationClassMagnitude = UPHY_MMC_DamageReduction::StaticClass();
Info.ModifierMagnitude = FGameplayEffectModifierMagnitude(Calc);
Modifiers.Add(Info);
}
// HealthRegenRate
{
FGameplayModifierInfo Info;
Info.Attribute = UPHYAttributeSet::GetHealthRegenRateAttribute();
Info.ModifierOp = EGameplayModOp::Override;
FCustomCalculationBasedFloat Calc;
Calc.CalculationClassMagnitude = UPHY_MMC_HealthRegenRate::StaticClass();
Info.ModifierMagnitude = FGameplayEffectModifierMagnitude(Calc);
Modifiers.Add(Info);
}
// InnerPowerRegenRate
{
FGameplayModifierInfo Info;
Info.Attribute = UPHYAttributeSet::GetInnerPowerRegenRateAttribute();
Info.ModifierOp = EGameplayModOp::Override;
FCustomCalculationBasedFloat Calc;
Calc.CalculationClassMagnitude = UPHY_MMC_InnerPowerRegenRate::StaticClass();
Info.ModifierMagnitude = FGameplayEffectModifierMagnitude(Calc);
Modifiers.Add(Info);
}
}

View File

@@ -0,0 +1,22 @@
// Copyright 2025 PHY. All Rights Reserved.
#pragma once
#include "CoreMinimal.h"
#include "GameplayEffect.h"
#include "PHYGE_DerivedAttributes.generated.h"
/**
* Infinite GE that drives derived attributes via MMCs.
*
* Applied once on spawn/login; re-apply if you need to refresh snapshot-based MMCs.
*/
UCLASS()
class PHY_API UPHYGE_DerivedAttributes : public UGameplayEffect
{
GENERATED_BODY()
public:
UPHYGE_DerivedAttributes();
};

View File

@@ -0,0 +1,30 @@
// Copyright 2025 PHY. All Rights Reserved.
#include "AbilitySystem/Effects/PHYGE_InitPrimary.h"
#include "AbilitySystem/Attributes/PHYAttributeSet.h"
#include "GameplayTags/InitAttributeTags.h"
UPHYGE_InitPrimary::UPHYGE_InitPrimary()
{
DurationPolicy = EGameplayEffectDurationType::Instant;
// Use SetByCaller so one GE works for every class.
auto AddSetByCaller = [this](FGameplayAttribute Attr, FGameplayTag DataTag)
{
FGameplayModifierInfo Info;
Info.Attribute = Attr;
Info.ModifierOp = EGameplayModOp::Override;
FSetByCallerFloat SetByCaller;
SetByCaller.DataTag = DataTag;
Info.ModifierMagnitude = FGameplayEffectModifierMagnitude(SetByCaller);
Modifiers.Add(Info);
};
AddSetByCaller(UPHYAttributeSet::GetStrengthAttribute(), InitAttributeTags::Tag__Data_Init_Primary_Strength);
AddSetByCaller(UPHYAttributeSet::GetConstitutionAttribute(), InitAttributeTags::Tag__Data_Init_Primary_Constitution);
AddSetByCaller(UPHYAttributeSet::GetInnerBreathAttribute(), InitAttributeTags::Tag__Data_Init_Primary_InnerBreath);
AddSetByCaller(UPHYAttributeSet::GetAgilityAttribute(), InitAttributeTags::Tag__Data_Init_Primary_Agility);
// Also initialize current Health to MaxHealth after derived GE runs. We'll set Health in code post-apply.
}

View File

@@ -0,0 +1,18 @@
// Copyright 2025 PHY. All Rights Reserved.
#pragma once
#include "CoreMinimal.h"
#include "GameplayEffect.h"
#include "PHYGE_InitPrimary.generated.h"
/** Instant GE that sets initial primary stats (四维). */
UCLASS()
class PHY_API UPHYGE_InitPrimary : public UGameplayEffect
{
GENERATED_BODY()
public:
UPHYGE_InitPrimary();
};

View File

@@ -0,0 +1,33 @@
// Copyright 2025 PHY. All Rights Reserved.
#include "AbilitySystem/Effects/PHYGE_RegenTick.h"
#include "AbilitySystem/Attributes/PHYAttributeSet.h"
#include "GameplayTags/RegenTags.h"
UPHYGE_RegenTick::UPHYGE_RegenTick()
{
DurationPolicy = EGameplayEffectDurationType::Instant;
// Add Health each tick
{
FGameplayModifierInfo Info;
Info.Attribute = UPHYAttributeSet::GetHealthAttribute();
Info.ModifierOp = EGameplayModOp::Additive;
FSetByCallerFloat SBC;
SBC.DataTag = RegenTags::Tag__Data_Regen_Health;
Info.ModifierMagnitude = FGameplayEffectModifierMagnitude(SBC);
Modifiers.Add(Info);
}
// Add InnerPower each tick
{
FGameplayModifierInfo Info;
Info.Attribute = UPHYAttributeSet::GetInnerPowerAttribute();
Info.ModifierOp = EGameplayModOp::Additive;
FSetByCallerFloat SBC;
SBC.DataTag = RegenTags::Tag__Data_Regen_InnerPower;
Info.ModifierMagnitude = FGameplayEffectModifierMagnitude(SBC);
Modifiers.Add(Info);
}
}

View File

@@ -0,0 +1,18 @@
// Copyright 2025 PHY. All Rights Reserved.
#pragma once
#include "CoreMinimal.h"
#include "GameplayEffect.h"
#include "PHYGE_RegenTick.generated.h"
/** Instant GE applied periodically (code-driven) to regen Health and InnerPower. */
UCLASS()
class PHY_API UPHYGE_RegenTick : public UGameplayEffect
{
GENERATED_BODY()
public:
UPHYGE_RegenTick();
};

View File

@@ -0,0 +1,31 @@
// Copyright 2025 PHY. All Rights Reserved.
#include "AbilitySystem/MMC/PHY_MMC_CounterChance.h"
#include "AbilitySystem/Attributes/PHYAttributeSet.h"
UPHY_MMC_CounterChance::UPHY_MMC_CounterChance()
{
AgilityDef = FGameplayEffectAttributeCaptureDefinition(
UPHYAttributeSet::GetAgilityAttribute(),
EGameplayEffectAttributeCaptureSource::Target,
true);
RelevantAttributesToCapture.Add(AgilityDef);
}
float UPHY_MMC_CounterChance::CalculateBaseMagnitude_Implementation(const FGameplayEffectSpec& Spec) const
{
FAggregatorEvaluateParameters Params;
Params.SourceTags = Spec.CapturedSourceTags.GetAggregatedTags();
Params.TargetTags = Spec.CapturedTargetTags.GetAggregatedTags();
float Agility = 0.f;
GetCapturedAttributeMagnitude(AgilityDef, Spec, Params, Agility);
Agility = FMath::Max(0.f, Agility);
constexpr float Base = 0.1f;
constexpr float K = 0.001f;
return FMath::Clamp(Base + Agility * K, 0.f, 1.f);
}

View File

@@ -0,0 +1,22 @@
// Copyright 2025 PHY. All Rights Reserved.
#pragma once
#include "CoreMinimal.h"
#include "GameplayModMagnitudeCalculation.h"
#include "PHY_MMC_CounterChance.generated.h"
/** CounterChance = Base(0.1) + Agility * 0.001 */
UCLASS()
class PHY_API UPHY_MMC_CounterChance : public UGameplayModMagnitudeCalculation
{
GENERATED_BODY()
public:
UPHY_MMC_CounterChance();
virtual float CalculateBaseMagnitude_Implementation(const FGameplayEffectSpec& Spec) const override;
protected:
FGameplayEffectAttributeCaptureDefinition AgilityDef;
};

View File

@@ -0,0 +1,31 @@
// Copyright 2025 PHY. All Rights Reserved.
#include "AbilitySystem/MMC/PHY_MMC_CritChance.h"
#include "AbilitySystem/Attributes/PHYAttributeSet.h"
UPHY_MMC_CritChance::UPHY_MMC_CritChance()
{
AgilityDef = FGameplayEffectAttributeCaptureDefinition(
UPHYAttributeSet::GetAgilityAttribute(),
EGameplayEffectAttributeCaptureSource::Target,
true);
RelevantAttributesToCapture.Add(AgilityDef);
}
float UPHY_MMC_CritChance::CalculateBaseMagnitude_Implementation(const FGameplayEffectSpec& Spec) const
{
FAggregatorEvaluateParameters Params;
Params.SourceTags = Spec.CapturedSourceTags.GetAggregatedTags();
Params.TargetTags = Spec.CapturedTargetTags.GetAggregatedTags();
float Agility = 0.f;
GetCapturedAttributeMagnitude(AgilityDef, Spec, Params, Agility);
Agility = FMath::Max(0.f, Agility);
constexpr float Base = 0.05f;
constexpr float K = 0.002f;
return FMath::Clamp(Base + Agility * K, 0.f, 1.f);
}

View File

@@ -0,0 +1,22 @@
// Copyright 2025 PHY. All Rights Reserved.
#pragma once
#include "CoreMinimal.h"
#include "GameplayModMagnitudeCalculation.h"
#include "PHY_MMC_CritChance.generated.h"
/** CritChance = Base(0.05) + Agility * 0.002 */
UCLASS()
class PHY_API UPHY_MMC_CritChance : public UGameplayModMagnitudeCalculation
{
GENERATED_BODY()
public:
UPHY_MMC_CritChance();
virtual float CalculateBaseMagnitude_Implementation(const FGameplayEffectSpec& Spec) const override;
protected:
FGameplayEffectAttributeCaptureDefinition AgilityDef;
};

View File

@@ -0,0 +1,31 @@
// Copyright 2025 PHY. All Rights Reserved.
#include "AbilitySystem/MMC/PHY_MMC_CritDamage.h"
#include "AbilitySystem/Attributes/PHYAttributeSet.h"
UPHY_MMC_CritDamage::UPHY_MMC_CritDamage()
{
StrengthDef = FGameplayEffectAttributeCaptureDefinition(
UPHYAttributeSet::GetStrengthAttribute(),
EGameplayEffectAttributeCaptureSource::Target,
true);
RelevantAttributesToCapture.Add(StrengthDef);
}
float UPHY_MMC_CritDamage::CalculateBaseMagnitude_Implementation(const FGameplayEffectSpec& Spec) const
{
FAggregatorEvaluateParameters Params;
Params.SourceTags = Spec.CapturedSourceTags.GetAggregatedTags();
Params.TargetTags = Spec.CapturedTargetTags.GetAggregatedTags();
float Strength = 0.f;
GetCapturedAttributeMagnitude(StrengthDef, Spec, Params, Strength);
Strength = FMath::Max(0.f, Strength);
constexpr float Base = 1.5f;
constexpr float K = 0.005f;
return FMath::Max(1.f, Base + Strength * K);
}

View File

@@ -0,0 +1,22 @@
// Copyright 2025 PHY. All Rights Reserved.
#pragma once
#include "CoreMinimal.h"
#include "GameplayModMagnitudeCalculation.h"
#include "PHY_MMC_CritDamage.generated.h"
/** CritDamage = Base(1.5) + Strength * 0.005 */
UCLASS()
class PHY_API UPHY_MMC_CritDamage : public UGameplayModMagnitudeCalculation
{
GENERATED_BODY()
public:
UPHY_MMC_CritDamage();
virtual float CalculateBaseMagnitude_Implementation(const FGameplayEffectSpec& Spec) const override;
protected:
FGameplayEffectAttributeCaptureDefinition StrengthDef;
};

View File

@@ -0,0 +1,31 @@
// Copyright 2025 PHY. All Rights Reserved.
#include "AbilitySystem/MMC/PHY_MMC_DamageReduction.h"
#include "AbilitySystem/Attributes/PHYAttributeSet.h"
UPHY_MMC_DamageReduction::UPHY_MMC_DamageReduction()
{
ConstitutionDef = FGameplayEffectAttributeCaptureDefinition(
UPHYAttributeSet::GetConstitutionAttribute(),
EGameplayEffectAttributeCaptureSource::Target,
true);
RelevantAttributesToCapture.Add(ConstitutionDef);
}
float UPHY_MMC_DamageReduction::CalculateBaseMagnitude_Implementation(const FGameplayEffectSpec& Spec) const
{
FAggregatorEvaluateParameters Params;
Params.SourceTags = Spec.CapturedSourceTags.GetAggregatedTags();
Params.TargetTags = Spec.CapturedTargetTags.GetAggregatedTags();
float Constitution = 0.f;
GetCapturedAttributeMagnitude(ConstitutionDef, Spec, Params, Constitution);
Constitution = FMath::Max(0.f, Constitution);
constexpr float Base = 0.f;
constexpr float K = 0.001f;
return FMath::Clamp(Base + Constitution * K, 0.f, 1.f);
}

View File

@@ -0,0 +1,22 @@
// Copyright 2025 PHY. All Rights Reserved.
#pragma once
#include "CoreMinimal.h"
#include "GameplayModMagnitudeCalculation.h"
#include "PHY_MMC_DamageReduction.generated.h"
/** DamageReduction = Base(0) + Constitution * 0.001 */
UCLASS()
class PHY_API UPHY_MMC_DamageReduction : public UGameplayModMagnitudeCalculation
{
GENERATED_BODY()
public:
UPHY_MMC_DamageReduction();
virtual float CalculateBaseMagnitude_Implementation(const FGameplayEffectSpec& Spec) const override;
protected:
FGameplayEffectAttributeCaptureDefinition ConstitutionDef;
};

View File

@@ -0,0 +1,31 @@
// Copyright 2025 PHY. All Rights Reserved.
#include "AbilitySystem/MMC/PHY_MMC_DodgeChance.h"
#include "AbilitySystem/Attributes/PHYAttributeSet.h"
UPHY_MMC_DodgeChance::UPHY_MMC_DodgeChance()
{
AgilityDef = FGameplayEffectAttributeCaptureDefinition(
UPHYAttributeSet::GetAgilityAttribute(),
EGameplayEffectAttributeCaptureSource::Target,
true);
RelevantAttributesToCapture.Add(AgilityDef);
}
float UPHY_MMC_DodgeChance::CalculateBaseMagnitude_Implementation(const FGameplayEffectSpec& Spec) const
{
FAggregatorEvaluateParameters Params;
Params.SourceTags = Spec.CapturedSourceTags.GetAggregatedTags();
Params.TargetTags = Spec.CapturedTargetTags.GetAggregatedTags();
float Agility = 0.f;
GetCapturedAttributeMagnitude(AgilityDef, Spec, Params, Agility);
Agility = FMath::Max(0.f, Agility);
constexpr float Base = 0.02f;
constexpr float K = 0.002f;
return FMath::Clamp(Base + Agility * K, 0.f, 1.f);
}

View File

@@ -0,0 +1,22 @@
// Copyright 2025 PHY. All Rights Reserved.
#pragma once
#include "CoreMinimal.h"
#include "GameplayModMagnitudeCalculation.h"
#include "PHY_MMC_DodgeChance.generated.h"
/** DodgeChance = Base(0.02) + Agility * 0.002 */
UCLASS()
class PHY_API UPHY_MMC_DodgeChance : public UGameplayModMagnitudeCalculation
{
GENERATED_BODY()
public:
UPHY_MMC_DodgeChance();
virtual float CalculateBaseMagnitude_Implementation(const FGameplayEffectSpec& Spec) const override;
protected:
FGameplayEffectAttributeCaptureDefinition AgilityDef;
};

View File

@@ -0,0 +1,30 @@
// Copyright 2025 PHY. All Rights Reserved.
#include "AbilitySystem/MMC/PHY_MMC_HealthRegenRate.h"
#include "AbilitySystem/Attributes/PHYAttributeSet.h"
UPHY_MMC_HealthRegenRate::UPHY_MMC_HealthRegenRate()
{
ConstitutionDef = FGameplayEffectAttributeCaptureDefinition(
UPHYAttributeSet::GetConstitutionAttribute(),
EGameplayEffectAttributeCaptureSource::Target,
true);
RelevantAttributesToCapture.Add(ConstitutionDef);
}
float UPHY_MMC_HealthRegenRate::CalculateBaseMagnitude_Implementation(const FGameplayEffectSpec& Spec) const
{
FAggregatorEvaluateParameters Params;
Params.SourceTags = Spec.CapturedSourceTags.GetAggregatedTags();
Params.TargetTags = Spec.CapturedTargetTags.GetAggregatedTags();
float Constitution = 0.f;
GetCapturedAttributeMagnitude(ConstitutionDef, Spec, Params, Constitution);
Constitution = FMath::Max(0.f, Constitution);
constexpr float K = 0.1f;
return Constitution * K;
}

View File

@@ -0,0 +1,22 @@
// Copyright 2025 PHY. All Rights Reserved.
#pragma once
#include "CoreMinimal.h"
#include "GameplayModMagnitudeCalculation.h"
#include "PHY_MMC_HealthRegenRate.generated.h"
/** HealthRegenRate = Constitution * 0.1 */
UCLASS()
class PHY_API UPHY_MMC_HealthRegenRate : public UGameplayModMagnitudeCalculation
{
GENERATED_BODY()
public:
UPHY_MMC_HealthRegenRate();
virtual float CalculateBaseMagnitude_Implementation(const FGameplayEffectSpec& Spec) const override;
protected:
FGameplayEffectAttributeCaptureDefinition ConstitutionDef;
};

View File

@@ -0,0 +1,31 @@
// Copyright 2025 PHY. All Rights Reserved.
#include "AbilitySystem/MMC/PHY_MMC_HitChance.h"
#include "AbilitySystem/Attributes/PHYAttributeSet.h"
UPHY_MMC_HitChance::UPHY_MMC_HitChance()
{
AgilityDef = FGameplayEffectAttributeCaptureDefinition(
UPHYAttributeSet::GetAgilityAttribute(),
EGameplayEffectAttributeCaptureSource::Target,
true);
RelevantAttributesToCapture.Add(AgilityDef);
}
float UPHY_MMC_HitChance::CalculateBaseMagnitude_Implementation(const FGameplayEffectSpec& Spec) const
{
FAggregatorEvaluateParameters Params;
Params.SourceTags = Spec.CapturedSourceTags.GetAggregatedTags();
Params.TargetTags = Spec.CapturedTargetTags.GetAggregatedTags();
float Agility = 0.f;
GetCapturedAttributeMagnitude(AgilityDef, Spec, Params, Agility);
Agility = FMath::Max(0.f, Agility);
constexpr float Base = 0.9f;
constexpr float K = 0.001f;
return FMath::Clamp(Base + Agility * K, 0.f, 1.f);
}

View File

@@ -0,0 +1,22 @@
// Copyright 2025 PHY. All Rights Reserved.
#pragma once
#include "CoreMinimal.h"
#include "GameplayModMagnitudeCalculation.h"
#include "PHY_MMC_HitChance.generated.h"
/** HitChance = Base(0.9) + Agility * 0.001 */
UCLASS()
class PHY_API UPHY_MMC_HitChance : public UGameplayModMagnitudeCalculation
{
GENERATED_BODY()
public:
UPHY_MMC_HitChance();
virtual float CalculateBaseMagnitude_Implementation(const FGameplayEffectSpec& Spec) const override;
protected:
FGameplayEffectAttributeCaptureDefinition AgilityDef;
};

View File

@@ -0,0 +1,30 @@
// Copyright 2025 PHY. All Rights Reserved.
#include "AbilitySystem/MMC/PHY_MMC_InnerPowerRegenRate.h"
#include "AbilitySystem/Attributes/PHYAttributeSet.h"
UPHY_MMC_InnerPowerRegenRate::UPHY_MMC_InnerPowerRegenRate()
{
InnerBreathDef = FGameplayEffectAttributeCaptureDefinition(
UPHYAttributeSet::GetInnerBreathAttribute(),
EGameplayEffectAttributeCaptureSource::Target,
true);
RelevantAttributesToCapture.Add(InnerBreathDef);
}
float UPHY_MMC_InnerPowerRegenRate::CalculateBaseMagnitude_Implementation(const FGameplayEffectSpec& Spec) const
{
FAggregatorEvaluateParameters Params;
Params.SourceTags = Spec.CapturedSourceTags.GetAggregatedTags();
Params.TargetTags = Spec.CapturedTargetTags.GetAggregatedTags();
float InnerBreath = 0.f;
GetCapturedAttributeMagnitude(InnerBreathDef, Spec, Params, InnerBreath);
InnerBreath = FMath::Max(0.f, InnerBreath);
constexpr float K = 0.15f;
return InnerBreath * K;
}

View File

@@ -0,0 +1,22 @@
// Copyright 2025 PHY. All Rights Reserved.
#pragma once
#include "CoreMinimal.h"
#include "GameplayModMagnitudeCalculation.h"
#include "PHY_MMC_InnerPowerRegenRate.generated.h"
/** InnerPowerRegenRate = InnerBreath * 0.15 */
UCLASS()
class PHY_API UPHY_MMC_InnerPowerRegenRate : public UGameplayModMagnitudeCalculation
{
GENERATED_BODY()
public:
UPHY_MMC_InnerPowerRegenRate();
virtual float CalculateBaseMagnitude_Implementation(const FGameplayEffectSpec& Spec) const override;
protected:
FGameplayEffectAttributeCaptureDefinition InnerBreathDef;
};

View File

@@ -0,0 +1,31 @@
// Copyright 2025 PHY. All Rights Reserved.
#include "AbilitySystem/MMC/PHY_MMC_MaxHealth.h"
#include "AbilitySystem/Attributes/PHYAttributeSet.h"
UPHY_MMC_MaxHealth::UPHY_MMC_MaxHealth()
{
ConstitutionDef = FGameplayEffectAttributeCaptureDefinition(
UPHYAttributeSet::GetConstitutionAttribute(),
EGameplayEffectAttributeCaptureSource::Target,
true /* snapshot */);
RelevantAttributesToCapture.Add(ConstitutionDef);
}
float UPHY_MMC_MaxHealth::CalculateBaseMagnitude_Implementation(const FGameplayEffectSpec& Spec) const
{
FAggregatorEvaluateParameters EvalParams;
EvalParams.SourceTags = Spec.CapturedSourceTags.GetAggregatedTags();
EvalParams.TargetTags = Spec.CapturedTargetTags.GetAggregatedTags();
float Constitution = 0.f;
GetCapturedAttributeMagnitude(ConstitutionDef, Spec, EvalParams, Constitution);
Constitution = FMath::Max(0.f, Constitution);
constexpr float BaseMaxHealth = 100.f;
constexpr float ConstitutionToHealth = 25.f;
return BaseMaxHealth + Constitution * ConstitutionToHealth;
}

View File

@@ -0,0 +1,22 @@
// Copyright 2025 PHY. All Rights Reserved.
#pragma once
#include "CoreMinimal.h"
#include "GameplayModMagnitudeCalculation.h"
#include "PHY_MMC_MaxHealth.generated.h"
/** MaxHealth = Base(100) + Constitution * 25 */
UCLASS()
class PHY_API UPHY_MMC_MaxHealth : public UGameplayModMagnitudeCalculation
{
GENERATED_BODY()
public:
UPHY_MMC_MaxHealth();
virtual float CalculateBaseMagnitude_Implementation(const FGameplayEffectSpec& Spec) const override;
protected:
FGameplayEffectAttributeCaptureDefinition ConstitutionDef;
};

View File

@@ -0,0 +1,31 @@
// Copyright 2025 PHY. All Rights Reserved.
#include "AbilitySystem/MMC/PHY_MMC_MaxInnerPower.h"
#include "AbilitySystem/Attributes/PHYAttributeSet.h"
UPHY_MMC_MaxInnerPower::UPHY_MMC_MaxInnerPower()
{
InnerBreathDef = FGameplayEffectAttributeCaptureDefinition(
UPHYAttributeSet::GetInnerBreathAttribute(),
EGameplayEffectAttributeCaptureSource::Target,
true);
RelevantAttributesToCapture.Add(InnerBreathDef);
}
float UPHY_MMC_MaxInnerPower::CalculateBaseMagnitude_Implementation(const FGameplayEffectSpec& Spec) const
{
FAggregatorEvaluateParameters Params;
Params.SourceTags = Spec.CapturedSourceTags.GetAggregatedTags();
Params.TargetTags = Spec.CapturedTargetTags.GetAggregatedTags();
float InnerBreath = 0.f;
GetCapturedAttributeMagnitude(InnerBreathDef, Spec, Params, InnerBreath);
InnerBreath = FMath::Max(0.f, InnerBreath);
constexpr float Base = 100.f;
constexpr float K = 20.f;
return Base + InnerBreath * K;
}

View File

@@ -0,0 +1,22 @@
// Copyright 2025 PHY. All Rights Reserved.
#pragma once
#include "CoreMinimal.h"
#include "GameplayModMagnitudeCalculation.h"
#include "PHY_MMC_MaxInnerPower.generated.h"
/** MaxInnerPower = Base(100) + InnerBreath * 20 */
UCLASS()
class PHY_API UPHY_MMC_MaxInnerPower : public UGameplayModMagnitudeCalculation
{
GENERATED_BODY()
public:
UPHY_MMC_MaxInnerPower();
virtual float CalculateBaseMagnitude_Implementation(const FGameplayEffectSpec& Spec) const override;
protected:
FGameplayEffectAttributeCaptureDefinition InnerBreathDef;
};

View File

@@ -0,0 +1,31 @@
// Copyright 2025 PHY. All Rights Reserved.
#include "AbilitySystem/MMC/PHY_MMC_MoveSpeed.h"
#include "AbilitySystem/Attributes/PHYAttributeSet.h"
UPHY_MMC_MoveSpeed::UPHY_MMC_MoveSpeed()
{
AgilityDef = FGameplayEffectAttributeCaptureDefinition(
UPHYAttributeSet::GetAgilityAttribute(),
EGameplayEffectAttributeCaptureSource::Target,
true /* snapshot */);
RelevantAttributesToCapture.Add(AgilityDef);
}
float UPHY_MMC_MoveSpeed::CalculateBaseMagnitude_Implementation(const FGameplayEffectSpec& Spec) const
{
FAggregatorEvaluateParameters EvalParams;
EvalParams.SourceTags = Spec.CapturedSourceTags.GetAggregatedTags();
EvalParams.TargetTags = Spec.CapturedTargetTags.GetAggregatedTags();
float Agility = 0.f;
GetCapturedAttributeMagnitude(AgilityDef, Spec, EvalParams, Agility);
Agility = FMath::Max(0.f, Agility);
constexpr float BaseMoveSpeed = 600.f;
constexpr float AgilityToMoveSpeed = 2.f;
return BaseMoveSpeed + Agility * AgilityToMoveSpeed;
}

View File

@@ -0,0 +1,22 @@
// Copyright 2025 PHY. All Rights Reserved.
#pragma once
#include "CoreMinimal.h"
#include "GameplayModMagnitudeCalculation.h"
#include "PHY_MMC_MoveSpeed.generated.h"
/** MoveSpeed = Base(600) + Agility * 2 */
UCLASS()
class PHY_API UPHY_MMC_MoveSpeed : public UGameplayModMagnitudeCalculation
{
GENERATED_BODY()
public:
UPHY_MMC_MoveSpeed();
virtual float CalculateBaseMagnitude_Implementation(const FGameplayEffectSpec& Spec) const override;
protected:
FGameplayEffectAttributeCaptureDefinition AgilityDef;
};

View File

@@ -0,0 +1,31 @@
// Copyright 2025 PHY. All Rights Reserved.
#include "AbilitySystem/MMC/PHY_MMC_ParryChance.h"
#include "AbilitySystem/Attributes/PHYAttributeSet.h"
UPHY_MMC_ParryChance::UPHY_MMC_ParryChance()
{
StrengthDef = FGameplayEffectAttributeCaptureDefinition(
UPHYAttributeSet::GetStrengthAttribute(),
EGameplayEffectAttributeCaptureSource::Target,
true);
RelevantAttributesToCapture.Add(StrengthDef);
}
float UPHY_MMC_ParryChance::CalculateBaseMagnitude_Implementation(const FGameplayEffectSpec& Spec) const
{
FAggregatorEvaluateParameters Params;
Params.SourceTags = Spec.CapturedSourceTags.GetAggregatedTags();
Params.TargetTags = Spec.CapturedTargetTags.GetAggregatedTags();
float Strength = 0.f;
GetCapturedAttributeMagnitude(StrengthDef, Spec, Params, Strength);
Strength = FMath::Max(0.f, Strength);
constexpr float Base = 0.03f;
constexpr float K = 0.001f;
return FMath::Clamp(Base + Strength * K, 0.f, 1.f);
}

View File

@@ -0,0 +1,22 @@
// Copyright 2025 PHY. All Rights Reserved.
#pragma once
#include "CoreMinimal.h"
#include "GameplayModMagnitudeCalculation.h"
#include "PHY_MMC_ParryChance.generated.h"
/** ParryChance = Base(0.03) + Strength * 0.001 */
UCLASS()
class PHY_API UPHY_MMC_ParryChance : public UGameplayModMagnitudeCalculation
{
GENERATED_BODY()
public:
UPHY_MMC_ParryChance();
virtual float CalculateBaseMagnitude_Implementation(const FGameplayEffectSpec& Spec) const override;
protected:
FGameplayEffectAttributeCaptureDefinition StrengthDef;
};

View File

@@ -0,0 +1,31 @@
// Copyright 2025 PHY. All Rights Reserved.
#include "AbilitySystem/MMC/PHY_MMC_PhysicalAttack.h"
#include "AbilitySystem/Attributes/PHYAttributeSet.h"
UPHY_MMC_PhysicalAttack::UPHY_MMC_PhysicalAttack()
{
StrengthDef = FGameplayEffectAttributeCaptureDefinition(
UPHYAttributeSet::GetStrengthAttribute(),
EGameplayEffectAttributeCaptureSource::Target,
true /* snapshot */);
RelevantAttributesToCapture.Add(StrengthDef);
}
float UPHY_MMC_PhysicalAttack::CalculateBaseMagnitude_Implementation(const FGameplayEffectSpec& Spec) const
{
FAggregatorEvaluateParameters EvalParams;
EvalParams.SourceTags = Spec.CapturedSourceTags.GetAggregatedTags();
EvalParams.TargetTags = Spec.CapturedTargetTags.GetAggregatedTags();
float Strength = 0.f;
GetCapturedAttributeMagnitude(StrengthDef, Spec, EvalParams, Strength);
Strength = FMath::Max(0.f, Strength);
constexpr float BaseAttack = 10.f;
constexpr float StrengthToAttack = 2.f;
return BaseAttack + Strength * StrengthToAttack;
}

View File

@@ -0,0 +1,22 @@
// Copyright 2025 PHY. All Rights Reserved.
#pragma once
#include "CoreMinimal.h"
#include "GameplayModMagnitudeCalculation.h"
#include "PHY_MMC_PhysicalAttack.generated.h"
/** PhysicalAttack = Base(10) + Strength * 2 */
UCLASS()
class PHY_API UPHY_MMC_PhysicalAttack : public UGameplayModMagnitudeCalculation
{
GENERATED_BODY()
public:
UPHY_MMC_PhysicalAttack();
virtual float CalculateBaseMagnitude_Implementation(const FGameplayEffectSpec& Spec) const override;
protected:
FGameplayEffectAttributeCaptureDefinition StrengthDef;
};

View File

@@ -0,0 +1,31 @@
// Copyright 2025 PHY. All Rights Reserved.
#include "AbilitySystem/MMC/PHY_MMC_PhysicalDefense.h"
#include "AbilitySystem/Attributes/PHYAttributeSet.h"
UPHY_MMC_PhysicalDefense::UPHY_MMC_PhysicalDefense()
{
ConstitutionDef = FGameplayEffectAttributeCaptureDefinition(
UPHYAttributeSet::GetConstitutionAttribute(),
EGameplayEffectAttributeCaptureSource::Target,
true /* snapshot */);
RelevantAttributesToCapture.Add(ConstitutionDef);
}
float UPHY_MMC_PhysicalDefense::CalculateBaseMagnitude_Implementation(const FGameplayEffectSpec& Spec) const
{
FAggregatorEvaluateParameters EvalParams;
EvalParams.SourceTags = Spec.CapturedSourceTags.GetAggregatedTags();
EvalParams.TargetTags = Spec.CapturedTargetTags.GetAggregatedTags();
float Constitution = 0.f;
GetCapturedAttributeMagnitude(ConstitutionDef, Spec, EvalParams, Constitution);
Constitution = FMath::Max(0.f, Constitution);
constexpr float BaseDefense = 5.f;
constexpr float ConstitutionToDefense = 1.5f;
return BaseDefense + Constitution * ConstitutionToDefense;
}

View File

@@ -0,0 +1,22 @@
// Copyright 2025 PHY. All Rights Reserved.
#pragma once
#include "CoreMinimal.h"
#include "GameplayModMagnitudeCalculation.h"
#include "PHY_MMC_PhysicalDefense.generated.h"
/** PhysicalDefense = Base(5) + Constitution * 1.5 */
UCLASS()
class PHY_API UPHY_MMC_PhysicalDefense : public UGameplayModMagnitudeCalculation
{
GENERATED_BODY()
public:
UPHY_MMC_PhysicalDefense();
virtual float CalculateBaseMagnitude_Implementation(const FGameplayEffectSpec& Spec) const override;
protected:
FGameplayEffectAttributeCaptureDefinition ConstitutionDef;
};

View File

@@ -0,0 +1,31 @@
// Copyright 2025 PHY. All Rights Reserved.
#include "AbilitySystem/MMC/PHY_MMC_Tenacity.h"
#include "AbilitySystem/Attributes/PHYAttributeSet.h"
UPHY_MMC_Tenacity::UPHY_MMC_Tenacity()
{
InnerBreathDef = FGameplayEffectAttributeCaptureDefinition(
UPHYAttributeSet::GetInnerBreathAttribute(),
EGameplayEffectAttributeCaptureSource::Target,
true);
RelevantAttributesToCapture.Add(InnerBreathDef);
}
float UPHY_MMC_Tenacity::CalculateBaseMagnitude_Implementation(const FGameplayEffectSpec& Spec) const
{
FAggregatorEvaluateParameters Params;
Params.SourceTags = Spec.CapturedSourceTags.GetAggregatedTags();
Params.TargetTags = Spec.CapturedTargetTags.GetAggregatedTags();
float InnerBreath = 0.f;
GetCapturedAttributeMagnitude(InnerBreathDef, Spec, Params, InnerBreath);
InnerBreath = FMath::Max(0.f, InnerBreath);
constexpr float Base = 0.f;
constexpr float K = 1.f;
return Base + InnerBreath * K;
}

View File

@@ -0,0 +1,22 @@
// Copyright 2025 PHY. All Rights Reserved.
#pragma once
#include "CoreMinimal.h"
#include "GameplayModMagnitudeCalculation.h"
#include "PHY_MMC_Tenacity.generated.h"
/** Tenacity = Base(0) + InnerBreath * 1 */
UCLASS()
class PHY_API UPHY_MMC_Tenacity : public UGameplayModMagnitudeCalculation
{
GENERATED_BODY()
public:
UPHY_MMC_Tenacity();
virtual float CalculateBaseMagnitude_Implementation(const FGameplayEffectSpec& Spec) const override;
protected:
FGameplayEffectAttributeCaptureDefinition InnerBreathDef;
};

View File

@@ -0,0 +1,17 @@
// Copyright 2025 PHY. All Rights Reserved.
#pragma once
#include "PHYCharacterClass.generated.h"
/** 可根据项目需要扩展更多职业/门派/流派 */
UENUM(BlueprintType)
enum class EPHYCharacterClass : uint8
{
None UMETA(DisplayName = ""),
Warrior UMETA(DisplayName = "武者"),
Tank UMETA(DisplayName = "铁卫"),
Healer UMETA(DisplayName = "医者"),
Assassin UMETA(DisplayName = "刺客"),
};

View File

@@ -0,0 +1,67 @@
// Copyright 2025 PHY. All Rights Reserved.
#pragma once
#include "CoreMinimal.h"
#include "Engine/DataAsset.h"
#include "AbilitySystem/PHYCharacterClass.h"
#include "PHYClassDefaults.generated.h"
USTRUCT(BlueprintType)
struct FPHYPrimaryAttributes
{
GENERATED_BODY()
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly)
float Strength = 10.f;
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly)
float Constitution = 10.f;
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly)
float InnerBreath = 10.f;
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly)
float Agility = 10.f;
};
USTRUCT(BlueprintType)
struct FPHYClassDefaultsRow
{
GENERATED_BODY()
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly)
EPHYCharacterClass Class = EPHYCharacterClass::None;
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly)
FPHYPrimaryAttributes Primary;
};
/** DataAsset holding per-class initial attributes. */
UCLASS(BlueprintType)
class PHY_API UPHYClassDefaults : public UDataAsset
{
GENERATED_BODY()
public:
/** Defaults used when Class not found */
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly)
FPHYPrimaryAttributes FallbackPrimary;
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly)
TArray<FPHYClassDefaultsRow> Classes;
UFUNCTION(BlueprintCallable)
FPHYPrimaryAttributes GetPrimaryForClass(EPHYCharacterClass InClass) const
{
for (const FPHYClassDefaultsRow& Row : Classes)
{
if (Row.Class == InClass)
{
return Row.Primary;
}
}
return FallbackPrimary;
}
};

View File

@@ -0,0 +1,50 @@
//
#include "RetargeterAnim.h"
#include "Components/RetargeterComponent.h"
#include "Retargeter/IKRetargeter.h"
void URetargeterAnim::NativeInitializeAnimation()
{
Super::NativeInitializeAnimation();
// 尝试从拥有者获取RetargeterComponent
if (const AActor* Owner = GetOwningActor())
{
RetargeterComponent = Owner->FindComponentByClass<URetargeterComponent>();
if (RetargeterComponent)
{
// 绑定到retarget改变事件
RetargeterComponent->OnRetargetChanged.AddDynamic(this,&URetargeterAnim::OnRetargetChanged);
// 获取当前的retarget信息
CurrentRetargetInfo = RetargeterComponent->GetRetargetInfo_Implementation();
OnRetargetChanged(CurrentRetargetInfo);
}
}
}
void URetargeterAnim::BeginDestroy()
{
// 解绑代理以防止内存泄漏
if (RetargeterComponent)
{
RetargeterComponent->OnRetargetChanged.RemoveDynamic(this, &URetargeterAnim::OnRetargetChanged);
RetargeterComponent = nullptr;
}
Super::BeginDestroy();
}
void URetargeterAnim::OnRetargetChanged(const FRetargetInfo& NewRetargetInfo)
{
CurrentRetargetInfo = NewRetargetInfo;
if (!CurrentRetargetInfo.Retargeter.IsNull())
{
IkRetargeter = CurrentRetargetInfo.Retargeter.LoadSynchronous();
}
}

View File

@@ -0,0 +1,43 @@
//
#pragma once
#include "CoreMinimal.h"
#include "Animation/AnimInstance.h"
#include "Gameplay/PHYGameInstance.h"
#include "RetargeterAnim.generated.h"
struct FRetargetProfile;
struct FRetargetInfo;
class URetargeterComponent;
/**
* Retargeter动画实例用于处理角色网格的IK retarget逻辑
*/
UCLASS()
class PHY_API URetargeterAnim : public UAnimInstance
{
GENERATED_BODY()
protected:
virtual void NativeInitializeAnimation() override;
virtual void BeginDestroy() override;
private:
/** 当前的retarget信息 */
FRetargetInfo CurrentRetargetInfo;
/** IK Retargeter实例 */
UPROPERTY(BlueprintReadOnly, Category = "PHY|Retargeter", meta = (AllowPrivateAccess = "true"))
UIKRetargeter* IkRetargeter;
/** Retargeter组件的引用 */
UPROPERTY()
TObjectPtr<URetargeterComponent> RetargeterComponent;
/** 当retarget信息改变时调用 */
UFUNCTION()
void OnRetargetChanged(const FRetargetInfo& NewRetargetInfo);
};

View File

@@ -0,0 +1,32 @@
//
#include "AICharacter.h"
// Sets default values
AAICharacter::AAICharacter()
{
// Set this character to call Tick() every frame. You can turn this off to improve performance if you don't need it.
PrimaryActorTick.bCanEverTick = true;
}
// Called when the game starts or when spawned
void AAICharacter::BeginPlay()
{
Super::BeginPlay();
}
// Called every frame
void AAICharacter::Tick(float DeltaTime)
{
Super::Tick(DeltaTime);
}
// Called to bind functionality to input
void AAICharacter::SetupPlayerInputComponent(UInputComponent* PlayerInputComponent)
{
Super::SetupPlayerInputComponent(PlayerInputComponent);
}

View File

@@ -0,0 +1,28 @@
//
#pragma once
#include "CoreMinimal.h"
#include "PHYCharacter.h"
#include "AICharacter.generated.h"
UCLASS()
class PHY_API AAICharacter : public APHYCharacter
{
GENERATED_BODY()
public:
// Sets default values for this character's properties
AAICharacter();
protected:
// Called when the game starts or when spawned
virtual void BeginPlay() override;
public:
// Called every frame
virtual void Tick(float DeltaTime) override;
// Called to bind functionality to input
virtual void SetupPlayerInputComponent(class UInputComponent* PlayerInputComponent) override;
};

View File

@@ -0,0 +1,32 @@
// Fill out your copyright notice in the Description page of Project Settings.
#include "Character/PHYCharacter.h"
#include "GIS_InventorySystemComponent.h"
#include "GMS_CharacterMovementSystemComponent.h"
#include "Components/RetargeterComponent.h"
// Sets default values
APHYCharacter::APHYCharacter()
{
// Set this character to call Tick() every frame. You can turn this off to improve performance if you don't need it.
PrimaryActorTick.bCanEverTick = true;
InventorySystemComponent = CreateDefaultSubobject<UGIS_InventorySystemComponent>(TEXT("InventorySystem"));
MovementSystemComponent = CreateDefaultSubobject<UGMS_CharacterMovementSystemComponent>(TEXT("MovementSystem"));
RetargeterComponent = CreateDefaultSubobject<URetargeterComponent>(TEXT("Retargeter"));
}
// Called when the game starts or when spawned
void APHYCharacter::BeginPlay()
{
Super::BeginPlay();
// 库存初始化应由服务器执行(组件接口也标记了 BlueprintAuthorityOnly
if (HasAuthority() && InventorySystemComponent)
{
InventorySystemComponent->InitializeInventorySystem();
}
}

View File

@@ -0,0 +1,55 @@
// Fill out your copyright notice in the Description page of Project Settings.
#pragma once
#include "CoreMinimal.h"
#include "GameFramework/Character.h"
#include "PHYCharacter.generated.h"
class URetargeterComponent;
class UGIS_InventorySystemComponent;
class UGMS_CharacterMovementSystemComponent;
UCLASS()
class APHYCharacter : public ACharacter
{
GENERATED_BODY()
public:
// Sets default values for this character's properties
APHYCharacter();
UFUNCTION(BlueprintCallable, BlueprintPure, Category = "PHY|Inventory")
UGIS_InventorySystemComponent* GetInventorySystemComponent() const { return InventorySystemComponent; }
UFUNCTION(BlueprintCallable, BlueprintPure, Category = "PHY|Movement")
UGMS_CharacterMovementSystemComponent* GetMovementSystemComponent() const { return MovementSystemComponent; }
UFUNCTION(BlueprintCallable, BlueprintPure, Category = "PHY|Retargeter")
URetargeterComponent* GetRetargeterComponent() const { return RetargeterComponent; }
protected:
// Called when the game starts or when spawned
virtual void BeginPlay() override;
/**
* 角色的库存系统组件(来自 GenericInventorySystem 插件 GIS
* 由服务器初始化,客户端通过复制拿到数据。
*/
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "PHY|Inventory", meta = (AllowPrivateAccess = "true"))
TObjectPtr<UGIS_InventorySystemComponent> InventorySystemComponent;
/**
* 角色的移动系统组件(来自 GenericMovementSystem 插件 GMS
*/
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "PHY|Movement", meta = (AllowPrivateAccess = "true"))
TObjectPtr<UGMS_CharacterMovementSystemComponent> MovementSystemComponent;
/**
* 角色的Retargeter组件用于处理角色网格的retarget逻辑。
*/
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "PHY|Retargeter", meta = (AllowPrivateAccess = "true"))
TObjectPtr<URetargeterComponent> RetargeterComponent;
};

View File

@@ -0,0 +1,230 @@
//
#include "PHYPlayerCharacter.h"
#include "GIPS_InputSystemComponent.h"
#include "InputActionValue.h"
#include "Camera/CameraComponent.h"
#include "GameFramework/SpringArmComponent.h"
#include "Gameplay/Player/PHYPlayerState.h"
#include "Gameplay/PHYGameInstance.h"
#include "GameplayTags/InputTags.h"
#include "Components/RetargeterComponent.h"
#include "AbilitySystemComponent.h"
#include "AbilitySystem/Attributes/PHYAttributeSet.h"
#include "AbilitySystem/Effects/PHYGE_DerivedAttributes.h"
#include "AbilitySystem/Effects/PHYGE_InitPrimary.h"
#include "AbilitySystem/Effects/PHYGE_RegenTick.h"
#include "AbilitySystem/PHYClassDefaults.h"
#include "GameplayTags/InitAttributeTags.h"
#include "GameplayTags/RegenTags.h"
#include "GameFramework/CharacterMovementComponent.h"
#include "UI/HUD/PHYGameHUD.h"
APHYPlayerCharacter::APHYPlayerCharacter()
{
PrimaryActorTick.bCanEverTick = true;
CameraBoom = CreateDefaultSubobject<USpringArmComponent>(TEXT("CameraBoom"));
CameraBoom->SetupAttachment(RootComponent);
CameraBoom->TargetArmLength = 300.f;
CameraBoom->bUsePawnControlRotation = true;
Camera = CreateDefaultSubobject<UCameraComponent>(TEXT("Camera"));
Camera->SetupAttachment(CameraBoom, USpringArmComponent::SocketName);
Camera->bUsePawnControlRotation = false;
InputSystemComponent = CreateDefaultSubobject<UGIPS_InputSystemComponent>(TEXT("InputSystemComponent"));
RetargetMeshComponent = CreateDefaultSubobject<USkeletalMeshComponent>(TEXT("RetargetMeshComponent"));
RetargetMeshComponent->SetupAttachment(GetMesh());
}
FRotator APHYPlayerCharacter::GetRotationInput_Implementation() const
{
if (InputSystemComponent)
{
const FInputActionValue LookInputValue = InputSystemComponent->GetInputActionValueOfInputTag(::InputTags::Tag__Input_Look);
const FVector2D LookInputVector = LookInputValue.Get<FVector2D>();
return FRotator(LookInputVector.Y, LookInputVector.X, 0.f);
}
return FRotator::ZeroRotator;
}
FVector APHYPlayerCharacter::GetMovementInput_Implementation() const
{
if (InputSystemComponent)
{
const FInputActionValue MoveInputValue = InputSystemComponent->GetInputActionValueOfInputTag(::InputTags::Tag__Input_Move);
const FVector2D MoveInputVector = MoveInputValue.Get<FVector2D>();
return FVector(MoveInputVector.X, MoveInputVector.Y, 0.f);
}
return FVector::ZeroVector;
}
void APHYPlayerCharacter::PostInitializeComponents()
{
Super::PostInitializeComponents();
// 将自定义的RetargetMeshComponent设置到RetargeterComponent
if (RetargeterComponent && RetargetMeshComponent)
{
RetargeterComponent->CustomRetargetMeshComponent = RetargetMeshComponent;
}
}
void APHYPlayerCharacter::PossessedBy(AController* NewController)
{
Super::PossessedBy(NewController);
InitializeGAS();
// 初始化hud
InitializeHUD();
if (APHYPlayerState* PS = GetPlayerState<APHYPlayerState>())
{
if (RetargeterComponent && RetargeterComponent->GetDefaultRetargeterTag().IsValid() && !PS->GetReTargeterTag().IsValid())
{
PS->ServerSetReTargeterTag(RetargeterComponent->GetDefaultRetargeterTag());
}
}
}
void APHYPlayerCharacter::InitializeHUD()
{
if (const APlayerController* PC = Cast<APlayerController>(GetController()))
{
if (APHYGameHUD* GameHUD = Cast<APHYGameHUD>(PC->GetHUD()))
{
GameHUD->InitializeHUD();
}
}
}
void APHYPlayerCharacter::OnRep_PlayerState()
{
Super::OnRep_PlayerState();
InitializeGAS();
// 初始化hud
InitializeHUD();
}
void APHYPlayerCharacter::InitializeGAS()
{
APHYPlayerState* PS = GetPlayerState<APHYPlayerState>();
if (!PS) return;
UAbilitySystemComponent* ASC = PS->GetAbilitySystemComponent();
if (!ASC) return;
ASC->InitAbilityActorInfo(PS, this);
// Server applies init effects.
if (HasAuthority())
{
// Init primary values
{
FGameplayEffectContextHandle Ctx = ASC->MakeEffectContext();
Ctx.AddSourceObject(this);
FGameplayEffectSpecHandle Spec = ASC->MakeOutgoingSpec(UPHYGE_InitPrimary::StaticClass(), 1.f, Ctx);
if (Spec.IsValid())
{
// Per-class init values (fallback to defaults if no asset is set)
FPHYPrimaryAttributes Primary;
if (const UPHYGameInstance* GI = GetGameInstance<UPHYGameInstance>())
{
if (const UPHYClassDefaults* Defaults = GI->GetClassDefaults())
{
Primary = Defaults->GetPrimaryForClass(CharacterClass);
}
else
{
Primary = FPHYPrimaryAttributes{};
}
}
else
{
Primary = FPHYPrimaryAttributes{};
}
// Use SetByCaller so the same GE can be reused for every class.
Spec.Data->SetSetByCallerMagnitude(InitAttributeTags::Tag__Data_Init_Primary_Strength, Primary.Strength);
Spec.Data->SetSetByCallerMagnitude(InitAttributeTags::Tag__Data_Init_Primary_Constitution, Primary.Constitution);
Spec.Data->SetSetByCallerMagnitude(InitAttributeTags::Tag__Data_Init_Primary_InnerBreath, Primary.InnerBreath);
Spec.Data->SetSetByCallerMagnitude(InitAttributeTags::Tag__Data_Init_Primary_Agility, Primary.Agility);
ASC->ApplyGameplayEffectSpecToSelf(*Spec.Data.Get());
}
}
// Apply derived attributes (MMC driven)
{
FGameplayEffectContextHandle Ctx = ASC->MakeEffectContext();
Ctx.AddSourceObject(this);
FGameplayEffectSpecHandle Spec = ASC->MakeOutgoingSpec(UPHYGE_DerivedAttributes::StaticClass(), 1.f, Ctx);
if (Spec.IsValid())
{
ASC->ApplyGameplayEffectSpecToSelf(*Spec.Data.Get());
}
}
// Set current Health to MaxHealth once derived is applied.
if (const UPHYAttributeSet* AS = PS->GetAttributeSet())
{
ASC->SetNumericAttributeBase(UPHYAttributeSet::GetHealthAttribute(), AS->GetMaxHealth());
ASC->SetNumericAttributeBase(UPHYAttributeSet::GetInnerPowerAttribute(), AS->GetMaxInnerPower());
}
StopRegen();
if (RegenInterval > 0.f)
{
GetWorldTimerManager().SetTimer(RegenTimerHandle, this, &APHYPlayerCharacter::RegenTick, RegenInterval, true);
}
}
// Push MoveSpeed to CharacterMovement (both sides, will converge via replication)
if (UCharacterMovementComponent* MoveComp = GetCharacterMovement())
{
if (const UPHYAttributeSet* AS = PS->GetAttributeSet())
{
const float DesiredSpeed = AS->GetMoveSpeed();
if (DesiredSpeed > 0.f)
{
MoveComp->MaxWalkSpeed = DesiredSpeed;
}
}
}
}
void APHYPlayerCharacter::StopRegen()
{
if (!HasAuthority()) return;
GetWorldTimerManager().ClearTimer(RegenTimerHandle);
}
void APHYPlayerCharacter::RegenTick()
{
if (!HasAuthority()) return;
APHYPlayerState* PS = GetPlayerState<APHYPlayerState>();
if (!PS) return;
UAbilitySystemComponent* ASC = PS->GetAbilitySystemComponent();
if (!ASC) return;
const UPHYAttributeSet* AS = PS->GetAttributeSet();
if (!AS) return;
// Calculate per-tick amounts (rate is per-second)
const float HealthDelta = AS->GetHealthRegenRate() * RegenInterval;
const float InnerPowerDelta = AS->GetInnerPowerRegenRate() * RegenInterval;
if (HealthDelta <= 0.f && InnerPowerDelta <= 0.f) return;
FGameplayEffectContextHandle Ctx = ASC->MakeEffectContext();
Ctx.AddSourceObject(this);
FGameplayEffectSpecHandle Spec = ASC->MakeOutgoingSpec(UPHYGE_RegenTick::StaticClass(), 1.f, Ctx);
if (!Spec.IsValid()) return;
Spec.Data->SetSetByCallerMagnitude(RegenTags::Tag__Data_Regen_Health, FMath::Max(0.f, HealthDelta));
Spec.Data->SetSetByCallerMagnitude(RegenTags::Tag__Data_Regen_InnerPower, FMath::Max(0.f, InnerPowerDelta));
ASC->ApplyGameplayEffectSpecToSelf(*Spec.Data.Get());
}

View File

@@ -0,0 +1,73 @@
//
#pragma once
#include "CoreMinimal.h"
#include "PHYCharacter.h"
#include "AbilitySystem/PHYCharacterClass.h"
#include "Pawn/UGC_PawnInterface.h"
#include "PHYPlayerCharacter.generated.h"
class UGIPS_InputSystemComponent;
class USpringArmComponent;
class UCameraComponent;
class URetargeterComponent;
UCLASS()
class PHY_API APHYPlayerCharacter : public APHYCharacter,
public IUGC_PawnInterface
{
GENERATED_BODY()
public:
APHYPlayerCharacter();
protected:
/** 职业/门派/流派:决定初始四维 */
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category="PHY|Class")
EPHYCharacterClass CharacterClass = EPHYCharacterClass::Warrior;
/**
* 角色的相机组件
*/
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "PHY|Camera")
TObjectPtr<UCameraComponent> Camera;
/**
* 角色的相机臂组件
*/
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "PHY|Camera")
TObjectPtr<USpringArmComponent> CameraBoom;
/**
* 角色的输入组件
*/
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "PHY|Input")
TObjectPtr<UGIPS_InputSystemComponent> InputSystemComponent;
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "PHY|Retarget")
TObjectPtr<USkeletalMeshComponent> RetargetMeshComponent;
//~ Begin IUGC_PawnInterface
virtual FRotator GetRotationInput_Implementation() const override;
virtual FVector GetMovementInput_Implementation() const override;
//~ End IUGC_PawnInterface
protected:
virtual void PossessedBy(AController* NewController) override;
void InitializeHUD();
virtual void OnRep_PlayerState() override;
/** Server: apply init effects. Both sides: init actor info. */
void InitializeGAS();
protected:
virtual void PostInitializeComponents() override;
/** Regen tick interval (seconds). Server-only. */
UPROPERTY(EditDefaultsOnly, Category="PHY|Regen")
float RegenInterval = 1.0f;
FTimerHandle RegenTimerHandle;
void RegenTick();
void StopRegen();
};

View File

@@ -0,0 +1,128 @@
//
#include "RetargeterComponent.h"
#include "Gameplay/PHYGameInstance.h"
#include "Components/SkeletalMeshComponent.h"
#include "GameFramework/Character.h"
URetargeterComponent::URetargeterComponent()
{
PrimaryComponentTick.bCanEverTick = false;
}
void URetargeterComponent::BeginPlay()
{
Super::BeginPlay();
// 延迟一帧初始化,确保所有组件都已完全初始化
if (bAutoInitializeRetargeter)
{
GetWorld()->GetTimerManager().SetTimerForNextTick([this]()
{
if (IsValid(this))
{
InitializeRetargeter();
}
});
}
}
void URetargeterComponent::InitializeRetargeter()
{
// 先刷新网格设置
RefreshMeshSettings();
// 然后设置retargeter
SetupRetargeter();
}
void URetargeterComponent::SetupRetargeter()
{
// 获取retarget mesh
USkeletalMeshComponent* RetargetMeshComponent = GetRetargetMeshComponent_Implementation();
// 获取动画实例
TSubclassOf<UAnimInstance> RetargeterAnim = GetRetargeterAnimInstance_Implementation();
// 设置retarget mesh的skeletal mesh和动画实例
if (RetargetMeshComponent && RetargeterAnim)
{
RetargetMeshComponent->SetAnimInstanceClass(RetargeterAnim);
RetargetMeshComponent->SetCollisionProfileName(TEXT("CharacterMesh"));
RetargetMeshComponent->SetCollisionResponseToChannel(ECC_GameTraceChannel1, ECollisionResponse::ECR_Block);
}
}
void URetargeterComponent::RefreshMeshSettings()
{
UE_LOG(LogTemp, Log, TEXT("URetargeterComponent::RefreshMeshSettings()"));
// 获取retarget mesh组件
USkeletalMeshComponent* RetargetMeshComponent = GetRetargetMeshComponent_Implementation();
// 获取retarget信息
FRetargetInfo RetargetInfo = GetRetargetInfo_Implementation();
if (RetargetMeshComponent && !RetargetInfo.Mesh.IsNull())
{
USkeletalMesh* RetargetMesh = RetargetInfo.Mesh.LoadSynchronous();
RetargetMeshComponent->SetSkeletalMesh(RetargetMesh);
// 广播retarget改变事件
if (OnRetargetChanged.IsBound())
{
OnRetargetChanged.Broadcast(RetargetInfo);
}
}
}
void URetargeterComponent::ChangeRetargetTag(const FGameplayTag& NewRetargeterTag)
{
DefaultRetargeterTag = NewRetargeterTag;
RefreshMeshSettings();
}
FRetargetInfo URetargeterComponent::GetRetargetInfo_Implementation()
{
FRetargetInfo RetargetInfo;
if (UPHYGameInstance* GameInstance = Cast<UPHYGameInstance>(GetWorld()->GetGameInstance()))
{
if (TOptional<FRetargetInfo> Info = GameInstance->GetRetargetInfo(DefaultRetargeterTag); Info.IsSet())
{
RetargetInfo = Info.GetValue();
}
}
return RetargetInfo;
}
USkeletalMeshComponent* URetargeterComponent::GetMainMeshComponent_Implementation()
{
if (const ACharacter* CharacterOwner = Cast<ACharacter>(GetOwner()))
{
return CharacterOwner->GetMesh();
}
return nullptr;
}
USkeletalMeshComponent* URetargeterComponent::GetRetargetMeshComponent_Implementation()
{
// 如果设置了自定义的Retarget Mesh组件则使用它
if (CustomRetargetMeshComponent)
{
return CustomRetargetMeshComponent;
}
// 否则使用主mesh
return GetMainMeshComponent_Implementation();
}
TSubclassOf<UAnimInstance> URetargeterComponent::GetRetargeterAnimInstance_Implementation()
{
return RetargeterAnimInstance;
}

View File

@@ -0,0 +1,72 @@
//
#pragma once
#include "CoreMinimal.h"
#include "Components/ActorComponent.h"
#include "Interface/Retargetable.h"
#include "Gameplay/PHYGameInstance.h"
#include "RetargeterComponent.generated.h"
// 声明Retarget信息改变的代理
DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnRetargetChanged, const FRetargetInfo&, NewRetargetInfo);
UCLASS(ClassGroup=(Custom), meta=(BlueprintSpawnableComponent))
class PHY_API URetargeterComponent : public UActorComponent, public IRetargetable
{
GENERATED_BODY()
public:
URetargeterComponent();
/** 设置retargeter包括动画实例和碰撞设置 */
UFUNCTION(BlueprintCallable, Category = "PHY|Retargeter")
void SetupRetargeter();
/** 刷新网格设置,应用新的骨骼网格 */
UFUNCTION(BlueprintCallable, Category = "PHY|Retargeter")
void RefreshMeshSettings();
/** 初始化retargeter通常在BeginPlay中调用 */
UFUNCTION(BlueprintCallable, Category = "PHY|Retargeter")
void InitializeRetargeter();
/** 更改Retargeter Tag并刷新retarget设置 */
UFUNCTION(BlueprintCallable, Category = "PHY|Retargeter")
void ChangeRetargetTag(const FGameplayTag& NewRetargeterTag);
/** 获取默认的Retargeter Tag */
UFUNCTION(BlueprintCallable, BlueprintPure, Category = "PHY|Retargeter")
FGameplayTag GetDefaultRetargeterTag() const { return DefaultRetargeterTag; }
protected:
virtual void BeginPlay() override;
public:
// IRetargetable interface implementation
virtual FRetargetInfo GetRetargetInfo_Implementation() override;
virtual USkeletalMeshComponent* GetMainMeshComponent_Implementation() override;
virtual USkeletalMeshComponent* GetRetargetMeshComponent_Implementation() override;
virtual TSubclassOf<UAnimInstance> GetRetargeterAnimInstance_Implementation() override;
/** Retarget信息改变时触发的代理 */
UPROPERTY(BlueprintAssignable, Category = "PHY|Retargeter")
FOnRetargetChanged OnRetargetChanged;
/** 默认的Retargeter Tag用于从GameInstance获取Retargeter信息 */
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "PHY|Retargeter", meta = (Categories = "Retargeter"))
FGameplayTag DefaultRetargeterTag;
/** Retargeter使用的动画实例类 */
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "PHY|Retargeter")
TSubclassOf<UAnimInstance> RetargeterAnimInstance;
/** 是否在BeginPlay时自动初始化Retargeter */
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "PHY|Retargeter")
bool bAutoInitializeRetargeter = true;
/** 自定义的Retarget Mesh组件如果为空则使用角色的主Mesh */
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "PHY|Retargeter")
TObjectPtr<USkeletalMeshComponent> CustomRetargetMeshComponent;
};

View File

@@ -0,0 +1,11 @@
//
#include "PHYGameInstance.h"
TOptional<FRetargetInfo> UPHYGameInstance::GetRetargetInfo(const FGameplayTag& RetargetInfoTag) const
{
const FRetargetInfo* Info = Retargeters.Find(RetargetInfoTag);
if (!Info) return TOptional<FRetargetInfo>();
return TOptional<FRetargetInfo>(*Info);
}

View File

@@ -0,0 +1,42 @@
//
#pragma once
#include "CoreMinimal.h"
#include "GameplayTagContainer.h"
#include "Engine/GameInstance.h"
#include "PHYGameInstance.generated.h"
class UIKRetargeter;
class UPHYClassDefaults;
/**
*
*/
USTRUCT(BlueprintType)
struct FRetargetInfo
{
GENERATED_BODY()
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "PHY|Retargeter")
TSoftObjectPtr<UIKRetargeter> Retargeter;
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "PHY|Retargeter")
TSoftObjectPtr<USkeletalMesh> Mesh;
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "PHY|Retargeter")
bool bMale = false;
};
UCLASS()
class PHY_API UPHYGameInstance : public UGameInstance
{
GENERATED_BODY()
UPROPERTY(EditAnywhere, Category = "Config|Character",meta=(Categories = "Targeter"))
TMap<FGameplayTag,FRetargetInfo> Retargeters;
/** 全局职业/门派默认四维配置 */
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Config|Attributes", meta=(AllowPrivateAccess=true))
TObjectPtr<UPHYClassDefaults> ClassDefaults;
public:
TOptional<FRetargetInfo> GetRetargetInfo(const FGameplayTag& RetargetInfoTag) const;
const UPHYClassDefaults* GetClassDefaults() const { return ClassDefaults; }
};

View File

@@ -0,0 +1,4 @@
//
#include "PHYGameMode.h"

View File

@@ -0,0 +1,16 @@
//
#pragma once
#include "CoreMinimal.h"
#include "GameFramework/GameModeBase.h"
#include "PHYGameMode.generated.h"
/**
*
*/
UCLASS()
class PHY_API APHYGameMode : public AGameModeBase
{
GENERATED_BODY()
};

View File

@@ -0,0 +1,4 @@
//
#include "PHYGameState.h"

View File

@@ -0,0 +1,16 @@
//
#pragma once
#include "CoreMinimal.h"
#include "GameFramework/GameStateBase.h"
#include "PHYGameState.generated.h"
/**
*
*/
UCLASS()
class PHY_API APHYGameState : public AGameStateBase
{
GENERATED_BODY()
};

View File

@@ -0,0 +1,5 @@
#include "Gameplay/Player/PHYPlayerController.h"

View File

@@ -0,0 +1,17 @@
#pragma once
#include "CoreMinimal.h"
#include "GameFramework/PlayerController.h"
#include "PHYPlayerController.generated.h"
/**
*
*/
UCLASS()
class APHYPlayerController : public APlayerController
{
GENERATED_BODY()
};

View File

@@ -0,0 +1,83 @@
// Copyright 2025 PHY. All Rights Reserved.
#include "PHYPlayerState.h"
#include "AbilitySystemComponent.h"
#include "AbilitySystem/Attributes/PHYAttributeSet.h"
#include "Character/PHYPlayerCharacter.h"
#include "Components/RetargeterComponent.h"
#include "Gameplay/PHYGameInstance.h"
#include "Net/UnrealNetwork.h"
#include "Net/Core/PushModel/PushModel.h"
APHYPlayerState::APHYPlayerState()
{
// 创建AbilitySystemComponent
AbilitySystemComponent = CreateDefaultSubobject<UAbilitySystemComponent>(TEXT("AbilitySystemComponent"));
AttributeSet = CreateDefaultSubobject<UPHYAttributeSet>(TEXT("AttributeSet"));
// 设置AbilitySystemComponent的网络复制
AbilitySystemComponent->SetIsReplicated(true);
// 使用Minimal复制模式推荐用于PlayerState
AbilitySystemComponent->SetReplicationMode(EGameplayEffectReplicationMode::Minimal);
}
UAbilitySystemComponent* APHYPlayerState::GetAbilitySystemComponent() const
{
return AbilitySystemComponent;
}
void APHYPlayerState::OnRep_ReTargeterTagChanged(FGameplayTag OldTag)
{
//获取character
if (APHYPlayerCharacter* Character = Cast<APHYPlayerCharacter>(GetPawn()))
{
//获取retargeter组件
if (URetargeterComponent* RetargeterComponent = Character->GetRetargeterComponent())
{
//从GameInstance获取新的RetargetInfo
if (const UPHYGameInstance* GI = GetGameInstance<UPHYGameInstance>())
{
if (const TOptional<FRetargetInfo> NewRetargetInfo = GI->GetRetargetInfo(ReTargeterTag); NewRetargetInfo.IsSet())
{
//应用新的RetargetInfo到组件
RetargeterComponent->ChangeRetargetTag(ReTargeterTag);
}
}
}
}
}
void APHYPlayerState::GetLifetimeReplicatedProps(TArray<class FLifetimeProperty>& OutLifetimeProps) const
{
Super::GetLifetimeReplicatedProps(OutLifetimeProps);
FDoRepLifetimeParams SharedParams;
SharedParams.bIsPushBased = true;
DOREPLIFETIME_WITH_PARAMS_FAST(APHYPlayerState, ReTargeterTag, SharedParams);
}
void APHYPlayerState::SetReTargeterTag(const FGameplayTag& NewReTargeterTag)
{
MARK_PROPERTY_DIRTY_FROM_NAME(APHYPlayerState, ReTargeterTag, this);
if (const ENetMode NetMode = GetNetMode(); NetMode == NM_Standalone || NetMode == NM_ListenServer)
{
OnRep_ReTargeterTagChanged(ReTargeterTag);
}
ReTargeterTag = NewReTargeterTag;
ForceNetUpdate();
}
void APHYPlayerState::ServerSetReTargeterTag_Implementation(const FGameplayTag& NewReTargeterTag)
{
if (!NewReTargeterTag.IsValid() || NewReTargeterTag == ReTargeterTag) return;
if (const UPHYGameInstance* GI = GetGameInstance<UPHYGameInstance>())
{
if (GI->GetRetargetInfo(NewReTargeterTag).IsSet())
{
SetReTargeterTag(NewReTargeterTag);
}
}
}

View File

@@ -0,0 +1,62 @@
// Copyright 2025 PHY. All Rights Reserved.
#pragma once
#include "CoreMinimal.h"
#include "AbilitySystemInterface.h"
#include "GameplayTagContainer.h"
#include "GameFramework/PlayerState.h"
#include "PHYPlayerState.generated.h"
class UAbilitySystemComponent;
class UPHYAttributeSet;
/**
* 玩家状态类包含GAS支持
* 用于管理玩家属性、技能等游戏玩法系统
*/
UCLASS()
class PHY_API APHYPlayerState : public APlayerState, public IAbilitySystemInterface
{
GENERATED_BODY()
public:
APHYPlayerState();
// IAbilitySystemInterface implementation
virtual UAbilitySystemComponent* GetAbilitySystemComponent() const override;
protected:
/** GAS Ability System Component */
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Abilities", Meta = (AllowPrivateAccess = true))
TObjectPtr<UAbilitySystemComponent> AbilitySystemComponent;
/** GAS AttributeSet (Primary/Vitals/Derived). Lives on PlayerState for replication. */
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Abilities", Meta = (AllowPrivateAccess = true))
TObjectPtr<UPHYAttributeSet> AttributeSet;
public:
/** 获取Ability System Component */
UFUNCTION(BlueprintCallable, Category = "Abilities")
UAbilitySystemComponent* GetPHYAbilitySystemComponent() const { return AbilitySystemComponent; }
UFUNCTION(BlueprintCallable, Category = "Abilities")
const UPHYAttributeSet* GetAttributeSet() const { return AttributeSet; }
// 原有的ReTargeterTag相关代码
UPROPERTY(VisibleAnywhere,Category = "Config|Character",ReplicatedUsing=OnRep_ReTargeterTagChanged)
FGameplayTag ReTargeterTag;
UFUNCTION()
void OnRep_ReTargeterTagChanged(FGameplayTag OldTag);
virtual void GetLifetimeReplicatedProps(TArray<class FLifetimeProperty>& OutLifetimeProps) const override;
void SetReTargeterTag(const FGameplayTag& NewReTargeterTag);
public:
UFUNCTION(Server, Reliable,BlueprintCallable)
void ServerSetReTargeterTag(const FGameplayTag& NewReTargeterTag);
FGameplayTag GetReTargeterTag() const { return ReTargeterTag; }
};

View File

@@ -0,0 +1,12 @@
// Copyright 2025 PHY. All Rights Reserved.
#include "GameplayTags/InitAttributeTags.h"
namespace InitAttributeTags
{
UE_DEFINE_GAMEPLAY_TAG_COMMENT(Tag__Data_Init_Primary_Strength, "Data.Init.Primary.Strength", "Init Primary Strength (SetByCaller)");
UE_DEFINE_GAMEPLAY_TAG_COMMENT(Tag__Data_Init_Primary_Constitution, "Data.Init.Primary.Constitution", "Init Primary Constitution (SetByCaller)");
UE_DEFINE_GAMEPLAY_TAG_COMMENT(Tag__Data_Init_Primary_InnerBreath, "Data.Init.Primary.InnerBreath", "Init Primary InnerBreath (SetByCaller)");
UE_DEFINE_GAMEPLAY_TAG_COMMENT(Tag__Data_Init_Primary_Agility, "Data.Init.Primary.Agility", "Init Primary Agility (SetByCaller)");
}

View File

@@ -0,0 +1,16 @@
// Copyright 2025 PHY. All Rights Reserved.
#pragma once
#include "CoreMinimal.h"
#include "NativeGameplayTags.h"
/** GameplayTags used for initializing attributes (SetByCaller). */
namespace InitAttributeTags
{
UE_DECLARE_GAMEPLAY_TAG_EXTERN(Tag__Data_Init_Primary_Strength);
UE_DECLARE_GAMEPLAY_TAG_EXTERN(Tag__Data_Init_Primary_Constitution);
UE_DECLARE_GAMEPLAY_TAG_EXTERN(Tag__Data_Init_Primary_InnerBreath);
UE_DECLARE_GAMEPLAY_TAG_EXTERN(Tag__Data_Init_Primary_Agility);
}

View File

@@ -0,0 +1,12 @@
//
#include "InputTags.h"
namespace InputTags
{
UE_DEFINE_GAMEPLAY_TAG_COMMENT(Tag__Input_Move,"Input.Move","Tag for move input");
UE_DEFINE_GAMEPLAY_TAG_COMMENT(Tag__Input_Look,"Input.Look","Tag for look input");
UE_DEFINE_GAMEPLAY_TAG_COMMENT(Tag__Input_Jump,"Input.Jump","Tag for jump input");
}

View File

@@ -0,0 +1,16 @@
//
#pragma once
#include "CoreMinimal.h"
#include "NativeGameplayTags.h"
/**
*
*/
namespace InputTags
{
UE_DECLARE_GAMEPLAY_TAG_EXTERN(Tag__Input_Move);
UE_DECLARE_GAMEPLAY_TAG_EXTERN(Tag__Input_Look);
UE_DECLARE_GAMEPLAY_TAG_EXTERN(Tag__Input_Jump);
};

View File

@@ -0,0 +1,13 @@
//
#include "MovementTags.h"
namespace MovementTags
{
UE_DEFINE_GAMEPLAY_TAG_COMMENT(Tag__MovementSet_Default,"GMS.MovementSet.Default","Tag for GMS MovementSet Default");
UE_DEFINE_GAMEPLAY_TAG_COMMENT(Tag__MovementSet_ADS,"GMS.MovementSet.ADS","Tag for GMS MovementSet ADS");
UE_DEFINE_GAMEPLAY_TAG_COMMENT(Tag__MovementSet_ADS_Crouched,"GMS.MovementSet.ADS.Crouched","Tag for GMS MovementSet ADS Crouched");
UE_DEFINE_GAMEPLAY_TAG_COMMENT(Tag__MovementSet_Crouched,"GMS.MovementSet.Crouched","Tag for GMS MovementSet Crouched");
}

View File

@@ -0,0 +1,17 @@
//
#pragma once
#include "CoreMinimal.h"
#include "NativeGameplayTags.h"
/**
*
*/
namespace MovementTags
{
UE_DECLARE_GAMEPLAY_TAG_EXTERN(Tag__MovementSet_Default);
UE_DECLARE_GAMEPLAY_TAG_EXTERN(Tag__MovementSet_ADS);
UE_DECLARE_GAMEPLAY_TAG_EXTERN(Tag__MovementSet_ADS_Crouched);
UE_DECLARE_GAMEPLAY_TAG_EXTERN(Tag__MovementSet_Crouched);
};

View File

@@ -0,0 +1,15 @@
//
#include "ReTargeterTags.h"
namespace ReTargeterTags
{
UE_DEFINE_GAMEPLAY_TAG_COMMENT(Tag__Retargeter_F_Warrior,"Retargeter.F.Warrior","Tag for retargeting to the F_Warrior retargeter");
UE_DEFINE_GAMEPLAY_TAG_COMMENT(Tag__Retargeter_F_Warrior_Newbie,"Retargeter.F.Warrior.Newbie","Tag for retargeting to the F_Warrior_Newbee retargeter");
UE_DEFINE_GAMEPLAY_TAG_COMMENT(Tag__Retargeter_F_Warrior_Addvanced,"Retargeter.F.Warrior.Addvanced","Tag for retargeting to the F_Warrior_Addvanced retargeter");
UE_DEFINE_GAMEPLAY_TAG_COMMENT(Tag__Retargeter_M_Warrior,"Retargeter.M.Warrior","Tag for retargeting to the M_Warrior retargeter");
UE_DEFINE_GAMEPLAY_TAG_COMMENT(Tag__Retargeter_M_Warrior_Newbie,"Retargeter.M.Warrior.Newbie","Tag for retargeting to the M_Warrior_Newbee retargeter");
UE_DEFINE_GAMEPLAY_TAG_COMMENT(Tag__Retargeter_M_Warrior_Addvanced,"Retargeter.M.Warrior.Addvanced","Tag for retargeting to the M_Warrior_Addvanced retargeter");
}

View File

@@ -0,0 +1,19 @@
//
#pragma once
#include "CoreMinimal.h"
#include "NativeGameplayTags.h"
/**
*
*/
namespace ReTargeterTags
{
UE_DECLARE_GAMEPLAY_TAG_EXTERN(Tag__Retargeter_F_Warrior);
UE_DECLARE_GAMEPLAY_TAG_EXTERN(Tag__Retargeter_F_Warrior_Newbie);
UE_DECLARE_GAMEPLAY_TAG_EXTERN(Tag__Retargeter_F_Warrior_Addvanced);
UE_DECLARE_GAMEPLAY_TAG_EXTERN(Tag__Retargeter_M_Warrior);
UE_DECLARE_GAMEPLAY_TAG_EXTERN(Tag__Retargeter_M_Warrior_Newbie);
UE_DECLARE_GAMEPLAY_TAG_EXTERN(Tag__Retargeter_M_Warrior_Addvanced);
};

View File

@@ -0,0 +1,10 @@
// Copyright 2025 PHY. All Rights Reserved.
#include "GameplayTags/RegenTags.h"
namespace RegenTags
{
UE_DEFINE_GAMEPLAY_TAG_COMMENT(Tag__Data_Regen_Health, "Data.Regen.Health", "SetByCaller: Health regen per tick");
UE_DEFINE_GAMEPLAY_TAG_COMMENT(Tag__Data_Regen_InnerPower, "Data.Regen.InnerPower", "SetByCaller: InnerPower regen per tick");
}

View File

@@ -0,0 +1,13 @@
// Copyright 2025 PHY. All Rights Reserved.
#pragma once
#include "CoreMinimal.h"
#include "NativeGameplayTags.h"
namespace RegenTags
{
UE_DECLARE_GAMEPLAY_TAG_EXTERN(Tag__Data_Regen_Health);
UE_DECLARE_GAMEPLAY_TAG_EXTERN(Tag__Data_Regen_InnerPower);
}

View File

@@ -0,0 +1,13 @@
//
#include "UITags.h"
namespace UITags
{
UE_DEFINE_GAMEPLAY_TAG(Tag__UI_Layer_Game, "UI.Layer.Game");
UE_DEFINE_GAMEPLAY_TAG(Tag__UI_Layer_GameMenu, "UI.Layer.GameMenu");
UE_DEFINE_GAMEPLAY_TAG(Tag__UI_Layer_Menu, "UI.Layer.Menu");
UE_DEFINE_GAMEPLAY_TAG(Tag__UI_Layer_Modal, "UI.Layer.Modal");
}

View File

@@ -0,0 +1,17 @@
//
#pragma once
#include "CoreMinimal.h"
#include "NativeGameplayTags.h"
/**
*
*/
namespace UITags
{
UE_DECLARE_GAMEPLAY_TAG_EXTERN(Tag__UI_Layer_Game);
UE_DECLARE_GAMEPLAY_TAG_EXTERN(Tag__UI_Layer_GameMenu);
UE_DECLARE_GAMEPLAY_TAG_EXTERN(Tag__UI_Layer_Menu);
UE_DECLARE_GAMEPLAY_TAG_EXTERN(Tag__UI_Layer_Modal);
};

View File

@@ -0,0 +1,17 @@
//
#pragma once
#include "CoreMinimal.h"
#include "GIPS_InputSystemComponent.h"
/**
*
*/
namespace InputHelper
{
static APawn* GetOwnerPawn(const UGIPS_InputSystemComponent* InputSystemComponent)
{
return Cast<APawn>(InputSystemComponent->GetOwner());
}
}

View File

@@ -0,0 +1,26 @@
//
#include "InputProcessor_Look.h"
#include "GameplayTags/InputTags.h"
#include "Input/InputHelper.h"
UInputProcessor_Look::UInputProcessor_Look()
{
InputTags.AddTag(::InputTags::Tag__Input_Look);
TriggerEvents.Empty();
TriggerEvents.AddUnique(ETriggerEvent::Triggered);
}
void UInputProcessor_Look::HandleInputTriggered_Implementation(UGIPS_InputSystemComponent* IC,
const FInputActionInstance& ActionData, FGameplayTag InputTag) const
{
if (InputTag != InputTags::Tag__Input_Look) return;
APawn* OwnerPawn = InputHelper::GetOwnerPawn(IC);
if (!OwnerPawn) return;
const FInputActionValue LookInputValue = IC->GetInputActionValueOfInputTag(InputTag);
const FVector2D LookInputVector = LookInputValue.Get<FVector2D>();
OwnerPawn->AddControllerYawInput(LookInputVector.X);
OwnerPawn->AddControllerPitchInput(LookInputVector.Y);
}

View File

@@ -0,0 +1,20 @@
//
#pragma once
#include "CoreMinimal.h"
#include "GIPS_InputProcessor.h"
#include "InputProcessor_Look.generated.h"
/**
*
*/
UCLASS()
class PHY_API UInputProcessor_Look : public UGIPS_InputProcessor
{
GENERATED_BODY()
public:
UInputProcessor_Look();
virtual void HandleInputTriggered_Implementation(UGIPS_InputSystemComponent* IC, const FInputActionInstance& ActionData, FGameplayTag InputTag) const override;
};

View File

@@ -0,0 +1,32 @@
//
#include "InputProcessor_Move.h"
#include "GameplayTags/InputTags.h"
#include "Input/InputHelper.h"
UInputProcessor_Move::UInputProcessor_Move()
{
InputTags.AddTag(::InputTags::Tag__Input_Move);
TriggerEvents.Empty();
TriggerEvents.AddUnique(ETriggerEvent::Triggered);
}
void UInputProcessor_Move::HandleInputTriggered_Implementation(UGIPS_InputSystemComponent* IC,
const FInputActionInstance& ActionData, FGameplayTag InputTag) const
{
if (InputTag != InputTags::Tag__Input_Move) return;
APawn* OwnerPawn = InputHelper::GetOwnerPawn(IC);
if (!OwnerPawn) return;
const FInputActionValue MoveInputValue = IC->GetInputActionValueOfInputTag(InputTag);
const FVector2D MoveInputVector = MoveInputValue.Get<FVector2D>();
// 计算向右
const FRotator Rotation = OwnerPawn->GetControlRotation();
const FRotator YawRotation(0.f, Rotation.Yaw, 0.f);
const FVector ForwardDirection = FRotationMatrix(YawRotation).GetUnitAxis(EAxis::X);
const FVector RightDirection = FRotationMatrix(YawRotation).GetUnitAxis(EAxis::Y);
// 添加输入
OwnerPawn->AddMovementInput(ForwardDirection, MoveInputVector.Y);
OwnerPawn->AddMovementInput(RightDirection, MoveInputVector.X);
}

View File

@@ -0,0 +1,21 @@
//
#pragma once
#include "CoreMinimal.h"
#include "GIPS_InputProcessor.h"
#include "InputProcessor_Move.generated.h"
/**
*
*/
UCLASS()
class PHY_API UInputProcessor_Move : public UGIPS_InputProcessor
{
GENERATED_BODY()
public:
UInputProcessor_Move();
virtual void HandleInputTriggered_Implementation(UGIPS_InputSystemComponent* IC, const FInputActionInstance& ActionData, FGameplayTag InputTag) const override;
};

View File

@@ -0,0 +1,7 @@
//
#include "Retargetable.h"
// Add default functionality here for any IRetargeterable functions that are not pure virtual.

View File

@@ -0,0 +1,37 @@
//
#pragma once
#include "CoreMinimal.h"
#include "Gameplay/PHYGameInstance.h"
#include "UObject/Interface.h"
#include "Retargetable.generated.h"
// This class does not need to be modified.
UINTERFACE()
class URetargetable : public UInterface
{
GENERATED_BODY()
};
/**
*
*/
class PHY_API IRetargetable
{
GENERATED_BODY()
// Add interface functions to this class. This is the class that will be inherited to implement this interface.
public:
UFUNCTION(BlueprintCallable, BlueprintNativeEvent, Category = "PHY|Retargeter")
FRetargetInfo GetRetargetInfo();
UFUNCTION(BlueprintCallable, BlueprintNativeEvent, Category = "PHY|Retargeter")
USkeletalMeshComponent* GetMainMeshComponent();
UFUNCTION(BlueprintCallable, BlueprintNativeEvent, Category = "PHY|Retargeter")
USkeletalMeshComponent* GetRetargetMeshComponent();
UFUNCTION(BlueprintCallable, BlueprintNativeEvent, Category = "PHY|Retargeter")
TSubclassOf<UAnimInstance> GetRetargeterAnimInstance();
};

View File

@@ -0,0 +1,63 @@
//
#include "PHYGameHUD.h"
#include "GameplayTags/UITags.h"
#include "UI/Actions/GUIS_AsyncAction_PushContentToUILayer.h"
UGUIS_GameUISubsystem* APHYGameHUD::GetUISubsystem() const
{
if (const UGameInstance* GameInstance = GetGameInstance())
{
return GameInstance->GetSubsystem<UGUIS_GameUISubsystem>();
}
return nullptr;
}
void APHYGameHUD::OnBeforePushOverlapWidget(UCommonActivatableWidget* UserWidget)
{
}
void APHYGameHUD::OnAfterPushOverlapWidget(UCommonActivatableWidget* UserWidget)
{
}
void APHYGameHUD::InitializeHUD()
{
// 获取当前的player controller
APlayerController* PC = GetOwningPlayerController();
if (!PC) return;
if (ULocalPlayer* LocalPlayer = PC->GetLocalPlayer())
{
if (UGUIS_GameUISubsystem* UISubsystem = GetUISubsystem())
{
// 创建并注册HUD的UI到本地玩家
UISubsystem->AddPlayer(LocalPlayer);
}
}
if (OverlapWidgetClass.IsNull()) return;
// 将重叠提示控件推送到游戏UI层
if (UGUIS_AsyncAction_PushContentToUILayer * PushAction = UGUIS_AsyncAction_PushContentToUILayer::PushContentToUILayerForPlayer(PC, OverlapWidgetClass, UITags::Tag__UI_Layer_Game))
{
PushAction->BeforePush.AddDynamic(this, &APHYGameHUD::OnBeforePushOverlapWidget);
PushAction->AfterPush.AddDynamic(this, &APHYGameHUD::OnAfterPushOverlapWidget);
PushAction->Activate();
}
}
void APHYGameHUD::EndPlay(const EEndPlayReason::Type EndPlayReason)
{
if (const APlayerController* PC = GetOwningPlayerController())
{
if (ULocalPlayer* LocalPlayer = PC->GetLocalPlayer())
{
if (UGUIS_GameUISubsystem* UISubsystem = GetUISubsystem())
{
// 创建并注册HUD的UI到本地玩家
UISubsystem->RemovePlayer(LocalPlayer);
}
}
}
Super::EndPlay(EndPlayReason);
}

View File

@@ -0,0 +1,38 @@
//
#pragma once
#include "CoreMinimal.h"
#include "GameFramework/HUD.h"
#include "UI/GUIS_GameUISubsystem.h"
#include "PHYGameHUD.generated.h"
class UCommonActivatableWidget;
/**
*
*/
UCLASS()
class PHY_API APHYGameHUD : public AHUD
{
GENERATED_BODY()
// 获取GUIS_GameSubsystem
UGUIS_GameUISubsystem* GetUISubsystem() const;
public:
void InitializeHUD();
protected:
virtual void EndPlay(const EEndPlayReason::Type EndPlayReason) override;
UPROPERTY(EditDefaultsOnly, Category="PHY|UI")
TSoftClassPtr<UCommonActivatableWidget> OverlapWidgetClass;
private:
UFUNCTION()
void OnBeforePushOverlapWidget(UCommonActivatableWidget* UserWidget);
UFUNCTION()
void OnAfterPushOverlapWidget(UCommonActivatableWidget* UserWidget);
};

View File

@@ -0,0 +1,4 @@
//
#include "Menu_Overlap.h"

View File

@@ -0,0 +1,16 @@
//
#pragma once
#include "CoreMinimal.h"
#include "UI/GUIS_ActivatableWidget.h"
#include "Menu_Overlap.generated.h"
/**
*
*/
UCLASS()
class PHY_API UMenu_Overlap : public UGUIS_ActivatableWidget
{
GENERATED_BODY()
};

View File

@@ -0,0 +1,37 @@
// Copyright 2026 PHY. All Rights Reserved.
#include "UI/PHYGameUILayout.h"
#include "GameplayTags/UITags.h"
#include "Widgets/CommonActivatableWidgetContainer.h"
#include UE_INLINE_GENERATED_CPP_BY_NAME(PHYGameUILayout)
UPHYGameUILayout::UPHYGameUILayout(const FObjectInitializer& ObjectInitializer)
: Super(ObjectInitializer)
{
}
void UPHYGameUILayout::NativeOnInitialized()
{
Super::NativeOnInitialized();
// Register available layers. Widget Blueprint can bind any subset.
if (Layer_Game)
{
RegisterLayer(UITags::Tag__UI_Layer_Game, Layer_Game);
}
if (Layer_GameMenu)
{
RegisterLayer(UITags::Tag__UI_Layer_GameMenu, Layer_GameMenu);
}
if (Layer_Menu)
{
RegisterLayer(UITags::Tag__UI_Layer_Menu, Layer_Menu);
}
if (Layer_Modal)
{
RegisterLayer(UITags::Tag__UI_Layer_Modal, Layer_Modal);
}
}

View File

@@ -0,0 +1,47 @@
// Copyright 2026 PHY. All Rights Reserved.
#pragma once
#include "CoreMinimal.h"
#include "UI/GUIS_GameUILayout.h"
#include "PHYGameUILayout.generated.h"
class UCommonActivatableWidgetStack;
class UCommonActivatableWidgetContainerBase;
/**
* Root UI Layout for a single local player.
*
* In BP (Widget Blueprint derived from this), you should:
* - Create several UCommonActivatableWidgetStack (or other ContainerBase) widgets
* - Bind them to the properties below
* - In PreConstruct/Construct, call RegisterLayer for each stack with tags from UITags
*/
UCLASS(Abstract, BlueprintType)
class PHY_API UPHYGameUILayout : public UGUIS_GameUILayout
{
GENERATED_BODY()
public:
UPHYGameUILayout(const FObjectInitializer& ObjectInitializer);
protected:
virtual void NativeOnInitialized() override;
/** Game HUD layer stack. Tag: UI.Layer.Game */
UPROPERTY(meta=(BindWidgetOptional), BlueprintReadOnly)
TObjectPtr<UCommonActivatableWidgetContainerBase> Layer_Game = nullptr;
/** In-game menu layer stack. Tag: UI.Layer.GameMenu */
UPROPERTY(meta=(BindWidgetOptional), BlueprintReadOnly)
TObjectPtr<UCommonActivatableWidgetContainerBase> Layer_GameMenu = nullptr;
/** Main menu layer stack. Tag: UI.Layer.Menu */
UPROPERTY(meta=(BindWidgetOptional), BlueprintReadOnly)
TObjectPtr<UCommonActivatableWidgetContainerBase> Layer_Menu = nullptr;
/** Modal layer stack. Tag: UI.Layer.Modal */
UPROPERTY(meta=(BindWidgetOptional), BlueprintReadOnly)
TObjectPtr<UCommonActivatableWidgetContainerBase> Layer_Modal = nullptr;
};

View File

@@ -0,0 +1,20 @@
// Copyright 2026 PHY. All Rights Reserved.
#pragma once
#include "CoreMinimal.h"
#include "UI/GUIS_GameUIPolicy.h"
#include "PHYGameUIPolicy.generated.h"
/**
* Game-specific UI policy.
*
* This class mainly exists so we can set up a default LayoutClass in a Blueprint child,
* and keep all UI wiring inside the game module.
*/
UCLASS(BlueprintType)
class PHY_API UPHYGameUIPolicy : public UGUIS_GameUIPolicy
{
GENERATED_BODY()
};

View File

@@ -0,0 +1,40 @@
//
#include "Synty_IconFrame.h"
#include "Components/Image.h"
#include "Kismet/KismetMaterialLibrary.h"
void USynty_IconFrame::UpdateIconImage(TSoftObjectPtr<UTexture2D> NewTexture)
{
IconTexture = NewTexture;
if (IconImage && !IconTexture.IsNull())
{
if (IconMaterialInstance)
{
IconMaterialInstance->SetTextureParameterValue(FName("InputTexture"), IconTexture.LoadSynchronous());
FSlateBrush Brush;
Brush.SetResourceObject(IconMaterialInstance);
Brush.DrawAs = ESlateBrushDrawType::Box;
Brush.SetImageSize(IconSize);
Brush.Margin = IconPadding;
IconImage->SetBrush(Brush);
}
else
{
IconImage->SetBrushFromTexture(IconTexture.LoadSynchronous());
}
}
}
void USynty_IconFrame::NativePreConstruct()
{
Super::NativePreConstruct();
if (IconMaterialInterface)
{
IconMaterialInstance = UKismetMaterialLibrary::CreateDynamicMaterialInstance(this, IconMaterialInterface);
}
UpdateIconImage(IconTexture);
}

View File

@@ -0,0 +1,34 @@
//
#pragma once
#include "CoreMinimal.h"
#include "Blueprint/UserWidget.h"
#include "Synty_IconFrame.generated.h"
/**
* Synty风格的图标框架Widget
*/
UCLASS()
class PHY_API USynty_IconFrame : public UUserWidget
{
GENERATED_BODY()
UPROPERTY(meta=(BindWidget))
class UImage* IconImage;
UPROPERTY(EditAnywhere,Category="Synty|Config")
TSoftObjectPtr<UTexture2D> IconTexture;
UPROPERTY(EditAnywhere,Category="Synty|Config")
FVector2D IconSize;
UPROPERTY(EditAnywhere,Category="Synty|Config")
FMargin IconPadding;
UPROPERTY(EditAnywhere,Category="Synty|Config")
UMaterialInterface* IconMaterialInterface;
UPROPERTY()
UMaterialInstanceDynamic* IconMaterialInstance;
public:
void UpdateIconImage(TSoftObjectPtr<UTexture2D> NewTexture);
protected:
virtual void NativePreConstruct() override;
};

View File

@@ -0,0 +1,21 @@
// Copyright Epic Games, Inc. All Rights Reserved.
using UnrealBuildTool;
using System.Collections.Generic;
public class PHYEditorTarget : TargetRules
{
public PHYEditorTarget( TargetInfo Target) : base(Target)
{
Type = TargetType.Editor;
DefaultBuildSettings = BuildSettingsVersion.V6;
IncludeOrderVersion = EngineIncludeOrderVersion.Unreal5_7;
ExtraModuleNames.Add("PHY");
RegisterModulesCreatedByRider();
}
private void RegisterModulesCreatedByRider()
{
ExtraModuleNames.AddRange(new string[] { "PHYInventory" });
}
}

View File

@@ -0,0 +1,34 @@
using UnrealBuildTool;
public class PHYInventory : ModuleRules
{
public PHYInventory(ReadOnlyTargetRules Target) : base(Target)
{
PCHUsage = ModuleRules.PCHUsageMode.UseExplicitOrSharedPCHs;
PublicDependencyModuleNames.AddRange(
new string[]
{
"Core",
"GenericUISystem",
"GenericInputSystem",
"CommonUI",
"UMG",
"Slate",
"SlateCore",
"GameplayTags",
"GenericInventorySystem"
}
);
PrivateDependencyModuleNames.AddRange(
new string[]
{
"CoreUObject",
"Engine",
"Slate",
"SlateCore"
}
);
}
}

View File

@@ -0,0 +1,7 @@
//
#include "ItemFilterInterface.h"
// Add default functionality here for any IItemFilterInterface functions that are not pure virtual.

View File

@@ -0,0 +1,17 @@
#include "PHYInventory.h"
#define LOCTEXT_NAMESPACE "FPHYInventoryModule"
void FPHYInventoryModule::StartupModule()
{
}
void FPHYInventoryModule::ShutdownModule()
{
}
#undef LOCTEXT_NAMESPACE
IMPLEMENT_MODULE(FPHYInventoryModule, PHYInventory)

View File

@@ -0,0 +1,35 @@
//
#include "UI/ItemStacks/ItemData.h"
#include "UI/ItemStacks/ItemStackContainer.h"
UItemStackContainer* UItemData::GetContainer() const
{
return OwningItemStackContainer.Get();
}
void UItemData::SetContainer(UItemStackContainer* InContainer)
{
OwningItemStackContainer = InContainer;
}
void UItemData::Reset()
{
ItemInfo = FGIS_ItemInfo();
}
bool UItemData::IsValidItem() const
{
return ItemInfo.IsValid();
}
int32 UItemData::GetItemSlotIndex() const
{
if (OwningItemStackContainer.IsValid())
{
return OwningItemStackContainer.Get()->FindItemSlotIndex(this);
}
return INDEX_NONE;
}

View File

@@ -0,0 +1,66 @@
//
#include "UI/ItemStacks/ItemDataDragDropOperation.h"
#include "UI/ItemStacks/ItemData.h"
#include "UI/ItemStacks/ItemStackContainer.h"
UItemStackContainer* UItemDataDragDropOperation::GetTargetItemStackView() const
{
return TargetItemStackView.Get();
}
void UItemDataDragDropOperation::SetTargetItemStackView(UItemStackContainer* InItemStackContainer)
{
TargetItemStackView = TWeakObjectPtr<UItemStackContainer>(InItemStackContainer);
}
UItemData* UItemDataDragDropOperation::GetTargetItemData() const
{
return TargetItemData.Get();
}
void UItemDataDragDropOperation::SetTargetItemData(UItemData* InItemData)
{
TargetItemData = TWeakObjectPtr<UItemData>(InItemData);
}
int32 UItemDataDragDropOperation::GetTargetItemIndex() const
{
return TargetItemIndex;
}
void UItemDataDragDropOperation::SetTargetItemIndex(int32 InTargetItemIndex)
{
TargetItemIndex = InTargetItemIndex;
}
UItemStackContainer* UItemDataDragDropOperation::GetSourceItemStackView() const
{
return SourceItemStackView.Get();
}
void UItemDataDragDropOperation::SetSourceItemStackView(UItemStackContainer* InItemStackContainer)
{
SourceItemStackView = TWeakObjectPtr<UItemStackContainer>(InItemStackContainer);
}
UItemData* UItemDataDragDropOperation::GetSourceItemData() const
{
return SourceItemData.Get();
}
void UItemDataDragDropOperation::SetSourceItemData(UItemData* InItemData)
{
SourceItemData = TWeakObjectPtr<UItemData>(InItemData);
}
int32 UItemDataDragDropOperation::GwtSourceItemIndex() const
{
return SourceItemIndex;
}
void UItemDataDragDropOperation::SetSourceItemIndex(int32 InSourceItemIndex)
{
SourceItemIndex = InSourceItemIndex;
}

View File

@@ -0,0 +1,38 @@
//
#include "UI/ItemStacks/ItemDataDraggingWidget.h"
#include "CommonTextBlock.h"
#include "Components/Image.h"
#include "Components/SizeBox.h"
void UItemDataDraggingWidget::NativePreConstruct()
{
Super::NativePreConstruct();
if (MainSizeBox)
{
MainSizeBox->SetHeightOverride(Height);
MainSizeBox->SetWidthOverride(Width);
}
SetAmount(ItemAmount);
SetIcon(IconTexture);
}
void UItemDataDraggingWidget::SetAmount(const int32 InAmount)
{
ItemAmount = InAmount;
if (AmountText)
{
AmountText->SetText(FText::AsNumber(ItemAmount));
}
}
void UItemDataDraggingWidget::SetIcon(UTexture2D* InIconTexture)
{
IconTexture = InIconTexture;
if (IconImage)
{
IconImage->SetBrushFromTexture(IconTexture);
}
}

View File

@@ -0,0 +1,493 @@
//
#include "UI/ItemStacks/ItemStackContainer.h"
#include "CommonTabListWidgetBase.h"
#include "GIS_InventoryFunctionLibrary.h"
#include "GIS_InventorySystemComponent.h"
#include "GIS_ItemCollection.h"
#include "GIS_ItemSlotCollection.h"
#include "ItemFilterInterface.h"
#include "Async/GIS_AsyncAction_WaitInventorySystem.h"
#include "Components/ListView.h"
void UItemStackContainer::SetOwningActor_Implementation(AActor* NewOwningActor)
{
OwningActor = NewOwningActor;
}
void UItemStackContainer::OnDeactivated_Implementation()
{
IGUIS_UserWidgetInterface::OnDeactivated_Implementation();
}
void UItemStackContainer::HandleTabSelected(const FName TabId)
{
ApplyFilter(TabId);
}
void UItemStackContainer::HandleInventorySystemInitialized()
{
WaitInventorySystem = nullptr;
if (OwningActor.IsValid())
{
InventorySystemComponent = UGIS_InventorySystemComponent::GetInventorySystemComponent(OwningActor.Get());
if (InventorySystemComponent.IsValid())
{
// 订阅库存系统的消息
InventorySystemComponent.Get()->OnInventoryStackUpdate.AddDynamic(this,&UItemStackContainer::HandleInventoryStackChanged);
}
if (FilterTabsReference)
{
FilterTabsReference.Get()->OnTabSelected.AddDynamic(this,&UItemStackContainer::HandleTabSelected);
}
// 创建默认的item data槽位
CreateDefaultItemDataSlots();
// 同步数据
SyncAll();
}
}
void UItemStackContainer::CreateDefaultItemDataSlots()
{
if (!InventorySystemComponent.IsValid()) return;
UGIS_ItemCollection* OutCollection;
// 从库存系统中获取集合并添加到item data中
if (InventorySystemComponent->FindTypedCollectionByTag(CollectionToTrackTag,UGIS_ItemSlotCollection::StaticClass(),OutCollection))
{
if (UGIS_ItemSlotCollection* SlotCollection = Cast<UGIS_ItemSlotCollection>(OutCollection))
{
const TArray<FGIS_ItemSlotDefinition>& Definitions = SlotCollection->GetMyDefinition()->GetSlotDefinitions();
for (int i = 0; i < Definitions.Num(); ++i)
{
SetOrAddItemInfoToItemDataArray(FGIS_ItemInfo(),i);
}
for (int i = 0; i < ItemDataArray.Num(); ++i)
{
ItemDataArray[i]->SetItemSlotDefinition(Definitions[i]);
}
return;
}
}
// 如果没有找到slot collection则创建默认数量的item data槽位
if (bCreateDefaultItemDataSlots)
{
for (int i = 0; i < DefaultItemDataSlotCount; ++i)
{
SetOrAddItemInfoToItemDataArray(FGIS_ItemInfo(),i);
}
}
}
void UItemStackContainer::OnActivated_Implementation()
{
// 注册list view events
RegisterListViewEvents();
OwningActor = Execute_GetOwningActor(this);
if (OwningActor.IsValid())
{
WaitInventorySystem = UGIS_AsyncAction_WaitInventorySystemInitialized::WaitInventorySystemInitialized(this,OwningActor.Get());
if (WaitInventorySystem)
{
WaitInventorySystem->OnCompleted.AddDynamic(this,&UItemStackContainer::HandleInventorySystemInitialized);
}
}
}
AActor* UItemStackContainer::GetOwningActor_Implementation()
{
if (!OwningActor.IsValid())
{
if (GetOwningPlayerPawn())
{
OwningActor = GetOwningPlayerPawn();
}
else
{
OwningActor = GetOwningPlayer();
}
}
return OwningActor.Get();
}
void UItemStackContainer::AddFilterObject(UObject* InFilterObject)
{
if (InFilterObject && InFilterObject->Implements<UItemFilterInterface>())
{
ItemFilterObjects.AddUnique(InFilterObject);
}
}
void UItemStackContainer::RemoveFilterObject(UObject* InFilterObject)
{
if (InFilterObject && InFilterObject->Implements<UItemFilterInterface>())
{
ItemFilterObjects.Remove(InFilterObject);
}
}
void UItemStackContainer::SetSelectedIndexForNewItem(const int32 NewSelectedIndex)
{
SelectedIndexForNewItem = NewSelectedIndex;
}
bool UItemStackContainer::SwapItemDataSlots(const int32 IndexA, const int32 IndexB)
{
if (!CanMoveItem(IndexA,this,IndexB)) return false;
if (ItemDataArray.IsValidIndex(IndexA) && ItemDataArray.IsValidIndex(IndexB))
{
// 缓存A的数据
const UItemData* TempItemData = ItemDataArray[IndexA];
const UItemData* TargetItemData = ItemDataArray[IndexB];
// 缓存其中的数据
TOptional<FGIS_ItemInfo> TempItemInfo = TempItemData ? TOptional<FGIS_ItemInfo>(TempItemData->GetItemInfo()) : TOptional<FGIS_ItemInfo>();
// 交换数据
if (TOptional<FGIS_ItemInfo> TempTargetItemInfo = TargetItemData ? TOptional<FGIS_ItemInfo>(TargetItemData->GetItemInfo()) : TOptional<FGIS_ItemInfo>(); TempTargetItemInfo.IsSet())
{
AssignItemInfoToItemData(TempTargetItemInfo.GetValue(), IndexA);
}
if (TempItemInfo.IsSet())
{
AssignItemInfoToItemData(TempItemInfo.GetValue(), IndexB);
}
LastDropIndex = IndexB;
// 同步list view
SyncItemDataToListView();
return true;
}
return false;
}
bool UItemStackContainer::SwapItemDataToOtherContainer(const int32 SourceIndex, UItemStackContainer* TargetContainer,
const int32 TargetIndex)
{
if (!TargetContainer) return false;
if (!CanMoveItem(SourceIndex,TargetContainer,TargetIndex)) return false;
if (ItemDataArray.IsValidIndex(SourceIndex) && TargetContainer->GetItemDataArray().IsValidIndex(TargetIndex))
{
// 缓存A的数据
const UItemData* TempItemData = ItemDataArray[SourceIndex];
const UItemData* TargetItemData = TargetContainer->GetItemDataArray()[TargetIndex];
// 缓存其中的数据
TOptional<FGIS_ItemInfo> TempItemInfo = TempItemData ? TOptional<FGIS_ItemInfo>(TempItemData->GetItemInfo()) : TOptional<FGIS_ItemInfo>();
if (TOptional<FGIS_ItemInfo> TempTargetItemInfo = TargetItemData ? TOptional<FGIS_ItemInfo>(TargetItemData->GetItemInfo()) : TOptional<FGIS_ItemInfo>(); TempTargetItemInfo.IsSet())
{
AssignItemInfoToItemData(TempTargetItemInfo.GetValue(), SourceIndex);
}
if (TempItemInfo.IsSet())
{
TargetContainer->AssignItemInfoToItemData(TempItemInfo.GetValue(), TargetIndex);
}
TargetContainer->LastDropIndex = TargetIndex;
// 同步list view
SyncItemDataToListView();
TargetContainer->SyncItemDataToListView();
return true;
}
return false;
}
bool UItemStackContainer::CanMoveItem(const int32 IndexA,UItemStackContainer* TargetContainer,const int32 IndexB)
{
return true;
}
int32 UItemStackContainer::FindItemSlotIndex(const UItemData* InItemData) const
{
if (!InItemData) return INDEX_NONE;
return ItemDataArray.IndexOfByKey(InItemData);
}
void UItemStackContainer::AssignItemInfoToItemData(const FGIS_ItemInfo& InInfo, const int32 SlotIndex)
{
if (ItemDataArray.IsValidIndex(SlotIndex))
{
if (UItemData* ItemData = ItemDataArray[SlotIndex])
{
ItemData->SetItemInfo(InInfo);
}
}
}
void UItemStackContainer::UnassignItemInfoToItemData(const FGIS_ItemInfo& InInfo)
{
if (const int32 Index = FindItemIndexFromDataByStackID(InInfo.StackId); Index != INDEX_NONE)
{
const FGIS_ItemInfo EmptyInfo;
AssignItemInfoToItemData(EmptyInfo, Index);
}
}
int32 UItemStackContainer::FindItemIndexFromDataByStackID(const FGuid& StackID) const
{
for (int32 Index = 0; Index < ItemDataArray.Num(); ++Index)
{
if (const UItemData* ItemData = ItemDataArray[Index])
{
if (ItemData->GetItemInfo().StackId == StackID)
{
return Index;
}
}
}
return INDEX_NONE;
}
TArray<FGIS_ItemInfo> UItemStackContainer::GetItemInfoArrayFromInventorySystem()
{
TArray<FGIS_ItemInfo> LocalItemInfos;
if (InventorySystemComponent.IsValid())
{
if (CollectionToTrackTag.IsValid())
{
if (const UGIS_ItemCollection* ItemCollection = InventorySystemComponent.Get()->GetCollectionByTag(CollectionToTrackTag))
{
LocalItemInfos = ItemCollection->GetAllItemInfos();
}
}
}
if (LocalItemInfos.IsEmpty()) return LocalItemInfos;
// 通过配置的name和query 过滤
if (ItemFilterMap.Num() > 0 && ItemFilterName.IsValid())
{
if (const FGameplayTagQuery* Query = ItemFilterMap.Find(ItemFilterName))
{
LocalItemInfos = UGIS_InventoryFunctionLibrary::FilterItemInfosByTagQuery(LocalItemInfos,*Query);
}
}
// 通过Object过滤
if (ItemFilterObjects.Num() > 0)
{
for (UObject* FilterObject : ItemFilterObjects)
{
if (FilterObject && FilterObject->Implements<UItemFilterInterface>())
{
LocalItemInfos = IItemFilterInterface::Execute_FilterItemInfo(FilterObject,LocalItemInfos);
}
}
}
return LocalItemInfos;
}
void UItemStackContainer::ResetItemDataArray()
{
for (UItemData* ItemData : ItemDataArray)
{
ItemData->Reset();
}
}
void UItemStackContainer::SetOrAddItemInfoToItemDataArray(const FGIS_ItemInfo& InInfo, const int32 SlotIndex)
{
if (SlotIndex < 0)
{
return;
}
// 确保数组大小足够
if (ItemDataArray.Num() <= SlotIndex)
{
ItemDataArray.SetNum(SlotIndex + 1);
}
// 确保该槽位有对象
UItemData* ItemDataRef = ItemDataArray[SlotIndex];
if (!ItemDataRef)
{
ItemDataRef = NewObject<UItemData>(this);
}
ItemDataRef->SetItemInfo(InInfo);
}
void UItemStackContainer::SyncItemInfoToItemDataArray(const TArray<FGIS_ItemInfo>& InItemInfos)
{
if (InventorySystemComponent.IsValid() && CollectionToTrackTag.IsValid())
{
// 获取集合,如果是slotted collection则根据slot来赋值
if (const UGIS_ItemSlotCollection* SlotCollection = Cast<UGIS_ItemSlotCollection>(InventorySystemComponent->GetCollectionByTag(CollectionToTrackTag)))
{
for (int i = 0; i < InItemInfos.Num(); ++i)
{
const FGIS_ItemInfo& Info = InItemInfos[i];
const int32 SlotIndex = SlotCollection->GetItemSlotIndex(Info.Item);
SetOrAddItemInfoToItemDataArray(Info, SlotIndex == INDEX_NONE ? i : SlotIndex);
}
}
else
{
// 如果是普通的集合,则直接按顺序赋值
for (int i = 0; i < InItemInfos.Num(); ++i)
{
SetOrAddItemInfoToItemDataArray(InItemInfos[i], i);
}
}
}
}
void UItemStackContainer::SyncItemDataToListView()
{
if (ItemListView == nullptr) return;
ItemListView->ClearSelection();
ItemListView->ClearListItems();
ItemListView->SetListItems(ItemDataArray);
ItemListView->RegenerateAllEntries();
// 恢复上次拖拽的选中状态
if (ItemDataArray.IsValidIndex(LastDropIndex) && LastDropIndex != INDEX_NONE)
{
ItemListView->SetSelectedItem(ItemDataArray[LastDropIndex]);
LastDropIndex = INDEX_NONE;
} else
{
// 默认选中第一个
if (ItemDataArray.IsValidIndex(0) && ItemDataArray[0]->IsValidItem())
{
ItemListView->SetSelectedItem(ItemDataArray[0]);
}
}
}
int32 UItemStackContainer::FindSuitableItemDataSlotForNewItem()
{
if (SelectedIndexForNewItem == INDEX_NONE)
{
for (int i = 0; i < ItemDataArray.Num(); ++i)
{
if (const UItemData* ItemData = ItemDataArray[i])
{
if (!ItemData->IsValidItem())
{
return i;
}
}
}
return ItemDataArray.Num();
}
const int32 ReturnIndex = SelectedIndexForNewItem;
SelectedIndexForNewItem = INDEX_NONE;
return ReturnIndex;
}
void UItemStackContainer::SyncAll()
{
ResetItemDataArray();
SyncItemInfoToItemDataArray(GetItemInfoArrayFromInventorySystem());
SyncItemDataToListView();
}
void UItemStackContainer::ApplyFilter(const FName& InFilterName)
{
if (InFilterName.IsNone()) return;
if (ItemFilterMap.Contains(InFilterName))
{
ItemFilterName = InFilterName;
SyncAll();
}
}
void UItemStackContainer::HandleItemSelectionChanged(UObject* Object) const
{
if (UItemData* SelectedItemData = Cast<UItemData>(Object))
{
OnItemSelectionChanged.Broadcast(SelectedItemData,true);
return;
}
OnItemSelectionChanged.Broadcast(nullptr,false);
}
void UItemStackContainer::HandleItemHoveredChanged(UObject* Object, bool bIsHovered) const
{
if (UItemData* HoveredItemData = Cast<UItemData>(Object))
{
OnItemHoveredChanged.Broadcast(HoveredItemData,bIsHovered);
}
}
void UItemStackContainer::HandleItemClicked(UObject* Object) const
{
if (UItemData* ClickedItemData = Cast<UItemData>(Object))
{
OnItemClicked.Broadcast(ClickedItemData);
}
}
void UItemStackContainer::HandleListViewEntryWidgetGenerated(UUserWidget& UserWidget) const
{
OnItemEntryGenerated.Broadcast(&UserWidget);
}
void UItemStackContainer::HandleItemDoubleClicked(UObject* Object) const
{
if (UItemData* ClickedItemData = Cast<UItemData>(Object))
{
OnItemDoubleClicked.Broadcast(ClickedItemData);
}
}
void UItemStackContainer::RegisterListViewEvents()
{
if (ItemListView)
{
// 注册事件
ItemSelectionChangedHandle = ItemListView->OnItemSelectionChanged().AddUObject(this, &UItemStackContainer::HandleItemSelectionChanged);
ItemHoveredChangedHandle = ItemListView->OnItemIsHoveredChanged().AddUObject(this, &UItemStackContainer::HandleItemHoveredChanged);
ItemClickedHandle = ItemListView->OnItemClicked().AddUObject(this, &UItemStackContainer::HandleItemClicked);
ListViewEntryWidgetGeneratedHandle = ItemListView->OnEntryWidgetGenerated().AddUObject(this, &UItemStackContainer::HandleListViewEntryWidgetGenerated);
ItemDoubleClickedHandle = ItemListView->OnItemDoubleClicked().AddUObject(this, &UItemStackContainer::HandleItemDoubleClicked);
}
}
void UItemStackContainer::UnregisterListViewEvents() const
{
if (ItemListView)
{
// 注销事件
ItemListView->OnItemSelectionChanged().Remove(ItemSelectionChangedHandle);
ItemListView->OnItemIsHoveredChanged().Remove(ItemHoveredChangedHandle);
ItemListView->OnItemClicked().Remove(ItemClickedHandle);
ItemListView->OnEntryWidgetGenerated().Remove(ListViewEntryWidgetGeneratedHandle);
ItemListView->OnItemDoubleClicked().Remove(ItemDoubleClickedHandle);
}
}
void UItemStackContainer::HandleInventoryStackChanged(const FGIS_InventoryStackUpdateMessage& Message)
{
if (const UGIS_InventorySystemComponent* Inventory = Message.Inventory)
{
// 仅处理关联的库存系统组件的消息
if (!InventorySystemComponent.IsValid() && Inventory != InventorySystemComponent.Get()) return;
// 获取集合
if (UGIS_ItemCollection* ItemCollection = Inventory->GetCollectionById(Message.CollectionId))
{
// 仅处理配置的集合tag的消息
if (ItemCollection->GetCollectionTag() != CollectionToTrackTag) return;
FGIS_ItemInfo ChangedItemInfo = FGIS_ItemInfo(Message.Instance,Message.NewCount,ItemCollection,Message.StackId);
switch (Message.ChangeType) {
case EGIS_ItemStackChangeType::WasAdded:
// 添加
{
const int32 SlotIndex = FindSuitableItemDataSlotForNewItem();
SetOrAddItemInfoToItemDataArray(ChangedItemInfo, SlotIndex);
}
break;
case EGIS_ItemStackChangeType::WasRemoved:
// 移除
{
ChangedItemInfo.Amount = Message.NewCount - Message.Delta;
UnassignItemInfoToItemData(ChangedItemInfo);
}
break;
case EGIS_ItemStackChangeType::Changed:
// 修改
{
if (const int32 ChangedIndex = FindItemIndexFromDataByStackID(Message.StackId); ChangedIndex != INDEX_NONE)
{
SetOrAddItemInfoToItemDataArray(ChangedItemInfo, ChangedIndex);
}
}
break;
}
SyncItemDataToListView();
}
}
}

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