(UE4 4.20)UE4 繼承AnimNotify建立自定義動畫通知事件(結合PoseableMeshComponent實現技能殘影效果)

小毛狗發表於2018-12-23

 

AnimNotify

在UE4的動畫系統中,動畫通知事件(AnimNotify),就是在一段動畫的某幀觸發的事件。

動畫通知事件分為:Native和Custom(除了一個用藍圖實現,一個用C++實現,沒感覺有多大區別)

Custom動畫通知事件:

通過編輯器直接新增的 "AnimNotify" 事件

 

Native動畫通知事件

其中我們系統已經存在的 “播放粒子特效PlayParticleEffect” , "播放聲音PalySound", “重置布料模擬Reset Clothing Simulation”都是Native動畫通知事件,如下所示

如果我們碰上一個需求,(用C++)得實現一種新的AnimNotify事件,怎麼做?很簡單,整合UAnumNotify原始碼就行了,看看下面

"PlayParticleEffect" 和“PalySound”的原始碼,都是繼承UAnumNotify

UCLASS(const, hidecategories=Object, collapsecategories, meta=(DisplayName="Play Particle Effect"))
class ENGINE_API UAnimNotify_PlayParticleEffect : public UAnimNotify
{
	GENERATED_BODY()
UCLASS(const, hidecategories=Object, collapsecategories, meta=(DisplayName="Play Sound"))
class ENGINE_API UAnimNotify_PlaySound : public UAnimNotify
{
	GENERATED_BODY()

 

像我們增加一個“TestAnimNotify”的動畫通知事件,像下面這樣

UCLASS()
class MYPROJECT3_API UTestAnimNotify : public UAnimNotify
{
	GENERATED_BODY()
	
}

 

實現動畫通知事件,就是改寫類的Notify函式

	virtual void Notify(USkeletalMeshComponent* MeshComp, UAnimSequenceBase* Animation) override;

下面是PlaySound的

void UAnimNotify_PlaySound::Notify(class USkeletalMeshComponent* MeshComp, class UAnimSequenceBase* Animation)
{
	// Don't call super to avoid call back in to blueprints
	if (Sound)
	{
		if (Sound->IsLooping())
		{
			UE_LOG(LogAudio, Warning, TEXT("PlaySound notify: Anim %s tried to spawn infinitely looping sound asset %s. Spawning suppressed."), *GetNameSafe(Animation), *GetNameSafe(Sound));
			return;
		}

		if (bFollow)
		{
			UGameplayStatics::SpawnSoundAttached(Sound, MeshComp, AttachName, FVector(ForceInit), EAttachLocation::KeepRelativeOffset, false, VolumeMultiplier, PitchMultiplier);
		}
		else
		{
			UGameplayStatics::PlaySoundAtLocation(MeshComp->GetWorld(), Sound, MeshComp->GetComponentLocation(), VolumeMultiplier, PitchMultiplier);
		}
	}
}

 

其中 USkeletalMeshComponent* MeshComp 就是我們角色的SkeletalMesh

 

UPoseableMeshComponent和AnimNotify實現殘影

我們遊戲中某些移動性技能,在播放動畫(AnimNotify)的時候,會在某些“動畫幀”凍結姿勢,產生一個半溶解的Mesh, 也就是殘影效果。利用 UPoseableMeshComponent(複製骨骼網格姿勢的Mesh元件) 和 AnimNotify 實現殘影。

 

(1) AGhostTrail  ---- UPoseableMeshComponent的介面 CopyPoseFromSkeletalComponent負責複製骨骼網格姿勢

#pragma once

#include "CoreMinimal.h"
#include "GameFramework/Actor.h"
#include "GhostTrail.generated.h"
class UPoseableMeshComponent;
class USkeletalMeshComponent;

UCLASS(Blueprintable)
class MYPROJECT3_API AGhostTrail : public AActor
{
	GENERATED_BODY()
	
public:	
	// Sets default values for this actor's properties
	AGhostTrail();

protected:
	// Called when the game starts or when spawned
	virtual void BeginPlay() override;

	UPROPERTY(VisibleAnywhere)
		UPoseableMeshComponent* poseableMeshComponent;

	UPROPERTY(EditAnywhere, meta = (AllowedClasses = "Material,MaterialInstance"))
		FSoftObjectPath GhostMaterialPath;

	UPROPERTY(EditAnywhere, Category = "Time", meta = (UIMin = "0.0", UImax = "1.0"))
		float DestroyTime;
	
	FTimerHandle destroyTimerHandle;
private:
	void ReplaceMaterial();
	void DestroyActor();

public:	
	void MakeGhost(USkeletalMeshComponent* skeletalMeshComponent);
	

};
#include "GhostTrail.h"
#include "Components/PoseableMeshComponent.h"
#include "Materials/MaterialInterface.h"
#include "Engine/StreamableManager.h"
#include "TimerManager.h"
#include "Engine/SkeletalMesh.h"


// Sets default values
AGhostTrail::AGhostTrail()
{
 	// Set this actor to call Tick() every frame.  You can turn this off to improve performance if you don't need it.
	PrimaryActorTick.bCanEverTick = false;
	poseableMeshComponent = CreateDefaultSubobject<UPoseableMeshComponent>(TEXT("poseableMeshComponent"));
	poseableMeshComponent->SetupAttachment(RootComponent);
}

// Called when the game starts or when spawned
void AGhostTrail::BeginPlay()
{
	Super::BeginPlay();
	
}


void AGhostTrail::MakeGhost(USkeletalMeshComponent* skeletalMeshComponent)
{
	poseableMeshComponent->SetSkeletalMesh(skeletalMeshComponent->SkeletalMesh);
	poseableMeshComponent->CopyPoseFromSkeletalComponent(skeletalMeshComponent);
	ReplaceMaterial();
}


void AGhostTrail::ReplaceMaterial()
{
	FStreamableManager streamableManager;
	UMaterialInterface* ghostMaterialInterface = streamableManager.LoadSynchronous<UMaterialInterface>(GhostMaterialPath);
	if (nullptr == ghostMaterialInterface)
		return;

	for (int nMaterialIndex = 0; nMaterialIndex < poseableMeshComponent->GetNumMaterials(); ++nMaterialIndex)
	{
		poseableMeshComponent->SetMaterial(nMaterialIndex, ghostMaterialInterface);
	}

	GetWorldTimerManager().SetTimer(destroyTimerHandle, this, &ThisClass::DestroyActor, DestroyTime, false);
}


void AGhostTrail::DestroyActor()
{
	GetWorldTimerManager().ClearTimer(destroyTimerHandle);

}

建立AGhostTrail藍圖:

 

(2)UTestAnimNotify -- 動畫通知事件,產生AGhostTrailActor


#include "CoreMinimal.h"
#include "Animation/AnimNotifies/AnimNotify.h"
#include "TestAnimNotify.generated.h"

UCLASS()
class MYPROJECT3_API UTestAnimNotify : public UAnimNotify
{
	GENERATED_BODY()
	
public:
	UTestAnimNotify();

protected:
	virtual void Notify(USkeletalMeshComponent* MeshComp, UAnimSequenceBase* Animation);
};
#include "TestAnimNotify.h"
#include "GhostTrail.h"
#include "Engine//World.h"
#include "Components/SkeletalMeshComponent.h"


UTestAnimNotify::UTestAnimNotify()
{

}


void UTestAnimNotify::Notify(USkeletalMeshComponent* MeshComp, UAnimSequenceBase* Animation)
{
	UWorld* pWorld = MeshComp->GetWorld();

	const FString ghostTrailActorBpPath = "/Game/Geometry/GhostTrailActor.GhostTrailActor_C";
	UClass* ghostTrailActorClass = LoadClass<AGhostTrail>(MeshComp->GetOuter(), *ghostTrailActorBpPath);
	if (nullptr == ghostTrailActorClass)
		return;

	AGhostTrail* ghostTrailActor = pWorld->SpawnActor<AGhostTrail>(ghostTrailActorClass);
	if (nullptr == ghostTrailActor)
		return;

	AActor* characterActor = MeshComp->GetOwner();
	if (nullptr == characterActor)
		return;

	ghostTrailActor->SetActorLocation(characterActor->GetActorLocation());
	ghostTrailActor->MakeGhost(MeshComp);
}

這裡比較注意的一個大坑是,雖然AnimNotify繼承於UObject, 但在在Notify函式中,直接通過 GetWorld() 獲取的UWorld為空物件,得通過MeshComp->GetWorld()來獲取。因為AnimNotify的不是執行時物件,而是類似資源的一種東西。執行時物件,才有所在“世界UWorld”這個說法。

 

在UE4編輯器裡編輯TestAnimNotify動畫通知事件,這裡是給 “Run”動作

 

 

相關文章