2个点旋转角度 unity3d

2015-05-19 13:40:34 yangyisen0713 阅读数 10669

主要涉及函数:

Input.GetAxis(“Mouse x”) 可取得鼠标横向(x轴)移动增量

Input.GetAxis(“Mouse y”) 可取得鼠标竖向(y轴)移动增量

通过勾股定理获取拖拽长度,长度越长旋转越快。在project setting--Input 可以设置。

这里用Cube来做例子,因为方体看旋转比较清楚,如图:


代码如下:

using UnityEngine;
using System.Collections;

public class NewBehaviourScript : MonoBehaviour {

	private bool onDrag = false;  //是否被拖拽//    
	public float speed = 6f;   //旋转速度//    
	private float tempSpeed;   //阻尼速度// 
	private float axisX = 1;
	//鼠标沿水平方向移动的增量//   
	private float axisY = 1;    //鼠标沿竖直方向移动的增量//   
	private float cXY;
	void OnMouseDown()
	{
		//接受鼠标按下的事件// 
		
		axisX = 0f; axisY = 0f;
	}
	void OnMouseDrag()     //鼠标拖拽时的操作// 
	{
		
		onDrag = true;
		axisX = -Input.GetAxis("moveX");
		//获得鼠标增量// 
		axisY = Input.GetAxis("moveY");
		cXY = Mathf.Sqrt(axisX * axisX + axisY * axisY); //计算鼠标移动的长度//
		if (cXY == 0f) { cXY = 1f; }
		
	}
	float Rigid()      //计算阻尼速度//    
	{
		if (onDrag)
		{
			tempSpeed = speed;
		}
		else
		{
			if (tempSpeed > 0)
			{
				tempSpeed -= speed * 2 * Time.deltaTime / cXY; //通过除以鼠标移动长度实现拖拽越长速度减缓越慢// 
			}
			else { 
				tempSpeed = 0; 
			}
		}
		return tempSpeed;
	}
	
	void Update()
	{
		// this.transform.Rotate(new Vector3(axisY, axisX, 0) * Rigid(), Space.World); //这个是是按照之前方向一直慢速旋转
		if (!Input.GetMouseButton(0))
		{ 
			onDrag = false;
			this.transform.Rotate(new Vector3(axisY, axisX, 0)*0.5f, Space.World); 
		}
	}
}

最终效果如图:




2018-05-11 14:24:39 u011232064 阅读数 21462

Unity 3D 中的旋转

一、Unity 3D 中 Rotation

在Unity中,旋转通常可以用一个三维向量(x,y,z)表示。实际上这是欧拉角。三个分量分别是绕x轴、y轴和z轴的旋转角度。

要对一个GameObject进行旋转,可以直接通过如下代码:

transform.Rotate(xAngle, yAngle, zAngle);

那么有如下疑问:

  1. 上述的x轴、y轴、z轴指的是哪组基?是世界坐标系下的xyz轴,还是局部坐标系下的xyz轴?还是其他?
  2. 旋转的正方向如何?
  3. 旋转的顺序如何?

下面一一解答。

二、旋转轴

首先,回答第一个问题,到底旋转轴是哪个坐标系的基?分为如下三种情况。

1. 旋转轴:Inspector 中 Transform 的旋转数值

TransformExample1

对于这一个情况,Unity Doc 中有明确的说明,

The position, rotation and scale values of a Transform are measured relative to the Transform’s parent. If the Transform has no parent, the properties are measured in world space.

即,Editor中Transform组件的旋转轴是父节点的模型空间坐标轴,如果没有父节点,则旋转轴是世界空间坐标轴。

unity_transform

上图显示了如果Transform有父节点,如图中的”Mesh”,则Position将是在其父节点(这里是”Cow”)的模型空间中的位置;如果没有父节点,Position就是在世界空间中的位置。同样,Transform中的Rotation和Scale也是相同的道理。

2. 旋转轴:在Script中使用Rotate函数,在Space.Self中旋转

public void Rotate(Vector3 eulerAngles, Space relativeTo = Space.Self);
public void Rotate(float xAngle, float yAngle, float zAngle, Space relativeTo = Space.Self);
public void Rotate(Vector3 axis, float angle, Space relativeTo = Space.Self);

有上述三种重载函数,这里主要以第一种为例。其中第二个参数的取值有两种:Space.Self 或者 Space.World。

使用如下代码,测试上述函数的作用。

using UnityEngine;
using System.Collections;

public class Rotate : MonoBehaviour {
    public Space m_RotateSpace;
    public float m_RotateSpeed = 20f;

    // Update is called once per frame
    void Update()
    {
        transform.Rotate(Vector3.up * m_RotateSpeed * Time.deltaTime, m_RotateSpace);
    }
}

场景中进行测试的是一个长方体,其父节点的旋转为(30,30,0),圆柱体的初始旋转为(0,0,0)。在Inspector中将Rotate Space设置为Self后,运行结果见下图。可见,长方体是绕着局部坐标系的Y轴旋转的。

得出结论:在Space.Self中进行旋转,旋转轴就是局部坐标系的坐标轴。

RotateSpaceSelf

3. 旋转轴:在Script中使用Rotate函数,在Space.World中旋转

在Inspector中将Rotate Space设置为World后,运行结果见下图。这里我们知道,长方体的父节点的Y轴不是World的Y轴,而这里的长方体是绕着世界坐标系下的Y轴旋转的。

所以得出结论:在Space.World中进行旋转,旋转轴是世界坐标系的坐标轴。

RotateSpaceWorld

4. 静态欧拉角和动态欧拉角

前面说到的旋转轴的问题,在数学上有对应的概念。这就是所谓的静态欧拉角和动态欧拉角。

所谓静态欧拉角,就是其旋转轴使用的是静止不动的参考系。动态欧拉角,使用的是刚体本身作为参考系,因而参考系会随着刚体的旋转而旋转。

因此,使用Space.World旋转,以及Inspector中的旋转是静态欧拉角;使用Space.Self旋转,是动态欧拉角。

三、旋转的正方向

来到第二个问题,由于Unity中局部坐标系和世界坐标系都是左手坐标系,所以这里旋转的正方向可由右手法则判定。

四、旋转的顺序

下面来看第三个问题,旋转的顺序,即我们的欧拉角(xAngle, yAngle, zAngle)由三个分量组成,分别对应着绕x轴旋转,绕y轴旋转和绕z轴旋转,那么是如何绕着这三个轴进行旋转的呢?

这里也分为静态欧拉角和动态欧拉角的情况进行讨论。

1. 静态欧拉角

这种情况对应着上面所述的使用Space.World进行旋转,以及Inspector中的旋转。即使旋转轴在旋转的过程中保持不变,旋转的顺序会决定最后的旋转结果。我们看下面的例子会很清晰的理解:

  • 情形一:首先绕世界坐标系的x轴旋转90度,再绕世界坐标系的y轴旋转90度

RotateOrder1

  • 情形二:首先绕世界坐标系的y轴旋转90度,再绕世界坐标系的x轴旋转90度

RotateOrder2

可以看到,由于旋转顺序的不同,最终导致了旋转结果的不同!(究其本质,是因为矩阵乘法不满足交换律)

对于旋转的顺序,一般没有定式,因此,需要在使用时明确的指定出其顺序。对此有一个专门的术语,称为顺规。如果在坐标系中的旋转,先绕x轴旋转,再绕y轴,最后再绕z轴,则称之为X-Y-Z顺规。以此类推。

对于Unity,从文档中可以看到,其transform.Rotate()使用的是Z-X-Y顺规。因此如果在Unity中,使用静态欧拉角旋转(90,90,0)得到情形一的情况。

2. 动态欧拉角

这种情况对应着上面所述的使用Space.Self进行旋转。动态欧拉角除了上面说到的顺规问题(同样是Z-X-Y顺规),还有一个疑问:比如一个物体,初始状态记为A,以Z-X-Y顺规旋转(90,90,0),由于没有z轴旋转,第一步当然是绕着当前的x轴旋转90度,此时状态记为B,那么第二步要绕着y轴旋转90的时候,是绕着初始状态A时的y轴旋转,还是绕着此时的B状态下的y轴旋转呢?

首先来看下两者的区别:

  • 情形一:以状态A时的y轴旋转

RotateOrder3

  • 情形二:以状态B时的y轴旋转

RotateOrder4

Unity中的情况究竟如何呢?直接运行下面的代码会看到结果:

void Start () {
    transform.Rotate(90, 90, 0, Space.Self);
}

RotateOrder5

可以发现Unity中的情况与情形一相同。所以第二步要绕着y轴旋转90的时候,是绕着初始状态A时的y轴旋转。

为了得到情形二中的效果,可以分两次旋转,运行如下代码:

void Start () {
    transform.Rotate(90, 0, 0, Space.Self);
    transform.Rotate(0, 90, 0, Space.Self);
}

可以发现,此时的效果与情形一中相同了。

最终,我们的结论是:Unity中每次使用Space.Self进行Rotate时,都是绕着调用时刻的局部坐标系的坐标轴进行旋转的。

3. 静态欧拉角和动态欧拉角的等价形式

静态欧拉角和动态欧拉角是可以相互转换的。

转化规则就是:静态欧拉角中,在某一坐标系E下按照某一顺规如X-Y-Z旋转角度(a, b, c),等价于动态欧拉角中,在E下旋转(0, 0, c),在旋转后的坐标系E’中旋转(0, b, 0),在旋转后的新坐标系E”中旋转(a, 0, 0)。

在Space.Self中旋转以 Z-X-Y 顺规旋转角度(a, b, c),等价于在Space.Self中旋转(0, b, 0),在新的Space.Self中旋转(a, 0, 0),在更新的Space.Self中旋转(0, 0, c)。

下面我们来证明上述两种旋转是等价的。通过复合旋转矩阵的方式。

记:

绕坐标系E下的Z轴旋转c的旋转矩阵为Rz,
绕坐标系E下X轴旋转a的旋转矩阵为Rx,
绕坐标系E下Y轴旋转b的旋转矩阵为Ry;

绕坐标系E下的Y轴旋转b的矩阵为Rb(Rb == Ry),
绕坐标系E在绕Y轴旋转b后的新坐标系E’下的X轴旋转a的旋转矩阵为Ra,
绕坐标系E’在绕X轴旋转a后的新坐标系E”下的Z轴旋转c的旋转矩阵为Rc。

另外,这里将矩阵R的逆记为R~。

求证:Rz * Rx * Ry == Rb * Ra * Rc

证明:
Rb == Ry,由定义相同可知。
Ra = (Rb~) * Rx * Rb,要得到绕坐标系E在绕Y轴旋转b后的新坐标系E’下的X轴旋转a的旋转矩阵Ra,先应用Rb~旋转到坐标系E下,然后绕坐标系E下的X轴旋转a,最后应用Rb转回到坐标系E’。
Rc = ((Rb * Ra)~) * Rz * (Rb * Ra),理由同上。
所以有,
右边 = Rb * Ra * Rc
= Rb * Ra * ((Rb * Ra)~) * Rz * (Rb * Ra)
= Rz * Rb * Ra
= Rz * Rb * (Rb~) * Rx * Rb
= Rz * Rx * Rb
= Rz * Rx * Ry = 左边
证毕!

从代码上来说,就是下面两个函数是等价的。

private void RotateStatic(float a, float b, float c)
{
    // 静态欧拉角,依次绕着调用Rotate时的局部坐标系的z,x,y轴旋转a,b,c角度
    transform.Rotate(a, b, c, Space.Self);
}

private void RotateDynamic(float a, float b, float c)
{
    // 动态欧拉角,绕着调用Rotate时的局部坐标系的y轴旋转b角度
    transform.Rotate(0, b, 0, Space.Self);
    // 动态欧拉角,绕着调用Rotate时的局部坐标系的y轴旋转a角度
    transform.Rotate(a, 0, 0, Space.Self);
    // 动态欧拉角,绕着调用Rotate时的局部坐标系的y轴旋转c角度
    transform.Rotate(0, 0, c, Space.Self);
}

五、万向节锁(Gimbal Lock)

1. 什么是万向节锁

2. 如何产生万向节锁

3. 万向节锁的问题

3. 在欧拉旋转中尽力避免万向节锁

六、四元数(Quaternion)旋转

1. 什么是四元数

2. 用四元数进行旋转

参考

  1. Unity 中的旋转
  2. 欧拉角与万向节死锁
  3. 《Unity Shader 入门精要》第四章
2018-08-16 16:19:22 pz789as 阅读数 5495

在做项目时,很多时候可能需要做旋转功能,这个很简单,直接使用Rotate方法即可,如果在加上一个限制旋转角度,可不是简单的判断下角度就可以了。在Unity中,角度在它内部是用四元数表示的,这个我也弄不清楚,所以,在面板展示和你实际打印出来的localEulerAngles不一样!!可能你只是想改变x的角度,但是你转着转着可能看到变成x,y,z三个值都变了。

之前在网上看到一个解决方案,说是拿取localEulerAngles之后,对角度做一个变化:

float CheckAngle(float value){
	float angle = value - 180;
	if (angle > 0){
		return angle - 180;
	}
	return angle + 180;
}

这个虽然可以解决部分问题,但是我这里还是遇到了一个很奇特的问题,有时候突然变成反向的了。

我自己加了一个方法:

float angle = 0;
        Vector3 axis = Vector3.left;
        rotateXAnim.localRotation.ToAngleAxis(out angle, out axis);

这个可以得到某一个轴的角度,然后在根据axis的方向,就能得到实际的角度

angle = CheckAngle(angle*axis.x);

        if (angle > 90){
            angle = 90;
            rotateXAnim.localEulerAngles = new Vector3(angle, 0, 0);
        }else if (angle < -90){
            angle = -90;
            rotateXAnim.localEulerAngles = new Vector3(angle, 0, 0);
        }

结合起来,也能解决大部分问题。

可是,今天又出现一个奇怪问题,就是我前面说的,突然变成反向的角度,而且只有第一次出现,之后旋转都是正常的。

思来想去,最后想到一个曲线解决方案:

我们在初始化物体时,是知道物体的旋转角度的,那么我们就可以存储一个临时的欧拉角度,在需要旋转角度时,直接用这个临时的欧拉角度进行计算,计算得到结果之后,直接先判断角度是否超过我们给定的值,这样就不用经过四元数那一层计算了,那么也就不会出现万向锁和我上面说的问题了。

void MapRotating(Vector3 tempPosition){
		Vector3 tempAngle = Vector3.zero;
		tempAngle.x = tempPosition.y * 15f / scaleTrans.localScale.x;
		tempAngle.y = -tempPosition.x * 15f / scaleTrans.localScale.x;
		nowRotate += tempAngle;
		nowRotate.x = ClampAngle(nowRotate.x, -90, 90);
		SetNowRotValue(nowRotate.x, nowRotate.y, true);
	}
	static float ClampAngle(float angle, float min, float max) {
        if (angle < -360){
            angle += 360;
        }
        if (angle > 360){
            angle -= 360;
        }
        return Mathf.Clamp(angle, min, max);
    }

哈哈,想问题真的进入到一个思维死角里面去了,反而忘记后面这个最简单的方式了-

2019-01-15 18:29:24 qq_37896049 阅读数 1174

 

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class TTT : MonoBehaviour
{
    public float turnSpeed=5;
    // Use this for initialization
    void Start()
    {

    }

    // Update is called once per frame
    void Update()
    {
        Rotate();
        ClampRot(-30,30);
    }

    void Rotate()
    {
        if (Input.GetKey(KeyCode.LeftArrow))
        {
            transform.Rotate(0, 0, turnSpeed * Time.deltaTime);
        }
        if (Input.GetKey(KeyCode.RightArrow))
        {
            transform.Rotate(0, 0, -turnSpeed * Time.deltaTime);
        }

    }


    Vector3 v3;
    /// <summary>
    /// 限制倾斜角度
    /// </summary>
    void ClampRot(float minZ,float maxZ)
    {
        v3 = transform.localEulerAngles;

        if (transform.localEulerAngles.z > maxZ && transform.localEulerAngles.z <= maxZ+10)//z控制在30一下
        {
            v3.z = maxZ;
        }
        else if (transform.localEulerAngles.z > 350+ minZ && transform.localEulerAngles.z < 360+ minZ)
        {
            v3.z = 360 + minZ;
        }
        transform.localEulerAngles = v3;
    }


}

2016-05-03 17:51:19 qiaoquan3 阅读数 46333

1,让一个物体围绕某一点旋转,有几种方法?分别是什么?

答:在这个点处放一个空物体B,则问题变为A绕着B旋转,

方法1:B不动,A挂脚本实现transform的RotateAround(vector3 point, vector3 axis, float angle)函数

例如 A.RotateAround(B.position, Vector3.up,  30*Time.deltaTime);   //A绕着B的y轴进行旋转。

方法2:A不动,A作为B的子物体,B挂脚本实现自转,然后带着A转,类似于模拟围绕中心天体旋转。

例如:B.Rotate (Vector3.up, 30*Time.deltaTime, Space.Self); //B绕着自身的up方向自转,从而带着A一起转。

方法3:矩阵变换,研究中。       

注:旋转的常用方法:

(1)绕坐标轴旋转

public void Rotate (Vector3 eulerAngles, Space relativeTo = Space.Self);
或者

public void Rotate (float xAngle, float yAngle, float zAngleSpace, relativeTo = Space.Self);

其中relativeTo = Space.Self表示绕自身坐标系旋转,ralativeTo = Space.World表示绕世界坐标系旋转。

(2)饶某个向量旋转

public void Rotate(Vector3 axis, float angle, Space relativeTo);

其中axis为旋转轴方向,angle为旋转角度,relativeTo为参考坐标系,默认为Space.self。此方法的功能是使得GameObject对象在relativeTo坐标系中绕轴向量axis旋转angle度。

(3)绕轴点旋转

public void RotateAround(Vector3 point, Vector3 axis, float angel);

功能是使得GameObject对象绕着point点的axis方向旋转angle度。