实验题目:基于VR的情绪调节系统
-
设置VR场景(如突然出现鬼怪唤醒害怕,沙滩海边唤醒愉悦等)醒情绪;
-
搭建虚拟云空间:背景为蓝天河流,语音提问(你刚刚有什么感受,请尽可能用多的词语表达)让患者表达情绪感受并将用户语音回答转化成文本,并在此空间以“云”的可视化方式展示(云上写着情绪文本即可),其中正性情绪表达聚成白云,负性情绪表达聚成乌云。用户表达的越多,云团越多,形成虚拟云空间;
-
交互(设置3个虚拟云空间,分别进行不同的交互)
模块1:进入虚拟云空间,使用手柄将虚拟乌云打散–类似于切水果
模块2:进入虚拟云空间,静等1分钟,虚拟云自动散开
模块3:进入虚拟云空间,使用手柄将虚拟乌云拿下来放到小河中,云消融于水中
在Unity中进行Pico VR设备开发的准备
PICO Unity Integration SDK 为 PICO 基于 Unity 引擎研发的软件开发工具包。SDK 封装了一系列功能,涉及渲染、输入&追踪、混合现实、平台服务等。要使用Unity进行Pico VR设备的开发,首先我们要在项目中使用PICO Unity Integration SDK ,可以从官网直接获取。
接下来,为了简化开发,我们下载Unity官方提供的工具集 XR Interaction Toolkit ——一个基于组件的高级交互系统,用于创建 VR 和 AR 体验。它提供了一个框架,使 3D 和 UI 交互可从 Unity 输入事件中获得。该系统的核心是一组基本的交互器和可交互组件,以及将这两种类型的组件联系在一起的交互管理器。它还包含辅助组件,我们可以使用这些组件来扩展用于绘制视觉效果和挂钩您自己的交互事件的功能。
XR Interaction Toolkit
XR Interaction Toolkit (XRI) 是 Unity 提供的一个扩展包,旨在简化创建跨平台的 XR(扩展现实)交互体验。它支持多种 XR 平台,包括但不限于 Oculus、HTC Vive、Windows Mixed Reality、Magic Leap 和 Pico 等。其核心特性如下:
- 统一的交互框架:
- XRI 提供了一套统一的交互模式和组件,使得开发者可以更容易地在不同平台上实现一致的用户体验。
- 预构建交互组件:
- 包含了如抓取、推拉、旋转、缩放等常见的交互行为预制件,可以直接拖放到场景中使用,极大提高了开发效率。
- 控制器映射:
- 内置对多个主流控制器的支持,并提供了一个灵活的输入系统来处理来自不同设备的输入。
- UI 交互支持:
- 支持创建与用户界面元素互动的方式,例如按钮点击、菜单导航等,适用于 VR/AR 应用中的 UI 设计。
- 物理模拟和碰撞检测:
- 集成了 Unity 的物理引擎,确保虚拟物体之间能够进行真实的碰撞和反应,增强了交互的真实感。
- 示例项目和文档:
- 提供了丰富的示例项目和详尽的文档,帮助开发者快速理解如何使用这些工具。
- 事件系统:
- 包括一个强大的事件系统,允许开发者定义和响应各种交互事件,从而控制游戏逻辑或触发特定的动作。
- 可扩展性:
- 开发者可以根据自己的需求自定义新的交互模式或者修改现有的组件,以满足项目的特殊要求。
- 性能优化:
- 提供了最佳实践指导,帮助开发者优化他们的应用程序,确保流畅的用户体验。
我们通过通过 Unity Package Manager下载并安装了 XR Interaction Toolkit 并且在在 Edit -> Project Settings -> XR Plug-in Management
中启用了该插件。
Starter Assets
然后我们导入了 XR Interaction Toolkit 的 Samples 中 的 Starter Assets,这个包提供了 一些 Preset 和 Input System 中和 XR 有关的一些常用输入动作。
XR Device Simulator
此外,我们还导入了XR Device Simulator,用于在没有Pico设备的情况下模拟VR的运行,开发和调试我们的项目。
获取Unity场景素材
- Unity Asset Store:Unity官方提供的资源商店,其中有很多免费素材
- 除了官方商城外,还有很多其它的网站或方式可以找到更多的素材
XR Interaction Setup
XR Interaction Setup
是Starter Assets
提供的一个预制件,可以方便开发者快速地上手XR开发,我们把该预制件复制到Scene中去,就可以在场景中快速实现用户输入的处理(包括用户移动逻辑、左右手柄控制器输入处理逻辑等)。
例如,把XR Interaction Setup
复制到MainRoomScene
场景下:
模拟凝视交互实现场景跳转
ScreenCenterGazeTransition脚本
这个脚本的主要功能是通过用户在屏幕中心凝视对象来实现场景的切换。用户需要凝视一个指定时间(gazeDuration
),在此期间,物体会逐渐放大,达到阈值后,脚本会调用 SceneManager.LoadScene
切换到指定的场景。如果用户没有持续凝视该对象,则会重置计时器并恢复原始大小。
using UnityEngine;
using UnityEngine.XR.Interaction.Toolkit;
using UnityEngine.SceneManagement;
public class ScreenCenterGazeTransition : MonoBehaviour
{
[Tooltip("要加载的目标场景名称")]
public string targetSceneName; // 内部存储要加载的场景名称,默认可以在Inspector中设置
[Tooltip("凝视时间阈值(秒)")]
public float gazeDuration = 3f; // 凝视时间阈值(秒)
[Tooltip("凝视时物体放大比例")]
public float scaleMultiplier = 1.3f; // 凝视时物体放大的比例
private float currentGazeTime = 0f; // 当前凝视时间
private Transform objectTransform; // 存储目标对象的Transform组件
private Vector3 originalScale; // 原始缩放比例,用于恢复
private void Start()
{
// 初始化Transform和保存原始缩放比例
objectTransform = GetComponent<Transform>();
if (objectTransform != null)
{
originalScale = objectTransform.localScale;
}
else
{
Debug.LogError("No Transform component found on the target object.");
}
}
private void Update()
{
// 从屏幕中心发射射线
Ray ray = Camera.main.ScreenPointToRay(new Vector3(Screen.width / 2, Screen.height / 2, 0));
RaycastHit hit;
// 检查射线是否击中了当前对象
if (Physics.Raycast(ray, out hit) && hit.collider.gameObject == gameObject)
{
currentGazeTime += Time.deltaTime;
// 如果物体没有达到最大缩放,则逐渐放大
if (objectTransform.localScale != originalScale * scaleMultiplier)
{
ScaleObject(scaleMultiplier);
}
// 如果凝视时间超过了阈值,则加载目标场景
if (currentGazeTime >= gazeDuration && !string.IsNullOrEmpty(targetSceneName))
{
SceneManager.LoadScene(targetSceneName);
ResetGazeTimer();
}
}
else
{
// 如果没有击中当前对象,重置计时并恢复原始缩放
ResetGazeTimer();
RestoreOriginalScale();
}
}
private void ResetGazeTimer()
{
currentGazeTime = 0f;
}
private void ScaleObject(float multiplier)
{
if (objectTransform != null)
{
// 渐进式放大,确保平滑过渡
objectTransform.localScale = Vector3.Lerp(objectTransform.localScale, originalScale * multiplier, Time.deltaTime * 3);
}
}
private void RestoreOriginalScale()
{
if (objectTransform != null)
{
// 渐进式恢复原始缩放,确保平滑过渡
objectTransform.localScale = Vector3.Lerp(objectTransform.localScale, originalScale, Time.deltaTime * 3);
}
}
/// <summary>
/// 在运行时动态设置目标场景名称。
/// </summary>
/// <param name="sceneName">目标场景的名称。</param>
public void SetTargetScene(string sceneName)
{
this.targetSceneName = sceneName;
}
}
变量部分
targetSceneName
: 存储要加载的目标场景名称。可以在Unity的Inspector面板中设置。gazeDuration
: 定义用户需要凝视该对象的时间阈值(以秒为单位)。如果用户凝视时间达到该值,脚本将加载目标场景。scaleMultiplier
: 当用户凝视对象时,物体会放大的比例。currentGazeTime
: 当前用户凝视对象的时间累积值。objectTransform
: 存储当前对象的Transform组件,以便于操作它的大小和位置。originalScale
: 用于保存物体的原始缩放比例,以便于在用户停止凝视后恢复。
Unity生命周期方法
Start()
:- 脚本启动时调用,初始化
objectTransform
和originalScale
。如果没有找到Transform组件,打印错误消息。
- 脚本启动时调用,初始化
Update()
:- 每帧调用,处理用户的凝视交互。
- 利用
Camera.main.ScreenPointToRay
从屏幕中心发射一条射线。 - 如果射线击中当前对象,增加
currentGazeTime
,并根据需要逐渐放大对象。 - 检查是否达到凝视时间阈值,如果达到,则加载目标场景。
- 如果射线没有击中当前对象,重置凝视计时器并恢复对象的原始缩放。
辅助方法
ResetGazeTimer()
:- 重置
currentGazeTime
为0。
- 重置
ScaleObject(float multiplier)
:- 渐进式放大对象,使用
Vector3.Lerp
方法实现平滑过渡。
- 渐进式放大对象,使用
RestoreOriginalScale()
:- 渐进式恢复对象的原始缩放,确保过渡平滑。
SetTargetScene(string sceneName)
:- 允许在运行时动态设置目标场景名称。这个方法可以在其他脚本中调用,以更改要加载的场景。
可交互物体设置
例如我需要在凝视这朵蓝色的睡莲3秒后,跳转到场景Pond Night
:
首先,给该睡莲对应的GameObject
添加component
Box Collider
,并勾选“Is Trigger”
;
然后将脚本ScreenCenterGazeTransition
绑定到object上,并设置相关参数;
当用户的视线中心落在睡莲上连续超过3秒时,就会跳转到 Pond Night
对应的场景,在这个过程中,睡莲会均匀放大到原来的1.3倍(效果详见演示视频)。
一些细节
设置提示文字,指导用户使用系统
交互设计
- 在用户刚进入系统时,给予相应的人文关怀,表明实验系统的用途(即情绪疗愈与情绪管理)
-
为了帮助用户更好得调节情绪,系统也提供了一些情绪管理小建议
-
用户可以选择前往自己喜欢的场景,文字将提示用户如何在场景中进行交互
比如将视线中心放在旁边的枫树上超过3秒钟,可以跳转场景到“温暖热烈的秋日林地”;其它的场景跳转也是同理。
- 进入情绪唤醒场景后,会有引导用户发散思维,感受情绪的提示词;
提示词的旁边还有引导语,用户在情绪逐渐唤醒后,可以通过凝视交互(长看云朵超过3s)前往虚拟云空间
- 在进入虚拟云空间的初始位置,用户前方有提示语,引导用户说出个人感受(点击开始录音——说出感受——点击结束录音)
录音结束后,用户周围会慢慢生成与用户感受相关的情绪云,用户可以按照一旁的文字提示,与情绪云进行交互
- 按照文字提示,凝视圣诞树,看着它慢慢变大,3s后返回主空间
- 至此,回到主空间,一次情绪调节过程结束
技术实现
悬浮文本的结构如下:
在Unity中,Inspector窗口显示了游戏对象(GameObject)的组件(Component)。悬浮文本中最外层的GameObject被命名为“main room hint_info”,属于UI层,其上附加的组件及功能如下:
- Rect Transform:这是一个用于2D UI元素的变换组件,用于在屏幕上精确地定位和调整UI元素的大小、位置和旋转。
Position (Pos)值表示了UI元素在其父Canvas中的位置。X和Y坐标决定了元素在屏幕上的水平和垂直位置,而Z坐标则决定了元素的深度顺序。Width和Height定义了UI元素的宽度和高度。Anchors用于指定UI元素相对于其父对象(通常是Canvas)的锚点位置。Pivot点是UI元素的旋转中心。Rotation值表示UI元素的旋转角度。Scale值表示UI元素的缩放比例。
-
Canvas:这是所有UI元素的父对象。Canvas决定了UI元素如何渲染以及它们是否与相机相关联。在这个例子中,Canvas组件的存在意味着这个GameObject是一个UI元素的一部分。
-
Canvas Scaler:这个组件负责根据屏幕分辨率或设备尺寸自动调整Canvas及其子对象的大小。这对于确保UI在不同设备上看起来一致非常重要。
-
Graphic Raycaster:Raycaster组件用于检测用户输入(如点击或触摸),并将这些输入映射到UI元素上。这对于使UI元素可交互至关重要。
-
Tracked Device Graphic Raycaster:这是一个特定类型的Raycaster,通常用于VR环境中的UI交互。它能够跟踪虚拟现实设备的位置,并据此确定用户的输入。
添加背景音乐,渲染场景氛围
交互设计
实验系统为每一个用来唤醒(调节)情绪的场景都添加了与之氛围相符的、能激发用户相关情绪的背景音乐,增强用户的沉浸感,完善用户的交互体验。
技术实现
在Asset下创建Music文件夹,用于存放下载的音频资源:
在需要添加背景音乐的场景中,创建了专门用来音频播放的GameObject:music player
在右侧 music player
对应的 Inspector
窗口中,点击 Add Component
,添加Audio Source
组件,配置音频源的基本属性:
-
AudioClip:
ForestFlower
这是指定要播放的音频文件。可以在AudioClip中手动选择需要的音频资源,或者直接将音频资源的MP3文件拖动到游戏对象 music player 上。
-
Output:
None (Audio Mixer Group)
这个设置决定了音频输出到哪个音频混音组。如果设置为“None”,则音频将直接输出。
-
Mute:
这个选项用于静音音频源。当前未选中,表示音频源不会被静音。
-
Bypass Effects:
这个选项用于跳过音频效果。当前未选中,表示音频源会应用所有音频效果。
-
Bypass Listener Effects:
这个选项用于跳过监听器效果。当前未选中,表示音频源会应用所有监听器效果。
-
Bypass Reverb Zones:
这个选项用于跳过混响区域。当前未选中,表示音频源会应用所有混响区域的效果。
-
Play On Awake:
这个选项用于在脚本唤醒时自动播放音频。当前选中,表示音频将在脚本唤醒时自动播放。
-
Loop:
这个选项用于循环播放音频。当前选中,表示音频将循环播放。
-
Priority: 128
这个值表示音频源的优先级。较高的优先级意味着音频源在与其他音频源冲突时更有可能被播放。
-
Volume: 1
这个值表示音频源的音量。范围通常是0到1,其中1表示最大音量。
-
Pitch: 1
这个值表示音频源的音调。范围通常是0.5到2,其中1表示原始音调。
-
Stereo Pan: 0
这个值表示音频源的立体声平衡。范围通常是-1(完全左声道)到1(完全右声道),0表示居中。
-
Spatial Blend: 0
这个值表示音频源的空间混合。范围通常是0(2D)到1(3D),0表示音频源是2D的。
-
Reverb Zone Mix: 1
这个值表示音频源在混响区域中的混合程度。范围通常是0到1,1表示完全混合。
基于个人审美的场景跳转设计
交互设计
整个实验系统涉及到的场景如下:
场景跳转是通过与游戏对象凝视交互实现的,与不同的对象交互,可以跳转到不同的场景,而对应于不同目标场景的交互对象是目标场景中的特色物体(目标场景中存在的对象or与目标场景风格相近的对象)。
主空间 MainRoomScene -> 深秋的密林 Forest Automn
主空间 MainRoomScene -> 生机盎然的绿野仙踪 Forest Day
主空间 MainRoomScene -> 明朗的秘密花园 Forest Flower
主空间 MainRoomScene -> 阴森的丛林夜色 Forest Night
主空间 MainRoomScene -> 安宁奇幻的月球表面 Lunar Landscape 3D
主空间 MainRoomScene -> 瑰丽晶莹的林中池塘 Pond Day
主空间 MainRoomScene -> 静谧幽美的荷塘月色 Pond Night
主空间 MainRoomScene -> 晚风温柔的金色黄昏 Pond Sunset
主空间 MainRoomScene -> 科幻炫酷的宇宙空间站 Space Station
科幻炫酷的宇宙空间站 Space Station -> 虚拟云空间 VirtualCloudSpace
虚拟云空间 VirtualCloudSpace -> 主空间 MainRoomScene
技术实现
通过给可交互物体对象绑定ScreenCenterGazeTransition脚本,详细解释见……
场景光线的修改
技术实现
以主空间为例,因为是室内场景,所有光线的修改主要从场景现有的光源入手(更自然、更真实),比如修改顶灯对应的游戏对象:
选中该游戏对象,为其添加 Light 组件(如果不存在):
在Unity中,Light
组件用于模拟光源,为场景中的物体提供照明效果。
-
Type: Point
这个选项指定了光源的类型。在这里,光源被设置为点光源(Point),这意味着它从一个点向四周均匀地发射光线。
-
Range: 10
这个值表示光源的有效范围。在这个例子中,光源的有效范围是10单位,意味着距离光源超过10单位的物体将不会受到该光源的影响。
-
Color: Realtime
这个选项可能是一个错误,因为通常颜色应该是一个具体的RGB值。这里可能是文本输入错误,正常情况下应该是如(1, 1, 1)这样的值。
-
Mode: Realtime
这个选项指定了光源的渲染模式。在这里,光源被设置为实时模式(Realtime),这意味着光源的效果将在运行时实时计算。
-
Intensity: 1
这个值表示光源的强度。在这个例子中,光源的强度是1,意味着光源发出的光量是标准强度。
-
Indirect Multiplier: 1
这个值表示间接光照的倍数。在这个例子中,间接光照的倍数是1,意味着间接光照的效果与直接光照相同。
-
Shadow Type: No Shadows
这个选项指定了光源是否投射阴影。在这里,光源被设置为不投射阴影(No Shadows)。
-
Cookie: None
这个选项用于指定光源的纹理贴图,通常用于创建复杂的光影效果。在这里,没有使用任何纹理贴图。
-
Draw Halo: None (Flare)
这个选项用于指定光源的光环效果。在这里,没有使用任何光环效果。
-
Flare: Auto
这个选项用于指定光源的光晕效果。在这里,光晕效果被设置为自动(Auto)。
-
Render Mode: Everything
这个选项指定了光源的渲染模式。在这里,光源被设置为渲染所有物体(Everything)。
-
Culling Mask: Standard
这个选项用于指定光源影响的层。在这里,光源影响所有层(Standard)。
除了修改场景中原有的光源外,也可以通过同样的操作修改设置使得其它的游戏对象发光。
间接光照的效果与直接光照相同。
-
Shadow Type: No Shadows
这个选项指定了光源是否投射阴影。在这里,光源被设置为不投射阴影(No Shadows)。
-
Cookie: None
这个选项用于指定光源的纹理贴图,通常用于创建复杂的光影效果。在这里,没有使用任何纹理贴图。
-
Draw Halo: None (Flare)
这个选项用于指定光源的光环效果。在这里,没有使用任何光环效果。
-
Flare: Auto
这个选项用于指定光源的光晕效果。在这里,光晕效果被设置为自动(Auto)。
-
Render Mode: Everything
这个选项指定了光源的渲染模式。在这里,光源被设置为渲染所有物体(Everything)。
-
Culling Mask: Standard
这个选项用于指定光源影响的层。在这里,光源影响所有层(Standard)。
除了修改场景中原有的光源外,也可以通过同样的操作修改设置使得其它的游戏对象发光。