【Unity学习笔记】第十五 Transform 查缺补漏 (TransformDirection / InverseTransformDirection解释,三种坐标系下的Translate())

转载请注明出处: https://blog.csdn.net/weixin_44013533/article/details/138476907

作者:CSDN@|Ringleader|

主要参考:

前言

Transform组件是我们最常接触的组件,它的translate方法也是我们经常使用的东西,但似乎对于Transform类的理解并不是很到位。所以这次我重新搜索文档并梳理,希望能对Transform掌握得更加明晰。

Transform 继承关系

Transform继承自Component类,知道其继承关系有利于更好地使用类中的各种属性和方法。完整Unity类继承关系树如下。(rider用户的话可以用ctrl+H 查看继承关系)
请添加图片描述

Transform 类结构

Transform 包含很多属性和方法,从官方API去看的话很容易懵逼失去中心,这篇文章Unity编程标准导引-3.3 Transform的思路就很棒,我将参考他的结构进行撰文,并查缺补漏。

  • 层级关系、层级变动、IEnumerable遍历相关属性和方法
  • 坐标系以及坐标变换相关属性和方法
  • Translate位移与Time.deltaTimeTime.fixedDeltaTime
  • scale等其他

层级相关属性

  • childCount:代表当前GameObject节点下方有几个子节点。

  • hierarchyCount:代表在当前GameObject所处的相互关联的树状结构中,存在的层次数目。
    相互关联的树状结构指的是:以Hierarchy面板中的一个顶层GameObject作为根节点出发的一棵树。任何一个GameObject都会存在于一棵树中。
    可以预想的是,这个树状结构中的任意一个节点的hierarchyCount属性都是相同的。

  • hierarchyCapacity:代表当前所在的树的层次容量。就是这棵树最大可以容纳的节点数目,从整个树中的任意一个节点访问此属性,所获取的层次容量都是相同的。
    这个参数是自动增长的,即当GameObject发生层次变动时,如果当前树的容量不足,会自动扩容。
    可以推测出:树中所有节点所查询的结果实际来自最顶层节点的属性,而当层次所发生变动时,Unity内部只需要修改顶层节点的这个属性即可。
    需要注意的是,当频繁变化hierarchyCapacity时,是需要带来额外的内存消耗和性能消耗的,这与List类的内存扩容是一个道理。因此,应该为频繁增长的树的根节点在一开始就设置一个比较大的容量。

  • parent:代表当前节点的父节点,返回一个Transform对象。当此parent为null时,就代表自己已经是顶层节点,也即树状结构中的根节点了。

  • root:获得当前树状结构中的根节点。

层级相关方法

  • DetachChildren():分离子节点,意思就是将当前节点下方的所有直接子节点都分离出去,让他们成为根节点。有n个子节点,将产生n棵新的树。

  • Find(stringname):根据路径查找子节点,虽然这里的参数在文档上显示为name,但是它实际代表一个路径。它可以是"magazine/ammo"这种格式,即可以向着叶节点深度方向查询多个层次,找到目标Transform并返回。如果找不到返回空。

  • GetChild(intindex):根据索引index,返回当前节点的直接子节点。

  • GetSiblingIndex():获得当前节点处于其父节点下的编号索引,即处于兄弟列表中的Index。

  • SetAsFirstSibling():设置当前节点为兄弟节点列表中的第一个节点。

  • SetAsLastSibling():设置当前节点为兄弟节点列表中的最后一个节点。

  • SetSiblingIndex(intindex):将当前节点设置到其兄弟列表中的index位置。

  • SetParent(Transformparent,boolworldPositionStays):设置当前节点的父节点,如果worldPositionStays设置为true,则保持其世界坐标下的位置、旋转和缩放。这会相应地修改其局部坐标、旋转和缩放信息。

测试:

public GameObject ob;
    private Transform obt;

    private void Awake()
    {
    
    
        obt = ob.transform;
    }

    private void OnEnable()
    {
    
    
        //transform的层级关系相关属性
        
        int childCount = obt.childCount;
        print("transform的直接子项数量:" + childCount);

        var obtHierarchyCapacity = obt.hierarchyCapacity;
        print("transform的层级视图数据结构的变换容量:" + obtHierarchyCapacity);
        var obtHierarchyCount = obt.hierarchyCount;
        print("transform的层级视图数据结构中变换的数量:" + obtHierarchyCount);

        var obtParent = obt.parent;
        print("变换的父级:" + obtParent?.name);
        var obtRoot = obt.root;
        print("返回层级视图中最顶层的变换:"+obtRoot.name);
        
        //transform层级关系相关方法
        var childUseFind = obt.Find("Cube (4)");
        print("Finds a child by name n and returns it:"+childUseFind?.name);

        // var child = obt.GetChild(0);
        // print("按索引返回变换子项:"+child.name);
        
        print("获得当前节点处于其父节点下的编号索引:"+obt.GetSiblingIndex());
        obt.SetAsFirstSibling();
        print("获得当前节点处于其父节点下的编号索引:"+obt.GetSiblingIndex());
        obt.SetAsLastSibling();
        print("获得当前节点处于其父节点下的编号索引:"+obt.GetSiblingIndex());
    }

    // Update is called once per frame
    void Update()
    {
    
    
        if (Input.GetKeyDown(KeyCode.A))
        {
    
    
            //清除直接子项的父级
            obt.root.DetachChildren();
        }
    }

IEnumerable

参考:

Transform类是实现了IEnumerable接口的class Transform : Component, IEnumerable,所以可以用迭代器或者foreach遍历树上所有直接子项:

print("——开始打印层级结构:");

IEnumerator enumerator = obt.GetEnumerator();
while (enumerator.MoveNext())
{
    
    
    var current = (Transform)enumerator.Current;
    print(current.name);
}
print("——结束打印层级结构——");

// C# 语言的 foreach 语句隐藏了枚举数的复杂性。 因此,建议使用 foreach 而不是直接操作枚举器。
foreach (var childTransform in obt)
{
    
    
    print(((Transform)childTransform).name);
}

坐标系以及坐标变换

坐标系相关属性

  • Vector3 localPosition:局部坐标系下的位置
  • Vector3 position:全局坐标系下的位置
  • Quaternion localRotation:局部坐标系下的旋转
  • Quaternion rotation:全局坐标系下的旋转
  • Vector3 localScale:局部坐标系下的缩放
  • Vector3 lossyScale:全局坐标系下的缩放
  • Vector3 localEulerAngles:局部坐标系下的欧拉角
  • Vector3 eulerAngles:全局坐标系下的欧拉角
  • Vector3 forward:全局坐标系下的前方矢量
  • Vector3 up:全局坐标系下的上方矢量
  • Vector3 right:全局坐标系下的右方矢量

坐标系相关方法

参考:

  • TransformDirection : 将 direction 从本地空间变换到世界空间。
  • TransformPoint : 将 position 从本地空间变换到世界空间。
  • TransformVector : 将 vector 从本地空间变换到世界空间。
  • InverseTransformDirection : 将一个方向从世界空间转换到本地空间。与 Transform.TransformDirection 相反。
  • InverseTransformPoint : 将 position 从世界空间变换到本地空间。
  • InverseTransformVector : 将vector 从世界空间变换到本地空间。与 Transform.TransformVector 相反。

前 3 种方法是从本地空间到世界空间的变换,后 3 种则是相反的操作。Direction / Point / Vector的区别在于:

  • TransformPoint / InverseTransformPoint 受位置、旋转和缩放影响;
  • TransformDirection / InverseTransformDirection(变换方向)仅受旋转影响;
  • TransformVector / InverseTransformVector(变换向量)受旋转和缩放影响。

由于存在本地坐标系和世界坐标系,如果我们希望对空间中的点/向量/方向在本地坐标和世界坐标进行转换,就需要利用上面的Transform/ InverseTransform 方法。

举例说明:

  1. 假设对cube对象go绕Y轴顺时针旋转90°,rotation(0,90,0)
    在这里插入图片描述
    那么,对于vector3(1,0,0)这个向量,
  • 如果这个坐标是世界坐标系下的,则就是上面黄色箭头(和对象本地z轴同向),所以这个vector3(1,0,0)在对象本地坐标系下就是(0,0,1),也就是go.InverseTransformDirection(vector3(1,0,0)) = vector3(0,0,1)
  • 如果这个坐标是本地坐标系下的,则就是上面黑色箭头,在世界坐标系下看这个黑色箭头vector3就是(0,0,-1),就是go.TransformDirection(vector3(1,0,0)) = vector3(0,0,-1)
  1. 当对象go绕Y轴顺时针旋转45°,rotation(0,45,0)
  • 本地坐标系→世界坐标系
    TransformDirection((1,0,0)) = (0.707,0,- 0.707)
  • 世界坐标系→本地坐标系
    InverseTransformDirection((1,0,0))=(0.707,0,0.707)
    在这里插入图片描述

转换的目的就是能在不同坐标空间下表示同一方向,比如Translate()方法有Space.World/Space.Self参数,就可以通过TransformVector方法达到同样的效果

// 使用TransformVector方法转换坐标空间,这种效果同 Space.Self的right
transform.Translate(transform.TransformVector(Vector3.right) * Time.deltaTime, Space.World);
transform.Translate(Vector3.right * Time.deltaTime, Space.World);

Translate()方法

  • public void Translate(Vector3 translation, Space relativeTo = Space.Self);
  • public void Translate(Vector3 translation, Space relativeTo = Space.World);
  • public void Translate(Vector3 translation, Transform relativeTo);

三种方法都是沿着向量方向移动translation距离,只不过这个方向可以相对对象本地坐标系、世界坐标系或者其他对象的本地坐标系。

注意这里的translation不是速度,是距离,如果方法写在update方法里就是每帧移动translation距离,写在fixedUpdate就是每fixedDeltaTime(默认0.02s)移动translation距离。如果想要用宏观的方式就可以用Translate(Vector3 velocity * Time.deltaTime) 或者 Translate(Vector3 velocity * Time.fixedDeltaTime)表示,那么这个velocity 单位就是m/s

测试代码:

public class TransformTest : MonoBehaviour
{
    
    
    public GameObject ob1;
    public GameObject ob2;
    private Transform obt1;
    private Transform obt2;
    public bool selfSpace = true;
    public bool useOb2Space;

    private void Start()
    {
    
    
        obt1 = ob1.transform;
        obt2 = ob2.transform;
    }

    void Update()
    {
    
    

        // 三种不同坐标系的移动
        if (useOb2Space)
        {
    
    
            obt1.Translate(Vector3.right * Time.deltaTime, obt2);
        }else if (selfSpace)
        {
    
    
            obt1.Translate(Vector3.right * Time.deltaTime, Space.Self);
        }
        else
        {
    
    
            // 使用TransformVector方法转换坐标空间,这种效果同 Space.Self的right
            // obt1.Translate(obt1.TransformVector(Vector3.right) * Time.deltaTime, Space.World);
            obt1.Translate(Vector3.right * Time.deltaTime, Space.World);
        }
    }
    private void FixedUpdate()
    {
    
    
        // 这种和Update的 *deltaTime类似,也是做到每秒1米。
        // obt1.Translate(Vector3.right * Time.fixedDeltaTime);
    }
}

scale等其他

注意非等比scale导致的变形问题:
在这里插入图片描述
euler与Quaternion放到后面Quaternion进行补充。

总结

本文对Transform类进行较详细的分析,分别从Transform层级关系相关属性和方法、 坐标系相关属性和方法以及Translate方法等结构进行清晰地梳理,特别是对TransformDirection / InverseTransformDirection这个坐标系变换方法进行解释,同时提到了Translate()方法可以在三种不同参考系中进行移动。通过上面的查缺补漏,我想未来再使用Transform将会变得更得心应手。

猜你喜欢

转载自blog.csdn.net/weixin_44013533/article/details/138476907