272 lines
8.3 KiB
C++
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();
|
|
}
|