Google Cardboard 虚拟现实眼镜开发技巧(一)之放置一个固定在视野中的物体
利用CardboardMain下的Head轻松放置一个固定在视野中的物体
大家知道在游戏开发中,我们经常会需要放置一些操作面板啊,血量槽啊,比如CS中的枪械,子弹,血量等等,这些UI因素是应该永远显示在用户视野当中的,而不是一转头就看不到自己的血量了。
这个问题在传统的Unity游戏开发中自然是小菜一碟,但是在虚拟现实应用里就不一样了,我们的手机屏幕被分成了两部分,所以这些UI也应该是相应的分离显示,而如果在开发中使用传统的解决方案自然是不可以了,因为只会在屏幕上层显示一个UI,这显然在虚拟现实眼睛里就没法看了。而且根据我的尝试和对API的阅读,CardBoard好像甚至禁止了GUI等UI的显示,而提供了一个CardBoard专用的UI接口,至于这个所谓的CardBoard GUI我并没有尝试过,因为我们直接去让某个组件一直相对于视野不变就可以了,这样的可扩展性更强,而且最关键的是非常简单,一会下面就会为大家介绍具体的做法。
另外去实现一个固定在视野中的物体的应用可不是仅仅局限于UI的,虚拟现实中的注视点(之前提到的那个小黄点)就是一个永远在视野中央的物体,再比如我之前的项目里是开发了一个基于虚拟现实的无人机驾驶,用户是坐在虚拟机舱里进行操作的,虚拟机舱是一个模型场景,视角是要随着用户转头而改变的,比如用户低头会看到操纵杆,回头会看到自己的座椅背,但是窗外的景象是无人机实时传回的视频,因此需要一个不随视角变动的视频播放器,显然用代码让视频播放器随着视角动是非常麻烦的,需要考虑相位和旋转,而利用这个方法就很轻松的解决了这个问题。
CardboardMain下的Head组件的理解
大家看到CardboardMain,其下层为Head,我们去看Head的Inspector面板:
我们看到Head绑定了一个CardboardHead的脚本,这个脚本是Cardboard Unity SDK所提供的一个脚本,在API中CardboardHead.cs的描述部分的第一句是这样叙述的:将此脚本附加到任何与用户头部运动相匹配的游戏对象上。也就是说Head模拟了现实中用户的头,Head下的部分都是头的一部分,所以也就不难理解Head下的Main Camera与GazePointer了。
Main Camera其实就是用户的双眼,下属的Main Camera Left 和 Main Camera Right就是用户的左眼和右眼,双眼毫无疑问是头部的一部分,所以Main Camera作为Head的下属,也就是头的一部分。也因此模拟双眼的两个自摄像头是随着头部运动而运动的,所以我们在转动头部的时候才具有不同的视角。
GazePointer其实就是之前提到多次的注视点的实体,将这个注视点作为头部的一部分可以这样理解,如果想象一个戴着摩托车头盔的人,就可以理解为这个光标是头盔前挡风玻璃上的一个点,无论这个人把头转向哪,这个点总是在他的视野中央。
所以如果我们需要去放置一个固定在视野中的物体,遵循GazePoint的想法,我们就应该把这个物体放在人的头盔前玻璃上,所以我们只需要把物体放在Head下并且调整好位置和角度就可以让这个物体成为人头部的一部分随头部运动而运动了。也就是实现了本篇所说的放置一个固定在视野中的物体,比如一些血槽之类UI。
Demo:放置一个显示帧数的UI
现在理解了原理,下面我会基于官方的Demo去实现一个很简单的UI,具体功能就是能够在视野中的固定位置显示目前的帧数。
所以先在Head下放置一个能显示Text的组件,我是用的Demo中显示脚下按钮的方式,在Head下新建了一个Canvas(GameObjects - UI - Canvas),RenderMode选择World Space。canvas下是一个Panel(GameObjects - UI - Panel),Panel下是一个Text(GameObjects - UI - Text),并且给这个Text组件加了一个TextView1的Tag,方便在脚本中使用,具体结构和组件属性参考下图:
将UI(红框部分)放在Head下
Canvas组件,不要忘记设置World Space,不然不能调大小
Panel组件
Text组件,这里设置了一个TextView1的Tag
将UI放置在摄像头正前方
代码方面相对于Cardboard虚拟现实开发初步(四)中讲解的Demo代码修改很少,就是在Teleport.cs中通过Tag获取到Text组件并且在update()方法中设置文字为帧速(这里我为了简单用的是1/Time.deltaTime.严格讲这不是精确的fps)
代码:
// Copyright 2014 Google Inc. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
using UnityEngine;
using System.Collections;
[RequireComponent(typeof(Collider))]
/*
* 瞬移类 cube的脚本代码
*/
public class Teleport : MonoBehaviour
{
//定义开始的位置向量
private Vector3 startingPosition;
/**
* UI TextView
*/
public UnityEngine.UI.Text textView;
/*
* start()方法
* 如果脚本实例是enabled的,则Start函数在第一帧更新之前被调用,在脚本实例生命周期中仅被调用一次
*/
void Start()
{
//获得当前位置
startingPosition = transform.localPosition;
//关闭线
Cardboard.SDK.EnableAlignmentMarker = true;
//关闭设置
Cardboard.SDK.EnableSettingsButton = true;
Cardboard.SDK.NeckModelScale = 1f;
//初始化注视状态为false
SetGazedAt(false);
//初始化TextView
textView = GameObject.FindGameObjectWithTag("TextView1").GetComponent<UnityEngine.UI.Text>();
}
/**
* 假如MonoBehaviour是enabled时,该函数每帧被调用一次,是最主要最常用的帧更新函数
*/
void Update()
{
//显示FPS(近似)
textView.text = "FPS:" + 1/Time.deltaTime;
}
/*
* SetGazedAt(bool gazedAt)方法
* 根据是否凝视方块改变方块的颜色
*/
public void SetGazedAt(bool gazedAt)
{
GetComponent<Renderer>().material.color = gazedAt ? Color.green : Color.red;
}
/*
* Reset()方法
* 将方块位置设置为初始位置
* 由ButtonCanvas -> Panel -> ResetButton调用
*/
public void Reset()
{
transform.localPosition = startingPosition;
}
/*
* ToggleVRMode()方法
* 切换VR模式
* 由ButtonCanvas -> Panel -> VRModeButton调用
*/
public void ToggleVRMode()
{
//打开关闭VR模式
Cardboard.SDK.VRModeEnabled = !Cardboard.SDK.VRModeEnabled;
}
/*
* TeleportRandomly()方法
* 将方块位置设置为随机位置
* 由Cardboard.SDK.Triggered && isLookedAt调用
*/
public void TeleportRandomly()
{
//返回半径为1的球体在表面上的一个随机点
Vector3 direction = Random.onUnitSphere;
//static function Clamp (value : float, min : float, max : float) : float
//限制value的值在min和max之间, 如果value小于min,返回min。 如果value大于max,返回max,否则返回value
direction.y = Mathf.Clamp(direction.y, 0.5f, 1f);
// Random.value 返回一个随机数,在0.0(包括)~1.0(包括)之间
// 返回1.5 ~ 3.5 之间的随机数
float distance = 2 * Random.value + 1.5f;
// 位置为半径为1.5~3.5的球面上的随机点
transform.localPosition = direction * distance;
}
}
然后运行就可以看到最终效果了:
我们可以看到无论怎么样移动视角或者歪头,UI总是显示在视野的固定位置
结语