首页 > 代码库 > RootMotionComputer 根运动计算机

RootMotionComputer 根运动计算机

using UnityEngine;using System.Collections;/* * ---------------------------------------------------------------------------- * Creation Info * ---------------------------------------------------------------------------- * Root Motion Computer * Version: 1.2 * Date: 2010.12.08 * Author: Adam Mechtley (http://adammechtley.com) * Created for Mixamo, Inc. (http://mixamo.com) *  * ---------------------------------------------------------------------------- * Description: * ---------------------------------------------------------------------------- * Like many other game engines, Unity was developed with the intent that * character animations be created in-place, as though the characters were * moving and acting on a treadmill, and the characters‘ root nodes would then * be moved programmatically using physics, a character controller, or another * procedural mechanism. Unfortunately, many character movements, such as a * zombie lurching forward, do not move forward with a constant velocity. * Consequently, such motions can introduce foot sliding or dramatic bobbing * back and forth when moved procedurally. *  * The Root Motion Computer was designed to solve this problem, particularly by * leveraging the fact that motion-capture animations actually capture a moving * actor rather than an actor performing in place. Using all of the default * settings, the root motion computer uses the movement of the character‘s * pelvis to move move the root node. Thus, in-place motions will play back in- * place, while motions captured with forward or sideways movement will * actually move the character when looping, rather than snapping back to the * starting location. Users can also configure the computer‘s various settings * to instead pipe its output to another script to drive the velocity of a * character controller or another movement tool. *  * ---------------------------------------------------------------------------- * Usage: * ---------------------------------------------------------------------------- * You can place this script anywhere in your project folder. Because it is * written in C#, however, you must put it in your Plugins folder in your * project if you are coding in UnityScript. *  * In many cases, you can simply add the component to your character and it * should "just work." Otherwise you can manually specify various properties: *  * isManagedExternally: Specifies that another script will invoke Initialize() *     and ComputeRootMotion(). This is used if you need to manage the *     execution order to prevent the computer from interfering with animation *     requests you may make in your own Start(), Awake(), or LateUpdate() *     functions. * rootNode: The transform that is actually moved, whether by the computer or *     by another mechanism like a character controller. * anim: The animation component from which to process AnimationStates. * pelvis: The character‘s pelvis transform. This object is used to determine *     changes in the character‘s overall position or rotation. * pelvisForwardAxis: The axis on the pelvis that points to the character‘s *     front in the bind pose. * pelvisRightAxis: The axis on the pelvis that points to the character‘s right *     in the bind pose. * computationMode: Specifies whether the computer should compute only forward *     translation, all translation (forward-back and side-to-side), or all *     translation as well as turning rotation. * applyMotion: Specifies whether the computation results should be applied to *     rootNode. Set to false if output is going to be read and processed by *     another script to move a character controller, for example. * deltaPosition: Represents the character‘s change in translation since the *     last frame, given in the space of rootNode. * deltaRotation: Represents the character‘s change in orientation since the *     last frame, given in the space of rootNode. * deltaEulerAngles: Same as deltaRotation but converted into Euler angles. * isDebugMode: Renders pelvis axes in the scene view when the character is *     selected. Renders axis tripods to illustrate the position and *     orientation of the pelvis and root node when the game is playing. * debugGizmoSize: A scalar for the debug gizmos. *  * ---------------------------------------------------------------------------- * Notes and Limitations: * ---------------------------------------------------------------------------- * 1. The computer is currently only designed to handle movement of characters * forward-back and side-to-side with rotation about their up-axes. As such, it * offers no generalized mechanism for adjusting the height of a character * (e.g. jumping, going up stairs). Because the computer operates using delta  * values in LateUpdate(), however, you can implement your own custom logic for * adjusting a character‘s height in your own movement code and the computer * will simply work on top if it. *  * 2. As of Unity 2.x, there is no way to query the post-normalized weights of * AnimationStates. The computer attempts to work around this by rebuilding * normalized weights for each state using the same process that Unity uses * (applying weights to top-most layers first, and then working down). *  * 3. The computer currently assumes that the clip for any particular * AnimationState will not change. (Generally speaking, it should not once it * has been added to the animation component anyway.) *  * 4. The computer should support adding new clips at run-time, though the * feature has been tested only briefly. *  * 5. Because of how rotation computation works, if a character is on his * stomach and then rolls onto his back (or vice-versa), then it is inadvisable * to blend in other motions at the time the actual roll occurs (unless it is * another synchronized rolling motion). * */// a struct to store information about all of the animation states/// <summary>/// 存放动画状态的结构体/// </summary>public struct AnimInfo{    /// <summary>    /// 当前动作的规范化时间,直接指向对应动画的规范化时间    /// </summary>    public float currentNormalizedTime;    /// <summary>    /// 之前动作的规范化时间    /// </summary>    public float previousNormalizedTime;    /// <summary>    /// 当前动作的权重,直接指向对应动画的权重    /// </summary>    public float currentWeight; // the actual weight value queried from the AnimationState    /// <summary>    /// 实际播放出来的权重    /// </summary>    public float contributingWeight; // the weight the AnimationState is actually contributing to the final result based on layers    /// <summary>    /// 当前位置    /// </summary>    public Vector3 currentPosition;    /// <summary>    /// 之前的位置    /// </summary>    public Vector3 previousPosition;    /// <summary>    /// 开始位置    /// </summary>    public Vector3 startPosition;    /// <summary>    /// 结束位置    /// </summary>    public Vector3 endPosition;    /// <summary>    /// 当前的轴    /// </summary>    public Vector3 currentAxis;    /// <summary>    /// 之前的轴    /// </summary>    public Vector3 previousAxis;    /// <summary>    /// 开始的轴    /// </summary>    public Vector3 startAxis;    /// <summary>    /// 结束的轴    /// </summary>    public Vector3 endAxis;    /// <summary>    /// 全部旋转    /// </summary>    public Quaternion totalRotation;}// an enum to describe how delta values should be computed/// <summary>/// 增量值计算枚举/// </summary>public enum RootMotionComputationMode{    ZTranslation,    XZTranslation,    TranslationAndRotation}/// <summary>/// 管理所有动画,权重,混合/// </summary>[AddComponentMenu("Mixamo/Root Motion Computer")]public class RootMotionComputer : MonoBehaviour{    // the transform to have root motion applied    /// <summary>    /// 脚本所在对象    /// </summary>    public Transform rootNode;    // the animation component where all of the clips for this model exist    /// <summary>    /// 当前所有的动画数组    /// </summary>    public Animation anim;    // the pelvis joint from which the script obtains x-z motion and y-rotation for the root    /// <summary>    /// 模型所在的对象,设置X向Z移动、Y旋转    /// </summary>    public Transform pelvis;    /// <summary>    /// 骨盆局部的轴,右方向 也是X轴    /// </summary>    public Vector3 pelvisRightAxis = Vector3.right; // its local axis specifying the right direction    /// <summary>    /// 暂时存储骨盆的局部位置    /// </summary>    private Vector3 pLocalPosition; // a variable to temporarily store and set the pelvis local position after computation    // parameters for computation and application of result    /// <summary>    /// 是否是外部管理    /// </summary>    public bool isManagedExternally = false; // if the computer is managed externally, then its calls are invoked manually    /// <summary>    /// 增量结构    /// </summary>    public RootMotionComputationMode computationMode = RootMotionComputationMode.TranslationAndRotation;    /// <summary>    /// 应用运动    /// </summary>    public bool applyMotion = true;    // information about the computed root position    /// <summary>    /// 来自上一帧的三角州位置    /// </summary>    private Vector3 dPosition = Vector3.zero; // local-space delta position since previous frame    /// <summary>    /// 来自上一帧的三角州位置    /// </summary>    public Vector3 deltaPosition { get { return dPosition; } }    /// <summary>    /// 减少分配的一个容器    /// </summary>    private Vector3 p; // a simple container to minimize allocations    // information about the computed root rotation    /// <summary>    /// 来自上一帧的三角州旋转    /// </summary>    private Quaternion dRotation = Quaternion.identity; // local-space delta rotation since previous frame    /// <summary>    /// 来自上一帧的三角州旋转    /// </summary>    public Quaternion deltaRotation { get { return dRotation; } }    /// <summary>    /// 来自上一帧的三角州旋转的欧拉角    /// </summary>    public Vector3 deltaEulerAngles { get { return dRotation.eulerAngles; } }    // a hashtable storing information about each AnimationState    /// <summary>    /// 存放所有动画的哈希表    /// </summary>    private Hashtable animInfoTable;    /// <summary>    /// 动画状态信息结构体,为了减少分配    /// </summary>    private AnimInfo info; // a simple container to minimize allocations    // specify whether the component should be running in debug mode    /// <summary>    /// 是否是调试模式下运行    /// </summary>    public bool isDebugMode = true;    /// <summary>    /// 调试线框的大小    /// </summary>    public float debugGizmoSize = 0.25f;    // is the computation occuring on the first frame of execution?    /// <summary>    /// 计算上的第一帧执行?    /// </summary>    private bool isFirstFrame = true;    // the highest and lowest layers on which there is an AnimationState    /// <summary>    /// 最高的层    /// </summary>    private int highestLayer = 0;    /// <summary>    /// 最低的层    /// </summary>    private int lowestLayer = 0;    /*     * Initialize the component if it is not managed externally     * */    void Start()    {        //如果不是外部管理        if (!isManagedExternally) Initialize();    }    /*     * Initialize all necessary variables and warn user as needed     * */    /// <summary>    /// 必须的初始化    /// </summary>    public void Initialize()    {        // validate component references        if (anim == null)        {            //取子对象的所有动画            anim = gameObject.GetComponentInChildren(typeof(Animation)) as Animation;            if (anim == null) Debug.LogError("No animation component has been specified.", this);            else if (isDebugMode) Debug.LogWarning(string.Format("No animation component has been specified. Using the animation component on {0}.", gameObject.name), this);        }        if (rootNode == null)        {            //等于自身            rootNode = transform;            if (isDebugMode) Debug.LogWarning(string.Format("No root object has been manually specified. Assuming that {0} is the root object to be moved.", gameObject.name), this);        }        if (pelvis == null)        {            //取自身的所有组件            Component[] hierarchy = GetComponentsInChildren(typeof(Transform));            // first try to figure out the pelvis based on name            //给骨盆赋值            foreach (Transform joint in hierarchy)                if (pelvis == null && (joint.name.ToLower() == "hips" || joint.name.ToLower().Contains("pelvis"))) pelvis = joint;            // if no named pelvis was found, then try to find the first skinned mesh renderer with children            if (pelvis == null)            {                foreach (Transform joint in hierarchy)                {                    if (joint.GetComponent(typeof(SkinnedMeshRenderer)) == null) continue;                    Component[] children = joint.GetComponentsInChildren(typeof(Transform));                    if (children.Length > 1) pelvis = joint;                }            }            if (pelvis == null) Debug.LogError("No pelvis transform has been specified.", this);            else if (isDebugMode) Debug.LogWarning(string.Format("No pelvis object as been manually specified. Assuming that {0} is the pelvis object to track.", pelvis.name));        }        // store whether or not the animation component is playing        bool isAnimationPlaying = anim.isPlaying;        // store information about each AnimationState in a hashtable for easy lookup later        animInfoTable = new Hashtable();        // first, figure out what all AnimationStates are currently doing        //默认设置给每一个动画        foreach (AnimationState aState in anim)        {            AddAnimInfoToTable(aState);        }        //动画采样        anim.Sample(); // BUG: need to call Sample() once up front or AnimationStates in Animation component may reorder during iteration        //停止所有动画,以确保所有的动画权重为0        anim.Stop(); // call Stop() to ensure that all weights go to 0        //重新启动,以确保采样时的值是正确的        anim.enabled = true; // reenable the animation component to ensure that values will be correct when sampling        // store properties for each state one at a time        foreach (AnimationState aState in anim)        {            SetupNewAnimInfo(aState);        }        // revert the animation component to whatever it was doing beforehand        //权重,规范化时间 再次赋值为结构体里面的!当时为何不在设置结构体的时候就直接做了呢?        foreach (AnimationState aState in anim)        {            info = (AnimInfo)animInfoTable[aState];            //尼玛。这里不是重赋值么            aState.weight = info.currentWeight;            aState.normalizedTime = info.currentNormalizedTime;        }        if (isAnimationPlaying) anim.Play();        else anim.Stop();    }    /*     * Add information about the provided state to the hashtable     * */    /// <summary>    /// 设置一个动画的详细信息为结构体,并将之 动画名,结构体 的形式存入哈希表    /// </summary>    /// <param name="aState">动画</param>    public void AddAnimInfoToTable(AnimationState aState)    {        // create the new info object        AnimInfo newInfo = new AnimInfo();        // store the current properties        //设置规范化时间        newInfo.currentNormalizedTime = aState.normalizedTime;        //设置权重        newInfo.currentWeight = aState.weight;        // add a new hashtable entry for the AnimInfo        //存入动画哈希表中        animInfoTable.Add(aState, newInfo);    }    /*     * Set up further properties for a newly-created info object after calling AddAnimInfoToTable()     * */    /// <summary>    /// 设置哈希表里动画对应的结构体,重置这个动画    /// </summary>    /// <param name="aState">动画名</param>    public void SetupNewAnimInfo(AnimationState aState)    {        //取这个动画的结构体        AnimInfo newInfo = (AnimInfo)animInfoTable[aState];        // store information about the animation state up front        //当前的状态,应当是false 。        bool isEnabled = aState.enabled;        WrapMode wrapMode = aState.wrapMode;        // activate the animation state 激活动画状态        aState.weight = 1f;        aState.enabled = true;        //循环模式 确保该值在normalizedTime=1f是不一定相同normalizedTime= 0F ,官方没有 Clamp 的解释 操        aState.wrapMode = WrapMode.Clamp; // ensures the value at normalizedTime = 1f is not necessarily the same as normalizedTime = 0f        // scrub to the beginning of the animation state and store initial position and rotation values        //净化开始动画状态的初始位置的和旋转值        aState.normalizedTime = 0f;        anim.Sample();        newInfo.startPosition = GetProjectedPosition(pelvis);        newInfo.previousPosition = GetProjectedPosition(pelvis);        newInfo.startAxis = GetProjectedAxis(pelvis, pelvisRightAxis);        newInfo.previousAxis = GetProjectedAxis(pelvis, pelvisRightAxis);        // scrub to the end of the animation state and store final position and rotation values        //净化结束的动画状态,并存储最后的位置和旋转值        aState.normalizedTime = 1f;        anim.Sample();        newInfo.endPosition = GetProjectedPosition(pelvis);        newInfo.endAxis = GetProjectedAxis(pelvis, pelvisRightAxis);        // store the total rotation over the course of the animation        //从开始到结束的一个旋转的四元数        newInfo.totalRotation = Quaternion.FromToRotation(newInfo.startAxis, newInfo.endAxis);        // reset the clip to its starting point and scrub it down to 0 weight        //重置这个动画到开始的点到0的权重        aState.normalizedTime = 0f;        aState.weight = 0f;        aState.enabled = isEnabled;        aState.wrapMode = wrapMode;        anim.Sample();        //最后赋值        animInfoTable[aState] = newInfo;    }    /*     * All motion is applied in LateUpdate() since it is called after all animation states have been set     * */    //以保万一才在LatUpdate里运行,因为开头设置了。    void LateUpdate()    {        if (!isManagedExternally) ComputeRootMotion();    }    /*     * Compute the root motion variables     * */    /// <summary>    /// 计算根运动变量    /// </summary>    public void ComputeRootMotion()    {        // early out if no animation is playing 没运动        if (!anim.isPlaying) return;        // store whether or not we should be bothering to compute rotation parameters存储是否我们应该费心去计算旋转参数        bool isRotationMode = (computationMode == RootMotionComputationMode.TranslationAndRotation);        #region 设置最高层低层,是否所有的动画都已加入哈希表?没有则添加并设置        // an array to store any AnimationStates that have been added to the animation component since the last frame        //一个数组来存储已被添加到自上一帧的动画组件任何AnimationStates,用于初始化没有添加进的动画        ArrayList newlyAddedAnimationStates = null;        // first store current actual weight and time information for all AnimationStates        foreach (AnimationState aState in anim)        {            // store the highest and lowest layers for use in a later iteration            //给最高层和最低层赋值 . 这里每次都要赋一下,让我感到很费解            highestLayer = Mathf.Max(highestLayer, aState.layer);            lowestLayer = Mathf.Min(lowestLayer, aState.layer);            // if any new animation states have been added, then deal with them in a following iteration            // 这里判断有没有初始化没添加到哈希表的动画            if (!animInfoTable.ContainsKey(aState))            {                AddAnimInfoToTable(aState);                newlyAddedAnimationStates.Add(aState);                continue;            }            info = (AnimInfo)animInfoTable[aState];            //我擦。又一次分配            info.currentNormalizedTime = aState.normalizedTime;            info.currentWeight = aState.weight;            animInfoTable[aState] = info;            // scrub the weight down to 0 for the next iteration            //为下一次做好准备??            aState.weight = 0f;        }        // if any new AnimationStates have been added, add their info to the table        //有新的动画添加,就设置        if (newlyAddedAnimationStates != null && newlyAddedAnimationStates.Count > 0)        {            // first set all weights to 0, which will include newly added states            foreach (AnimationState aState in anim) aState.weight = 0f;            // store all the properties for the new states            foreach (AnimationState aState in newlyAddedAnimationStates) SetupNewAnimInfo(aState);        }        #endregion        #region 计算层的整合,权重的分配。这里有些不懂的地方        // compute normalized AnimationState weights across layers since Unity does not expose them        //计算归AnimationState的重量跨越层,因为Unity不暴露他们        //总的权重,余下的权重        float remainingWeight = 1f;        //从最高层开始        for (int i = highestLayer; i >= lowestLayer; i--)        {            //所有动画(指当前的层)权重的总和            float weightOnThisLayer = 0f;            //用循环取当前层。layer层中可能有多个动画            foreach (AnimationState aState in anim)            {                if (aState.layer != i) continue;                //取当前层的当前动画信息                info = (AnimInfo)animInfoTable[aState];                // find out how much weight the animation state is actually contributing this frame                //没有启用动画,或者已分配完                if (!aState.enabled || remainingWeight <= 0f)                {                    info.contributingWeight = 0f;                }                else                {                    //实际上播放出来的权重                    info.contributingWeight = remainingWeight * info.currentWeight;                    //print(aState.name + "-- layer: " + aState.layer  + "还余下的权重:" + remainingWeight + "这个动画当前的权重:"                    //    + info.currentWeight + "实际播放出来的权重" + info.contributingWeight);                }                //将这个层上所有的动画的权重加起来                weightOnThisLayer += info.contributingWeight;                //print(weightOnThisLayer);                animInfoTable[aState] = info;            }            // if the weight on this layer is > 1, then normalize it            // using Blend() or setting weights manually will not affect other weights on the layer, so they must be manually renormalized            //这个层上所有动画权重相加后的数大于1了            if (weightOnThisLayer > 1f)            {                //设置一个百分数来减少实际播放出来的权重                float oneOverWeightOnThisLayer = 1f / weightOnThisLayer;                //循环这个层的动画,并做出一些播出的权重的减少                foreach (AnimationState aState in anim)                {                    if (aState.layer != i) continue;                    info = (AnimInfo)animInfoTable[aState];                    info.contributingWeight = info.contributingWeight * oneOverWeightOnThisLayer;                    animInfoTable[aState] = info;                }                //重置为1                weightOnThisLayer = 1f;            }            //余下的权重 等于 减去分配后的权重            remainingWeight -= weightOnThisLayer;        }        #endregion        // reset the delta values for this frame        //重设此帧的增量值        dPosition = Vector3.zero;        dRotation = Quaternion.identity;        // compute each AnimationState‘s individual contribution to the current frame‘s delta values        //计算每个动画的贡献,以当前帧的增量值        foreach (AnimationState aState in anim)        {            info = (AnimInfo)animInfoTable[aState];            // early out if this state was contributing nothing this frame            //如果没有权重,下一位            if (info.contributingWeight == 0f) continue;            // early out if aState uses additive blending            // NOTE: Not entirely sure if this is ideal or not, but it generally should be            //如果混合模式为附加,则下一位            if (aState.blendMode == AnimationBlendMode.Additive) continue;            // scrub the weight up to 1 for sampling values            //设置权重为1            aState.weight = 1f;            // sample the values for the projected root configuration back one frame            // NOTE: cannot simply store these values from one frame to the next since user may manually change the time value at any point            //采样数值为投影机的根目录配置一帧            //注意:不能简单地存储这些值从一帧到下一个,因为用户可以在任何时候手动更改时间值            aState.time = aState.time - Time.deltaTime * aState.speed;            info.previousNormalizedTime = aState.normalizedTime;            anim.Sample();            info.previousAxis = GetProjectedAxis(pelvis, pelvisRightAxis);            info.previousPosition = GetProjectedPosition(pelvis);            // sample the values for the projected root configuration at the current frame            aState.normalizedTime = info.currentNormalizedTime;            anim.Sample();            info.currentPosition = GetProjectedPosition(pelvis);            info.currentAxis = GetProjectedAxis(pelvis, pelvisRightAxis);            // ensure both normalizedTime values are positive            info.previousNormalizedTime = 1f + info.previousNormalizedTime - (int)info.previousNormalizedTime;            info.currentNormalizedTime = 1f + info.currentNormalizedTime - (int)info.currentNormalizedTime;            // determine the contribution to the root‘s delta this frame based on whether the animation looped since the previous frame            if (info.previousNormalizedTime - (int)info.previousNormalizedTime > info.currentNormalizedTime - (int)info.currentNormalizedTime)            {                // compute displacement with respect to identity                p = info.contributingWeight * ((info.endPosition - info.previousPosition) + (info.currentPosition - info.startPosition));                if (isRotationMode)                {                    // rotate displacement into current orientation                    p = Quaternion.FromToRotation(info.currentAxis, info.totalRotation * Vector3.right) * p;                    // compute angular displacement and append to result                    dRotation *= Quaternion.Slerp(Quaternion.identity,                        Quaternion.FromToRotation(info.previousAxis, info.endAxis) * Quaternion.FromToRotation(info.startAxis, info.currentAxis),                        info.contributingWeight);                }                // append displacement to result                dPosition += p;            }            else            {                // compute displacement with respect to identity                p = info.contributingWeight * (info.currentPosition - info.previousPosition);                if (isRotationMode)                {                    // rotate displacement into current orientation                    p = Quaternion.FromToRotation(info.currentAxis, Vector3.right) * p;                    // compute angular displacement and append to result                    dRotation *= Quaternion.Slerp(Quaternion.identity, Quaternion.FromToRotation(info.previousAxis, info.currentAxis), info.contributingWeight);                }                // append displacement to result                dPosition += p;            }            // scrub the weight back down to 0 so as to not affect sampling of other states            aState.weight = 0f;        }        // reset weights to where they were before computation        foreach (AnimationState aState in anim)        {            info = (AnimInfo)animInfoTable[aState];            aState.weight = info.currentWeight;        }        // return the character to its current pose        anim.Sample();        // delta values for the first frame should simply move from the starting configuration into the current frame of animation        if (isFirstFrame)        {            // simply translate and rotate to the current projected position and orientation            dPosition = GetProjectedPosition(pelvis);            dRotation = Quaternion.FromToRotation(Vector3.right, GetProjectedAxis(pelvis, pelvisRightAxis));            // rotate displacement into current orientation            if (isRotationMode) dPosition = Quaternion.FromToRotation(GetProjectedAxis(pelvis, pelvisRightAxis), Vector3.right) * dPosition;            isFirstFrame = false;        }        // store the local position of the pelvis before returning it to hover over the root        pLocalPosition = pelvis.localPosition;        // zero out the local x-component of the position delta if root translation method is z-only        if (computationMode == RootMotionComputationMode.ZTranslation) dPosition = Vector3.forward * Vector3.Dot(dPosition, Vector3.forward);        // otherwise zero out the local x-position of the pelvis        else pLocalPosition.x = 0f;        // return the pelvis to a point hovering over the root        pLocalPosition.z = 0f;        pelvis.localPosition = pLocalPosition;        // if computing rotation, then zero out local y-rotation of the pelvis        if (isRotationMode) pelvis.localRotation = Quaternion.FromToRotation(GetProjectedAxis(pelvis, pelvisRightAxis), Vector3.right) * pelvis.localRotation;        // draw debug lines if requested        if (isDebugMode) DrawDebug();        // return if root movement is not requested (e.g. a character controller will use delta values)        if (!applyMotion) return;        // apply rotation if requested        if (isRotationMode) rootNode.localRotation *= dRotation;        // apply translation        rootNode.Translate(dPosition, Space.Self);    }    /*     * Obtain the position of t projected onto rootNode‘s zx plane     * */    /// <summary>    /// 获取传入位置投射在根结点的ZX轴坐标的一个点 ,y = 0    /// </summary>    /// <param name="t">传入的位置</param>    /// <returns></returns>    private Vector3 GetProjectedPosition(Transform t)    {        Vector3 p = rootNode.InverseTransformPoint(t.position);        p.y = 0f;        return p;    }    /*     * Obtain the projection of axis on t onto rootNode‘s zx plane     * */    /// <summary>    /// 获取根节点的zx平面上的投影轴    /// </summary>    /// <param name="t">传入的位置</param>    /// <param name="axis">方向</param>    /// <returns></returns>    private Vector3 GetProjectedAxis(Transform t, Vector3 axis)    {        Vector3 p = rootNode.InverseTransformDirection(t.TransformDirection(axis));        p.y = 0f;        return p;    }    /*     * Draw axis tripods to show how root motion is being determined and applied     * */    private void DrawDebug()    {        // draw pelvis right axis        Debug.DrawRay(pelvis.position, pelvis.TransformDirection(pelvisRightAxis) * debugGizmoSize, Color.red);        // draw root node axes        Debug.DrawRay(rootNode.position, rootNode.rotation * Vector3.forward * debugGizmoSize, Color.blue);        Debug.DrawRay(rootNode.position, rootNode.rotation * Vector3.right * debugGizmoSize, Color.red);        Debug.DrawRay(rootNode.position, rootNode.rotation * Vector3.up * debugGizmoSize, Color.green);    }}

 

RootMotionComputer 根运动计算机