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

最近一直在研究做一个坦克类小游戏,希望做成儿时玩的《赤色要塞》那样。
在做主角控制的时候,遇到了一点问题。拿模拟器又玩了玩《赤色要塞》,发现它的操作模式是,按下方向键,当方向与自己的前方(即车头方向)相同时,才会向前移动,否则,会先将车头旋转到这个方向,然后再开始移动。
需求确定了,开始动手做。刚开始觉着很简单,使用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所在平面,通过左手定则来判断最终要的向量。
u3d菜鸟入门:unity中物体旋转初探_第1张图片

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

    Vector3 dstVec ;    //旋转的终点向量
    Vector3 srcVec; //旋转的起点向量
    //初始化srcVec, dstVec
    ......
    Vector3 CrossVec = Vector3.Cross(srcVec,dstVec);        //获得叉乘向量
    //c = a x b,那么|c| = |a||b|sin
    //根据上述公式,可以计算得出sin,然后通过反正弦值来获得向量夹角的大小
    //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
        //根据上述公式,可以计算得出sin,然后通过反正弦值来获得向量夹角的大小
        //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的值,如下图所示:
u3d菜鸟入门:unity中物体旋转初探_第2张图片
这张图只是为了展示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:在查阅资料过程中,查到了一个求两个向量夹角的方法,代码如下:

/// 
/// AngleBetween - the angle between 2 vectors
/// 
/// 
/// Returns the the angle in degrees between vector1 and vector2
/// 
///  The first Vector 
///  The second Vector 
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);
}

这个是什么意思呢?没看懂,求哪位知道的达人给解答下,或者告知下应该查什么知识?

你可能感兴趣的:(U3D,个人心得)