Unity笔记-08-三维数学
Unity里的三维数学
1. 向量
所有物体的Position
属性指的都是从世界坐标原点到位置坐标的向量
代码:position.magnitude;
可以获得此向量的模
代码:position.normalized;
可以获得与此向量同向的单位向量
这里可以说明:Translate()
的移动方法的参数就可以给予一个向量,对应物体就会沿着该向量方向移动大小等同于该向量模大小的距离
通过代码:Direction=A.position-B.position
就可以获得两个物体的向量差,向量指向被减数,方向指向A
将这个方向向量给到B.Translate()
方法,就可以使得B沿着去往A的方向到达A这个点的位置
2. 三角函数
坐标系转换
代码:Vector WorldPoint=transform.TransformPoint(Vector3 E)
参数E为物体自身坐标系的对应位置,该方法将会返回物体自身坐标系对应E位置的点在世界坐标系中的位置
获得物体右前放30度,10米远的坐标点
float x = Mathf.Sin(30 * Mathf.Deg2Rad) * 10;
float z = Mathf.Cos(30 * Mathf.Deg2Rad) * 10;
Vector3 point = transform.TransformPoint(x, 0, z);
3. 向量点乘和叉乘的应用
float dot = Vector3.Dot(A.normalized,B.normalized);//单位向量的点积即为两个向量之间的cos值
float angle = Mathf.Acos(dot);
Vector3.normalized
方法可以获得此向量方向上的单位向量,A
向量上的单位向量和B
向量上的单位向量的积即为两个向量夹角的cos
值,通过Acos
方法可获得两个向量间的夹角,但是这种方法获得的夹角只能在0-180
度以内
如果需要获得一圈大于180
度的夹角就必须用到叉乘
float dot = Vector3.Dot(A.position.normalized, B.position.normalized);
angle = Mathf.Acos(dot)*Mathf.Rad2Deg;//计算180内的夹角
if (cross.y < 0)//判断方位
{
angle = 360 - angle;
}
两个向量的叉乘会因为二者的方位不同导致叉乘结果向量的方向相反,通过判断叉乘结果向量的方向可以判断A
向量和B
向量的左右方位,也能够获得大于180
度的外夹角
应用场景
当玩家走进这个敌人弧形120度检测范围内才会受到敌人攻击,该如何检测?
首先需要判断敌人的朝向向量和由敌人指向玩家的向量之间的角度,判断是否进入角度范围,通过点乘获得角度,在通过叉乘判断左右方位,如果要加距离判断,那么只需要判断玩家与敌人之间的距离即可。
那么敌人的朝向向量如何获取?
一般情况下,为了方便,敌人的朝向向量设定为自身坐标系的Z轴或X轴,只需要代码:
transform.TransformPoint(0,0,1)
或者更加简便的代码:
transform.forward
注意:敌人的朝向向量需要的是在世界坐标系中的向量,因此需要将敌人以自身坐标系的朝向向量转化为世界坐标系中的向量坐标,上述两种代码做的是同样的事情,都做到了转化。
敌人指向玩家的向量如何获取?
代码:Vector3 distance=Player.position-Enemy.position
向量相减即可,注意向量相减的朝向指向被减向量
4. 欧拉角
使用三个角度来保存方位,X与Z轴以自身坐标系为准旋转,Y轴则以世界坐标系旋转
API:Vector3 eulerAngle=transform.eulerAngles;
优点
仅仅用三个数字表达方位,占用空间小
沿坐标轴旋转的单位为角度,符合人的思考方式
任意三个数字都是合法的,不存在不合法的欧拉角
缺点
但是方位的表达不唯一,对于一个方位,存在多个欧拉角描述,因此无法判断多个欧拉角代表的角位移是否相同
例如:0
`5`0
和 0
`365`0
0
`-5`0
和 0
`355`0
为了保证任意方位都只有独一无二的表示,Unity引擎限制了角度范围,即沿X轴旋转限制在-90
到90
之间,沿Y与Z旋转限制在0
到360
之间,不过Unity最新版本有所改动
注意:欧拉角虽然也是Vector3
数据类型但是,它没有方向没有大小的概念,他的x
-y
-z
表示的是各个轴向 上的旋转角度,这一点注意和position
位置区分,他们虽然都是Vector3
数据类型,但是意义不同。
position
有方向(从世界原点指向当前位置),有大小(世界原点到当前位置的直线距离),他的x
-y
-z
表示的是各个轴向 上的有向位移
万向节死锁
当X轴的旋转度为±90度的时候,此时,物体自身的Z轴和世界坐标系的Y轴重合,此时若再沿Z或Y旋转将失去一个自由度,在这种情况下,规定沿着Z轴完成绕竖直轴的全部旋转,即此时Y轴为0
由此我们看到欧拉角是有缺陷的,并且自身无法解决这个问题,因此我们要学四元数,它可以解决这个问题
5. 四元数
Quaternion在3D图形学中代表旋转,由一个三维向量(X/Y/Z)
和一个标量W
组成
旋转轴为V(Vector3)
,旋转弧度为θ
,如果使用四元数表示,那么四个分量为
x=sin(θ/2)*V.x
y=sin(θ/2)*V.y
z=sin(θ/2)*V.z
w=cos(θ/2)
x
y
z
w
的取值范围为 -1
~ 1
API:Quaternion qt=transform.rotation
代码:Quaternion.Euler(Vector3 eulerAngle)
此方法可以通过欧拉角转化为四元数进行旋转
代码:tramsform.Rotate(Vector3 eulerAngle)
此方法也是四元数变换,不会有万向节死锁的问题,此代码等价于
transform.rotation*=Quaternion.Euler(Vector3 eulerAngle)
代码:Vector3 newVec = Quaternion.AngleAxis(angle,axis)*oriVec
angle
:旋转度数
axis
:围绕哪个轴旋转
oriVec
:初始向量
此方法可使得某个向量绕任意轴旋转任意角度
四元素相乘
两个四元数相乘可以达到组合旋转的作用
Quaternion A=Quaternion.Euler(0,20,0)*Quaternion.Euler(0,30,0);
//等价于下面这个代码
Quaternion B=Quaternion.Euler(0,50,0);
与向量相乘
四元数左乘向量,表示将该向量按照四元数表示的角度在世界坐标系中旋转对应角度,例如:
Vector3 point=new Vector3(0,20,0);
Vector3 newPoint=Quaternion.Euler(0,10,0)*point;
优点
可以避开万向节死锁
缺点
难于使用,不建议单独修改某个值
存在不合法的四元数
举例与说明
//注意所有的旋转正为顺,负为逆
transform.rotation *= Quaternion.Euler(1, 0, 0);//自身X轴旋转1度
transform.position = Quaternion.Euler(1, 0, 0) * transform.position;//绕世界坐标轴旋转1度
transform.position = transform.position + Quaternion.Euler(30, 0, 0) * new Vector3(0, 1, 0);//指定朝向移动:此处为沿Y轴顺时针偏移30度的方向移动1单位
transform.position = transform.position + Quaternion.Euler(30, 0, 0) *transform.rotation* new Vector3(0, 1, 0);//跟随对象旋转朝向而做出响应变化
炸弹爆炸的范围判定
假定玩家为一个圆柱体,有身体半径,判定范围通过炸弹到玩家的左右切线的模长判断玩家处于炸弹的范围区域
获得左右切线
private Transform Player;
private float radius;//半径
private Vector3 leftPoint;//左切点
private Vector3 rightPoint;//右切点
private float angle;//角度
private void Start()
{
//这里储存的是引用,玩家位置变化,这里也会变化
Player = GameObject.FindObjectOfType<PlayerTag_1>().transform;
radius = Player.GetComponent<CapsuleCollider>().radius;
}
/// <summary>
/// 伤害范围判定
/// </summary>
private void DamageVote()
{
//向量作差获得炸弹和玩家之间的方向向量
Vector3 distance =transform.position-Player.position;
//将此向量的模与玩家半径一致
Vector3 direction = distance.normalized * radius;
//获得距离向量与切点向量之间的角度
angle = Mathf.Acos(radius/distance.magnitude)*Mathf.Rad2Deg;//算出来的是弧度,一定要记得转化为角度!!!
//旋转角度,获得切点向量
rightPoint = Quaternion.Euler(0, angle, 0) * direction;
leftPoint = Quaternion.Euler(0, -angle, 0) * direction;
rightPoint = Player.position + rightPoint;//注意这里一定要加上玩家位置,因为单纯的向量是从世界原点开始画的
//注意:如果表示方向,单纯的向量不会出错,如果表示位置单纯的向量通常会出错,因为单纯的向量是从世界原点开始画的,而正确的位置应该要加上初始位置向量
leftPoint = Player.position + leftPoint;
//这里为了调试直接画出来
Debug.DrawLine(rightPoint, transform.position,Color.red);
Debug.DrawLine(leftPoint, transform.position, Color.red);
}
private void Update()
{
DamageVote();
}
Vector3(常用方法说明)
float angle = Angle(Vector a, Vector b);
返回a
向量和b
向量之间的夹角
Vector3 b = Vector3.ClampMagnitude(a, float length);
返回一个和a
向量同方向的b
向量,模长为length
,但是最大模长不会超过a
Vector3 c = Vector3.Cross(Vector a, Vector b)
;
返回a
与b
的叉乘
float distance = Vector3.Distance(Vector a, Vector b)
;
返回a
与b
坐标之间的直线距离
float c = Vector3.Dot(Vector a, Vector b)
返回a
与b
之间的点乘结果
Vector3 c = Vector3.Lerp(Vector a, Vector b, float t)
返回两个点之间的线性插值
通过插值t
在点a
和点b
之间插值。参数t固定在范围[0,1]
内。这通常用于沿两个端点之间的直线找到一个点(例如,在这些点之间逐渐移动对象)。
返回的值等于a+(b-a)*t
(也可以写为a*(1-t)+b*t
)。
当t=0
时,Vector3.Lerp(a,b,t)
返回a
。
当t=1
时,Vector3.Lerp(a,b,t)
返回b
。
当t=0.5
时,Vector3.Lerp(a,b,t)
返回a
和b
之间的中点。
如果你传的t
超过1
,则按照1
处理,这里要提到另外一个方法:
Vector3 c = Vector3.LerpUnclamped(Vector a, Vector b, float t)
该方法的t
允许超过1
自然运动调试,可以在UnityInspecto
r界面通过Curve
界面通过函数图像调试速度
transform.position = Vector3.Lerp(transform.position, target, curve.Evaluate(x));
通过AnimationCurve来通过函数控制物体的移动速度
Vector3 force = Vector3.Project(Vector3 a, Vector3 b);
参数解释:a
为目标向量,b
为投影向量,返回值为a
在b
上的投影向量
Vector3 force = Vector3.ProjectOnPlane(Vector3 a, Vector b)
参数解释:a
为目标向量,b
为平面法向量,返回值为a
在b
对应过世界原点平面的投影
Vector3 result = Vector3.Reflect(Vector3 a, Vector3 b)
参数解释:a
为目标向量,b
为法线方向向量,返回值为a
以b
为法线反射出去的反射光线向量
Vector3 result = Vector3.MoveTowards(Vector3 a, Vector3 b, float speed)
一般用于物体的匀速移动,a
向量为起点,b
向量为目标点,speed
为移动速度,一般如此使用:
transform.position = Vector3.MoveTowards(transform.position, target, speed)
Quaternion(常用方法说明)
Quaternion.Euler(Vector3 angle)
以欧拉角构建四元数,使该四元数变化以该欧拉角旋转,该旋转以世界坐标为准旋转,返回该旋转
四元数如何转换欧拉角?代码:
Vector3 eulerAngle=qt(Quaternion).eulerAngles;
Quaternion.AngleAxis(float angle, Vector3 axis);
angle
为角度,axis
为轴向,意为该四元数变化绕着axis
轴向旋转angle
角度,注意angle
为角度不是弧度,返回该旋转
Quaternion.LookRotation(Vector3 target)
当前对象注视旋转,target
为从对象指向目标的方向向量,一般为目标向量减去对象向量,返回朝向目标方向的旋转
注意:这种注视旋转的注视方向为z
轴,一般情况下物体的rotation朝向为z
轴,如果要设置x
轴朝向,可以将方向向量赋值给transform.right
Quaternion.lerp(Quaternion a, Quaternion b, float t);
差值旋转
a
为初始四元数表示的旋转朝向,b
为终点四元数表示的旋转朝向,t
为比例
一般联合LookRotation
方法一起使用,通过LookRotation
获得旋转终值,再通过此方法达到逐渐旋转的效果,而非瞬间旋转,但是差值旋转只能无限接近,却不能相等,因此需要判断如果一个旋转及其接近那么便直接将终点四元数直接赋值
这个接近判断一般使用Quaternion.Angle
方法判断角度大小,当角度小于某个值的时候就算已经旋转到了
Quaternion.RotationTowards(Quaternion a, Quaternion b, float speed)
匀速旋转
参数解释:a
为旋转起点,b
为旋转终点,speed
为旋转速度
Quaternion.Angle(Quaternion a, Quaternion b);
获得a
和b
两个四元数之间的角度
Quaternion.FromToRotation(Vector3 a, Vector3 b);
参数解释:a为旋转起点,b为旋转终点,返回从a旋转到b的相对旋转量