【Unity3d基础】Unity3d 中如何旋转一张2D图片到指定角度

引言

近日在开发项目中,遇到一个这样的需求:将炮弹旋转至开火方到受击方所成的角度上,简言之就是将一枚炮弹旋转到开或者到敌人的方向上。

如何旋转2D贴图?

查阅了很多资料,都是关于四元数,欧拉角的问题。有点杀鸡焉用牛刀的意味,于是自己摸索着利用已有的接口用一种简单的方法实现。(注意,我的需求存在于2D场景内)

用到的接口

Vector3.Angle

public static float  Angle( Vector3  fromVector3  to);

Parameters

from The angle extends round from this vector.
to The angle extends round to this vector.

Description

Returns the angle in degrees between from and to.

The angle returned is always the non reflex angle between the two vectors - ie the smaller of the two possible angles between them and never greater than 180 degrees.

using UnityEngine;

public class AngleExample : MonoBehaviour
{
	public Transform     target;

	// prints "close" if the z-axis of this transform looks
	// almost towards the target

	void Update ()
	{
		Vector3 targetDir = target.position - transform.position;
		float angle = Vector3.Angle( targetDir, transform.forward );

		if( angle < 5.0f )
			print( "close" );
	}
}

Vector3.Angle()代表的实际含义其实是,原点到from点与原点到to点的夹角,即OA与OB的夹角。俗话说不会美术的策划不是好程序:


然而当我们需要AB与水平坐标的夹角该怎么办呢?这时候就需要引入传说中的单位向量了。

解决方案

using UnityEngine;
using System.Collections;

public class ArrowTest : MonoBehaviour {

    public UISprite arrow;
	// Use this for initialization
	void Start () {
        TestForRotation(); 
	
	}
	
	// Update is called once per frame
	void Update () {
	
	}

    void TestForRotation()
    {
        GameObject pointA = GameObject.Find("PointA");
        GameObject pointB = GameObject.Find("PointB");
        Vector3 vecA = pointA.GetComponent<Transform>().localPosition;
        Vector3 vecB = pointB.GetComponent<Transform>().localPosition;
        Vector3 direction = vecB - vecA;                                    ///< 终点减去起点
        float angle = Vector3.Angle(direction, Vector3.right);              ///< 计算旋转角度
        arrow.GetComponent<Transform>().Rotate(0, 0, angle);
    }
}
运行结果:

箭头指向AB方向,垂直结果:



任意“角度”运行结果:



为什么“角度”要加引号?我们来看如果需要旋转到BA所成的夹角会出现什么情况:


显然,箭头的指向于BA所成的角度并不一致。

这是由于Vector3.Angle()的定义中有这样一句话:The angle returned is always the non reflex angle between the two vectors - ie the smaller of the two possible angles between them and never greater than 180 degrees.

返回的角度总是两个角度中较小的一个,且不会超过180°,因此当所需旋转的角度大于90°时,需要进行适当的变换。如何界定是否超过90°呢?这时候Vector3.Dot,向量点乘就派上用场了,当旋转角度与Vector3.up相反时,需要对角度进行适当的变换。

修正后的代码如下:

using UnityEngine;
using System.Collections;

public class ArrowTest : MonoBehaviour {

    public UISprite arrow;
	// Use this for initialization
	void Start () {
        TestForRotation(); 
	
	}
	
	// Update is called once per frame
	void Update () {
	
	}

    void TestForRotation()
    {
        GameObject pointA = GameObject.Find("PointA");
        GameObject pointB = GameObject.Find("PointB");
        Vector3 vecA = pointA.GetComponent<Transform>().localPosition;
        Vector3 vecB = pointB.GetComponent<Transform>().localPosition;
        //Vector3 direction = vecB - vecA;                                    ///< 终点减去起点(AB方向与X轴的夹角)
        Vector3 direction = vecA - vecB;                                  ///< (BA方向与X轴的夹角)
        float angle = Vector3.Angle(direction, Vector3.right);              ///< 计算旋转角度
        direction = Vector3.Normalize(direction);                           ///< 向量规范化
        float dot = Vector3.Dot(direction, Vector3.up);                  ///< 判断是否Vector3.right在同一方向
        if (dot < 0)
            angle = 360 - angle;
        Debug.LogWarning("vecA:" + vecA.ToString() + ", vecB:" + vecB.ToString() + ", angle: " + angle.ToString());
        arrow.GetComponent<Transform>().Rotate(0, 0, angle);
    }
}
箭头转向BA向量的结果如图:


7.11补充:

1)更新一种计算角度的方法,通过Atan()接口

2)通过欧拉角来旋转对象

3) 通过四元数的插值函数来平滑过渡旋转过程

using UnityEngine;
using System.Collections;

public class ArrowTest : MonoBehaviour
{

    public UISprite arrow;

    private Vector3 targetVec;
    private float targetAngle, AtanTarget;
    // Use this for initialization
    void Start()
    {
        TestForRotation();

    }

    // Update is called once per frame
    void Update()
    {
        ///< 补充点3: 通过插值使箭头平滑的转向指定的方向,在游戏中常用于人物转头,转移视角等操作,当然是在3D空间中,这里就抛块砖了。0.1f只是我偷懒写的一个单位值,正式的项目中一般会deltaTime * speed来控制转向的速度。
        arrow.GetComponent<Transform>().rotation = Quaternion.Slerp(arrow.GetComponent<Transform>().rotation, Quaternion.Euler(0, 0, AtanTarget), 0.1f);
    }

    void TestForRotation()
    {
        GameObject pointA = GameObject.Find("PointA");
        GameObject pointB = GameObject.Find("PointB");
        Vector3 vecA = pointA.GetComponent<Transform>().localPosition;
        Vector3 vecB = pointB.GetComponent<Transform>().localPosition;
        //Vector3 direction = vecB - vecA;                                    ///< 终点减去起点(AB方向与X轴的夹角)
        Vector3 direction = vecA - vecB;                                  ///< (BA方向与X轴的夹角)
        float angle = Vector3.Angle(direction, Vector3.right);              ///< 计算旋转角度
        direction = Vector3.Normalize(direction);                           ///< 向量规范化
        float dot = Vector3.Dot(direction, Vector3.up);                  ///< 判断是否Vector3.right在同一方向
        if (dot < 0)
            angle = 360 - angle;
        
        targetAngle = angle;
        targetVec = new Vector3(0, 0, angle);

        ///< 补充点1: 通过Atan2与方向向量的两条边可以计算出转向的角度,通过计算结果可以看到targetAngle与-AtanTarget相加正好是360°,即二者都指向同一方向。具体使用场景需要根据具体需求分析。
        AtanTarget = Mathf.Atan2(direction.y, direction.x) * Mathf.Rad2Deg;
        Debug.LogWarning("vecA:" + vecA.ToString() + ", vecB:" + vecB.ToString() + ", targetAngle: " + targetAngle.ToString() + ", AtanTarget: " + AtanTarget.ToString());
        //arrow.GetComponent<Transform>().Rotate(0, 0, angle);

        ///< 补充点2: 使用欧拉角来控制物体的旋转
        //arrow.GetComponent<Transform>().eulerAngles = new Vector3(0, 0, angle);
    }
}


猜你喜欢

转载自blog.csdn.net/u010832643/article/details/51842634