關於這個效果的名稱,我一直沒找到一個比較正式的說法。Spring Bone這個說法是來自於Anima2D這個插件中的一個演示用的腳本,我直接譯成彈簧骨骼。
一般常見於對人物的頭發的模擬上。
當然也可以直接用在普通物體上
效果的實現是通過改變物體的旋轉進行的。在LateUpdate執行代碼。我們在腳本中保存上一幀的骨骼末端位置,當運行當前幀時,如AnimatorController之類的組件會在Update中將物體的旋轉設置為指定值。我們在LateUpdate中,通過保存的上一幀的骨骼末端位置以及當前的旋轉值,進行計算,得到一個類似的插值位置,然后轉化為旋轉值,將物體旋轉到對應位置。
部分代碼如下:
currentTipPos = transform.TransformPoint(springEnd);
currentTipPos = Vector3.Lerp(lastFrameTip, currentTipPos, Time.deltaTime);
currentTipPos = springLength * (currentTipPos - transform.position).normalized + transform.position; //clamp length.
transform.rotation =
Quaternion.FromToRotation(transform.TransformDirection(springEnd), (currentTipPos - transform.position).normalized)
* transform.rotation;
currentTipPos = springLength * (currentTipPos - transform.position).normalized + transform.position; //clamp length.
transform.rotation =
Quaternion.FromToRotation(transform.TransformDirection(springEnd), (currentTipPos - transform.position).normalized)
* transform.rotation;
這樣的實現快速有效,但是最終效果僅僅是物體的運動變為慢慢靠近目標點,顯得不夠真實。
想要加入類似彈簧的效果,我們需要進行真實的力、速度的計算。我們保存當前的速度,根據位置計算當前受到的力,然后根據力修改速度,通過這個速度去修改目標位置。
currentTipPos = transform.TransformPoint(springEnd);
var force = bounciness * (currentTipPos - lastFrameTip); //spring force.
force += stiffness * (currentTipPos - transform.position).normalized; //stiffness
force -= dampness * velocity; //damp force.
velocity = velocity + force * Time.deltaTime; //v = v0 + at. we don't need integration here, you won't notice any "wrong".
currentTipPos = lastFrameTip + velocity * Time.deltaTime; //s = s0 + vt
currentTipPos = springLength * (currentTipPos - transform.position).normalized + transform.position; //clamp length.
transform.rotation =
Quaternion.FromToRotation(transform.TransformDirection(springEnd), (currentTipPos - transform.position).normalized)
* transform.rotation;
我設置了3個力的選項,分別是bounciness彈性力,提供“歸位“的力,力由當前骨骼末端指向歸位時的骨骼末端,stiffness剛性力,提供保持原狀的力,力方向往骨骼方向延長,以及dampbess阻力,沿着速度反方向。
通過設置三個力的大小,可以實現不同的效果。最終效果如文章開篇的圖2
此外還有一個細節需要注意,骨骼更新的順序應該嚴格的遵守從父物體的骨骼更新到子物體。不然可能會出現奇怪的情況。