From 3499cf363c7623c6a6d97aabccf5759174f7201a Mon Sep 17 00:00:00 2001 From: cit110 <840418418@qq.com> Date: Sun, 26 Apr 2026 17:28:37 +0800 Subject: [PATCH] Use blueprint retarget pose graph --- Config/DefaultPHYClass.ini | 2 +- .../Retargeters/ABP_Retargeter.uasset | 4 +- .../PHYCharacterMeshBridgeComponent.cpp | 5 +- .../Animation/PHYRetargetPoseAnimInstance.cpp | 96 +------------------ .../PHY/Private/Class/PHYClassComponent.cpp | 22 ++++- .../Animation/PHYRetargetPoseAnimInstance.h | 60 +++--------- Source/PHY/Public/Class/PHYClassComponent.h | 7 +- Source/PHY/Public/Class/PHYClassSettings.h | 5 + 8 files changed, 54 insertions(+), 147 deletions(-) diff --git a/Config/DefaultPHYClass.ini b/Config/DefaultPHYClass.ini index 71c97fa..01713dd 100644 --- a/Config/DefaultPHYClass.ini +++ b/Config/DefaultPHYClass.ini @@ -4,7 +4,7 @@ DefaultAIClassTag=(TagName="Class.Saber") bApplyPlayerClassMeshToPlayers=True bApplyPlayerClassMeshToAI=False !PlayerClassMeshes=ClearArray -+PlayerClassMeshes=(ClassTag=(TagName="Class.Saber"),PlayerMesh="/Game/AGame/Character/Ida/SK_MMO_ADVANCED_F_01_AGame.SK_MMO_ADVANCED_F_01_AGame",PlayerRetargeter="/Game/AGame/Animation/Retargeters/RTG_Manny_To_Ida.RTG_Manny_To_Ida") ++PlayerClassMeshes=(ClassTag=(TagName="Class.Saber"),PlayerMesh="/Game/AGame/Character/Ida/SK_MMO_ADVANCED_F_01_AGame.SK_MMO_ADVANCED_F_01_AGame",PlayerDisplayAnimClass="/Game/AGame/Animation/Retargeters/ABP_Retargeter.ABP_Retargeter_C",PlayerRetargeter="/Game/AGame/Animation/Retargeters/RTG_Manny_To_Ida.RTG_Manny_To_Ida") +PlayerClassMeshes=(ClassTag=(TagName="Class.Lancer"),PlayerMesh=None,PlayerRetargeter=) +PlayerClassMeshes=(ClassTag=(TagName="Class.Archer"),PlayerMesh=None,PlayerRetargeter=) +PlayerClassMeshes=(ClassTag=(TagName="Class.Rider"),PlayerMesh=None,PlayerRetargeter=) diff --git a/Content/AGame/Animation/Retargeters/ABP_Retargeter.uasset b/Content/AGame/Animation/Retargeters/ABP_Retargeter.uasset index ed89dd1..dbcb2ed 100644 --- a/Content/AGame/Animation/Retargeters/ABP_Retargeter.uasset +++ b/Content/AGame/Animation/Retargeters/ABP_Retargeter.uasset @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:4d37f7b9f2d9a212c28d1650da26a2787c63e54acc6991a24e94d198de286822 -size 28351 +oid sha256:433940385a1a810da8a29bfd4f3f6bc811d29b6684444e40da9b4e317ad6399c +size 51382 diff --git a/Source/PHY/Private/Animation/PHYCharacterMeshBridgeComponent.cpp b/Source/PHY/Private/Animation/PHYCharacterMeshBridgeComponent.cpp index 1c6ae00..4e03126 100644 --- a/Source/PHY/Private/Animation/PHYCharacterMeshBridgeComponent.cpp +++ b/Source/PHY/Private/Animation/PHYCharacterMeshBridgeComponent.cpp @@ -252,7 +252,10 @@ TSubclassOf UPHYCharacterMeshBridgeComponent::ResolveRetargetDisp if (UClass* CurrentAnimClass = DisplayMeshComponent->GetAnimClass()) { - return CurrentAnimClass; + if (CurrentAnimClass != UPHYRetargetPoseAnimInstance::StaticClass()) + { + return CurrentAnimClass; + } } const UPHYAnimationSettings* Settings = GetDefault(); diff --git a/Source/PHY/Private/Animation/PHYRetargetPoseAnimInstance.cpp b/Source/PHY/Private/Animation/PHYRetargetPoseAnimInstance.cpp index 6b02b17..c44e979 100644 --- a/Source/PHY/Private/Animation/PHYRetargetPoseAnimInstance.cpp +++ b/Source/PHY/Private/Animation/PHYRetargetPoseAnimInstance.cpp @@ -7,84 +7,7 @@ #include "Components/SkeletalMeshComponent.h" #include "Retargeter/IKRetargeter.h" -FPHYRetargetPoseAnimInstanceProxy::FPHYRetargetPoseAnimInstanceProxy(UAnimInstance* InAnimInstance, FAnimNode_RetargetPoseFromMesh* InRetargetNode) - : FAnimInstanceProxy(InAnimInstance) - , RetargetNode(InRetargetNode) -{ -} - -void FPHYRetargetPoseAnimInstanceProxy::Initialize(UAnimInstance* InAnimInstance) -{ - FAnimInstanceProxy::Initialize(InAnimInstance); - - if (RetargetNode) - { - FAnimationInitializeContext InitContext(this); - RetargetNode->Initialize_AnyThread(InitContext); - } -} - -void FPHYRetargetPoseAnimInstanceProxy::PreUpdate(UAnimInstance* InAnimInstance, float DeltaSeconds) -{ - FAnimInstanceProxy::PreUpdate(InAnimInstance, DeltaSeconds); - - if (RetargetNode && RetargetNode->HasPreUpdate()) - { - RetargetNode->PreUpdate(InAnimInstance); - } -} - -void FPHYRetargetPoseAnimInstanceProxy::CacheBones() -{ - if (bBoneCachesInvalidated && RetargetNode) - { - FAnimationCacheBonesContext Context(this); - RetargetNode->CacheBones_AnyThread(Context); - bBoneCachesInvalidated = false; - } -} - -bool FPHYRetargetPoseAnimInstanceProxy::Evaluate(FPoseContext& Output) -{ - if (!RetargetNode) - { - Output.ResetToRefPose(); - return true; - } - - RetargetNode->Evaluate_AnyThread(Output); - return true; -} - -void FPHYRetargetPoseAnimInstanceProxy::UpdateAnimationNode(const FAnimationUpdateContext& InContext) -{ - UpdateCounter.Increment(); - - if (RetargetNode) - { - RetargetNode->Update_AnyThread(InContext); - } -} - -void FPHYRetargetPoseAnimInstanceProxy::ConfigureRetargetPose(UIKRetargeter* InRetargeter, USkeletalMeshComponent* InSourceMeshComponent) -{ - if (!RetargetNode) - { - return; - } - - RetargetNode->IKRetargeterAsset = InRetargeter; - RetargetNode->RetargetFrom = ERetargetSourceMode::CustomSkeletalMeshComponent; - RetargetNode->SourceMeshComponent = InSourceMeshComponent; - RetargetNode->bSuppressWarnings = false; - - if (FIKRetargetProcessor* Processor = RetargetNode->GetRetargetProcessor()) - { - Processor->SetNeedsInitialized(); - } -} - -bool UPHYRetargetPoseAnimInstance::ConfigureRetargetPoseFromMesh(USkeletalMeshComponent* InSourceMeshComponent, FSoftObjectPath InRetargeterPath) +bool UPHYRetargetPoseAnimInstance::ConfigureRetargetPoseFromMesh(USkeletalMeshComponent* InSourceMeshComponent, const FSoftObjectPath InRetargeterPath) { RetargetSourceMeshComponent = InSourceMeshComponent; RetargeterPath = InRetargeterPath; @@ -110,21 +33,6 @@ bool UPHYRetargetPoseAnimInstance::ConfigureRetargetPoseFromMesh(USkeletalMeshCo return false; } - FPHYRetargetPoseAnimInstanceProxy& Proxy = GetProxyOnGameThread(); - Proxy.ConfigureRetargetPose(LoadedRetargeter, RetargetSourceMeshComponent); + // AnimGraph 中的 Retarget Pose From Mesh 节点通过属性绑定读取 LoadedRetargeter。 return true; } - -void UPHYRetargetPoseAnimInstance::NativeInitializeAnimation() -{ - Super::NativeInitializeAnimation(); - - FPHYRetargetPoseAnimInstanceProxy& Proxy = GetProxyOnGameThread(); - Proxy.Initialize(this); - Proxy.ConfigureRetargetPose(LoadedRetargeter, RetargetSourceMeshComponent); -} - -FAnimInstanceProxy* UPHYRetargetPoseAnimInstance::CreateAnimInstanceProxy() -{ - return new FPHYRetargetPoseAnimInstanceProxy(this, &RetargetNode); -} diff --git a/Source/PHY/Private/Class/PHYClassComponent.cpp b/Source/PHY/Private/Class/PHYClassComponent.cpp index deaedb2..9241f41 100644 --- a/Source/PHY/Private/Class/PHYClassComponent.cpp +++ b/Source/PHY/Private/Class/PHYClassComponent.cpp @@ -5,6 +5,7 @@ #include UE_INLINE_GENERATED_CPP_BY_NAME(PHYClassComponent) #include "AbilitySystemComponent.h" +#include "Animation/AnimInstance.h" #include "AbilitySystem/Attributes/PHYAttributeCalculationLibrary.h" #include "AbilitySystem/Attributes/PHYCombatAttributeSet.h" #include "AbilitySystem/Attributes/PHYCoreAttributeSet.h" @@ -92,6 +93,25 @@ USkeletalMesh* UPHYClassComponent::GetConfiguredPlayerMesh() const return nullptr; } +TSubclassOf UPHYClassComponent::GetConfiguredPlayerDisplayAnimClass() const +{ + const UPHYClassSettings* ClassSettings = GetDefault(); + if (!ClassSettings || !CurrentClassTag.IsValid()) + { + return nullptr; + } + + for (const FPHYPlayerClassMeshConfig& MeshConfig : ClassSettings->PlayerClassMeshes) + { + if (MeshConfig.ClassTag == CurrentClassTag) + { + return MeshConfig.PlayerDisplayAnimClass.LoadSynchronous(); + } + } + + return nullptr; +} + FSoftObjectPath UPHYClassComponent::GetConfiguredPlayerRetargeter() const { const UPHYClassSettings* ClassSettings = GetDefault(); @@ -139,7 +159,7 @@ bool UPHYClassComponent::ApplyConfiguredPlayerMeshIfAllowed(AActor* AvatarActor) } MeshBridge->InitializeMeshBridge(Character, Character->GetDisplayMeshComponent()); - return MeshBridge->ApplyDisplayMesh(PlayerMesh, nullptr, PlayerRetargeter); + return MeshBridge->ApplyDisplayMesh(PlayerMesh, GetConfiguredPlayerDisplayAnimClass(), PlayerRetargeter); } bool UPHYClassComponent::ApplyClassAttributesToAbilitySystem(UAbilitySystemComponent* AbilitySystemComponent) const diff --git a/Source/PHY/Public/Animation/PHYRetargetPoseAnimInstance.h b/Source/PHY/Public/Animation/PHYRetargetPoseAnimInstance.h index a587ee8..14a6740 100644 --- a/Source/PHY/Public/Animation/PHYRetargetPoseAnimInstance.h +++ b/Source/PHY/Public/Animation/PHYRetargetPoseAnimInstance.h @@ -3,8 +3,6 @@ #pragma once #include "Animation/AnimInstance.h" -#include "Animation/AnimInstanceProxy.h" -#include "AnimNodes/AnimNode_RetargetPoseFromMesh.h" #include "UObject/SoftObjectPath.h" #include "PHYRetargetPoseAnimInstance.generated.h" @@ -12,46 +10,19 @@ class UIKRetargeter; class USkeletalMeshComponent; /** - * @brief 显示层 Retarget Pose From Mesh 动画实例代理。 + * @brief PHY 显示层 Retarget 动画实例基类。 * - * 代理直接驱动 UE IKRig 的 FAnimNode_RetargetPoseFromMesh,用于把 SourceMesh 的运动姿态重定向到显示层 Mesh。 + * 该类只保存 SourceMesh 和 IK Retargeter 运行时参数,不再由 C++ 自动执行 AnimGraph 节点; + * 具体的 Retarget Pose From Mesh 节点必须在派生 AnimBlueprint 中显式连接。 */ -USTRUCT() -struct PHY_API FPHYRetargetPoseAnimInstanceProxy : public FAnimInstanceProxy -{ - GENERATED_BODY() - - friend class UPHYRetargetPoseAnimInstance; - - FPHYRetargetPoseAnimInstanceProxy() = default; - FPHYRetargetPoseAnimInstanceProxy(UAnimInstance* InAnimInstance, FAnimNode_RetargetPoseFromMesh* InRetargetNode); -protected: - virtual void Initialize(UAnimInstance* InAnimInstance) override; - virtual void PreUpdate(UAnimInstance* InAnimInstance, float DeltaSeconds) override; - virtual void CacheBones() override; - virtual bool Evaluate(FPoseContext& Output) override; - virtual void UpdateAnimationNode(const FAnimationUpdateContext& InContext) override; - - /** @brief 配置 Retarget 节点使用的源 Mesh 与 IK Retargeter。 */ - void ConfigureRetargetPose(UIKRetargeter* InRetargeter, USkeletalMeshComponent* InSourceMeshComponent); - -private: - FAnimNode_RetargetPoseFromMesh* RetargetNode = nullptr; -}; - -/** - * @brief PHY 显示层原生 Retarget Pose 动画实例。 - * - * MeshBridge 在异骨架显示层路径中使用该类,把 ACharacter::GetMesh() 的运动姿态通过 IK Retargeter 输出到 DisplayMesh。 - */ -UCLASS(Transient, BlueprintType) +UCLASS(Transient, BlueprintType, Blueprintable) class PHY_API UPHYRetargetPoseAnimInstance : public UAnimInstance { GENERATED_BODY() public: /** - * @brief 设置 Retarget Pose From Mesh 的运行时输入。 + * @brief 设置 Retarget AnimBlueprint 读取的运行时输入。 * @param InSourceMeshComponent 提供运动姿态的源 Mesh 组件。 * @param InRetargeterPath IK Retargeter 资产软路径。 * @return SourceMesh 与 Retargeter 均有效时返回 true。 @@ -63,29 +34,24 @@ public: UFUNCTION(BlueprintCallable, BlueprintPure, Category="PHY|Animation") USkeletalMeshComponent* GetRetargetSourceMeshComponent() const { return RetargetSourceMeshComponent; } + /** @brief 获取当前已加载的 IK Retargeter 资产,供 AnimBlueprint 节点绑定。 */ + UFUNCTION(BlueprintCallable, BlueprintPure, Category="PHY|Animation") + UIKRetargeter* GetLoadedRetargeter() const { return LoadedRetargeter; } + /** @brief 获取当前 IK Retargeter 资产路径。 */ UFUNCTION(BlueprintCallable, BlueprintPure, Category="PHY|Animation") FSoftObjectPath GetRetargeterPath() const { return RetargeterPath; } - - virtual void NativeInitializeAnimation() override; protected: - virtual FAnimInstanceProxy* CreateAnimInstanceProxy() override; - -private: - /** @brief Retarget Pose From Mesh 运行时节点。 */ - UPROPERTY() - FAnimNode_RetargetPoseFromMesh RetargetNode; - /** @brief 当前用于重定向的源 Mesh 组件。 */ - UPROPERTY(Transient) + UPROPERTY(Transient, BlueprintReadOnly, Category="PHY|Animation") TObjectPtr RetargetSourceMeshComponent; - /** @brief 当前使用的 IK Retargeter 资产。 */ - UPROPERTY(Transient) + /** @brief 当前使用的 IK Retargeter 资产,AnimBlueprint 的节点应绑定到该属性或 Getter。 */ + UPROPERTY(Transient, BlueprintReadOnly, Category="PHY|Animation") TObjectPtr LoadedRetargeter; /** @brief 当前 IK Retargeter 资产软路径。 */ - UPROPERTY(Transient) + UPROPERTY(Transient, BlueprintReadOnly, Category="PHY|Animation") FSoftObjectPath RetargeterPath; }; diff --git a/Source/PHY/Public/Class/PHYClassComponent.h b/Source/PHY/Public/Class/PHYClassComponent.h index 209a661..7c73a51 100644 --- a/Source/PHY/Public/Class/PHYClassComponent.h +++ b/Source/PHY/Public/Class/PHYClassComponent.h @@ -8,8 +8,9 @@ #include "UObject/SoftObjectPath.h" #include "PHYClassComponent.generated.h" -class USkeletalMesh; class UAbilitySystemComponent; +class UAnimInstance; +class USkeletalMesh; /** * @brief PHY 职业组件。 @@ -44,6 +45,10 @@ public: UFUNCTION(BlueprintCallable, Category="PHY|Class") USkeletalMesh* GetConfiguredPlayerMesh() const; + /** @brief 按当前职业读取配置的显示层 AnimClass,未配置时返回空并交给动画默认配置。 */ + UFUNCTION(BlueprintCallable, Category="PHY|Class") + TSubclassOf GetConfiguredPlayerDisplayAnimClass() const; + /** @brief 按当前职业读取配置的玩家 IK Retargeter 软路径,未配置时返回空路径。 */ UFUNCTION(BlueprintCallable, BlueprintPure, Category="PHY|Class") FSoftObjectPath GetConfiguredPlayerRetargeter() const; diff --git a/Source/PHY/Public/Class/PHYClassSettings.h b/Source/PHY/Public/Class/PHYClassSettings.h index f7b232f..79717d9 100644 --- a/Source/PHY/Public/Class/PHYClassSettings.h +++ b/Source/PHY/Public/Class/PHYClassSettings.h @@ -9,6 +9,7 @@ #include "UObject/SoftObjectPtr.h" #include "PHYClassSettings.generated.h" +class UAnimInstance; class USkeletalMesh; /** @@ -74,6 +75,10 @@ public: UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category="PHY|Class") TSoftObjectPtr PlayerMesh; + /** @brief 玩家使用该职业时优先使用的显示层 AnimClass,留空时回退动画默认配置。 */ + UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category="PHY|Class") + TSoftClassPtr PlayerDisplayAnimClass; + /** @brief 玩家使用该职业时优先使用的 IK Retargeter 软路径,留空时回退动画默认配置。 */ UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category="PHY|Class") FSoftObjectPath PlayerRetargeter;