[Unity 3D] 重力感应与罗盘(一)

  iPad 的玩家大概都用过 StarWalk 这款应用——强力到无以附加的星图软件。StarWalk 里的世界其实就是虚拟了一个环绕用户的天球,当然还能够与真实的天球对应得上,当用户举着 iPad 对向不同方位时,能够从 iPad 屏幕上看到天球上对应的那一方向上的区域,就如同真的在用望远镜观察天球一样。这里面的原理其实很简单,整个虚拟的天球就是游戏世界,主摄像机就在这个游戏世界的正中心点,当 iPad 屏幕正对的方位发生变化时,主摄像机根据获取到的重力感应输入和罗盘输入计算出新的朝向,并转向正确的方向。

重力感应:

  iPad 重力感应的 X,Y,Z 三方向分量分布为:以 Home 按键在下的屏幕摆放方式为基准,垂直屏幕向外的方向为 +Z 轴,沿屏幕边缘向上的方向为 +Y 轴,沿屏幕边缘向右的方向为 +X 轴。想象在 iPad 屏幕正中央栓了一个重锤,重锤指向的方向自然永远是真实世界的重力方向,也是 iPad 重力感应器所接收到的重力方向。现在,把重力感应方向映射到前面重力感应三方向分量组成的坐标系中去,就可以用三分量来描述这个重力输入方向了,分量的取值范围是 (-1, 1)。

  当 iPad 屏幕朝上平躺放置时,重力方向描述为 (0,0,-1);当 iPad 的 Home 按键朝下,做屏幕竖立放置时,重力方向描述为 (0, -1, 0);当 iPad 的 Home 按键在左,做屏幕侧立放置时,重力方向描述为 (1,0,0)。如下图:

[Unity 3D] 重力感应与罗盘(一)_第1张图片

[Unity 3D] 重力感应与罗盘(一)_第2张图片

[Unity 3D] 重力感应与罗盘(一)_第3张图片

  同理,当 iPad 屏幕朝下平躺时,重力方向为 (0,0,1);当 iPad 倒竖放置(Home 键在上竖立放置)时,重力方向为 (0,1,0);当 iPad 侧立放置,Home 键在右时,重力方向为 (-1,0,0)。这六种摆放下重力与某一坐标轴重合,当 iPad 以其他角度摆放时,只需将重力输入看作一个大小为 1 单位的矢量来做坐标轴映射。 当然,这个映射不需要自己计算,因为我们可以——

获得重力输入:

  要获得重力输入很简单,使用 Input.acceleration 即可获得最近一次的重力输入,其值是一个 Vector3 型变量,也就是重力输入方向在上述坐标系下的映射三分量。我们可以通过一个简单的脚本来观察当 iPad 翻转时,这个重力输入的三分量数值会如何变化:

using UnityEngine;
using System.Collections;
using System;

public class ShowGSensorValue : MonoBehaviour
{

	// Use this for initialization
	void Start()
	{

	}

	// Update is called once per frame
	void Update()
	{
	}

	void OnGUI()
	{
		GUI.Box(new Rect(5, 5, 100, 20), String.Format("{0:0.000}", Input.acceleration.x));
		GUI.Box(new Rect(5, 30, 100, 20), String.Format("{0:0.000}", Input.acceleration.y));
		GUI.Box(new Rect(5, 55, 100, 20), String.Format("{0:0.000}", Input.acceleration.z));
	}
}

上面这个脚本中,通过在屏幕左上角绘制纵向排列的三个 Box 控件,每个空间显示一个分量,来实时显示当前游戏帧内重力输入方向的三个分量大小。如下图所示:

[Unity 3D] 重力感应与罗盘(一)_第4张图片

  仅仅是拿来观察重力输入分量,那么直接取 Input.acceleration 就足够了。但是,如果要利用重力输入来影响游戏世界里的物体,那么这里就产生了一个问题:重力输入的分量是以上面提到的用 iPad 屏幕为标准的坐标系进行表述的,但是游戏世界坐标系与 iPad 屏幕坐标系并不一致。就医上面这截图里的球来说,它所处的坐标系的三坐标轴方向是下面这个样子的:

[Unity 3D] 重力感应与罗盘(一)_第5张图片

绿色是 +Y 轴,红色是 +X 轴,蓝色是 +Z 轴。或者我们把游戏的主摄像机调整一个角度,好看得更清楚一点:

[Unity 3D] 重力感应与罗盘(一)_第6张图片

把摄像机挪到球的正上方,现在球的坐标系看起来跟 iPad 的屏幕坐标系很像了,但显然这里的 +Y 轴和 +Z 轴方向与 iPad 屏幕坐标系不一样。现在切换到摄像机:

[Unity 3D] 重力感应与罗盘(一)_第7张图片

其实 iPad 屏幕坐标系就是 Z 轴反向的主摄像机本地坐标系:

[Unity 3D] 重力感应与罗盘(一)_第8张图片

所以,如果直接取 Input.acceleration 的方向来对这个球施加作用力的话,它不会按照我们所预期的那样运动。这就需要做——

坐标变换:

  iPad 玩游戏时,是通过游戏里的主摄像机在观察游戏世界,而屏幕就是主摄像机的镜头,因此在主摄像机的指向方向没有发生变化时,可以认为整个游戏世界是“粘在” iPad 屏幕“后头”的,而屏幕就是一块透明玻璃,我们正透过这块玻璃观察粘在玻璃后头的游戏世界。

[Unity 3D] 重力感应与罗盘(一)_第9张图片

  而坐标变换就是把屏幕,也就是这块玻璃的坐标系换算成玻璃后面游戏世界的坐标系。最简单的一种坐标变换,就是当摄像机像上面例子中那样从游戏世界的正上方向下俯视,此时只需要简单的把 +Y 换成 +Z 轴,+Z 轴换成 +Y 轴即可。例如把上面的代码修改一下:

using UnityEngine;
using System.Collections;
using System;

public class ShowGSensorValue : MonoBehaviour
{
	public Rigidbody Target = null;
	public float ForceFactor = 10.0f;

	// Use this for initialization
	void Start()
	{

	}

	// Update is called once per frame
	void FixedUpdate()
	{
		if (Target != null)
		{
			Target.AddForce(new Vector3(Input.acceleration.x, Input.acceleration.z, Input.acceleration.y) * ForceFactor, ForceMode.Force);
		}
	}

	void OnGUI()
	{
		GUI.Box(new Rect(5, 5, 100, 20), String.Format("{0:0.000}", Input.acceleration.x));
		GUI.Box(new Rect(5, 30, 100, 20), String.Format("{0:0.000}", Input.acceleration.y));
		GUI.Box(new Rect(5, 55, 100, 20), String.Format("{0:0.000}", Input.acceleration.z));
	}
}

而更复杂的时候,屏幕坐标系与游戏世界坐标系之间有倾斜角,不能直接调换坐标分量完事,需要用三角函数计算;更更复杂的时候,屏幕坐标系和游戏世界坐标系之间的相对关系是时刻在变化的,因为,我们的主摄像机时刻在移动和旋转,这时需要更为复杂的坐标变换。


备注:iPad 用以支持重力感应的硬件设备是加速度计,当 iPad 除了重力加速度之外不承受其他加速度时,从加速度计上读取的数值可以认为是当前重力加速度,归一化之后即可认为是当前地理位置的重力方向。因此,如果 iPad 设备处于复杂加速环境中,那么从加速度计上读入的数据就不能作为判断重力方向的依据。Unity 3D 3.5 版本后开始支持陀螺仪,看 API 文档里专门有一项 gravity 属性,说明是重力加速度方向,但是手头没有 iPad 2 也没有 iPhone 4 无法实验。



你可能感兴趣的:(游戏,vector,脚本,iPhone,Class,ipad)