目录
转载请注明出处: https://blog.csdn.net/weixin_44013533/article/details/138476907
主要参考:
前言
Transform组件是我们最常接触的组件,它的translate方法也是我们经常使用的东西,但似乎对于Transform类的理解并不是很到位。所以这次我重新搜索文档并梳理,希望能对Transform掌握得更加明晰。
Transform 继承关系
Transform继承自Component类,知道其继承关系有利于更好地使用类中的各种属性和方法。完整Unity类继承关系树如下。(rider用户的话可以用ctrl+H
查看继承关系)
Transform 类结构
Transform 包含很多属性和方法,从官方API去看的话很容易懵逼失去中心,这篇文章Unity编程标准导引-3.3 Transform的思路就很棒,我将参考他的结构进行撰文,并查缺补漏。
- 层级关系、层级变动、
IEnumerable
遍历相关属性和方法 - 坐标系以及坐标变换相关属性和方法
Translate
位移与Time.deltaTime
和Time.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 方法。
举例说明:
- 假设对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)
- 当对象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将会变得更得心应手。