您的位置:首页 > 移动开发 > Unity3D

u3d菜鸟入门:unity中物体旋转初探

2015-08-16 23:39 351 查看
最近一直在研究做一个坦克类小游戏,希望做成儿时玩的《赤色要塞》那样。

在做主角控制的时候,遇到了一点问题。拿模拟器又玩了玩《赤色要塞》,发现它的操作模式是,按下方向键,当方向与自己的前方(即车头方向)相同时,才会向前移动,否则,会先将车头旋转到这个方向,然后再开始移动。

需求确定了,开始动手做。刚开始觉着很简单,使用transform.Rotate()方法可以很轻松的解决问题。但随后发现了一个问题:Rotate()方法只能让物体顺时针旋转,假如主角现在车头朝上(平面上方向只有上下左右四个大方向),我按下左键的话,车头会顺时针旋转270°转到左边,然后再移动。我觉着这是个不好的设计,车头完全可以逆时针选择90°就可以了。

于是乎,就开始了漫漫探索路。自己想到的解决方案:

1.既然是要旋转物体,那当然需要确定旋转角度了。所以得先求得角度。

2.求得角度后,要知道如何旋转吧?(顺时针or逆时针)

OK,那我们就先来求角度:

在unity里,如何来求角度呢?首先想到的是,既然有角度,肯定有角,而这个角的两边,就是我们当前车头方向,和要旋转到的方向。通过向量的内积公式(点乘公式):|A·B| = |A|·|B|·cos<A,B>可以很快求得cos<A,B>的值。然后再通过反余弦求得夹角的大小,因为在计算时可以将向量规范化(也就是将其模变为1),所以cos<A,B>=|A·B|。然后我们就能通过Mathf.Acos(|A·B|) * Mathf.Rad2Deg 来计算出夹角值。

这里的Mathf.Acos方法返回的是一个弧度值,需要转换成角度值(也就是乘以180/PI,也就是这个Mathf.Rad2Deg)。

这样夹角大小就求出来了。但很快我又发现一个让我很沮丧的问题!夹角是算出来了,而且计算这玩意已经肥了不少功夫!复习向量知识,查API怎么用,计算验证……但是,谁能告诉我这是顺时针还是逆时针啊?!无奈,只能继续探索。好吧,好歹夹角算出来了,只要知道旋转方向即可。

然后又开始查API,查旋转的方法。偶然间在CSDN上一个帖子了看到了解决办法,是zhao4zhong1老师给出的解决方法,言简意赅:去查向量叉乘。。看到这几个字,猛地一拍大腿,艾玛,向量叉乘就可以判断方向啊,而且可以计算夹角!

根据外积公式(叉乘公式)|A x B| = |A| ·|B|·sin<A,B>,那么我们首先要计算

|A x B|(记为C),这里unity脚本中给出了API,可以直接计算得到其值(但我还是推荐不懂得小伙伴一起研究下向量叉乘,毕竟搞这个还是很基础的说):Vector3 Vector3.Cross(Vector3 left,Vector3 right)。

要注意下:这里两个向量不能随便填,left填起始向量(也就是当前车头方向),right填目标向量(要旋转到的方向)。得到的向量是垂直于left和right向量的一个向量,也就是它们的旋转轴。因为left和right向量都是在y=0的平面上,所以C的y值肯定不等于0。之所以不能随便填left和right,是因为这个轴表示从left旋转到right向量,而根据左手定则(unity坐标系是左手坐标系)判断求得向量C的正方向。而C的y值如果大于零,则表示从left旋转到right是顺时针,反之是逆时针。

下面是一张示意图(大家凑合着看。。),a=(0,0,1),b=(1,0,0),则a x b 可以得到c1和c2两个向量,因为均垂直于a,b所在平面,通过左手定则来判断最终要的向量。



终于,搞定了这个问题。然后去敲代码,实现之!

代码大致如下:

Vector3 dstVec ;    //旋转的终点向量
Vector3 srcVec; //旋转的起点向量
//初始化srcVec, dstVec
......
Vector3 CrossVec = Vector3.Cross(srcVec,dstVec);        //获得叉乘向量
//c = a x b,那么|c| = |a||b|sin<a,b>
//根据上述公式,可以计算得出sin<a,b>,然后通过反正弦值来获得向量夹角的大小
//PS:这里需要判断下srcVec和dstVec是否是零向量,
//如果是的话,用这个方法是会出错的!
float angle = Mathf.Asin( CrossVec.magnitude / ( srcVec.magnitude * dstVec.magnitude) )* Mathf.Rad2Deg;
//这里的Mathf.Asin方法返回的是一个弧度值,
//所以要转换成角度,就得乘以Mathf.Rad2Deg(也就是180/Pi)
int dir = 1;
if( CrossVec.y < 0 )
dir = -1;
transform.Rotate(0,dir*angle,0);


经过实际验证,上述代码可以实现目标功能!

但实践中仍然有两个问题困扰着我:

1.当两个向量夹角为180°时,可能会出现无法旋转的问题。仔细思考了下,可能是因为当夹角为180度时,sin

Vector3 dstVec ;    //旋转的终点向量
Vector3 srcVec; //旋转的起点向量
//初始化srcVec, dstVec
......
int dir = 1;
float angle = 0;
if( Mathf.Abs(dstVec.x) == Mathf.Abs(srcVec.x) ||
Mathf.Abs(dstVec.z) == Mathf.Abs(srcVec.z))
angle = 10;          //初始化一个旋转角度
else
{
Vector3 CrossVec = Vector3.Cross(srcVec,dstVec);        //获得叉乘向量
//c = a x b,那么|c| = |a||b|sin<a,b>
//根据上述公式,可以计算得出sin<a,b>,然后通过反正弦值来获得向量夹角的大小
//PS:这里需要判断下srcVec和dstVec是否是零向量,
//如果是的话,用这个方法是会出错的!
angle = Mathf.Asin( CrossVec.magnitude / ( srcVec.magnitude * dstVec.magnitude) )* Mathf.Rad2Deg;
//这里的Mathf.Asin方法返回的是一个弧度值,
//所以要转换成角度,就得乘以Mathf.Rad2Deg(也就是180/Pi)
if( CrossVec.y < 0 )
dir = -1;
}
transform.Rotate(0,dir*angle,0);


2.Unity中的向量显示真心蛋疼!

上面代码中

if( Mathf.Abs(dstVec.x) == Mathf.Abs(srcVec.x) ||
Mathf.Abs(dstVec.z) == Mathf.Abs(srcVec.z))
angle = 10;          //初始化一个旋转角度


这里比较x、z的值,经常会出现这里不执行跳过的情况,但调试的时候也看了下,srcVec和dstVec一般都显示为比如(0,0,1),(0,0,-1),也就是说,它们是方向相反的向量。但这里就是不执行,转而进入else里执行。我很纳闷,想不明白,打印出来这两个向量,打印结果也是一样的。在某次调试的过程中,无意间鼠标放到了srcVec上,我点开看了下,看到虽然它显示为(0,0,1),但这只是它的normalized的值,如下图所示:



这张图只是为了展示unity中调试向量的信息。。

当时遇到的问题是:srcVec的值像上图一样,NextDir和normalized里显示的都是一样的(0,0,1),但下方的z值却显示的是0.9999999。。然后我拿它去与1做比较,当然不会相等啦!

网上多方寻求没找到合适的处理方法,没辙,只好自己写一个方法来判断:

static bool IsEqual(float a , float b)
{
float res = Mathf.Abs (a) - Mathf.Abs (b);
return res < 1f / 1000000 || res > (-1f / 1000000);
}


当然,上述代码中的

if( Mathf.Abs(dstVec.x) == Mathf.Abs(srcVec.x) ||
Mathf.Abs(dstVec.z) == Mathf.Abs(srcVec.z))
angle = 10;          //初始化一个旋转角度


最终换成了

if( IsEqual(Mathf.Abs(transform.forward.x),Mathf.Abs(NextDir.x))||
IsEqual(Mathf.Abs(transform.forward.z),Mathf.Abs(NextDir.z)))
angle = 10;          //初始化一个旋转角度


这样,问题最终得以解决!

PS:在查阅资料过程中,查到了一个求两个向量夹角的方法,代码如下:

/// <summary>
/// AngleBetween - the angle between 2 vectors
/// </summary>
/// <returns>
/// Returns the the angle in degrees between vector1 and vector2
/// </returns>
/// <param name="vector1"> The first Vector </param>
/// <param name="vector2"> The second Vector </param>
public static double AngleBetween(Vector vector1, Vector vector2)
{
double sin = vector1._x * vector2._y - vector2._x * vector1._y;
double cos = vector1._x * vector2._x + vector1._y * vector2._y;

return Math.Atan2(sin, cos) * (180 / Math.PI);
}


这个是什么意思呢?没看懂,求哪位知道的达人给解答下,或者告知下应该查什么知识?
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: