HTC Vive小场地与大场景空间的解决方案
HTC Vive的运动追踪功能是其一大亮点,让众多适配该设备的游戏能够让用户动起来,无论是听起来还是实际体验都相当出色。在小范围移动(1:1范围内活动)或固定位置的游戏中,HTC Vive定位精确,延迟极低,优点显著。
然而,空间问题却让许多想在小空间体验大场景游戏的玩家望而却步。HTC官方建议消费者至少在20平米的空间内使用全套装备,这意味着需要较大的游戏房。对于普通用户而言,实际场地大小往往成为限制因素,玩家很难拥有恰好与游戏场景大小匹配的场地,当游戏场地过大而玩家实际场地过小时,该如何解决呢?
此外,若使用1:1的场地,会让玩家感觉游戏场景十分局促,仿佛没走多远就到达了边界,体验感不佳。因此,本文将给出针对HTC头盔变比空间移动问题的解决方案。当然,若您有更好的办法,欢迎一起讨论。
待解决的核心问题
当游戏场景过大而实际场地较小时,我们需要解决以下三个问题:
- 边界放大
- 起点位置平移
- 游戏空间与场地非等比空间速度缩放
边界放大
首先,我们来看官方SteamVR给出的预制体的顶视图。在HTC头盔的游戏中,玩家通常处于蓝色框的中间位置。对于那些需要以(0,0)为起点,玩家需在房间内四处移动,且根据游戏剧情需要进行空间移动的游戏,仅能使用原有场地1/4的面积,这造成了场地的严重浪费。
我们首先要解决位置偏移问题。其次,注意到蓝色边框的范围其实很小。以下是相关代码:
public enum Size
{
Calibrated,
_400x300,
_300x225,
_200x150
}
由于蓝色框面积小,无法满足需求,我们可以自行扩大场地。我自己制作了一个区域标识,小的蓝色框为原本400300的区域,大的是我自己缩放后的区域。选择Size为Calibrated选项进行缩放,为防止在直接节点上缩放出问题,特地添加了一个父节点,将xz均缩放为4,Y仍旧为1。这样,这个标识基本能满足游戏中99米的大场地需求。
起点位置平移
接下来解决起点的平移问题,分为两个部分:
视觉平移
在游戏编辑器模式下,让玩家处于蓝色区域的起点角点位置。这相对容易,只需平移蓝色区域即可。我们将刚才缩放后的预制体的父节点进行平移,项目中的平移位置设置为(4.2,0,4.2)。
代码处理
这部分相对繁琐,在讨论该问题之前,需要了解一下SteamVR插件在运行时和编辑状态下相机的差别。
编辑器模式下的相机状态与运行时不同,运行时相机的层级结构会发生较大变化。可以看到,eye作为父节点,ears作为子节点,而head则被隐藏了。具体的代码处理在SteamVR_Camera.cs中的OnEnable方法里,部分代码如下:
if (head != t)
{
Expand();
t.parent = origin;
while (head.childCount > 0)
head.GetChild(0).parent = t;
// Keep the head around, but parent to the camera now since it moves with the hmd
// but existing content may still have references to this object.
head.parent = t;
head.localPosition = Vector3.zero;
head.localRotation = Quaternion.identity;
head.localScale = Vector3.one;
head.gameObject.SetActive(false);
_head = t;
}
还有ears的处理代码,用于设置ears的相机参数:
if (ears == null)
{
var e = transform.GetComponentInChildren();
if (e != null)
_ears = e.transform;
}
if (ears != null)
ears.GetComponent().vrcam = this;
了解这些结构是因为它们会影响玩家起点平移的算法和处理。具体处理方法是,在相机的父节点初始化时,重置一个与相机初始化相反的参数,以抵消相机在场景中从(0,0)点作为起点的变化。以下是具体代码:
using UnityEngine;
using System.Collections;
public class PlayerPosIntitial : MonoBehaviour
{
private bool bUseOnce = false;
public Transform eye;
private float starttime;
private float delaytime = 4.0f;
// Use this for initialization
void Start ()
{
starttime = Time.time;
}
// Update is called once per frame
void Update ()
{
if (!bUseOnce && (Input.GetMouseButton(0) || Input.GetKey(KeyCode.JoystickButton0) || Time.time - starttime >= delaytime))
{
bUseOnce = true;
if (null != eye)
{
this.transform.localPosition = new Vector3(-eye.transform.localPosition.x, 0, -eye.transform.localPosition.z);
Debug.Log ("set player initial position");
}
}
}
}
这里的按键和时间自动开始的设置可以根据自身需求进行调整。另外,若要添加手柄,只需将手柄脚本放在该节点下,然后设置左右手柄即可。手柄在后续使用中不会因位移加速而受到影响。
游戏空间与场地非等比空间速度缩放
当场景为9*9米,而实际场地只有3米或5米时,该如何处理呢?其实解决方案已经比较明确,就是利用之前提到的FPSController的缩放父节点。
可能有人会提出,直接控制HTC Vive相机的脚本来修改相机位置进行缩放是否可行?这个方法看似不错,但实际上无法实现。因为相机无法在脚本层进行控制,在编辑器下运行模型时,即使将所有脚本代码勾选掉,相机的位置和旋转仍能正常使用,这表明相机的控制在脚本层的操作空间很小。
为了验证,我们在脚本中进行了各种尝试,代码如下:
// 在数据更新层,让它设置为zero.
void Update()
{
transform.position = Vector3.zero;
}
void LateUpdate()
{
transform.position = Vector3.zero;
}
//在渲染层的某个阶段处理,设置为零。
void PreRender()
{
transform.position = Vector3.zero;
}
void OnPostRender()
{
transform.position = Vector3.zero;
}
实际测试表明,以上代码对HTC头盔相机的实际效果没有任何作用。在编辑器模式下,虽然显示为零,但相机数据仍会变化,这只是编辑器数据显示先于真实相机坐标导致的假象。
最终的解决方案是为FPSController添加一个父节点,以下是相关代码:
using UnityEngine;
using System.Collections;
public class UpdateCamra : MonoBehaviour
{
public Transform ViveCamera;
public float ScaleLength = 0.5f;
// Use this for initialization
void Start ()
{
#if UNITY_EDITOR
#else
ScaleLength = PlayerControl.m_Cfg.MaxDeltaVel;
#endif
}
// Update is called once per frame
void Update ()
{
if (null != ViveCamera)
{
transform.position = new Vector3(ViveCamera.position.x * ScaleLength, ViveCamera.position.y, ViveCamera.position.z * ScaleLength);
}
}
}
为了方便根据场地大小和游戏场景大小进行调节匹配,缩放参数被设置在一个配置文件中,这里不再详述。
至此,我们完成了边界放大、起点位置平移以及游戏空间与场地非等比空间速度缩放这三个问题的解决。实际测试中,当游戏场景为99米,而实际场地为2.5米左右时,缩放系数为0.5,由于场地过小、速度过快,玩家可能会感到不适;而当场地为55米左右时,调节参数后整体感觉较为舒适,玩家不会因空间大小而在游戏中感到局促。
参考资料
SteamVR Unity官方的插件quikstart.pdf。