Add PHY class framework

This commit is contained in:
2026-04-26 01:06:46 +08:00
parent d392c103ca
commit ddb5f599f9
13 changed files with 353 additions and 0 deletions

View File

@@ -0,0 +1,13 @@
[/Script/PHY.PHYClassSettings]
DefaultPlayerClassTag=(TagName="Class.Saber")
DefaultAIClassTag=(TagName="Class.Saber")
bApplyPlayerClassMeshToPlayers=True
bApplyPlayerClassMeshToAI=False
!PlayerClassMeshes=ClearArray
+PlayerClassMeshes=(ClassTag=(TagName="Class.Saber"),PlayerMesh=None)
+PlayerClassMeshes=(ClassTag=(TagName="Class.Lancer"),PlayerMesh=None)
+PlayerClassMeshes=(ClassTag=(TagName="Class.Archer"),PlayerMesh=None)
+PlayerClassMeshes=(ClassTag=(TagName="Class.Rider"),PlayerMesh=None)
+PlayerClassMeshes=(ClassTag=(TagName="Class.Caster"),PlayerMesh=None)
+PlayerClassMeshes=(ClassTag=(TagName="Class.Assassin"),PlayerMesh=None)
+PlayerClassMeshes=(ClassTag=(TagName="Class.Berserker"),PlayerMesh=None)

View File

@@ -4,6 +4,8 @@
#include UE_INLINE_GENERATED_CPP_BY_NAME(PHYAICharacter)
#include "Class/PHYClassComponent.h"
#include "Class/PHYClassSettings.h"
#include "GGA_AbilitySystemComponent.h"
APHYAICharacter::APHYAICharacter(const FObjectInitializer& ObjectInitializer)
@@ -12,6 +14,9 @@ APHYAICharacter::APHYAICharacter(const FObjectInitializer& ObjectInitializer)
AbilitySystemComponent = CreateDefaultSubobject<UGGA_AbilitySystemComponent>(TEXT("AbilitySystemComponent"));
AbilitySystemComponent->SetIsReplicated(true);
AbilitySystemComponent->SetReplicationMode(EGameplayEffectReplicationMode::Mixed);
ClassComponent = CreateDefaultSubobject<UPHYClassComponent>(TEXT("ClassComponent"));
ClassComponent->SetClassTag(GetDefault<UPHYClassSettings>()->DefaultAIClassTag);
}
void APHYAICharacter::BeginPlay()
@@ -34,4 +39,9 @@ void APHYAICharacter::InitializeAIAbilitySystem()
{
InitializeAbilitySystem(AbilitySystemComponent, this);
}
if (ClassComponent)
{
ClassComponent->ApplyConfiguredPlayerMeshIfAllowed(this);
}
}

View File

@@ -6,6 +6,7 @@
#include "Camera/CameraComponent.h"
#include "Camera/PHYUGCSpringArmComponent.h"
#include "Class/PHYClassComponent.h"
#include "GameFramework/SpringArmComponent.h"
#include "GIPS_InputSystemComponent.h"
#include "Player/PHYPlayerState.h"
@@ -51,4 +52,9 @@ void APHYPlayerCharacter::InitializeAbilitySystemFromPlayerState()
{
InitializeAbilitySystem(PlayerASC, PHYPlayerState);
}
if (UPHYClassComponent* ClassComponent = PHYPlayerState->GetClassComponent())
{
ClassComponent->ApplyConfiguredPlayerMeshIfAllowed(this);
}
}

View File

@@ -0,0 +1,112 @@
// Copyright Epic Games, Inc. All Rights Reserved.
#include "Class/PHYClassComponent.h"
#include UE_INLINE_GENERATED_CPP_BY_NAME(PHYClassComponent)
#include "Characters/PHYAICharacter.h"
#include "Characters/PHYPlayerCharacter.h"
#include "Class/PHYClassSettings.h"
#include "Components/SkeletalMeshComponent.h"
#include "GameFramework/Character.h"
#include "Net/UnrealNetwork.h"
#include "PHYGameplayTags.h"
#include "Player/PHYPlayerState.h"
UPHYClassComponent::UPHYClassComponent(const FObjectInitializer& ObjectInitializer)
: Super(ObjectInitializer)
{
SetIsReplicatedByDefault(true);
CurrentClassTag = PHYGameplayTags::Class_Saber;
}
void UPHYClassComponent::GetLifetimeReplicatedProps(TArray<FLifetimeProperty>& OutLifetimeProps) const
{
Super::GetLifetimeReplicatedProps(OutLifetimeProps);
DOREPLIFETIME(ThisClass, CurrentClassTag);
}
void UPHYClassComponent::SetClassTag(const FGameplayTag NewClassTag)
{
if (!NewClassTag.IsValid())
{
return;
}
if (AActor* OwnerActor = GetOwner())
{
if (!OwnerActor->HasAuthority())
{
return;
}
}
CurrentClassTag = NewClassTag;
}
bool UPHYClassComponent::IsPlayerClassMeshAllowed() const
{
const UPHYClassSettings* ClassSettings = GetDefault<UPHYClassSettings>();
if (!ClassSettings)
{
return false;
}
const AActor* OwnerActor = GetOwner();
if (OwnerActor && OwnerActor->IsA<APHYAICharacter>())
{
return ClassSettings->bApplyPlayerClassMeshToAI;
}
if (OwnerActor && (OwnerActor->IsA<APHYPlayerState>() || OwnerActor->IsA<APHYPlayerCharacter>()))
{
return ClassSettings->bApplyPlayerClassMeshToPlayers;
}
return ClassSettings->bApplyPlayerClassMeshToPlayers;
}
USkeletalMesh* UPHYClassComponent::GetConfiguredPlayerMesh() const
{
const UPHYClassSettings* ClassSettings = GetDefault<UPHYClassSettings>();
if (!ClassSettings || !CurrentClassTag.IsValid())
{
return nullptr;
}
for (const FPHYPlayerClassMeshConfig& MeshConfig : ClassSettings->PlayerClassMeshes)
{
if (MeshConfig.ClassTag == CurrentClassTag)
{
return MeshConfig.PlayerMesh.LoadSynchronous();
}
}
return nullptr;
}
bool UPHYClassComponent::ApplyConfiguredPlayerMeshIfAllowed(AActor* AvatarActor) const
{
if (!IsPlayerClassMeshAllowed())
{
return false;
}
AActor* MeshOwner = AvatarActor ? AvatarActor : GetOwner();
ACharacter* Character = Cast<ACharacter>(MeshOwner);
if (!Character)
{
return false;
}
USkeletalMesh* PlayerMesh = GetConfiguredPlayerMesh();
if (!PlayerMesh)
{
return false;
}
Character->GetMesh()->SetSkeletalMesh(PlayerMesh);
return true;
}

View File

@@ -0,0 +1,24 @@
// Copyright Epic Games, Inc. All Rights Reserved.
#include "Class/PHYClassSettings.h"
#include UE_INLINE_GENERATED_CPP_BY_NAME(PHYClassSettings)
#include "PHYGameplayTags.h"
UPHYClassSettings::UPHYClassSettings()
{
DefaultPlayerClassTag = PHYGameplayTags::Class_Saber;
DefaultAIClassTag = PHYGameplayTags::Class_Saber;
PlayerClassMeshes =
{
{ PHYGameplayTags::Class_Saber, nullptr },
{ PHYGameplayTags::Class_Lancer, nullptr },
{ PHYGameplayTags::Class_Archer, nullptr },
{ PHYGameplayTags::Class_Rider, nullptr },
{ PHYGameplayTags::Class_Caster, nullptr },
{ PHYGameplayTags::Class_Assassin, nullptr },
{ PHYGameplayTags::Class_Berserker, nullptr }
};
}

View File

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

View File

@@ -4,6 +4,8 @@
#include UE_INLINE_GENERATED_CPP_BY_NAME(PHYPlayerState)
#include "Class/PHYClassComponent.h"
#include "Class/PHYClassSettings.h"
#include "GGA_AbilitySystemComponent.h"
APHYPlayerState::APHYPlayerState(const FObjectInitializer& ObjectInitializer)
@@ -14,6 +16,9 @@ APHYPlayerState::APHYPlayerState(const FObjectInitializer& ObjectInitializer)
AbilitySystemComponent = CreateDefaultSubobject<UGGA_AbilitySystemComponent>(TEXT("AbilitySystemComponent"));
AbilitySystemComponent->SetIsReplicated(true);
AbilitySystemComponent->SetReplicationMode(EGameplayEffectReplicationMode::Mixed);
ClassComponent = CreateDefaultSubobject<UPHYClassComponent>(TEXT("ClassComponent"));
ClassComponent->SetClassTag(GetDefault<UPHYClassSettings>()->DefaultPlayerClassTag);
}
UAbilitySystemComponent* APHYPlayerState::GetAbilitySystemComponent() const

View File

@@ -7,6 +7,7 @@
#include "PHYAICharacter.generated.h"
class UGGA_AbilitySystemComponent;
class UPHYClassComponent;
/**
* @brief PHY AI 角色。
@@ -32,6 +33,10 @@ public:
UFUNCTION(BlueprintCallable, BlueprintPure, Category="PHY|AI")
UGGA_AbilitySystemComponent* GetAIAbilitySystemComponent() const { return AbilitySystemComponent; }
/** @brief 获取 AI 职业分类组件。 */
UFUNCTION(BlueprintCallable, BlueprintPure, Category="PHY|Class")
UPHYClassComponent* GetClassComponent() const { return ClassComponent; }
protected:
/** @brief 初始化 AI 自身作为 Owner 和 Avatar 的 ASC。 */
virtual void InitializeAIAbilitySystem();
@@ -39,4 +44,8 @@ protected:
/** @brief AI 自身 ASC。 */
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category="PHY|AI")
TObjectPtr<UGGA_AbilitySystemComponent> AbilitySystemComponent;
/** @brief AI 职业分类组件,默认不应用玩家职业 Mesh。 */
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category="PHY|Class")
TObjectPtr<UPHYClassComponent> ClassComponent;
};

View File

@@ -0,0 +1,53 @@
// Copyright Epic Games, Inc. All Rights Reserved.
#pragma once
#include "CoreMinimal.h"
#include "Components/ActorComponent.h"
#include "GameplayTagContainer.h"
#include "PHYClassComponent.generated.h"
class USkeletalMesh;
/**
* @brief PHY 职业组件。
*
* 组件复制当前职业分类;玩家可按配置把职业 Mesh 应用到 AvatarAI 默认只保留分类不套用玩家 Mesh。
*/
UCLASS(BlueprintType, Blueprintable, meta=(BlueprintSpawnableComponent))
class PHY_API UPHYClassComponent : public UActorComponent
{
GENERATED_BODY()
public:
/** @brief 构造可复制的职业组件。 */
UPHYClassComponent(const FObjectInitializer& ObjectInitializer = FObjectInitializer::Get());
/** @brief 注册需要网络复制的职业字段。 */
virtual void GetLifetimeReplicatedProps(TArray<FLifetimeProperty>& OutLifetimeProps) const override;
/** @brief 设置当前职业 Tag通常由服务器调用。 */
UFUNCTION(BlueprintCallable, Category="PHY|Class")
void SetClassTag(FGameplayTag NewClassTag);
/** @brief 获取当前职业 Tag。 */
UFUNCTION(BlueprintCallable, BlueprintPure, Category="PHY|Class")
FGameplayTag GetClassTag() const { return CurrentClassTag; }
/** @brief 查询当前所有者是否允许应用玩家职业 Mesh。 */
UFUNCTION(BlueprintCallable, BlueprintPure, Category="PHY|Class")
bool IsPlayerClassMeshAllowed() const;
/** @brief 按当前职业读取配置的玩家 Mesh未配置时返回空。 */
UFUNCTION(BlueprintCallable, Category="PHY|Class")
USkeletalMesh* GetConfiguredPlayerMesh() const;
/** @brief 如果策略允许,则把当前职业配置的玩家 Mesh 应用到指定 Avatar 或组件所有者。 */
UFUNCTION(BlueprintCallable, Category="PHY|Class")
bool ApplyConfiguredPlayerMeshIfAllowed(AActor* AvatarActor = nullptr) const;
protected:
/** @brief 当前职业 Tag由服务器复制给客户端。 */
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Replicated, Category="PHY|Class")
FGameplayTag CurrentClassTag;
};

View File

@@ -0,0 +1,64 @@
// Copyright Epic Games, Inc. All Rights Reserved.
#pragma once
#include "CoreMinimal.h"
#include "GameplayTagContainer.h"
#include "UObject/Object.h"
#include "UObject/SoftObjectPtr.h"
#include "PHYClassSettings.generated.h"
class USkeletalMesh;
/**
* @brief 玩家职业 Mesh 配置项。
*/
USTRUCT(BlueprintType)
struct PHY_API FPHYPlayerClassMeshConfig
{
GENERATED_BODY()
public:
/** @brief 职业 Tag必须来自 Class.* 原生 Gameplay Tag。 */
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category="PHY|Class")
FGameplayTag ClassTag;
/** @brief 玩家使用该职业时应用的专属 Skeletal Mesh 软引用。 */
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category="PHY|Class")
TSoftObjectPtr<USkeletalMesh> PlayerMesh;
};
/**
* @brief PHY 职业系统配置。
*
* 职业分类同时服务玩家和 AI玩家 Mesh 仅表达玩家外观AI 默认不复用这些 Mesh。
*/
UCLASS(Config=PHYClass, DefaultConfig)
class PHY_API UPHYClassSettings : public UObject
{
GENERATED_BODY()
public:
/** @brief 构造默认七职阶配置。 */
UPHYClassSettings();
/** @brief 玩家默认职业。 */
UPROPERTY(Config, EditDefaultsOnly, Category="PHY|Class")
FGameplayTag DefaultPlayerClassTag;
/** @brief AI 默认职业分类。 */
UPROPERTY(Config, EditDefaultsOnly, Category="PHY|Class")
FGameplayTag DefaultAIClassTag;
/** @brief 玩家职业到玩家专属 Mesh 的配置表。 */
UPROPERTY(Config, EditDefaultsOnly, Category="PHY|Class")
TArray<FPHYPlayerClassMeshConfig> PlayerClassMeshes;
/** @brief 是否允许玩家 Avatar 应用玩家职业 Mesh。 */
UPROPERTY(Config, EditDefaultsOnly, Category="PHY|Class")
bool bApplyPlayerClassMeshToPlayers = true;
/** @brief 是否允许 AI 应用玩家职业 Mesh默认关闭以保持 AI 外观独立。 */
UPROPERTY(Config, EditDefaultsOnly, Category="PHY|Class")
bool bApplyPlayerClassMeshToAI = false;
};

View File

@@ -0,0 +1,32 @@
// Copyright Epic Games, Inc. All Rights Reserved.
#pragma once
#include "NativeGameplayTags.h"
/**
* @brief PHY 职业相关原生 Gameplay Tag。
*/
namespace PHYGameplayTags
{
/** @brief Saber 职阶。 */
UE_DECLARE_GAMEPLAY_TAG_EXTERN(Class_Saber);
/** @brief Lancer 职阶。 */
UE_DECLARE_GAMEPLAY_TAG_EXTERN(Class_Lancer);
/** @brief Archer 职阶。 */
UE_DECLARE_GAMEPLAY_TAG_EXTERN(Class_Archer);
/** @brief Rider 职阶。 */
UE_DECLARE_GAMEPLAY_TAG_EXTERN(Class_Rider);
/** @brief Caster 职阶。 */
UE_DECLARE_GAMEPLAY_TAG_EXTERN(Class_Caster);
/** @brief Assassin 职阶。 */
UE_DECLARE_GAMEPLAY_TAG_EXTERN(Class_Assassin);
/** @brief Berserker 职阶。 */
UE_DECLARE_GAMEPLAY_TAG_EXTERN(Class_Berserker);
}

View File

@@ -3,6 +3,7 @@
#pragma once
#include "GameplayTags/PHYGameplayTags_Ability.h"
#include "GameplayTags/PHYGameplayTags_Class.h"
#include "GameplayTags/PHYGameplayTags_Effect.h"
#include "GameplayTags/PHYGameplayTags_Event.h"
#include "GameplayTags/PHYGameplayTags_Input.h"

View File

@@ -8,6 +8,7 @@
#include "PHYPlayerState.generated.h"
class UGGA_AbilitySystemComponent;
class UPHYClassComponent;
/**
* @brief PHY 玩家状态。
@@ -30,8 +31,16 @@ public:
UFUNCTION(BlueprintCallable, BlueprintPure, Category="PHY|PlayerState")
UGGA_AbilitySystemComponent* GetGGAAbilitySystemComponent() const { return AbilitySystemComponent; }
/** @brief 获取玩家持久职业组件。 */
UFUNCTION(BlueprintCallable, BlueprintPure, Category="PHY|Class")
UPHYClassComponent* GetClassComponent() const { return ClassComponent; }
protected:
/** @brief 玩家持久 ASC。 */
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category="PHY|PlayerState")
TObjectPtr<UGGA_AbilitySystemComponent> AbilitySystemComponent;
/** @brief 玩家持久职业组件,职业不保存在 PlayerCharacter 上。 */
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category="PHY|Class")
TObjectPtr<UPHYClassComponent> ClassComponent;
};