Files
PHY/Source/PHY/Private/Animation/PHYCharacterMeshBridgeComponent.cpp

272 lines
8.3 KiB
C++

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