前言

在AdvancedLocomotionV4中大量使用同步組、AnimNotify、AnimationCurve。但那麼多動畫Asset都要手動新增,我肯定是拒絕的。

AdvancedLocomotionV4學習筆記(2)——AnimModifier

幸虧Ue4在4。16推出了AnimModifier功能以進行自動化操作。文件地址:

本人有幸找到了一篇相關Blog:

並以此為基礎編寫了生成LocoMotion相關同步組、AnimNotify、AnimationCurve的AnimModifier(主要還是因為作者寫的東西不太行),並且已經將其放入之前寫的外掛中,如覺得好用請給我點贊。

AdvancedLocomotionV4學習筆記(2)——AnimModifier

使用效果與使用說明

因為圖片上傳有大小限制,所以我儘可能得壓縮了圖片,大家湊合得看看吧。

AdvancedLocomotionV4學習筆記(2)——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學習筆記(2)——AnimModifier

可以看得出 AdvancedLocomotionV4的同步組與AnimNotify略微往前。但我認為我的生成的才是正確的。

具體實現說明

本人已經在藍圖中做了詳細的註釋,所以直接去看藍圖也是沒問題的。下面將介紹具體實現方式:

AnimModifier有2個需要實現的事件,分別是OnApply與OnRevert,指代了應用AnimModifier生成資料與撤銷AnimModifier生成的資料。以下展示的是外掛中OnApply事件的實現。(OnRevert只包含下圖的3個節點)

AdvancedLocomotionV4學習筆記(2)——AnimModifier

ShouldApply

透過判斷設定的PathFilter來判斷是否處理AnimSequence。

AdvancedLocomotionV4學習筆記(2)——AnimModifier

RemoveAll

移除對應的Notify、Curve、Sync軌道。

AdvancedLocomotionV4學習筆記(2)——AnimModifier

建立所需軌道與取得所需資料

在進行完初始化後,按照設定的變數建立各種軌道:

AdvancedLocomotionV4學習筆記(2)——AnimModifier

按照設定的腳部骨骼進行迴圈,生成對應的曲線軌道,並且取得AnimSequence的時長,之後再對AnimSequence的每一幀進行逐幀處理。

AdvancedLocomotionV4學習筆記(2)——AnimModifier

圖A

透過GetBoneLocationRelativeToAtTime函式計算相對位移之後再根據的軸向取得偏移值(一般都是Z軸方向)

AdvancedLocomotionV4學習筆記(2)——AnimModifier

GetBoneLocationRelativeToAtTime

透過FindBonePathToRoot函式取得指定骨骼到根骨骼的骨骼鏈陣列,之後將各個的骨骼的Translation值相乘,以得到指定骨骼與根骨骼的相對位移值。

資料處理

因為之後需要對生成的曲線進行歸一化處理,所以在紅框處,我將所取得的每幀曲線資料進行儲存。

白框處先是透過計算腳部骨骼的相對位移來判斷腳是踩到地還是抬起來,之後再設定同步組。

AdvancedLocomotionV4學習筆記(2)——AnimModifier

藍框處則是在判斷左右腳之後設定了對應的曲線值(Feet_Position)。

AdvancedLocomotionV4學習筆記(2)——AnimModifier

這裡我重寫了AddFloatCurvekeyWithType函式,這樣就可以給關鍵幀指定插值方式了。具體程式碼我放在本文最後了。

迴圈完每一幀之後

歸一化曲線

AdvancedLocomotionV4學習筆記(2)——AnimModifier

新增曲線(對應骨骼名稱曲線),之後將資料清零。進行下一個骨骼的迴圈。

AdvancedLocomotionV4學習筆記(2)——AnimModifier

最後處理

Feet_Position曲線的開頭與結尾處沒有關鍵幀,此時在進行判斷後新增。

AdvancedLocomotionV4學習筆記(2)——AnimModifier

最後再執行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

();

}

}

}