Unity3D-VR《静夜诗》2-凝视宝剑和书籍时出现提示文本信息
接下来我们要实现凝视宝剑时出现转圈进度条提示,进度条完成即转圈确认后在场景适当地方出现介绍李白和宝剑的文本信息。
墙上宝剑对象的交互实现
1.添加预设宝剑对象
先在场景中做好可视部分的设计,场景中需要添加宝剑和提示宝剑介绍的文本对象。
在导入的素材中已经做好了宝剑的预设对象,把NDVRresources\SceneRcs\JingYeSi\Prefabs\Build目录下的预设对象BaoJian拖放到场景中的JiaoHu对象下成为其子对象即可,所谓预设是已经做好了的对象,其位置信息也已经设定好了的,不需要重新调整位置等信息了。
因为宝剑本身没有任何动画要求,可以移去宝剑对象上的动画控制器组件(Animator)。
把可交互的对象尽可能在组合在一起,也是为了便于管理,并不一定非要这样的,只要你知道对象放在哪就可以了。如果需要你也可以在场景中添加其他预设的对象,如果对象是不参与交互的,建议放在场景对象JingYeShi的相应子对象下。
2.添加宝剑介绍文本对象
凝视宝剑转圈确认后,在场景适当地方出现介绍宝剑的文本信息,所以在场景中要再添加一个介绍宝剑的文本对象。
先通过Create | UI | Canvas添加一个画布对象Canvas,设置属性如下:
注意
对象的变换属性Transform值都是相对的,只要场景中的对象摆放合适就可以了,并不一定要与建议的参考值一致,不过作为容器对象的,一般会设置其在世界坐标的原点位置,即(0,0,0)。
再通过Create | UI | Text添加一个文本对象TxtSword,设置属性如下(要设置的地方都作了高亮显示了):
文本信息内容是:
“李白受道家思想的影响,好剑术,喜任侠。十五岁时出蜀,“仗剑去国,辞亲远游”,成为一段佳话”。
3.凝视转圈功能的实现
3.1认识GearVRSDK中的脚本
要实现按钮凝视转圈触发功能,先要熟悉GearVRSDK\ Common中VRInteractiveItem脚本,打开脚本可以看到它提供了以下几个功能接口。
- 属性bool IsOver:表示是否凝视在对象上
- 事件代理(回调函数接口,也称为钩子函数接口)
事件 | 说明 |
---|---|
OnOve | 当凝视进入对象上时被调用(停留在对象上不会被反复调用的) |
OnOut | 当凝视离开对象(即不在凝视对象)时被调用 |
OnClick | 当检测到有Click事件时被调用(需要硬件设备支持) |
OnDoubleClick | 当检测到有DoubleClick事件时被调用(需要硬件设备支持) |
OnUp | 当检测到有开火按钮被释放时被调用(需要硬件设备支持) |
OnDown | 当检测到有开火按钮被按下时被调用(需要硬件设备支持) |
3.2宝剑添加碰撞器
根据GearVRSDK中其他几个脚本的描述,可以知道,如果场景中的对象能够被凝视交互,那么该对象除了挂有脚本VRInteractiveItem外,对象还必须有碰撞器组件,为此通过Add Component | Physics | Mesh Collider给宝剑对象添加网格碰撞器。当然也可以添加盒子碰撞器,但要调整其大小能包含宝剑的大小。
3.3显示转圈进度条功能的脚本
在你自己建立的工作目录MyWork下,新建脚本VRReticleTriggerItem(可以根据自己认知命名),该脚本实现了凝视转圈确认触发的功能。主要参考代码如下:
using System;
using UnityEngine;
using UnityEngine.UI;
using VRStandardAssets.Utils;
public class VRReticleTriggerItem : MonoBehaviour {
public float progressDuration = 2f; //进度条填充需要的总时间
public Image imageProgressBar; //进度条图片
public event Action OnTrigger; //定义类的订阅事件变量,以便被类的使用者订阅事件
private VRInteractiveItem interactiveItem; //GearVRSDK的对象可交互脚本
private bool isTrigger;//是否触发(进度条填充完成)
private float timer = 0;//计时器
// Use this for initialization
void Awake () {
//要保证对象也有VRInteractiveItem脚本,实例变量interactiveItem初始化
interactiveItem = GetComponent<VRInteractiveItem> () ?? gameObject.AddComponent<VRInteractiveItem>();
if (imageProgressBar == null)//确保有进程条图片组件
imageProgressBar = GameObject.Find("ring").GetComponent<Image>();
imageProgressBar.fillAmount = 0f; //初始为0
}
//对象使能(激活)时调用
void OnEnable()
{
interactiveItem.OnOver += HandleOver;//订阅事件,可以根据需要增加订阅的事件
interactiveItem.OnOut += HandleOut;
}
//对象不使能(不激活)时调用
void OnDisable()
{
interactiveItem.OnOver -= HandleOver;//去掉订阅的事件
interactiveItem.OnOut -= HandleOut;
}
// 每一帧调用
private void Update () {
//准星凝视移入对象上面,且触发变量为false
if (interactiveItem.IsOver && isTrigger == false)
{
timer += Time.deltaTime;//累计当前帧时间
if (timer < progressDuration)
{ //计时变量的值转换为进度条的填充量值
imageProgressBar.fillAmount = timer / progressDuration;
}
else
{ //进度条总计时间到了
imageProgressBar.fillAmount = 1;
isTrigger = true;
timer = 0;
if (OnTrigger != null)
OnTrigger();
}
}
}
//准星凝视移入对象上时调用
private void HandleOver()
{
}
//准星凝视移出对象时调用
private void HandleOut()
{
isTrigger = false;
timer = 0;
imageProgressBar.fillAmount = 0f;//进度条填充置0
}
}
将该脚本添加到宝剑对象BaoJian上。
在监视视图的脚本参数中指定显示转圈进度的图片对象,也就是场景中VRCamera中的ring子对象。
测试
- 凝视宝剑时准心周围有转圈进度条出现,一旦移出宝剑则转圈中止。
注意:
- 该脚本对GearVRSDK中VRInteractiveItem使用,是通过属性引用的。宝剑作为可以凝视交互的对象除了有碰撞器外,还要有VRInteractiveItem脚本的。如果在设计时宝剑对象没有挂上VRInteractiveItem脚本,在程序运行时也要检验宝剑对象上是否有该脚本,如果没有该脚本的话自动添加该脚本。同学们要领会这种加载脚本的技法。
- 因为要监测可交互对象的凝视移入移出状态,所以对可交互对象的脚本VRInteractiveItem的OnOver、OnOut事件设置了事件订阅函数HandleOver、HandleOut。
- 把凝视转圈进度条Image对象imageProgressBar设计为脚本类的一个Public属性,可以根据功能需要制定要作进度条显示的图片了(转圈进度条并不一定都是显示在准星周围,参见郑和下西洋的案例),可以在设计时指定转圈进度条Image对象,如果没有指定的话,程序运行时要确保imageProgressBar对象有默认的值,表达式GameObject.Find(“ring”).GetComponent()意思是在当前场景中找到命名为”ring”的游戏对象,取其Image组件作为默认值(”ring”对象在VR相机对象下)。
- 这里OnTrigger事件代理变量是一个回调用法,给转圈进度条完成后提供一个调用的机会,下面会继续讲到它的用法。
3.4宝剑凝视触发事件的处理
从VRReticleTriggerItem脚本的Update函数中可以看出,凝视对象转圈进度条完成后(凝视触发事件),会执行OnTrigger事件代理的,那么如何实现这个代理方法的功能编码呢?
当然继续在VRReticleTriggerItem类中写一些代码实现凝视触发后的功能(比如显示文本)也是可以的,但考虑到凝视转圈是一个普遍的功能,凝视触发的事件往往是正对某个特定功能的,比如凝视宝剑触发了宝剑简介文字的显示,凝视了某个按钮触发了某个功能的执行,我们尽量要把凝视转圈进度条显示的功能和凝视触发要执行的功能分离开来,使得VRReticleTriggerItem脚本类可以方便的地重复使用。
凝视桌子上的书对象时凝视触发也是显示一个相应位置的文本对象的,这样我们可以设计凝视触发显示文本对象的一个脚本。
在宝剑对象上添加新的脚本ReticleTrigger_for_DispayTextTip,方便对文件的管理,把脚本文件放在MyWork\Scripts目录下。参考GearVRSDK中VREyeRaycaster脚本对VRInput脚本类中可订阅事件OnDown、OnUp的使用,实现ReticleTrigger_for_DispayTextTip脚本类对VRReticleTriggerItem脚本类中的可订阅事件的使用。ReticleTrigger_for_DispayTextTip的代码参考如下:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
public class ReticleTrigger_for_DispayTextTip : MonoBehaviour {
private VRReticleTriggerItem vrReticleTriggerItem;//处理凝视转圈进度条显示的类变量
public Text text_UI;//要显示的文本对象
private void Awake ()
{
//要保证对象有VRReticleTriggerItem脚本
vrReticleTriggerItem = GetComponent<VRReticleTriggerItem> () ?? gameObject.AddComponent<VRReticleTriggerItem>() ;
text_UI.enabled = false;//开始时是不显示文本的
}
//对象使能(激活)时调用
private void OnEnable()
{
//订阅事件
vrReticleTriggerItem.OnTrigger += HandleTrigger;
}
//对象不使能(不激活)时调用
private void OnDisable()
{
//去掉订阅的事件,与OnEnable的语句对应
vrReticleTriggerItem.OnTrigger -= HandleTrigger;
}
//凝视触发(进度条满了)的事件代码
void HandleTrigger ()
{
text_UI.enabled = true; //显示文本提示
}
}
说明:
-
在脚本类中定义VRReticleTriggerItem类的私有变量vrReticleTriggerItem,通过该变量在脚本的OnEnable事件中订阅了凝视触发事件OnTrigger。还记得 VRReticleTriggerItem类中是怎么调用OnTrigger事件的呢?当VRReticleTriggerItem类中在凝视转圈完成(凝视触发)时,触发调用了OnTrigger事件,调用事件就是调用该事件上订阅的所有方法,于是在凝视触发后就会调用这里的HandleTrigger方法。
-
在监视视图中对该对象的Public属性进行拖放赋值,也就是把要显示的文本对象拖放给该变量。
测试
- 运行后凝视在宝剑上,会显示转圈进度条,转圈完成后,会显示宝剑简介的文字信息。
3.5对象延时n秒后消失的实现
根据功能要求,文字显示10秒后自动消失(不显示),这如何实现呢?
回顾一下文本对象的显示,是通过设置文本对象的属性enabled为true实现的,我们可以想到让文本对象不显示可以设置enabled为false来实现的。那么延时10秒又如何实现呢?可以用
yield return new WaitForSeconds(10);
这样的语句来实现延时,yield return语句返回的类型是 IEnumerator,所以我们可以设计一个返回值是 IEnumerator 的函数来实现延时调用的。参考代码如下:
IEnumerator DelayToDo ()
{
yield return new WaitForSeconds(10);
text_UI.enabled = false; //关闭文本显示
}
函数 DelayToDo()的功能是先等待10秒,然后设置text_UI对象的enabled值为false,这样对象就不再显示了。
可以用协同函数StartCoroutine()来调用返回值是IEnumerator的函数。这样前面的HandleTrigger函数的最后应该添加这样的协同调用语句了。
StartCoroutine(DelayToDo()); //协同调用
协同调用你可以理解为当前线程新开了一个线程去执行DelayToDo函数的功能,我们设计的DelayToDo函数是先等待了10秒后再往下执行的,从而实现了10秒后,文本消失的功能。
程序代码中涉及到的以下语句、类型、函数的详细使用请参考技术文档
yield return
WaitForSeconds
IEnumerator
StartCoroutine
当然这并不是实现延时消失的唯一方法,如Invoke函数也有类似的功能。
3.6运行时关闭对象的可交互性
文字显示后继续凝视在宝剑上还是有转圈进度条出现的,说明文字显示后宝剑对象还是可以继续交互的。真实的要求是在文字显示后,宝剑对象不能再与之交互了,10秒后文字不显示,宝剑对象恢复可交互,这又如何实现呢?
我们知道场景中的对象可以凝视交互必须要有一个碰撞器组件(Collider),只要设置该组件不使能也就是设置enabled的值为false,这样该对象就不能再有凝视交互的功能了。
脚本ReticleTrigger_for_DispayTextTip是挂在宝剑这个交互对象上的,要设置宝剑对象上的碰撞器不使能,可以通过这样的语句实现
this.GetComponent<Collider>().enabled = false;//关闭当前对象的可交互
该语句的意思是取得当前脚本所在对象的碰撞器组件,并设置enabled属性值为false。
注意:
- 碰撞器不管是网格碰撞器,或者是盒子碰撞器等,都可以用泛型基类Collider获取。
- 碰撞器的enabled什么时候要设置为false,什么时候要设置为true?根据程序的功能逻辑,同学们自行设定吧(记得文字显示10秒后宝剑是要恢复凝视交互功能的)
- 要关闭(使能)对象的可凝视交互性,除了通过更改碰撞器组件的enabled的值,还可以直接通过调用游戏对象的SetActive(bool value)方法来实现类似的功能,当前脚本所在的游戏对象可直接通过属性变量gameObject来获取,如要设置当前对象不活动(对象不活动,则对象上的所有组件都不使能),语句可以这样写:
this.gameObject.SetActive(false);
完整的ReticleTrigger_for_DispayTextTip脚本参考如下:
using UnityEngine;
using System.Collections;
using UnityEngine.UI;
public class ReticleTrigger_for_DispayTextTip : MonoBehaviour {
private VRReticleTriggerItem vrReticleTriggerItem;//处理凝视转圈进度条显示的类变量
public Text text_UI;//要显示的文本对象
private void Awake ()
{
//要保证对象有VRReticleTriggerItem脚本
vrReticleTriggerItem = GetComponent<VRReticleTriggerItem> () ?? gameObject.AddComponent<VRReticleTriggerItem>() ;
text_UI.enabled = false;//开始时是不显示文本的
}
//对象使能(激活)时调用
private void OnEnable()
{
//订阅事件
vrReticleTriggerItem.OnTrigger += HandleTrigger;
}
//对象不使能(不激活)时调用
private void OnDisable()
{
//去掉订阅的事件,与OnEnable的语句对应
vrReticleTriggerItem.OnTrigger -= HandleTrigger;
}
//凝视触发(进度条满了)的事件代码
void HandleTrigger ()
{
text_UI.enabled = true; //显示文本提示
StartCoroutine(DelayToDo()); //延时时间
this.GetComponent<Collider>().enabled = false;//设置当前对象的不可交互
}
IEnumerator DelayToDo()
{
yield return new WaitForSeconds(10);
text_UI.enabled = false; //关闭文本显示
GetComponent<Collider>().enabled = true;//设置当前对象可交互
}
}
桌子上书籍对象的交互实现
桌子上书籍对象的场景可视设计及交互功能的实现可以参照宝剑对象的场景设计交互功能的实现方法,要显示的文字信息是:
李白人称“诗仙”,这与其长期不懈的读书、学习密不可分,李白一生博览群书,是唐代少有的文豪。
具体实现这里就不再详细介绍了。
接下来实现开始信息文本和开始按钮的功能