在上一篇文章里,说明Full Body IK 有一个限制的因素,那就是当人物的手被拉的时候 FullBodyBipedIK 不会旋转他的肩膀
它将保持肩骨旋转相对于胸部,因为它在播放动画中。
演示中有一个解决脚本 叫ShoulderRotator ,让我们解析一下吧。
ShoulderRotator:
注意,它将要求FBBIK解决每帧两次,损害性能。对于更具体的、定制的用例,在FBBIK解决之前,您可以在LateUpdate中根据目标的高度或距离向目标旋转肩骨,从而更快地完成任务。
先看看两者有ShoulderRotator脚本和无ShoulderRotator脚本有什么不同:
ShoulderRotator脚本:
无ShoulderRotator脚本:
很明显有ShoulderRotator脚本人物动作比较真实,不僵硬。
using UnityEngine;
using System.Collections;
using RootMotion.FinalIK;
namespace RootMotion.FinalIK {
/// <summary>
/// Shoulder rotator is a workaround for FBBIK not rotating the shoulder bones when pulled by hands.
///ShoulderRotator脚本是基于FBBIK工作的,当我们拉动手时可以选择是否旋转肩膀的骨骼
/// 如果你需要,它就执行,但是需要2次迭代。
/// </summary>
public class ShoulderRotator : MonoBehaviour {
[Tooltip("肩膀的旋转权重")]
public float weight = 1.5f;
[Tooltip("偏移量越大,使用肩部骨骼的范围就越大。")]
public float offset = 0.2f;
private FullBodyBipedIK ik;
private bool skip;
void Start() {
ik = GetComponent<FullBodyBipedIK>();
// 您可以只使用LateUpdate,但是请注意,当您打开角色的animatePhysics(动画物理)时,它不工作。
ik.solver.OnPostUpdate += RotateShoulders;
//OnPostUpdate 后更新: 应该先更新IK,再更新RotateShoulder脚本 (注:个人理解)
}
private void RotateShoulders () {
if (ik == null)
return;
if (ik.solver.IKPositionWeight <= 0f)
return;
// 跳过第二个更新周期
if (skip) {
skip = false;
return;
}
RotateShoulder(FullBodyBipedChain.LeftArm, weight, offset); // 旋转左肩
RotateShoulder(FullBodyBipedChain.RightArm, weight, offset); // 旋转右肩
skip = true;
ik.solver.Update(); // 再次用旋转肩膀更新FBBIK
}
// 旋转人物FBBIK的肩膀
private void RotateShoulder(FullBodyBipedChain chain, float weight, float offset)
{
// Get FromToRotation from the current swing direction of the shoulder to the IK target direction
//使用FromToRotation 从当前旋转方向到IK目标方向旋转
Quaternion fromTo = Quaternion.FromToRotation
(GetParentBoneMap(chain).swingDirection, ik.solver.GetEndEffector(chain).position -GetParentBoneMap(chain).transform.position);
// IK目标的方向
Vector3 toTarget = ik.solver.GetEndEffector(chain).position - ik.solver.GetLimbMapping(chain).bone1.position;
// 肢体长度
float limbLength = ik.solver.GetChain(chain).nodes[0].length + ik.solver.GetChain(chain).nodes[1].length;
// 用肢体长度划分IK目标方向大小,知道肢体被拉了多少。
float delta = (toTarget.magnitude / limbLength) - 1f + offset;
delta = Mathf.Clamp(delta * weight, 0f, 1f);
// 计算肩关节的旋转偏移量
Quaternion rotationOffset = Quaternion.Lerp(Quaternion.identity, fromTo, delta * ik.solver.GetEndEffector(chain).positionWeight * ik.solver.IKPositionWeight);
// 旋转肩部
ik.solver.GetLimbMapping(chain).parentBone.rotation = rotationOffset * ik.solver.GetLimbMapping(chain).parentBone.rotation;
}
// 得到肩部骨
private IKMapping.BoneMap GetParentBoneMap(FullBodyBipedChain chain) {
return ik.solver.GetLimbMapping(chain).GetBoneMap(IKMappingLimb.BoneMapType.Parent);
}
//销毁时删除委托
void OnDestroy() {
if (ik != null) ik.solver.OnPostUpdate -= RotateShoulders;
}
}
}
代码的一些方法可以到官方网站这里去查:
http://www.root-motion.com/finalikdox/html/annotated.html