C# Vector3 和 transform.position 篇
〇、前言
系统自带计时器 -System.Diagnostics.StopWatch- 的使用
System.Diagnostics.StopWatch 的基本使用方法
//引用命名空间
using System.Diagnostics;
//创建一个计时器
Stopwatch timer = new Stopwatch();
//计时开始
timer.Start();
//计时结束
timer.Stop();
//转换成秒形式
decimal second = timer.Elapsed.Ticks * 0.000_000_1m;
//如果要转换成微秒形式:
decimal microSecond = timer.Elapsed.Ticks * 0.1m;
//在输出面板输出代码所用时间, ":F4"是保留4位小数的意思
Debug.Log($"Takes {second:F4} second");
一、三维向量测试
1. 三维向量的函数方法和属性
首先说说 Vector3 的所有函数方法和属性,如下:
- 重要属性:
|
|
---|---|
Vector3.right | 返回 new Vector3(1,0,0); ( static ) |
Vector3.left | 返回 new Vector3(-1,0,0); ( static ) |
Vector3.up | 返回 new Vector3(0,1,0); ( static ) |
Vector3.down | 返回 new Vector3(0,-1,0); ( static ) |
Vector3.forward | 返回 new Vector3(0,0,1); ( static ) |
Vector3.back | 返回 new Vector3(0,0,-1); ( static ) |
Vector3.one | 返回 new Vector3(1,1,1); ( static ) |
Vector3.zero | 返回 new Vector3(0,0,0); ( static ) |
.normalized | 返回该向量的单位向量:
|
.magnitude | 返回该向量的模:
|
.sqrMagnitude | 返回该向量的平方模: |
- 常用静态方法:
| 返回值类型 |
|
---|---|---|
Angle(Vector3 a, Vector3 b) | float | 返回两个向量的夹角(度数):
|
ClampMagnitude(Vector3 a, float maxLength) | Vector3 | 返回方向与 a 相同,长度最大为 maxLength 的向量:
|
Cross(Vector3 a, Vector3 b) | Vector3 | 返回两个向量的叉积:
|
Dot(Vector3 a, Vector3 b) | float | 返回两个向量的点积:
|
Scale(Vector3 a, Vector3 b) | Vector3 | 返回两个向量各部分对应相乘的结果:
|
Distance(Vector3 a, Vector3 b) | float | 返回a到b的距离:
|
Lerp(Vector3 a, Vector3 b, float t) | Vector3 | 返回a到b的插值( 其中t 会被限制在0~1之间 ):
|
LerpUnclamp(Vector3 a, Vector3 b, float t) | Vector3 | 返回a到b的插值( t 不会被限制在0~1之间 ):
|
Magnitude(Vector3 vector) | float | 返回 vector 的模:
|
Normalize(Vector3 vector) | Vector3 | 返回 vector 的单位向量:
|
SqrMagnitude(Vector3 vector) | float | 返回 vector 的平方模:
|
Max(Vector3 a, Vector3 b) | Vector3 | 返回由两个向量最大值部分组成的向量:
|
Min(Vector3 a, Vector3 b) | Vector3 | 返回由两个向量最小值部分组成的向量:
|
MoveTowards(Vector3 current, Vector3 target, float maxDistanceDelta) | Vector3 | 返回从 current 沿 “ current 指向 target ” 方向移动 maxDistanceDelta 距离后的向量(令 direction = (target - current).Normalized();):
|
Project(Vector3 vector, Vector3 onNormal) | Vector3 | 返回 vector 在 onNormal 上的投影(令 normal = onNormal.Normalized();):
|
ProjectOnPlane(Vector3 vector, Vector3 planeNormal) | Vector3 | 返回 vector 在面上的投影(令 normal = planeNormal.Normalized();):
|
Reflect(Vector3 inDirection, Vector3 inNormal) | Vector3 | 返回 inDirection 在 inNormal 上的反射(令 normal = inNormal.Normalized(); dir = inDirection;):
|
SignedAngle(Vector3 from, Vector3 to, Vector3 axis) | float | 返回两个向量在法向量为 axis 的面上的投影之间的夹角:
|
- 常用方法:
| 返回值类型 |
|
---|---|---|
.Scale(Vector3 scale) | void | 该向量为与 scale 各部分对应相乘的结果:
|
.Set(float newX, float newY, float newZ) | void | 不开新的内存空间的改变该向量值的方法:
|
.Normalize() | void | 单位化该向量:
|
2. 三维向量测试
介绍完三维向量的基本常用函数方法,接下来将进行一系列测试来说明各方法的最佳使用环境。
接下来所有测试都以以下代码为标准代码块(更详细的见测试代码运行所用时间(一)),并在测试不同内容时进行不同修改:
...
// ==!==(修改区 0) 修改 count 大小
private int count = 100_000;
...
private void OnClicked()
{
// ==!==(修改区 1) 最初初始化代码块
...
for (int testIndex = -1; testIndex < (n/*测试实验个数(预热和对比区除外)*/ + 1) * 3; ++testIndex)
{
// ==!==(修改区 2) 每次初始化代码块
...
int index = Mathf.FloorToInt(testIndex * 0.334f);
Stopwatch timer = new Stopwatch();
timer.Start();
switch (index)//每个测试 3 次
{
//传统"预热"
case -1:
break;
// case 0 一般都为普通循环(对比区)
case 0:
for (int i = 0; i < count; ++i) { }
break;
// ==!==(修改区 3) 其他测试代码块
case 1:
...
break;
...
}
timer.Stop();
UnityEngine.Debug.Log($"{index}: Takes -{timer.Elapsed.Ticks}- ticks");
// ==!==(修改区 4) 结束后进行其余操作的代码块
...
}
}
(1). 初始化、创建和赋值
//(修改区 2)
Vector3 dir = Vector3.zero;
其中 测试实验个数 n = 4
//(修改区 3)
case 1:
for (int i = 0; i < count; ++i)
{
Vector3 tmpDir = Vector3.up;
}
break;
case 2:
for (int i = 0; i < count; ++i)
{
dir = Vector3.up;
}
break;
case 3:
for (int i = 0; i < count; ++i)
{
dir = new Vector3(0, 1, 0);
}
case 4:
for (int i = 0; i < count; ++i)
{
dir.Set(0, 1, 0);
}
break;
在Unity中多次测试后取稳定值,记录实验数据:
次数\实验编号 | 0 | 1 | 2 | 3 | 4 |
---|---|---|---|---|---|
1 | 608 | 912 | 1824 | 5511 | 5477 |
2 | 609 | 912 | 1823 | 5488 | 5488 |
3 | 612 | 913 | 1824 | 5468 | 5511 |
(2). Distance、magnitude 和 sqrMagnitude
//(修改区 1)
Vector3 targetPos = Random.onUnitSphere * Random.Range(0f, 42f);
Vector3 startPos = Vector3.zero;
//(修改区 2)
float len = 0;
其中 测试实验个数 n = 5
//(修改区 3)
case 1:
for (int i = 0; i < count; ++i)
{
len = Vector3.Distance(startPos,targetPos);
}
break;
case 2:
for (int i = 0; i < count; ++i)
{
len = targetPos.magnitude;
}
break;
case 3:
for (int i = 0; i < count; ++i)
{
len = Vector3.Magnitude(targetPos);
}
case 4:
for (int i = 0; i < count; ++i)
{
len = targetPos.sqrMagnitude;
}
break;
case 5:
for (int i = 0; i < count; ++i)
{
len = Vector3.SqrMagnitude(targetPos);
}
break;
在Unity中多次测试后取稳定值,记录实验数据:
次数\实验编号 | 0 | 1 | 2 | 3 | 4 | 5 |
---|---|---|---|---|---|---|
1 | 629 | 66122 | 12316 | 11762 | 7518 | 8677 |
2 | 555 | 65797 | 23677 | 20418 | 13609 | 11996 |
3 | 503 | 65150 | 22043 | 15769 | 7084 | 7656 |
4 | 752 | 63884 | 12015 | 13409 | 11712 | 8624 |
5 | 630 | 62636 | 12397 | 16274 | 7156 | 7714 |
由上述实验数据可得:
在进行向量之间距离的比较时,最好用 sqrMagnitude;
如果需要用到向量的模就还是用 magnitude;
关于 Distance 和 magnitude 的区别,本博主认为是两个向量差引起的,所以再次进行实验:
//(修改区 1)
Vector3 targetPos = Random.onUnitSphere * Random.Range(0f, 42f);
Vector3 startPos = Vector3.zero;
Vector3 dir = targetPos - startPos;
//(修改区 2)
float len = 0;
其中 测试实验个数 n = 4
//(修改区 3)
case 1:
for (int i = 0; i < count; ++i)
{
len = Vector3.Distance(startPos,targetPos);
}
break;
case 2:
for (int i = 0; i < count; ++i)
{
len = (targetPos - startPos).magnitude;
}
break;
case 3:
for (int i = 0; i < count; ++i)
{
len = Vector3.Magnitude(targetPos - startPos);
}
case 4:
for (int i = 0; i < count; ++i)
{
len = dir.magnitude;
}
break;
再在Unity中进行多次测试后取稳定值,记录实验数据:
次数\实验编号 | 0 | 1 | 2 | 3 | 4 |
---|---|---|---|---|---|
1 | 837 | 38458 | 40470 | 46247 | 10289 |
2 | 1044 | 29661 | 40310 | 44343 | 9605 |
3 | 804 | 28686 | 39578 | 44613 | 9601 |
由此可发现:
在 distance 和 magnitude 中:
如果要计算两点之间的距离,用 distance 是最好的选择;
如果要计算一个向量的模,用 magnitude 是最好的选择;
(3). 由 normalize 引出的各个数字运算耗费时间
一般情况下,我们都会认为 单位化向量 的性能消耗比较大,以为其中有
1
n
⃗
⋅
n
⃗
\frac {1} {\sqrt{\vec n \cdot \vec n}}
n⋅n1,既有除的操作,又有根号的操作。
于是下面先来测试一下各个运算符的性能消耗:
//(修改区 1)
float creator = 1.0001f;
float sqrtNum = 114514.114514f;
double outsideNum = 0;
//(修改区 2)
double num = 1314.520f;
其中 测试实验个数 n = 8
//(修改区 3)
case 1:
for (int i = 0; i < count; ++i)
{
num += creator;
}
break;
case 2:
for (int i = 0; i < count; ++i)
{
num -= creator;
}
break;
case 3:
for (int i = 0; i < count; ++i)
{
num *= creator;
}
case 4:
for (int i = 0; i < count; ++i)
{
num /= creator;
}
break;
case 5:
for (int i = 0; i < count; ++i)
{
num %= creator;
}
break;
case 6:
for (int i = 0; i < count; ++i)
{
num = sqrtNum;
}
break;
case 7:
for (int i = 0; i < count; ++i)
{
num = Mathf.Sqrt(sqrtNum);
}
case 8:
for (int i = 0; i < count; ++i)
{
num = System.Math.Sqrt(sqrtNum);
}
break;
//(修改区 4)
outsideNum = num;//为了让 num 有被使用到,防止被优化没了
在Unity中进行多次测试后取稳定值,记录实验数据:
次数\实验编号 | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 |
---|---|---|---|---|---|---|---|---|---|
1 | 837 | 3827 | 3776 | 3761 | 8161 | 14868 | 3175 | 28922 | 2418 |
2 | 879 | 3760 | 3761 | 3760 | 8492 | 14777 | 2877 | 29522 | 2517 |
3 | 838 | 3950 | 3789 | 3760 | 8168 | 14890 | 3742 | 29861 | 2673 |
本博主为了方便观察,画出直观图:
但是 System.Math.Sqrt
的超短时间计算确实出乎意料。本博主测试了每次测试 count *= 10
,结果发现 System.Math.Sqrt
与 count 成线性相关 还是 1:1
那种。而且每次计算结果和 Unity.Mathf.Sqrt
的计算结果一样。
这次意料之外的测试在以后再进行探究。
接下来测试一下 Normalize 和 Sqrt 的时间消耗:
//(修改区 1)
Vector3 targetPos = Random.onUnitSphere * Random.Range(0f, 42f);
Vector3 norDir;
double outsideNum;
//(修改区 2)
double num = 1314.520f;
其中 测试实验个数 n = 7
//(修改区 3)
case 1:
for (int i = 0; i < count; ++i)
{
norDir = dir.normalized;
}
break;
case 2:
for (int i = 0; i < count; ++i)
{
dir.Normalize();
norDir = dir;
}
break;
case 3:
for (int i = 0; i < count; ++i)
{
Vector3.Normalize(dir);
norDir = dir;
}
case 4:
for (int i = 0; i < count; ++i)
{
outsideNum = Mathf.Sqrt((float)num);
}
break;
case 5:
for (int i = 0; i < count; ++i)
{
float sqrMag = dir.sqrMagnitude;
norDir = dir / Mathf.Sqrt(sqrMag);
}
break;
case 6:
for (int i = 0; i < count; ++i)
{
float sqrMag = dir.x * dir.x + dir.y * dir.y + dir.z * dir.z;
norDir = dir / Mathf.Sqrt(sqrMag);
}
break;
case 7:
for (int i = 0; i < count; ++i)
{
float sqrMag = dir.x * dir.x + dir.y * dir.y + dir.z * dir.z;
norDir = dir / System.Math.Sqrt(sqrMag);
}
break;
//(修改区 4)
norDir = Vector3.zero;
在Unity中多次测试后取稳定值,记录实验数据:
次数\实验编号 | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 |
---|---|---|---|---|---|---|---|---|
1 | 504 | 110790 | 89339 | 102991 | 36709 | 82813 | 70295 | 50343 |
2 | 602 | 103803 | 84620 | 97766 | 32458 | 80954 | 69105 | 49984 |
3 | 486 | 104342 | 84593 | 97936 | 34093 | 84698 | 69129 | 47001 |
由实验数据可知 Normalize 的性能与 直接 1/Mathf.Sqrt(.sqrMagnitude)
没有太大的区别。而且使用 1/System.Math.Sqrt(.sqrMagnitude)
会比正常1/Mathf.Sqrt(.sqrMagnitude)
快一些,速度比大概是10:7
;
还比 Normalize 快一倍多。
二、变换系统测试
1. transform 的结构
transform 中的所有属性如下:
transform 中的所有方法如下:
2. transform.position 赋值和获取
//(修改区 1)
Vector3 startPos = transform.position;
//(修改区 2)
Vector3 assignPos;
其中 测试实验个数 n = 4
//(修改区 3)
//获取 transform.position
case 1:
for (int i = 0; i < count; ++i)
{
assignPos = transform.position;
}
break;
//正常 transform.position 加赋值
case 2:
for (int i = 0; i < count; ++i)
{
transform.position += Vector3.up;
}
break;
//正常 transform.position 赋值
case 3:
for (int i = 0; i < count; ++i)
{
transform.position = Vector3.up;
}
break;
//用向量赋值和 tranform.position 赋值比较
case 4:
for (int i = 0; i < count; ++i)
{
assignPos = Vector3.up;
}
break;
//(修改区 4)
transform.position = startPos;
再在Unity中进行多次测试后取稳定值,记录实验数据:
次数\实验名称 | 对比组 | 获取 | 加赋值 | 赋值 | 向量赋值 |
---|---|---|---|---|---|
1 | 288 | 118292 | 192474 | 117565 | 1949 |
2 | 381 | 158737 | 267344 | 158888 | 2264 |
3 | 515 | 171582 | 285283 | 153551 | 2276 |
由实验记录可知,单单获取 transform.position
的时间消耗和赋值 transform.position
差不多,甚至还是给普通向量赋值的 70
倍。
可以得出结论:
如果要使一个物体移动,先计算好所有向量的合向量,再给
transform.position
赋值,也不能频繁获取transform.position
,如果常用的话就先储存在Vector3
中。
新人博主,请大家多多光照~~如果有什么不正确或不足的地方请在评论区积极指出哟,一起学习一起进步~