UE4:AnimSequence曲线和Tracks复制

前言

本文在4.27版本下完成
在编辑AnimSequence的时候,不同的动画可能需要服用Curves和AdditiveLayerTracks,为了提高效率,增加了一下原本编辑器不存在的复制粘贴方法

20220525205853

Curves

查看方法

在AnimSequenceBase.h文件中UAnimSequenceBase类有一个成员变量:RawCurveData

1
2
3
4
5
/**
* Raw uncompressed float curve data
*/
UPROPERTY()
struct FRawCurveTracks RawCurveData;

RawCurveData中的FloatCurves保存了Curves

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
USTRUCT()
struct FRawCurveTracks
{
GENERATED_USTRUCT_BODY()

UPROPERTY()
TArray<FFloatCurve> FloatCurves;

#if WITH_EDITORONLY_DATA
/**
* @note : Currently VectorCurves are not evaluated or used for anything else but transient data for modifying bone track
* Note that it doesn't have UPROPERTY tag yet. In the future, we'd like this to be serialized, but not for now
**/
UPROPERTY(transient)
TArray<FVectorCurve> VectorCurves;

/**
* @note : TransformCurves are used to edit additive animation in editor.
**/
UPROPERTY()
TArray<FTransformCurve> TransformCurves;
#endif // #if WITH_EDITORONLY_DATA
{...}
}

看一下FFloatCurve:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
USTRUCT()
struct FFloatCurve : public FAnimCurveBase
{
GENERATED_USTRUCT_BODY()

/** Curve data for float. */
UPROPERTY()
FRichCurve FloatCurve;

FFloatCurve(){}

FFloatCurve(FSmartName InName, int32 InCurveTypeFlags)
: FAnimCurveBase(InName, InCurveTypeFlags)
{
}

// we don't want to have = operator. This only copies curves, but leaving naming and everything else intact.
void CopyCurve(FFloatCurve& SourceCurve);
ENGINE_API float Evaluate(float CurrentTime) const;
ENGINE_API void UpdateOrAddKey(float NewKey, float CurrentTime);
ENGINE_API void GetKeys(TArray<float>& OutTimes, TArray<float>& OutValues);
void Resize(float NewLength, bool bInsert/* whether insert or remove*/, float OldStartTime, float OldEndTime);
};

对Curves的复制就是要把实例A的RawCurveData.FloatCurves复制后添加到实例B的RawCurveData.FloatCurves中

添加UI和方法

定位到这个编辑器按钮布局相关的地方:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
// AnimTimeLineTrack_Curves.cpp
// 这个函数是生成途中Curves那个按钮的子菜单的地方,可以在这里面增加复制粘贴的选项
TSharedRef<SWidget> FAnimTimelineTrack_Curves::BuildCurvesSubMenu()
{
// 省略一些源码
{...}
// 对于没有Curves的对象可以直接隐藏掉对应的选项
if (AnimSequenceBase->RawCurveData.FloatCurves.Num() > 0)
{
MenuBuilder.AddMenuEntry(
LOCTEXT("CopyAllCurves", "Copy All Curves"),
LOCTEXT("CopyAllCurves_ToolTip", "Copy All Curves"),
FSlateIcon(),
// 绑定复制方法
FUIAction(FExecuteAction::CreateSP(this, &FAnimTimelineTrack_Curves::CopyAllCurves))
);
}
// 定义一个静态变量来存储我们复制的数据
if (CurveNameKeyValueContainer.Num() > 0)
{
MenuBuilder.AddMenuEntry(
LOCTEXT("PasteAllCurves", "Paste All Curves"),
LOCTEXT("PasteAllCurves_ToolTip", "Paste All Curves"),
FSlateIcon(),
// 绑定粘贴方法
FUIAction(FExecuteAction::CreateSP(this, &FAnimTimelineTrack_Curves::PasteAllCurves))
);
}
}

然后实现一下两个方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
TMap< FName, TArray<FRichCurveKey> > FAnimTimelineTrack_Curves::CurveContainer;

void FAnimTimelineTrack_Curves::CopyAllCurves()
{
CurveContainer.Empty();

const FScopedTransaction Transaction(LOCTEXT("AnimCurve_CopyAllCurves", "Copy All Curves"));

UAnimSequenceBase* AnimSequenceBase = GetModel()->GetAnimSequenceBase();
for (FFloatCurve& Curve : AnimSequenceBase->RawCurveData.FloatCurves)
{
CurveContainer.Add(Curve.Name.DisplayName, Curve.FloatCurve.GetCopyOfKeys());
}
}

void FAnimTimelineTrack_Curves::PasteAllCurves()
{
const FScopedTransaction Transaction(LOCTEXT("AnimCurve_PasteAllCurves", "Paste All Curves"));

UAnimSequenceBase* AnimSequenceBase = GetModel()->GetAnimSequenceBase();
AnimSequenceBase->Modify();
USkeleton* Skeleton = AnimSequenceBase->GetSkeleton();
FSmartName NewName;

for (auto& T : CurveContainer) {
const FSmartNameMapping* NameMapping = Skeleton->GetSmartNameContainer(USkeleton::AnimCurveMappingName);
SmartName::UID_Type CurveUid = NameMapping->FindUID(T.Key);
ensureAlways(Skeleton->GetSmartNameByUID(USkeleton::AnimCurveMappingName, CurveUid, NewName));
if (!(AnimSequenceBase->RawCurveData.GetCurveData(NewName.UID) == NULL))
{
AnimSequenceBase->RawCurveData.DeleteCurveData(NewName);
}
AnimSequenceBase->RawCurveData.AddCurveData(NewName);
AnimSequenceBase->MarkRawDataAsModified();

for (auto& KV : T.Value)
{
AnimSequenceBase->RawCurveData.AddFloatCurveKey(NewName, AACF_Editable, KV.Time, KV.Value);
}
}

GetModel()->RefreshTracks();
}

其实就是简单的将原曲线的数据存下来之后,通过RawCurveData的AddCurveData方法将数据添加给新曲线

20220525213823

Tracks

Additive Layer Tracks的复制需要注意Bone,不同角色骨骼不同的时候要注意丢弃不可用数据

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
// AnimTimelineTrack.cpp
// 存储数据用
TMap< FName, FTransformCurve> FAnimTimelineTrack::TracksFTransformCurveContainer;

void FAnimTimelineTrack::CopyAllAdditiveLayerTracks()
{
TracksFTransformCurveContainer.Empty();

const FScopedTransaction Transaction(LOCTEXT("AnimTracks_CopyAllTracks", "Copy All Tracks"));

UAnimSequenceBase* SequenceBase = GetModel()->GetAnimSequenceBase();
// 按照名字和Curve存储数据
for (auto& Curve : SequenceBase->RawCurveData.TransformCurves)
{
TracksFTransformCurveContainer.Add(Curve.Name.DisplayName, Curve);
}
}

void FAnimTimelineTrack::PasteAdditiveLayerTracks()
{
const FScopedTransaction Transaction(LOCTEXT("AnimTracks_PasteAllTracks", "Paste All Tracks"));

UAnimSequenceBase* AnimSequenceBase = GetModel()->GetAnimSequenceBase();
const UAnimSequence* AnimSequence = Cast<UAnimSequence>(AnimSequenceBase);
if(AnimSequence)
{
UE_LOG(LogAnimation, Display, TEXT("Cast AnimSequence Success"));
AnimSequenceBase->Modify();
TArray<FTransformCurve> NewTransformCurvesContainer;
TArray<FName> AnimationTrackNames = AnimSequence->GetAnimationTrackNames();
for(auto& T: TracksFTransformCurveContainer)
{
const FName TrackName = T.Key;
for (auto& Name : AnimationTrackNames)
{
// 只能把相同名称的赋值,不同的无法成功应用,会造成Crash,可以自己看看为什么
if(Name == TrackName)
{
UE_LOG(LogAnimation, Display, TEXT("Animation track : %s ."), *TrackName.ToString());
NewTransformCurvesContainer.Add(T.Value);
break;
}
}
}

// 直接赋值 刷新就行了
AnimSequenceBase->RawCurveData.TransformCurves.Empty();
AnimSequenceBase->RawCurveData.TransformCurves = NewTransformCurvesContainer;
AnimSequenceBase->RefreshCurveData();
GetModel()->RefreshTracks();
}
}

// 这个地方原本是没有SubMenue的需要我们自己写个函数来生成
TSharedRef<SWidget> FAnimTimelineTrack::BuildCurvesSubMenu()
{
FMenuBuilder MenuBuilder(true, GetModel()->GetCommandList());
UAnimSequenceBase* AnimSequenceBase = GetModel()->GetAnimSequenceBase();

MenuBuilder.BeginSection("Tracks", LOCTEXT("AdditiveLayerTracksSection", "Tracks"));
{
if (AnimSequenceBase->RawCurveData.TransformCurves.Num() > 0)
{
MenuBuilder.AddMenuEntry(
LOCTEXT("CopyAllAdditiveLayerTracks", "Copy All Additive Layer Tracks"),
LOCTEXT("CopyAllAdditiveLayerTracks_ToolTip", "Copy All Additive Layer Tracks"),
FSlateIcon(),
FUIAction(FExecuteAction::CreateSP(this, &FAnimTimelineTrack::CopyAllAdditiveLayerTracks))
);
}
if (TracksFTransformCurveContainer.Num() > 0)
{
MenuBuilder.AddMenuEntry(
LOCTEXT("PasteAdditiveLayerTracks", "Paste Additive Layer Tracks"),
LOCTEXT("PasteAdditiveLayerTracks_ToolTip", "Paste Additive Layer Tracks"),
FSlateIcon(),
FUIAction(FExecuteAction::CreateSP(this, &FAnimTimelineTrack::PasteAdditiveLayerTracks))
);
}
}
MenuBuilder.EndSection();
return MenuBuilder.MakeWidget();
}

// 上面那个生成子菜单的函数在这个里面调用
TSharedRef<SWidget> FAnimTimelineTrack::GenerateContainerWidgetForOutliner(const TSharedRef<SAnimOutlinerItem>& InRow)
{
// ...

InnerHorizontalBox->AddSlot()
.AutoWidth()
.HAlign(HAlign_Right)
.VAlign(VAlign_Center)
.Padding(OutlinerRightPadding, 1.0f)
[
PersonaUtils::MakeTrackButton(LOCTEXT("EditTracksButtonText", "Tracks"), FOnGetContent::CreateSP(this, &FAnimTimelineTrack::BuildCurvesSubMenu), MakeAttributeSP(this, &FAnimTimelineTrack::IsHovered))
];

// ...
}

UE4:AnimSequence曲线和Tracks复制
http://muchenhen.com/posts/10235/
作者
木尘痕
发布于
2022年5月25日
许可协议