AdvancedLocomotionV4學習筆記(2)——AnimModifier
前言
在AdvancedLocomotionV4中大量使用同步組、AnimNotify、AnimationCurve。但那麼多動畫Asset都要手動新增,我肯定是拒絕的。
幸虧Ue4在4。16推出了AnimModifier功能以進行自動化操作。文件地址:
本人有幸找到了一篇相關Blog:
並以此為基礎編寫了生成LocoMotion相關同步組、AnimNotify、AnimationCurve的AnimModifier(主要還是因為作者寫的東西不太行),並且已經將其放入之前寫的外掛中,如覺得好用請給我點贊。
使用效果與使用說明
因為圖片上傳有大小限制,所以我儘可能得壓縮了圖片,大家湊合得看看吧。
PathFilter:路徑過濾,在批次處理時,只會處理指定路徑的AnimSequence。(使用前需要實現修改好對應變數的預設值)
MotionCheckDirection:骨骼位移的判斷軸向。
CreateBonePositionCurve:生成以Bone為名稱的曲線,值為對應時間的骨骼高度。
CreateFootPositionCurve:建立類似AdvancedLocomotionV4中的Feet_Position曲線。
NormalizeCurve:將BonePosition曲線歸一化。
FeetBones:定義腳部骨骼,一般使用foot_l與foot_r,offset為骨骼判斷偏移值。
AnimNotfiyClass:指定需要新增的AnimNotify。
StepOnValue:腳著地的判斷高度。
StepNextValue:腳離地的判斷值。(一般是1~5)
InterpMode:BonePosition曲線的插值模式。
AnimModifier生成的結果 VS AdvancedLocomotionV4原版曲線
可以看得出 AdvancedLocomotionV4的同步組與AnimNotify略微往前。但我認為我的生成的才是正確的。
具體實現說明
本人已經在藍圖中做了詳細的註釋,所以直接去看藍圖也是沒問題的。下面將介紹具體實現方式:
AnimModifier有2個需要實現的事件,分別是OnApply與OnRevert,指代了應用AnimModifier生成資料與撤銷AnimModifier生成的資料。以下展示的是外掛中OnApply事件的實現。(OnRevert只包含下圖的3個節點)
ShouldApply
透過判斷設定的PathFilter來判斷是否處理AnimSequence。
RemoveAll
移除對應的Notify、Curve、Sync軌道。
建立所需軌道與取得所需資料
在進行完初始化後,按照設定的變數建立各種軌道:
按照設定的腳部骨骼進行迴圈,生成對應的曲線軌道,並且取得AnimSequence的時長,之後再對AnimSequence的每一幀進行逐幀處理。
圖A
透過GetBoneLocationRelativeToAtTime函式計算相對位移之後再根據的軸向取得偏移值(一般都是Z軸方向)
GetBoneLocationRelativeToAtTime
透過FindBonePathToRoot函式取得指定骨骼到根骨骼的骨骼鏈陣列,之後將各個的骨骼的Translation值相乘,以得到指定骨骼與根骨骼的相對位移值。
資料處理
因為之後需要對生成的曲線進行歸一化處理,所以在紅框處,我將所取得的每幀曲線資料進行儲存。
白框處先是透過計算腳部骨骼的相對位移來判斷腳是踩到地還是抬起來,之後再設定同步組。
藍框處則是在判斷左右腳之後設定了對應的曲線值(Feet_Position)。
這裡我重寫了AddFloatCurvekeyWithType函式,這樣就可以給關鍵幀指定插值方式了。具體程式碼我放在本文最後了。
迴圈完每一幀之後
歸一化曲線
新增曲線(對應骨骼名稱曲線),之後將資料清零。進行下一個骨骼的迴圈。
最後處理
Feet_Position曲線的開頭與結尾處沒有關鍵幀,此時在進行判斷後新增。
最後再執行FinalizeBoneAnimation。
AnimationBlueprintLibrary擴充套件
AnimModifier中的新增曲線函式AddFloatCurveKey與AddFloatCurveKeys沒有提供修改曲線插值型別選項,所以我簡單擴充套件了一下AnimationBlueprintLibrary,具體的可以參考我的外掛程式碼。(懶得提交Pull Request了)
void
UAnimBlueprintLibrary
::
AddFloatCurveKeysWithType
(
UAnimSequence
*
AnimationSequence
,
FName
CurveName
,
const
TArray
<
float
>&
Times
,
const
TArray
<
float
>&
Values
,
EInterpCurveMode
InterpMode
)
{
if
(
AnimationSequence
)
{
if
(
Times
。
Num
()
==
Values
。
Num
())
{
AddCurveKeysInternal
<
float
,
FFloatCurve
>
(
AnimationSequence
,
CurveName
,
Times
,
Values
,
ERawCurveTrackTypes
::
RCT_Float
,
InterpMode
);
}
else
{
UE_LOG
(
LogAnimBlueprintLibrary
,
Warning
,
TEXT
(
“Number of Time values %i does not match the number of Values %i in AddFloatCurveKeys”
),
Times
。
Num
(),
Values
。
Num
());
}
}
else
{
UE_LOG
(
LogAnimBlueprintLibrary
,
Warning
,
TEXT
(
“Invalid Animation Sequence for AddFloatCurveKeys”
));
}
}
template
<
typename
DataType
,
typename
CurveClass
>
void
UAnimBlueprintLibrary
::
AddCurveKeysInternal
(
UAnimSequence
*
AnimationSequence
,
FName
CurveName
,
const
TArray
<
float
>&
Times
,
const
TArray
<
DataType
>&
KeyData
,
ERawCurveTrackTypes
CurveType
,
EInterpCurveMode
InterpMode
)
{
checkf
(
Times
。
Num
()
==
KeyData
。
Num
(),
TEXT
(
“Not enough key data supplied”
));
const
FName
ContainerName
=
RetrieveContainerNameForCurve
(
AnimationSequence
,
CurveName
);
if
(
ContainerName
!=
NAME_None
)
{
// Retrieve smart name for curve
const
FSmartName
CurveSmartName
=
RetrieveSmartNameForCurve
(
AnimationSequence
,
CurveName
,
ContainerName
);
// Retrieve the curve by name
CurveClass
*
Curve
=
static_cast
<
CurveClass
*>
(
AnimationSequence
->
RawCurveData
。
GetCurveData
(
CurveSmartName
。
UID
,
CurveType
));
if
(
Curve
)
{
const
int32
NumKeys
=
KeyData
。
Num
();
for
(
int32
KeyIndex
=
0
;
KeyIndex
<
NumKeys
;
++
KeyIndex
)
{
//Curve->UpdateOrAddKey(, );
FKeyHandle
handle
=
Curve
->
FloatCurve
。
UpdateOrAddKey
(
Times
[
KeyIndex
],
KeyData
[
KeyIndex
]);
FRichCurveKey
&
InKey
=
Curve
->
FloatCurve
。
GetKey
(
handle
);
InKey
。
InterpMode
=
RCIM_Linear
;
InKey
。
TangentWeightMode
=
RCTWM_WeightedNone
;
InKey
。
TangentMode
=
RCTM_Auto
;
if
(
InterpMode
==
CIM_Constant
)
{
InKey
。
InterpMode
=
RCIM_Constant
;
}
else
if
(
InterpMode
==
CIM_Linear
)
{
InKey
。
InterpMode
=
RCIM_Linear
;
}
else
{
InKey
。
InterpMode
=
RCIM_Cubic
;
if
(
InterpMode
==
CIM_CurveAuto
||
InterpMode
==
CIM_CurveAutoClamped
)
{
InKey
。
TangentMode
=
RCTM_Auto
;
}
else
if
(
InterpMode
==
CIM_CurveBreak
)
{
InKey
。
TangentMode
=
RCTM_Break
;
}
else
if
(
InterpMode
==
CIM_CurveUser
)
{
InKey
。
TangentMode
=
RCTM_User
;
}
}
}
AnimationSequence
->
BakeTrackCurvesToRawAnimation
();
}
}
}