项目中遇到需要角色在播动画的时候某几个指定骨骼指向目标的需求。
如果只是手的话可以做8方向动画再用动画插值,或者用IK功能。
因为我们的需求比较简单,只是指向(没有握住之类的),但是任何动画中的任意骨骼都有可能需要指向,所以做动画融合比较费劲,就想到直接控制骨骼朝向来实现。
Unity官方有一个Demo实现了用Animation Job来做的LookAt:
https://blogs.unity3d.com/cn/2018/08/27/animation-c-jobs/
于是我们把这个放到项目中用了一段时间,官方的例子中没有考虑根节点的偏移和旋转,这个可以处理,但是实测之后发现指向的并不精准,并且由于动画流内部的数据很难调试,导致修复问题比较困难,虽然Job System性能很高,但是在角色数量不多,骨骼数不多的情况下直接计算也是可以的,以下是修改的直接计算骨骼朝向的脚本:
using System;
using UnityEngine;
///
///朝向功能测试脚本
///
public class TestLookAt : MonoBehaviour
{
public enum Axis
{
Forward = 1,
Back,
Up,
Down,
Left,
Right
}
//关节
public Joints[] joints;
//目标
GameObject m_Target;
//创建指向
void CreateLookat()
{
if (joints == null || joints.Length == 0)
return;
var targetPosition = joints[0].bone.position + gameObject.transform.rotation * Vector3.forward;
m_Target = GameObject.CreatePrimitive(PrimitiveType.Sphere);
m_Target.transform.position = targetPosition;
m_Target.transform.rotation = Quaternion.identity;
m_Target.transform.localScale = Vector3.one * 0.1f;
return;
}
private void LateUpdate()
{
if (m_Target != null && joints != null)
{
for (int i = 0; i < joints.Length; ++i)
{
SingleBoneLookAt(joints[i], m_Target.transform);
}
}
}
private void SingleBoneLookAt(Joints joint, Transform target)
{
var selfTran = joint.bone;
//
//方法一
//计算出自身到目标位置的方向向量,此方法只能让forward朝向目标,可以设置朝向速度
//selfTran.rotation = Quaternion.Slerp(selfTran.rotation, Quaternion.LookRotation(target.position - selfTran.position), 1 * Time.deltaTime);
//
//方法二
//这种算法可以让某一跟指定的轴朝向目标,也可以控制其他轴的对应位置
//但是不能让其他轴的对应位置和之前一致,因此不适用于动画系统中改变骨骼
//var pos = selfTran.position;
//var rotation = selfTran.rotation;
//var targetDir = target.transform.position - pos;
//var fromDir = selfTran.up; //rotation * GetAxisVector(joints[0].axis);
//var axis = Vector3.Cross(fromDir, targetDir).normalized;
//var angle = Vector3.Angle(fromDir, targetDir);
//这里通过乘以不同的四元素达到使用不同的轴朝向目标的效果
//Quaternion upToForward = Quaternion.FromToRotation(Vector3.up, Vector3.forward);
//Quaternion rightToForward = Quaternion.FromToRotation(Vector3.right, Vector3.forward);
//var forwardRot = Quaternion.LookRotation(targetDir.normalized, selfTran.rotation * -Vector3.right);
//selfTran.rotation = forwardRot * rightToForward;
//
//方法三,参考的官方示例
//这种方法可以以当前指定轴最短直线路径的方法朝向目标,可用于动画系统中的指向
var pos = selfTran.position;
var rotation = selfTran.rotation;
var targetDir = target.transform.position - pos;
//指定哪根轴朝向目标
var fromDir = selfTran.rotation * GetAxisVector(joint.axis);
//计算垂直于当前方向和目标方向的轴
var axis = Vector3.Cross(fromDir, targetDir).normalized;
//计算当前方向和目标方向的夹角
var angle = Vector3.Angle(fromDir, targetDir);
//将当前朝向向目标方向旋转一定角度,这个角度值可以做插值
selfTran.rotation = Quaternion.AngleAxis(angle, axis) * rotation;
}
private void OnGUI()
{
if (GUILayout.Button("LookAt"))
{
CreateLookat();
}
if (GUILayout.Button("EndLook"))
{
m_Target = null;
}
//if (GUILayout.Button("SetWeight"))
//{
// SetWeight(lookAtWeight);
//}
}
Vector3 GetAxisVector(Axis axis)
{
switch (axis)
{
case Axis.Forward:
return Vector3.forward;
case Axis.Back:
return Vector3.back;
case Axis.Up:
return Vector3.up;
case Axis.Down:
return Vector3.down;
case Axis.Left:
return Vector3.left;
case Axis.Right:
return Vector3.right;
}
return Vector3.forward;
}
[Serializable]
public class Joints
{
public Transform bone;
public Axis axis = Axis.Up;
public float Angle = 45.0f;
public int weight = 100;
}
}
挂在角色身上,挂载上对应的骨骼,和目标之后就可以测试了,效果和官方的差不多,精度更高,并且方便调试,只是性能比官方的例子低一些,也可以手动集成到Job System中。