【UE4】Automatically generate montages through animation sequences (In Code)

This article uses the UE4.26 version and the ActionRPG tutorial as an example to implement the function of automatically generating a montage through animation sequences through code.
include:

  1. Generate corresponding montages through animation in batches (UE4 comes with it, but needs to be modified)
  2. Create a montage through multiple animations and set corresponding Sections

1. Functional background

1.1 Requirements and scenarios

  1. Many animated montages (AM) are generated directly through the animation sequence (A). Right-click to create a new montage in the Content directory. No modification is required. In theory, it should be able to be generated in batches.
  2. Some montages are spliced ​​together by multiple A's, in which the position and name of the Section are fixed (the position is aligned with the animation, and the name is fixed)

  In this case, the program directly generates batches, which will greatly improve efficiency and improve quality (as long as the animation is correct, there will be no mismatching of planning), and the planning is too lazy to match. .

1.2 Required background knowledge

  All that is needed is the very basics, which are the Section and Slot on the montage, as well as what exactly the Segment is and what it is used for.

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;
}

  Above are the two members of the montage class that this article is concerned about: CompositeSections (referred to as Sections) and SlotAnimTracks (referred to as Slot, mainly concerned with the Segments inside).

1.2.1 CompositeSections

  The three red boxes in the picture below are three Sections, and CompositeSectionsthe size of this montage is 3.
Insert image description here
  Section is used to divide animation into multiple segments. You can loop, skip, and other operations. The following is a function in the skill that jumps directly to a certain section of the montage:

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

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

  In the picture below, the Default.Default on the left is a Slot, that is, SlotAnimTracksthe size of this montage is 1 (usually just one Slot).
Insert image description here

USTRUCT()
struct FSlotAnimationTrack
{
    
    
	GENERATED_USTRUCT_BODY()

	UPROPERTY(EditAnywhere, Category=Slot)
	FName SlotName;

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

  There is one in a Slot AnimTrack.

USTRUCT()
struct FAnimTrack
{
    
    
	GENERATED_USTRUCT_BODY()

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

  Each Segment AnimTrackcontains AnimSegmentsan animation, including animation sequence, start time, end time, start time in montage, etc.

/** 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;
}

   have to be aware of is:

  1. The position of the section does not necessarily need to be aligned with the beginning of each animation. It can be at any position. However, if you drag the Section close to the beginning of the animation, there will be suction, and it will automatically align when you release it.
  2. Segment 's StartPosand AnimStartTimeare easily confused:
  • StartPos- At what time in the entire montage does this animation begin?
  • AnimStartTime- This Segment starts from the corresponding animation, usually 0, which means starting from the beginning.
  • AnimEndTime- Same as above, when does this Segment play to the end of the animation? It is usually the length of the animation, that is, it is played completely.
  • AnimPlayRate- The animation playback speed in this Segment defaults to 1. For example, if it is 2, the animation will only take half the time to be played at twice the speed.
  • LoopingCount- The number of loops for this animation, the default is 1. If it is 0, the animation will be gone, which does not mean it will loop forever (consistent loops are implemented through Section)

2. Realize the effect

3. Implementation method

3.1 Reference method

3.1.1 Right-click to generate montage effect

  The reference method is to right-click an animation sequence in Content, and then select Create -> Create AnimMontagethe function, as shown in the figure below:
Insert image description here
  The montage generated in this way includes a Section, called Default, and a Segment, which is the selected animation.
Insert image description here
  Notice:

  1. The name of the montage generated by this right-click operation defaults to xx_Montage, where xx is the name of the animation resource (if a montage with this name already exists, it will be xx_Montage1, and so on), so there will be no problem of overwriting the original file;
  2. This operation can be generated in batches. Select ten animations, and then right-click to generate a montage. It will generate ten montages corresponding to these ten animations, and will not splice the ten animations into one montage;

3.1.2 Right-click to generate montage code

  This kind of function has buttons and interfaces. Generally, you can find the function by directly searching for the corresponding Tips text, that is 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()
			)
		);

  That is ExecuteNewAnimMontage, the function is called inside 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;

  This method is divided into two parts according to the number of AnimSequences. One is for having only one animation resource, and the corresponding montage is directly generated; the other is for having multiple animation resources, and multiple montages are generated. Actually you only need to watch one part of the animation:

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);
		}
	}
}

  Among them CreateUniqueAssetNameis to determine the name of the generated resource. The default is xx_Montage. If there is a conflict, xx_Montage1 is actually generated. The purpose of CreateNewAssetthe above OnConfigureFactoryis to bind which animation it is.

3.2 Practical methods

3.2.1 Generate corresponding montages through animation in batches

  In fact, UE comes with this function and does not need to be changed. However, since the name will change automatically, if you want to generate a specific name and overwrite the original one, you need to change the engine.

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;
}

  When the resource is finally generated, the last function is called, but before the resource is generated, it will be checked to see if it exists. If so, a message box will pop up, which you need to click.
Insert image description here

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) ) 
		);
}

You can read this article   to customize the message box .
  If there are twenty conflicting montages, you need to click twenty times. If you don't want to click, you need to change the engine and CreateAssetadd a parameter - whether to consider conflicts, and then just stop checking it.

3.2.2 Multiple animations combined into one montage

  To splice multiple animations into a montage (as shown in 1.2.2), you can first use an animation to generate a montage according to the method in 3.2.1, and then adjust the corresponding CompositeSections and SlotAnimTracks.
  Note: After creating a Section, you need to set it up NextSectionName, otherwise only one Section animation will be played; if you want one of the sections to loop, just make the next section your own:

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

4. Reference materials

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

Guess you like

Origin blog.csdn.net/Bob__yuan/article/details/116767002