引言
近日在开发项目中,遇到一个这样的需求:将炮弹旋转至开火方到受击方所成的角度上,简言之就是将一枚炮弹旋转到开或者到敌人的方向上。
如何旋转2D贴图?
查阅了很多资料,都是关于四元数,欧拉角的问题。有点杀鸡焉用牛刀的意味,于是自己摸索着利用已有的接口用一种简单的方法实现。(注意,我的需求存在于2D场景内)
用到的接口
from | The angle extends round from this vector. |
to | The angle extends round to this vector. |
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().localPosition;
Vector3 vecB = pointB.GetComponent().localPosition;
Vector3 direction = vecB - vecA; ///< 终点减去起点
float angle = Vector3.Angle(direction, Vector3.right); ///< 计算旋转角度
arrow.GetComponent().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().localPosition;
Vector3 vecB = pointB.GetComponent().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().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().rotation = Quaternion.Slerp(arrow.GetComponent().rotation, Quaternion.Euler(0, 0, AtanTarget), 0.1f);
}
void TestForRotation()
{
GameObject pointA = GameObject.Find("PointA");
GameObject pointB = GameObject.Find("PointB");
Vector3 vecA = pointA.GetComponent().localPosition;
Vector3 vecB = pointB.GetComponent().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().Rotate(0, 0, angle);
///< 补充点2: 使用欧拉角来控制物体的旋转
//arrow.GetComponent().eulerAngles = new Vector3(0, 0, angle);
}
}