【UE4】通过动画序列自动生成蒙太奇(In Code)

本文使用 UE4.26 版本,ActionRPG 教程为例,通过代码实现通过动画序列自动生成蒙太奇的功能。
包括:

  1. 批量通过动画生成对应蒙太奇(UE4 自带,但是需要改)
  2. 通过多个动画拼成一个蒙太奇,并设置对应的 Sections

一、功能背景

1.1 需求和场景

  1. 很多动画蒙太奇(AM)是直接通过动画序列(A),在 Content 目录下直接右键新建蒙太奇生成的,不需要有什么修改,理论应该可以批量生成
  2. 有些蒙太奇是通过多个 A 拼接而成,其中 Section 的位置以及名字都是固定的(位置和动画对齐,名字固定)

  在这种情况下,程序直接批量生成,会大幅提升效率,并提升质量(只要保证动画没错,就不会出现策划配错了的情况),而且策划也懒得配。。

1.2 所需背景知识

  只需要很基础的只是,就是蒙太奇上的 Section 和 Slot,以及 Segment 具体是什么,以及是干嘛用的。

UCLASS(config=Engine, hidecategories=(UObject, Length), MinimalAPI, BlueprintType)
class UAnimMontage : public UAnimCompositeBase
{
    
    
	GENERATED_UCLASS_BODY()

	// composite section. 
	UPROPERTY()
	TArray<FCompositeSection> CompositeSections;
	
	// slot data, each slot contains anim track
	UPROPERTY()
	TArray<struct FSlotAnimationTrack> SlotAnimTracks;
}

  上边是蒙太奇类中,这篇文章中关心的两个成员: CompositeSections(简称 Sections)以及 SlotAnimTracks(简称 Slot,主要关心里边的 Segments)。

1.2.1 CompositeSections

  下图中三个红框就是三个 Section,这个蒙太奇的 CompositeSections 大小就是 3。
在这里插入图片描述
  Section 用来把动画分成多段。可以循环,可以跳过等等操作,如下,是技能中,直接跳到蒙太奇某一段的函数:

void UGameplayAbility::MontageJumpToSection(FName SectionName)
{
    
    
	check(CurrentActorInfo);

	UAbilitySystemComponent* const AbilitySystemComponent = GetAbilitySystemComponentFromActorInfo_Checked();
	if (AbilitySystemComponent->IsAnimatingAbility(this))
	{
    
    
		AbilitySystemComponent->CurrentMontageJumpToSection(SectionName);
	}
}
1.2.2 Slot 和 Segment

  下图中,左边这个 Default.Default 是一个 Slot,即这个蒙太奇 SlotAnimTracks 大小是 1(一般就一个 Slot)。
在这里插入图片描述

USTRUCT()
struct FSlotAnimationTrack
{
    
    
	GENERATED_USTRUCT_BODY()

	UPROPERTY(EditAnywhere, Category=Slot)
	FName SlotName;

	UPROPERTY(EditAnywhere, Category=Slot)
	FAnimTrack AnimTrack;
};

  一个 Slot 中有一个 AnimTrack

USTRUCT()
struct FAnimTrack
{
    
    
	GENERATED_USTRUCT_BODY()

	UPROPERTY(EditAnywhere, Category=AnimTrack, EditFixedSize)
	TArray<FAnimSegment>	AnimSegments;
}

  每个 AnimTrack 中有 AnimSegments,每个 Segment 中是一段动画,包括动画序列,开始时间,结束时间,在蒙太奇中的开始时间等。

/** this is anim segment that defines what animation and how **/
USTRUCT()
struct FAnimSegment
{
    
    
	GENERATED_USTRUCT_BODY()

	/** Anim Reference to play - only allow AnimSequence or AnimComposite **/
	UPROPERTY(EditAnywhere, Category=AnimSegment)
	UAnimSequenceBase* AnimReference;

	/** Start Pos within this AnimCompositeBase */
	UPROPERTY(VisibleAnywhere, Category=AnimSegment)
	float	StartPos;

	/** Time to start playing AnimSequence at. */
	UPROPERTY(EditAnywhere, Category=AnimSegment)
	float	AnimStartTime;

	/** Time to end playing the AnimSequence at. */
	UPROPERTY(EditAnywhere, Category=AnimSegment)
	float	AnimEndTime;

	/** Playback speed of this animation. If you'd like to reverse, set -1*/
	UPROPERTY(EditAnywhere, Category=AnimSegment)
	float	AnimPlayRate;

	UPROPERTY(EditAnywhere, Category=AnimSegment)
	int32		LoopingCount;
}

   需要注意的是:

  1. section 的位置不一定需要和每一段动画开头对上,可以在任意位置,不过拖动 Section 靠近动画开头,会有吸力,松手就自动对齐
  2. Segment 的 StartPosAnimStartTime 很容易混淆:
  • StartPos - 这一段动画,在整个蒙太奇的什么时间开始
  • AnimStartTime - 这个 Segment,是从对应的动画的什么时候开始,一般是 0,即从头开始
  • AnimEndTime - 同上,这个 Segment 播放到动画的什么时候结束,一般是动画长度,即完整播完
  • AnimPlayRate - 这个 Segment 中动画播放速度,默认1,例如,如果是 2,则这段动画只需要一半时间就两倍速播完
  • LoopingCount - 这段动画的循环次数,默认 1,如果是0,则这段动画就没了,不是一直循环的意思(一致循环通过 Section 实现)

二、实现效果

三、实现方法

3.1 参考方法

3.1.1 右键生成蒙太奇效果

  参考方法就是在 Content 中右键一个动画序列,然后选择 Create -> Create AnimMontage,的功能,如下图所示:
在这里插入图片描述
  这样生成的蒙太奇包括一个 Section,叫作 Default,和一个 Segment,就是选择的动画。
在这里插入图片描述
  注意:

  1. 这个右键操作生成的蒙太奇,名称默认为 xx_Montage,xx 是动画资源的名称(如果这个名字的蒙太奇已经有了,则会是 xx_Montage1,以此类推),所以不会出现把原有文件覆盖的问题;
  2. 这个操作可以批量生成,选十个动画,然后右键生成蒙太奇,就是会生成这十个动画对应的十个蒙太奇,并不会十个动画拼接成一个蒙太奇;

3.1.2 右键生成蒙太奇代码

  这种有按钮,有界面的功能,一般直接搜索对应的 Tips 文字就能找到功能,即 Creates an AnimMontage using the selected anim sequence

MenuBuilder.AddMenuEntry(
		LOCTEXT("AnimSequence_NewAnimMontage", "Create AnimMontage"),
		LOCTEXT("AnimSequence_NewAnimMontageTooltip", "Creates an AnimMontage using the selected anim sequence."),
		FSlateIcon(FEditorStyle::GetStyleSetName(), "ClassIcon.AnimMontage"),
		FUIAction(
			FExecuteAction::CreateSP(this, &FAssetTypeActions_AnimSequence::ExecuteNewAnimMontage, Sequences),
			FCanExecuteAction()
			)
		);

  即 ExecuteNewAnimMontage 函数,里边调用的就是 FAssetTypeActions_AnimSequence::CreateAnimationAssets

/** Creates animation assets of the supplied class */
void CreateAnimationAssets(
	const TArray<TWeakObjectPtr<UAnimSequence>>& AnimSequences,  /* 所选中的动画序列资源,可以是多个 */
	TSubclassOf<UAnimationAsset> AssetClass, /* 要生成的动画资源类型, 即 - UAnimMontage::StaticClass() */
	UFactory* AssetFactory,                  /* 蒙太奇生成工厂 - NewObject<UAnimMontageFactory>() */
	const FString& InSuffix,                 /* 生成的资源名称后缀, 默认 - TEXT("_Montage"); */
	FOnConfigureFactory OnConfigureFactory) const;

  这个方法中根据 AnimSequences 的数量分了两部分,一种是只有一个动画资源的,直接生成对应的蒙太奇;一种是有多个动画资源的,生成多个蒙太奇。实际只需要看一个动画的部分:

auto AnimSequence = AnimSequences[0].Get();

if ( AnimSequence )
{
    
    
	// Determine an appropriate name for inline-rename
	FString Name;
	FString PackageName;
	CreateUniqueAssetName(AnimSequence->GetOutermost()->GetName(), InSuffix, PackageName, Name);

	if (OnConfigureFactory.IsBound())
	{
    
    
		if (OnConfigureFactory.Execute(AssetFactory, AnimSequence))
		{
    
    
			FContentBrowserModule& ContentBrowserModule = FModuleManager::LoadModuleChecked<FContentBrowserModule>("ContentBrowser");
			ContentBrowserModule.Get().CreateNewAsset(Name, FPackageName::GetLongPackagePath(PackageName), AssetClass, AssetFactory);
		}
	}
}

  其中 CreateUniqueAssetName 是判断生成的资源叫什么名字,默认 xx_Montage,如果有冲突,则 xx_Montage1,实际生成,就是 CreateNewAsset,上边的 OnConfigureFactory 的用处就是绑定一下动画是哪个。

3.2 实际方法

3.2.1 批量通过动画生成对应蒙太奇

  其实 UE 自带的就是这个功能,不需要怎么改,但是由于名字会自动变,如果想生成特定名字,且覆盖原有的话,需要改引擎。

UObject* UAssetToolsImpl::CreateAsset(
	const FString& AssetName, 
	const FString& PackagePath, 
	UClass* AssetClass, 
	UFactory* Factory, 
	FName CallingContext)
{
    
    
	const FString PackageName = UPackageTools::SanitizePackageName(PackagePath + TEXT("/") + AssetName);

	// Make sure we can create the asset without conflicts
	if (!CanCreateAsset(AssetName, PackageName, LOCTEXT("CreateANewObject", "Create a new object")) )
	{
    
    
		return nullptr;
	}

	// 生成资源 blabla

	return NewObj;
}

  最终生成资源,调到的就是最后这个函数,但是在生成资源之前,会进行是不是 Exist 的检查,如果有,则会弹出消息框,需要点。
在这里插入图片描述

UObject* ExistingObject = StaticFindObject( UObject::StaticClass(), Pkg, *AssetName );
if( ExistingObject != nullptr )
{
    
    
	// Object already exists in either the specified package or another package.  Check to see if the user wants
	// to replace the object.
	bool bWantReplace =
		EAppReturnType::Yes == FMessageDialog::Open(
			EAppMsgType::YesNo,
			EAppReturnType::No,
			FText::Format(
				NSLOCTEXT("UnrealEd", "ReplaceExistingObjectInPackage_F", "An object [{0}] of class [{1}] already exists in file [{2}].  Do you want to replace the existing object?  If you click 'Yes', the existing object will be deleted.  Otherwise, click 'No' and choose a unique name for your new object." ),
				FText::FromString(AssetName),
				FText::FromString(ExistingObject->GetClass()->GetName()), 
				FText::FromString(PackageName) ) 
		);
}

  消息框自定义可以看这篇
  如果有二十个冲突的蒙太奇,则需要点二十下,如果不想点,就要改引擎,CreateAsset 加个参数 - 要不要考虑冲突,然后直接不检查了。

3.2.2 多个动画拼成一个蒙太奇

  多个动画拼接成一个蒙太奇(如 1.2.2 中所示),可以先用一个动画,按照 3.2.1 的方法生成一个蒙太奇,然后调整对应的 CompositeSections以及 SlotAnimTracks 即可。
  注意:创建了 Section 之后,需要设置 NextSectionName,不然只会播一个 Section 的动画;如果希望其中某一段循环,让它的下一段是自己即可:

CompositeSections[3].NextSectionName = CompositeSections[3].SectionName;

四、参考资料

  1. Animation Montage Overview(官方 docs)
  2. Introduction to AnimMontage(官方 blog)

猜你喜欢

转载自blog.csdn.net/Bob__yuan/article/details/116767002