Compare commits
2 Commits
349d603775
...
f6fda856bf
| Author | SHA1 | Date | |
|---|---|---|---|
| f6fda856bf | |||
| f8169a63e9 |
9
Config/DefaultPHYAnimation.ini
Normal file
9
Config/DefaultPHYAnimation.ini
Normal file
@@ -0,0 +1,9 @@
|
||||
[/Script/PHY.PHYAnimationSettings]
|
||||
DefaultSourceLocomotionMesh=None
|
||||
DefaultSourceAnimClass=None
|
||||
DefaultDisplayAnimClass=None
|
||||
DefaultIKRetargeter=
|
||||
bHideSourceMeshInGame=False
|
||||
bPreferLeaderPoseForSameSkeleton=True
|
||||
bAllowRuntimeVisualMeshSwitch=True
|
||||
bApplyPlayerClassMeshToAI=False
|
||||
@@ -26,6 +26,10 @@
|
||||
"Name": "ModularGameplay",
|
||||
"Enabled": true
|
||||
},
|
||||
{
|
||||
"Name": "IKRig",
|
||||
"Enabled": true
|
||||
},
|
||||
{
|
||||
"Name": "GenericCombatSystem",
|
||||
"Enabled": true,
|
||||
|
||||
@@ -25,6 +25,7 @@ public class PHY : ModuleRules
|
||||
"GenericGameSystem",
|
||||
"GenericEffectsSystem",
|
||||
"AuroraDevs_UGC",
|
||||
"IKRig",
|
||||
"AIModule"
|
||||
});
|
||||
|
||||
|
||||
7
Source/PHY/Private/Animation/PHYAnimationSettings.cpp
Normal file
7
Source/PHY/Private/Animation/PHYAnimationSettings.cpp
Normal file
@@ -0,0 +1,7 @@
|
||||
// Copyright Epic Games, Inc. All Rights Reserved.
|
||||
|
||||
#include "Animation/PHYAnimationSettings.h"
|
||||
|
||||
#include UE_INLINE_GENERATED_CPP_BY_NAME(PHYAnimationSettings)
|
||||
|
||||
// 动画配置仅承载默认值,实际应用由 Mesh Bridge 在角色初始化时读取。
|
||||
241
Source/PHY/Private/Animation/PHYCharacterMeshBridgeComponent.cpp
Normal file
241
Source/PHY/Private/Animation/PHYCharacterMeshBridgeComponent.cpp
Normal file
@@ -0,0 +1,241 @@
|
||||
// Copyright Epic Games, Inc. All Rights Reserved.
|
||||
|
||||
#include "Animation/PHYCharacterMeshBridgeComponent.h"
|
||||
|
||||
#include UE_INLINE_GENERATED_CPP_BY_NAME(PHYCharacterMeshBridgeComponent)
|
||||
|
||||
#include "Animation/AnimInstance.h"
|
||||
#include "Animation/PHYAnimationSettings.h"
|
||||
#include "Class/PHYClassComponent.h"
|
||||
#include "Animation/PHYRetargetPoseAnimInstance.h"
|
||||
#include "Components/SkeletalMeshComponent.h"
|
||||
#include "Engine/SkeletalMesh.h"
|
||||
#include "GameFramework/Character.h"
|
||||
|
||||
UPHYCharacterMeshBridgeComponent::UPHYCharacterMeshBridgeComponent(const FObjectInitializer& ObjectInitializer)
|
||||
: Super(ObjectInitializer)
|
||||
{
|
||||
PrimaryComponentTick.bCanEverTick = false;
|
||||
}
|
||||
|
||||
void UPHYCharacterMeshBridgeComponent::InitializeMeshBridge(ACharacter* Character, USkeletalMeshComponent* InDisplayMeshComponent)
|
||||
{
|
||||
SourceMeshComponent = Character ? Character->GetMesh() : nullptr;
|
||||
DisplayMeshComponent = InDisplayMeshComponent;
|
||||
|
||||
if (!SourceMeshComponent || !DisplayMeshComponent)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
DisplayMeshComponent->SetCollisionEnabled(ECollisionEnabled::NoCollision);
|
||||
DisplayMeshComponent->SetGenerateOverlapEvents(false);
|
||||
DisplayMeshComponent->bReceivesDecals = SourceMeshComponent->bReceivesDecals;
|
||||
DisplayMeshComponent->VisibilityBasedAnimTickOption = SourceMeshComponent->VisibilityBasedAnimTickOption;
|
||||
|
||||
ApplySourceDefaults();
|
||||
|
||||
const UPHYAnimationSettings* Settings = GetDefault<UPHYAnimationSettings>();
|
||||
CurrentRetargeter = Settings ? Settings->DefaultIKRetargeter : FSoftObjectPath();
|
||||
|
||||
if (Settings)
|
||||
{
|
||||
SourceMeshComponent->SetHiddenInGame(Settings->bHideSourceMeshInGame);
|
||||
}
|
||||
|
||||
RefreshFollowPoseMode();
|
||||
}
|
||||
|
||||
bool UPHYCharacterMeshBridgeComponent::ApplyDisplayMesh(USkeletalMesh* NewDisplayMesh, TSubclassOf<UAnimInstance> NewDisplayAnimClass, FSoftObjectPath NewRetargeter)
|
||||
{
|
||||
if (!DisplayMeshComponent || !NewDisplayMesh)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
const UPHYAnimationSettings* Settings = GetDefault<UPHYAnimationSettings>();
|
||||
if (DisplayMeshComponent->GetSkeletalMeshAsset() && Settings && !Settings->bAllowRuntimeVisualMeshSwitch)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
DisplayMeshComponent->SetSkeletalMesh(NewDisplayMesh);
|
||||
|
||||
if (NewDisplayAnimClass)
|
||||
{
|
||||
DisplayMeshComponent->SetAnimationMode(EAnimationMode::AnimationBlueprint);
|
||||
DisplayMeshComponent->SetAnimInstanceClass(NewDisplayAnimClass);
|
||||
}
|
||||
else if (Settings && !Settings->DefaultDisplayAnimClass.IsNull())
|
||||
{
|
||||
if (UClass* DefaultDisplayAnimClass = Settings->DefaultDisplayAnimClass.LoadSynchronous())
|
||||
{
|
||||
DisplayMeshComponent->SetAnimationMode(EAnimationMode::AnimationBlueprint);
|
||||
DisplayMeshComponent->SetAnimInstanceClass(DefaultDisplayAnimClass);
|
||||
}
|
||||
}
|
||||
|
||||
CurrentRetargeter = NewRetargeter.IsValid() ? NewRetargeter : (Settings ? Settings->DefaultIKRetargeter : FSoftObjectPath());
|
||||
RefreshFollowPoseMode();
|
||||
return true;
|
||||
}
|
||||
|
||||
bool UPHYCharacterMeshBridgeComponent::ApplyPlayerClassVisualFromClassComponent(const UPHYClassComponent* ClassComponent)
|
||||
{
|
||||
if (!ClassComponent)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!ClassComponent->IsPlayerClassMeshAllowed())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
USkeletalMesh* PlayerMesh = ClassComponent->GetConfiguredPlayerMesh();
|
||||
return PlayerMesh ? ApplyDisplayMesh(PlayerMesh) : false;
|
||||
}
|
||||
|
||||
void UPHYCharacterMeshBridgeComponent::RefreshFollowPoseMode()
|
||||
{
|
||||
if (!SourceMeshComponent || !DisplayMeshComponent)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (CanUseLeaderPose())
|
||||
{
|
||||
DisplayMeshComponent->SetLeaderPoseComponent(SourceMeshComponent);
|
||||
DisplayMeshComponent->SetAnimationMode(EAnimationMode::AnimationBlueprint);
|
||||
return;
|
||||
}
|
||||
|
||||
DisplayMeshComponent->SetLeaderPoseComponent(nullptr);
|
||||
|
||||
ConfigureRetargetFollowPose();
|
||||
}
|
||||
|
||||
USkeletalMeshComponent* UPHYCharacterMeshBridgeComponent::GetVisualMeshComponentForSocket(const FName SocketName) const
|
||||
{
|
||||
if (!SocketName.IsNone() && DisplayMeshComponent && DisplayMeshComponent->DoesSocketExist(SocketName))
|
||||
{
|
||||
return DisplayMeshComponent;
|
||||
}
|
||||
|
||||
if (!SocketName.IsNone() && SourceMeshComponent && SourceMeshComponent->DoesSocketExist(SocketName))
|
||||
{
|
||||
return SourceMeshComponent;
|
||||
}
|
||||
|
||||
return DisplayMeshComponent ? DisplayMeshComponent : SourceMeshComponent;
|
||||
}
|
||||
|
||||
void UPHYCharacterMeshBridgeComponent::ApplySourceDefaults()
|
||||
{
|
||||
if (!SourceMeshComponent)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
const UPHYAnimationSettings* Settings = GetDefault<UPHYAnimationSettings>();
|
||||
if (!Settings)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (!SourceMeshComponent->GetSkeletalMeshAsset() && !Settings->DefaultSourceLocomotionMesh.IsNull())
|
||||
{
|
||||
if (USkeletalMesh* DefaultSourceMesh = Settings->DefaultSourceLocomotionMesh.LoadSynchronous())
|
||||
{
|
||||
SourceMeshComponent->SetSkeletalMesh(DefaultSourceMesh);
|
||||
}
|
||||
}
|
||||
|
||||
if (!Settings->DefaultSourceAnimClass.IsNull())
|
||||
{
|
||||
if (UClass* DefaultSourceAnimClass = Settings->DefaultSourceAnimClass.LoadSynchronous())
|
||||
{
|
||||
SourceMeshComponent->SetAnimationMode(EAnimationMode::AnimationBlueprint);
|
||||
SourceMeshComponent->SetAnimInstanceClass(DefaultSourceAnimClass);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool UPHYCharacterMeshBridgeComponent::CanUseLeaderPose() const
|
||||
{
|
||||
const UPHYAnimationSettings* Settings = GetDefault<UPHYAnimationSettings>();
|
||||
if (Settings && !Settings->bPreferLeaderPoseForSameSkeleton)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
const USkeletalMesh* SourceMesh = SourceMeshComponent ? SourceMeshComponent->GetSkeletalMeshAsset() : nullptr;
|
||||
const USkeletalMesh* DisplayMesh = DisplayMeshComponent ? DisplayMeshComponent->GetSkeletalMeshAsset() : nullptr;
|
||||
return SourceMesh && DisplayMesh && SourceMesh->GetSkeleton() && SourceMesh->GetSkeleton() == DisplayMesh->GetSkeleton();
|
||||
}
|
||||
|
||||
void UPHYCharacterMeshBridgeComponent::ConfigureRetargetFollowPose()
|
||||
{
|
||||
if (!SourceMeshComponent || !DisplayMeshComponent)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
TSubclassOf<UAnimInstance> RetargetAnimClass = ResolveRetargetDisplayAnimClass();
|
||||
if (!RetargetAnimClass)
|
||||
{
|
||||
UE_LOG(LogTemp, Warning, TEXT("PHYCharacterMeshBridgeComponent: DisplayMesh %s has no retarget AnimClass."), *GetNameSafe(DisplayMeshComponent));
|
||||
return;
|
||||
}
|
||||
|
||||
if (!RetargetAnimClass->IsChildOf(UPHYRetargetPoseAnimInstance::StaticClass()))
|
||||
{
|
||||
UE_LOG(LogTemp, Warning, TEXT("PHYCharacterMeshBridgeComponent: Display AnimClass %s is not based on UPHYRetargetPoseAnimInstance; falling back to native retarget instance."), *GetNameSafe(RetargetAnimClass.Get()));
|
||||
RetargetAnimClass = UPHYRetargetPoseAnimInstance::StaticClass();
|
||||
}
|
||||
|
||||
DisplayMeshComponent->SetAnimationMode(EAnimationMode::AnimationBlueprint);
|
||||
if (DisplayMeshComponent->GetAnimClass() != RetargetAnimClass.Get())
|
||||
{
|
||||
DisplayMeshComponent->SetAnimInstanceClass(RetargetAnimClass);
|
||||
}
|
||||
|
||||
if (!CurrentRetargeter.IsValid())
|
||||
{
|
||||
UE_LOG(LogTemp, Warning, TEXT("PHYCharacterMeshBridgeComponent: DisplayMesh %s needs an IK Retargeter for cross-skeleton follow pose."), *GetNameSafe(DisplayMeshComponent));
|
||||
return;
|
||||
}
|
||||
|
||||
UPHYRetargetPoseAnimInstance* RetargetAnimInstance = Cast<UPHYRetargetPoseAnimInstance>(DisplayMeshComponent->GetAnimInstance());
|
||||
if (!RetargetAnimInstance)
|
||||
{
|
||||
UE_LOG(LogTemp, Warning, TEXT("PHYCharacterMeshBridgeComponent: DisplayMesh %s did not create a UPHYRetargetPoseAnimInstance."), *GetNameSafe(DisplayMeshComponent));
|
||||
return;
|
||||
}
|
||||
|
||||
RetargetAnimInstance->ConfigureRetargetPoseFromMesh(SourceMeshComponent, CurrentRetargeter);
|
||||
}
|
||||
|
||||
TSubclassOf<UAnimInstance> UPHYCharacterMeshBridgeComponent::ResolveRetargetDisplayAnimClass() const
|
||||
{
|
||||
if (!DisplayMeshComponent)
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
if (UClass* CurrentAnimClass = DisplayMeshComponent->GetAnimClass())
|
||||
{
|
||||
return CurrentAnimClass;
|
||||
}
|
||||
|
||||
const UPHYAnimationSettings* Settings = GetDefault<UPHYAnimationSettings>();
|
||||
if (Settings && !Settings->DefaultDisplayAnimClass.IsNull())
|
||||
{
|
||||
if (UClass* DefaultDisplayAnimClass = Settings->DefaultDisplayAnimClass.LoadSynchronous())
|
||||
{
|
||||
return DefaultDisplayAnimClass;
|
||||
}
|
||||
}
|
||||
|
||||
return UPHYRetargetPoseAnimInstance::StaticClass();
|
||||
}
|
||||
130
Source/PHY/Private/Animation/PHYRetargetPoseAnimInstance.cpp
Normal file
130
Source/PHY/Private/Animation/PHYRetargetPoseAnimInstance.cpp
Normal file
@@ -0,0 +1,130 @@
|
||||
// Copyright Epic Games, Inc. All Rights Reserved.
|
||||
|
||||
#include "Animation/PHYRetargetPoseAnimInstance.h"
|
||||
|
||||
#include UE_INLINE_GENERATED_CPP_BY_NAME(PHYRetargetPoseAnimInstance)
|
||||
|
||||
#include "Components/SkeletalMeshComponent.h"
|
||||
#include "Retargeter/IKRetargeter.h"
|
||||
|
||||
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)
|
||||
{
|
||||
RetargetSourceMeshComponent = InSourceMeshComponent;
|
||||
RetargeterPath = InRetargeterPath;
|
||||
LoadedRetargeter = nullptr;
|
||||
|
||||
if (!RetargetSourceMeshComponent)
|
||||
{
|
||||
UE_LOG(LogTemp, Warning, TEXT("PHYRetargetPoseAnimInstance: SourceMeshComponent is missing."));
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!RetargeterPath.IsValid())
|
||||
{
|
||||
UE_LOG(LogTemp, Warning, TEXT("PHYRetargetPoseAnimInstance: IK Retargeter path is missing for %s."), *GetNameSafe(GetOwningComponent()));
|
||||
return false;
|
||||
}
|
||||
|
||||
UObject* LoadedObject = RetargeterPath.TryLoad();
|
||||
LoadedRetargeter = Cast<UIKRetargeter>(LoadedObject);
|
||||
if (!LoadedRetargeter)
|
||||
{
|
||||
UE_LOG(LogTemp, Warning, TEXT("PHYRetargetPoseAnimInstance: %s is not a valid UIKRetargeter asset."), *RetargeterPath.ToString());
|
||||
return false;
|
||||
}
|
||||
|
||||
FPHYRetargetPoseAnimInstanceProxy& Proxy = GetProxyOnGameThread<FPHYRetargetPoseAnimInstanceProxy>();
|
||||
Proxy.ConfigureRetargetPose(LoadedRetargeter, RetargetSourceMeshComponent);
|
||||
return true;
|
||||
}
|
||||
|
||||
void UPHYRetargetPoseAnimInstance::NativeInitializeAnimation()
|
||||
{
|
||||
Super::NativeInitializeAnimation();
|
||||
|
||||
FPHYRetargetPoseAnimInstanceProxy& Proxy = GetProxyOnGameThread<FPHYRetargetPoseAnimInstanceProxy>();
|
||||
Proxy.Initialize(this);
|
||||
Proxy.ConfigureRetargetPose(LoadedRetargeter, RetargetSourceMeshComponent);
|
||||
}
|
||||
|
||||
FAnimInstanceProxy* UPHYRetargetPoseAnimInstance::CreateAnimInstanceProxy()
|
||||
{
|
||||
return new FPHYRetargetPoseAnimInstanceProxy(this, &RetargetNode);
|
||||
}
|
||||
@@ -5,6 +5,7 @@
|
||||
#include UE_INLINE_GENERATED_CPP_BY_NAME(PHYCharacterBase)
|
||||
|
||||
#include "AbilitySystemComponent.h"
|
||||
#include "Animation/PHYCharacterMeshBridgeComponent.h"
|
||||
#include "Camera/CameraComponent.h"
|
||||
#include "Characters/PHYCharacterSettings.h"
|
||||
#include "Characters/PHYCharacterStateComponent.h"
|
||||
@@ -34,6 +35,11 @@ APHYCharacterBase::APHYCharacterBase(const FObjectInitializer& ObjectInitializer
|
||||
InteractionSystemComponent = CreateDefaultSubobject<UGGS_InteractionSystemComponent>(TEXT("InteractionSystemComponent"));
|
||||
RagdollComponent = CreateDefaultSubobject<UGGS_RagdollComponent>(TEXT("RagdollComponent"));
|
||||
ContextEffectComponent = CreateDefaultSubobject<UGES_ContextEffectComponent>(TEXT("ContextEffectComponent"));
|
||||
MeshBridgeComponent = CreateDefaultSubobject<UPHYCharacterMeshBridgeComponent>(TEXT("MeshBridgeComponent"));
|
||||
DisplayMeshComponent = CreateDefaultSubobject<USkeletalMeshComponent>(TEXT("DisplayMeshComponent"));
|
||||
DisplayMeshComponent->SetupAttachment(GetMesh());
|
||||
DisplayMeshComponent->SetCollisionEnabled(ECollisionEnabled::NoCollision);
|
||||
DisplayMeshComponent->SetGenerateOverlapEvents(false);
|
||||
}
|
||||
|
||||
void APHYCharacterBase::PreInitializeComponents()
|
||||
@@ -49,6 +55,11 @@ void APHYCharacterBase::BeginPlay()
|
||||
|
||||
ApplyCharacterSettings();
|
||||
|
||||
if (MeshBridgeComponent)
|
||||
{
|
||||
MeshBridgeComponent->InitializeMeshBridge(this, DisplayMeshComponent);
|
||||
}
|
||||
|
||||
if (RagdollComponent)
|
||||
{
|
||||
RagdollComponent->SetMeshComponent(GetMesh());
|
||||
|
||||
@@ -9,11 +9,11 @@
|
||||
#include "AbilitySystem/Attributes/PHYCombatAttributeSet.h"
|
||||
#include "AbilitySystem/Attributes/PHYCoreAttributeSet.h"
|
||||
#include "AbilitySystem/Attributes/PHYElementAttributeSet.h"
|
||||
#include "Animation/PHYCharacterMeshBridgeComponent.h"
|
||||
#include "Characters/PHYAICharacter.h"
|
||||
#include "Characters/PHYCharacterBase.h"
|
||||
#include "Characters/PHYPlayerCharacter.h"
|
||||
#include "Class/PHYClassSettings.h"
|
||||
#include "Components/SkeletalMeshComponent.h"
|
||||
#include "GameFramework/Character.h"
|
||||
#include "Net/UnrealNetwork.h"
|
||||
#include "PHYGameplayTags.h"
|
||||
#include "Player/PHYPlayerState.h"
|
||||
@@ -100,7 +100,7 @@ bool UPHYClassComponent::ApplyConfiguredPlayerMeshIfAllowed(AActor* AvatarActor)
|
||||
}
|
||||
|
||||
AActor* MeshOwner = AvatarActor ? AvatarActor : GetOwner();
|
||||
ACharacter* Character = Cast<ACharacter>(MeshOwner);
|
||||
APHYCharacterBase* Character = Cast<APHYCharacterBase>(MeshOwner);
|
||||
if (!Character)
|
||||
{
|
||||
return false;
|
||||
@@ -112,8 +112,8 @@ bool UPHYClassComponent::ApplyConfiguredPlayerMeshIfAllowed(AActor* AvatarActor)
|
||||
return false;
|
||||
}
|
||||
|
||||
Character->GetMesh()->SetSkeletalMesh(PlayerMesh);
|
||||
return true;
|
||||
UPHYCharacterMeshBridgeComponent* MeshBridge = Character->GetMeshBridgeComponent();
|
||||
return MeshBridge ? MeshBridge->ApplyDisplayMesh(PlayerMesh) : false;
|
||||
}
|
||||
|
||||
bool UPHYClassComponent::ApplyClassAttributesToAbilitySystem(UAbilitySystemComponent* AbilitySystemComponent) const
|
||||
|
||||
56
Source/PHY/Public/Animation/PHYAnimationSettings.h
Normal file
56
Source/PHY/Public/Animation/PHYAnimationSettings.h
Normal file
@@ -0,0 +1,56 @@
|
||||
// Copyright Epic Games, Inc. All Rights Reserved.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
#include "UObject/Object.h"
|
||||
#include "UObject/SoftObjectPath.h"
|
||||
#include "UObject/SoftObjectPtr.h"
|
||||
#include "PHYAnimationSettings.generated.h"
|
||||
|
||||
class UAnimInstance;
|
||||
class USkeletalMesh;
|
||||
|
||||
/**
|
||||
* @brief PHY 动画桥接配置。
|
||||
*
|
||||
* 首期只保存运动源、显示层 AnimClass 与 Retargeter 软引用,不直接依赖 IKRig 模块或创建动画资产。
|
||||
*/
|
||||
UCLASS(Config=PHYAnimation, DefaultConfig)
|
||||
class PHY_API UPHYAnimationSettings : public UObject
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
public:
|
||||
/** @brief 默认源运动 Mesh,挂载到 ACharacter::GetMesh() 上承载统一运动动画。 */
|
||||
UPROPERTY(Config, EditDefaultsOnly, Category="PHY|Animation")
|
||||
TSoftObjectPtr<USkeletalMesh> DefaultSourceLocomotionMesh;
|
||||
|
||||
/** @brief 默认源运动 AnimClass,用于 ACharacter::GetMesh() 的统一 Locomotion 动画。 */
|
||||
UPROPERTY(Config, EditDefaultsOnly, Category="PHY|Animation")
|
||||
TSoftClassPtr<UAnimInstance> DefaultSourceAnimClass;
|
||||
|
||||
/** @brief 默认显示层 AnimClass,异骨架或未来 Retarget Pose From Mesh 路径使用。 */
|
||||
UPROPERTY(Config, EditDefaultsOnly, Category="PHY|Animation")
|
||||
TSoftClassPtr<UAnimInstance> DefaultDisplayAnimClass;
|
||||
|
||||
/** @brief 默认 IK Retargeter 资产路径,首期使用软路径避免新增 IKRig 模块依赖。 */
|
||||
UPROPERTY(Config, EditDefaultsOnly, Category="PHY|Animation")
|
||||
FSoftObjectPath DefaultIKRetargeter;
|
||||
|
||||
/** @brief 是否在游戏中隐藏源运动 Mesh。 */
|
||||
UPROPERTY(Config, EditDefaultsOnly, Category="PHY|Animation")
|
||||
bool bHideSourceMeshInGame = false;
|
||||
|
||||
/** @brief 同骨架时是否优先使用 LeaderPoseComponent 跟随源运动 Mesh。 */
|
||||
UPROPERTY(Config, EditDefaultsOnly, Category="PHY|Animation")
|
||||
bool bPreferLeaderPoseForSameSkeleton = true;
|
||||
|
||||
/** @brief 是否允许运行时切换显示层 Mesh。 */
|
||||
UPROPERTY(Config, EditDefaultsOnly, Category="PHY|Animation")
|
||||
bool bAllowRuntimeVisualMeshSwitch = true;
|
||||
|
||||
/** @brief 是否允许 AI 套用玩家职业 Mesh,默认关闭以保持 AI 外观独立。 */
|
||||
UPROPERTY(Config, EditDefaultsOnly, Category="PHY|Animation")
|
||||
bool bApplyPlayerClassMeshToAI = false;
|
||||
};
|
||||
@@ -0,0 +1,98 @@
|
||||
// Copyright Epic Games, Inc. All Rights Reserved.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
#include "Components/ActorComponent.h"
|
||||
#include "UObject/SoftObjectPath.h"
|
||||
#include "PHYCharacterMeshBridgeComponent.generated.h"
|
||||
|
||||
class ACharacter;
|
||||
class UAnimInstance;
|
||||
class UPHYClassComponent;
|
||||
class UPHYRetargetPoseAnimInstance;
|
||||
class USkeletalMesh;
|
||||
class USkeletalMeshComponent;
|
||||
|
||||
/**
|
||||
* @brief 角色运动源 Mesh 与显示层 Mesh 的桥接组件。
|
||||
*
|
||||
* ACharacter::GetMesh() 固定作为 SourceMeshComponent,承载统一运动动画;DisplayMeshComponent 承载职业和外观 Mesh。
|
||||
*/
|
||||
UCLASS(BlueprintType, Blueprintable, meta=(BlueprintSpawnableComponent))
|
||||
class PHY_API UPHYCharacterMeshBridgeComponent : public UActorComponent
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
public:
|
||||
/** @brief 构造 Mesh Bridge 默认状态。 */
|
||||
UPHYCharacterMeshBridgeComponent(const FObjectInitializer& ObjectInitializer = FObjectInitializer::Get());
|
||||
|
||||
/** @brief 使用角色和外部创建的显示 Mesh 组件初始化桥接关系。
|
||||
* @param Character 拥有源运动 Mesh 的角色。
|
||||
* @param InDisplayMeshComponent 由角色构造阶段创建的显示层 Mesh 组件。
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, Category="PHY|Animation")
|
||||
void InitializeMeshBridge(ACharacter* Character, USkeletalMeshComponent* InDisplayMeshComponent);
|
||||
|
||||
/** @brief 将新的显示 Mesh 应用到显示层,并按骨架关系刷新 Follow Pose 模式。
|
||||
* @param NewDisplayMesh 新显示层 Skeletal Mesh。
|
||||
* @param NewDisplayAnimClass 可选显示层 AnimClass,异骨架 Retarget 路径使用。
|
||||
* @param NewRetargeter 可选 Retargeter 软路径,异骨架 Retarget Pose 路径会加载并传递给显示层 AnimInstance。
|
||||
* @return 应用成功返回 true。
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, Category="PHY|Animation")
|
||||
bool ApplyDisplayMesh(USkeletalMesh* NewDisplayMesh, TSubclassOf<UAnimInstance> NewDisplayAnimClass = nullptr, FSoftObjectPath NewRetargeter = FSoftObjectPath());
|
||||
|
||||
/** @brief 从职业组件读取职业外观并应用到显示层。
|
||||
* @param ClassComponent 提供职业 Mesh 配置的职业组件。
|
||||
* @return 应用成功返回 true。
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, Category="PHY|Animation")
|
||||
bool ApplyPlayerClassVisualFromClassComponent(const UPHYClassComponent* ClassComponent);
|
||||
|
||||
/** @brief 根据源 Mesh 和显示 Mesh 的骨架关系刷新 Leader Pose 或显示层 AnimClass。 */
|
||||
UFUNCTION(BlueprintCallable, Category="PHY|Animation")
|
||||
void RefreshFollowPoseMode();
|
||||
|
||||
/** @brief 获取源运动 Mesh 组件。 */
|
||||
UFUNCTION(BlueprintCallable, BlueprintPure, Category="PHY|Animation")
|
||||
USkeletalMeshComponent* GetSourceMeshComponent() const { return SourceMeshComponent; }
|
||||
|
||||
/** @brief 获取显示层 Mesh 组件。 */
|
||||
UFUNCTION(BlueprintCallable, BlueprintPure, Category="PHY|Animation")
|
||||
USkeletalMeshComponent* GetDisplayMeshComponent() const { return DisplayMeshComponent; }
|
||||
|
||||
/** @brief 按 Socket 查询应使用的可视 Mesh 组件,优先返回拥有该 Socket 的显示层。 */
|
||||
UFUNCTION(BlueprintCallable, BlueprintPure, Category="PHY|Animation")
|
||||
USkeletalMeshComponent* GetVisualMeshComponentForSocket(FName SocketName) const;
|
||||
|
||||
/** @brief 获取当前缓存的 Retargeter 软路径。 */
|
||||
UFUNCTION(BlueprintCallable, BlueprintPure, Category="PHY|Animation")
|
||||
FSoftObjectPath GetCurrentRetargeter() const { return CurrentRetargeter; }
|
||||
|
||||
protected:
|
||||
/** @brief 应用配置中的源运动 Mesh 和 AnimClass 默认值。 */
|
||||
void ApplySourceDefaults();
|
||||
|
||||
/** @brief 判断显示层是否可使用 Leader Pose 跟随源运动 Mesh。 */
|
||||
bool CanUseLeaderPose() const;
|
||||
|
||||
/** @brief 为异骨架显示层配置 Retarget Pose From Mesh 动画实例。 */
|
||||
void ConfigureRetargetFollowPose();
|
||||
|
||||
/** @brief 获取异骨架显示层应使用的 AnimClass,缺省时回退到原生 Retarget AnimInstance。 */
|
||||
TSubclassOf<UAnimInstance> ResolveRetargetDisplayAnimClass() const;
|
||||
|
||||
/** @brief 源运动 Mesh 组件,固定来自 ACharacter::GetMesh()。 */
|
||||
UPROPERTY(VisibleInstanceOnly, BlueprintReadOnly, Category="PHY|Animation")
|
||||
TObjectPtr<USkeletalMeshComponent> SourceMeshComponent;
|
||||
|
||||
/** @brief 显示层 Mesh 组件,由 CharacterBase 创建并注入。 */
|
||||
UPROPERTY(VisibleInstanceOnly, BlueprintReadOnly, Category="PHY|Animation")
|
||||
TObjectPtr<USkeletalMeshComponent> DisplayMeshComponent;
|
||||
|
||||
/** @brief 当前显示层 Retargeter 软路径,异骨架 Retarget Pose From Mesh 路径使用。 */
|
||||
UPROPERTY(VisibleInstanceOnly, BlueprintReadOnly, Category="PHY|Animation")
|
||||
FSoftObjectPath CurrentRetargeter;
|
||||
};
|
||||
88
Source/PHY/Public/Animation/PHYRetargetPoseAnimInstance.h
Normal file
88
Source/PHY/Public/Animation/PHYRetargetPoseAnimInstance.h
Normal file
@@ -0,0 +1,88 @@
|
||||
// Copyright Epic Games, Inc. All Rights Reserved.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "Animation/AnimInstance.h"
|
||||
#include "Animation/AnimInstanceProxy.h"
|
||||
#include "AnimNodes/AnimNode_RetargetPoseFromMesh.h"
|
||||
#include "UObject/SoftObjectPath.h"
|
||||
#include "PHYRetargetPoseAnimInstance.generated.h"
|
||||
|
||||
class UIKRetargeter;
|
||||
class USkeletalMeshComponent;
|
||||
|
||||
/**
|
||||
* @brief 显示层 Retarget Pose From Mesh 动画实例代理。
|
||||
*
|
||||
* 代理直接驱动 UE IKRig 的 FAnimNode_RetargetPoseFromMesh,用于把 SourceMesh 的运动姿态重定向到显示层 Mesh。
|
||||
*/
|
||||
USTRUCT()
|
||||
struct PHY_API FPHYRetargetPoseAnimInstanceProxy : public FAnimInstanceProxy
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
FPHYRetargetPoseAnimInstanceProxy() = default;
|
||||
FPHYRetargetPoseAnimInstanceProxy(UAnimInstance* InAnimInstance, FAnimNode_RetargetPoseFromMesh* InRetargetNode);
|
||||
|
||||
virtual void Initialize(UAnimInstance* InAnimInstance) override;
|
||||
virtual void PreUpdate(UAnimInstance* InAnimInstance, float DeltaSeconds) override;
|
||||
virtual void CacheBones() override;
|
||||
virtual bool Evaluate(FPoseContext& Output) override;
|
||||
virtual void UpdateAnimationNode(const FAnimationUpdateContext& InContext) override;
|
||||
|
||||
/** @brief 配置 Retarget 节点使用的源 Mesh 与 IK Retargeter。 */
|
||||
void ConfigureRetargetPose(UIKRetargeter* InRetargeter, USkeletalMeshComponent* InSourceMeshComponent);
|
||||
|
||||
private:
|
||||
FAnimNode_RetargetPoseFromMesh* RetargetNode = nullptr;
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief PHY 显示层原生 Retarget Pose 动画实例。
|
||||
*
|
||||
* MeshBridge 在异骨架显示层路径中使用该类,把 ACharacter::GetMesh() 的运动姿态通过 IK Retargeter 输出到 DisplayMesh。
|
||||
*/
|
||||
UCLASS(Transient, BlueprintType)
|
||||
class PHY_API UPHYRetargetPoseAnimInstance : public UAnimInstance
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
public:
|
||||
/**
|
||||
* @brief 设置 Retarget Pose From Mesh 的运行时输入。
|
||||
* @param InSourceMeshComponent 提供运动姿态的源 Mesh 组件。
|
||||
* @param InRetargeterPath IK Retargeter 资产软路径。
|
||||
* @return SourceMesh 与 Retargeter 均有效时返回 true。
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, Category="PHY|Animation")
|
||||
bool ConfigureRetargetPoseFromMesh(USkeletalMeshComponent* InSourceMeshComponent, FSoftObjectPath InRetargeterPath);
|
||||
|
||||
/** @brief 获取当前源 Mesh 组件。 */
|
||||
UFUNCTION(BlueprintCallable, BlueprintPure, Category="PHY|Animation")
|
||||
USkeletalMeshComponent* GetRetargetSourceMeshComponent() const { return RetargetSourceMeshComponent; }
|
||||
|
||||
/** @brief 获取当前 IK Retargeter 资产路径。 */
|
||||
UFUNCTION(BlueprintCallable, BlueprintPure, Category="PHY|Animation")
|
||||
FSoftObjectPath GetRetargeterPath() const { return RetargeterPath; }
|
||||
|
||||
protected:
|
||||
virtual void NativeInitializeAnimation() override;
|
||||
virtual FAnimInstanceProxy* CreateAnimInstanceProxy() override;
|
||||
|
||||
private:
|
||||
/** @brief Retarget Pose From Mesh 运行时节点。 */
|
||||
UPROPERTY()
|
||||
FAnimNode_RetargetPoseFromMesh RetargetNode;
|
||||
|
||||
/** @brief 当前用于重定向的源 Mesh 组件。 */
|
||||
UPROPERTY(Transient)
|
||||
TObjectPtr<USkeletalMeshComponent> RetargetSourceMeshComponent;
|
||||
|
||||
/** @brief 当前使用的 IK Retargeter 资产。 */
|
||||
UPROPERTY(Transient)
|
||||
TObjectPtr<UIKRetargeter> LoadedRetargeter;
|
||||
|
||||
/** @brief 当前 IK Retargeter 资产软路径。 */
|
||||
UPROPERTY(Transient)
|
||||
FSoftObjectPath RetargeterPath;
|
||||
};
|
||||
@@ -17,7 +17,9 @@ class UGCS_TargetingSystemComponent;
|
||||
class UGES_ContextEffectComponent;
|
||||
class UGGS_InteractionSystemComponent;
|
||||
class UGGS_RagdollComponent;
|
||||
class UPHYCharacterMeshBridgeComponent;
|
||||
class UPHYCharacterStateComponent;
|
||||
class USkeletalMeshComponent;
|
||||
|
||||
/**
|
||||
* @brief PHY 玩家和 AI 共用角色基类。
|
||||
@@ -118,6 +120,14 @@ public:
|
||||
UFUNCTION(BlueprintCallable, BlueprintPure, Category="PHY|Character")
|
||||
UGES_ContextEffectComponent* GetContextEffectComponent() const { return ContextEffectComponent; }
|
||||
|
||||
/** @brief 获取角色 Mesh Bridge 组件。 */
|
||||
UFUNCTION(BlueprintCallable, BlueprintPure, Category="PHY|Character|Animation")
|
||||
UPHYCharacterMeshBridgeComponent* GetMeshBridgeComponent() const { return MeshBridgeComponent; }
|
||||
|
||||
/** @brief 获取显示层 Mesh 组件,职业和外观 Mesh 应应用到该组件。 */
|
||||
UFUNCTION(BlueprintCallable, BlueprintPure, Category="PHY|Character|Animation")
|
||||
USkeletalMeshComponent* GetDisplayMeshComponent() const { return DisplayMeshComponent; }
|
||||
|
||||
/** @brief 获取当前战斗目标 Actor。 */
|
||||
virtual AActor* GetCombatTargetActor_Implementation() const override;
|
||||
|
||||
@@ -234,6 +244,14 @@ protected:
|
||||
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category="PHY|Character")
|
||||
TObjectPtr<UGES_ContextEffectComponent> ContextEffectComponent;
|
||||
|
||||
/** @brief 运动源 Mesh 与显示层 Mesh 的桥接组件。 */
|
||||
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category="PHY|Character|Animation")
|
||||
TObjectPtr<UPHYCharacterMeshBridgeComponent> MeshBridgeComponent;
|
||||
|
||||
/** @brief 显示层 Mesh 组件,承载职业和外观 Mesh。 */
|
||||
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category="PHY|Character|Animation")
|
||||
TObjectPtr<USkeletalMeshComponent> DisplayMeshComponent;
|
||||
|
||||
/** @brief 当前 Avatar 缓存的 ASC,玩家来自 PlayerState,AI 来自自身。 */
|
||||
UPROPERTY(Transient)
|
||||
TObjectPtr<UAbilitySystemComponent> CachedAbilitySystemComponent;
|
||||
|
||||
Reference in New Issue
Block a user