如何实现类Unity3D层级游戏对象
发表于2016-11-14
如何实现类Unity3D层级游戏对象
在游戏引擎中,一般都会把游戏对象按父子关系组织起来,子对象相对于父对象坐标而动,非常方便,比如在Unity3D中:
那么如何实现一个类似的结构:可以获得、设置各个层级在本地、世界坐标的位置和旋转呢?本文将从代码层面实际构建一个这样层级结构,并对照Unity3D为例子分析其对应函数的性能和实现。
首先,和Unity3D一样,我们选用相对于父坐标系的本地位置(localPosition),本地旋转(localRotation)和本地缩放(localScale)做为我们游戏对象的基本数据结构,其他比如世界位置、世界旋转通过计算来获取和设置。
那么,我们能否缓存世界位置,世界旋转,以加快访问呢?这个需要看情况,如果游戏对象是静态的,那么是可以缓存世界位置,世界旋转,但如果游戏对象经常运动,缓存失效很快,那么是不合适的,具体我们可以参考Unity3D是怎么做的,为此我设计了一个具有四个Cube的层级结构,disable掉每个Cube的BoxCollider,如上图所示,然后写了一个简单的脚本,测试获取、设置这些Cube的世界位置效率,脚本如下图所示:
测试得到获取世界位置的耗时是:
设置世界位置的耗时是:
从实验结果分析,Unity3D的世界位置的设置和获取是没有缓存的。这也提示我们,在Unity3D中游戏对象层级不要太深。当然如果子对象很多的话,猜想是有优化的(比如人物骨骼),没有测试。
对于后台程序来说,我们就简单的使用相对坐标系就好了。
于是我们的Transform类大概就这样了:
class Transform
{
Vector3 local_position;
Quaternion local_rotation;
Vector3 local_scale;
};
获取或者设置本地位置、旋转很容易,直接操作变量就可以了,这里不再赘述。下面讲讲如何操作世界位置、世界旋转。
如何把本地位置变换到世界位置,原理大家都比较懂了:通过矩阵乗一下。但我们的Transform里只有local_position,local_rotation和local_scale,难道还要构造一个变换矩阵吗?其实想想是不需要的,游戏行业矩阵所做的无非只是做缩放、旋转和位移3种操作,别的不会(比如把一个正方形变成平行四边形?)那么我们有没有办法跳过矩阵,直接对游戏对象做这3种变换呢?答案肯定是肯定的:)
注意到在Unity3D中的Transform里的local_position,local_rotation和local_scale都是相对于父游戏对象坐标系的位置,旋转和缩放的(如果父游戏对象是世界,那么就是世界位置、世界旋转)。也就是说一个在子坐标系中的物体,经过local_scale,local_rotation和local_position的变换,可以变换到父坐标系中,这样递归到顶层,就到了世界坐标系,于是我们有了一个把本地位置变换到世界位置的函数以及其逆函数:
注意变换的顺序是先缩放、再旋转,最后位移,至于为什么这样,大概是人们觉得这样更符合习惯,于是就这样定下来了。
有了这两个函数,我们操作世界位置就方便多了,前面说过local_position是游戏对象相对于父对象的位置,把位置变换到父对象坐标系就可以了:
那么世界旋转怎么做呢?这个比世界位置要简单一点,世界旋转是不用考虑缩放和位移的(想想为什么?其实我有时也迷糊),那就直接旋转叠加好了:
当然,这里有些函数的实现没有贴出来,不过意思大家都懂。
另外在Unity3D的Transform实现中还有和TransformPoint类似的两个函数TransformVector和TransformDirection,很多人分不清楚这几个函数的用途,向量和点比较是没有原点的概念的,对位移变换保持不变,因此去掉TransformPoint里的位移变换就可以了:
方向是在向量的基础上再去掉缩放变换,实现如下:
可以看到,TransformVector和TransformDirection比TransformPoint要高效,不然Unity3D可以简单的使用TransformPoint模拟这两个函数。
在本文中,始终没有提及游戏对象在世界坐标系中的世界缩放,为什么呢?因为世界缩放不能精确的用一个Vector3表示(简单的在2维坐标系中,单位正方形沿X轴拉伸到2,再旋转45度,这个拉伸变换在世界坐标系不能用一个Vector2表示)。所以Unity3D用了一个lossyScale代表世界缩放,lossy,大家应该知道不精确,具体可以看看其文档。