Unity+SteamVR开发——交互

2023-11-15

一、前言

       本文使用两个工具为Unity2018.4.26和SteamVR2.6.1,SteamVR2.6.1相比之前的版本有了很大的改变,其中在交互上有了很大的提升,SteamVR2.6.1上给出的案例中提供了抛射物体、线性驱动、环形驱动以及复杂的射箭操作等。尽管给出了诸多的交互案例,但是在实际开发中依然会有新的交互情况出现,在SteamVR2.6.1中没有详细的使用说明下,本文首先大概介绍其各种交互案例,然后详细的介绍其交互的核心组件如Interactable和Hand等,最后结合我使用的案例实现如何动态添加各种交互。               

二、介绍

2.1、简单交互Simple Interactable

       如图1所示。,为实现的简单交互,手触碰到物体会有使物体呈现黄色轮廓框,然后按下扳机键既可以移动物体,但

图1

是在这里手的模型隐藏掉了。 该交互方式只需要添加核心交互组件Interactable到父物体上,子物体中有带碰撞体的就可以实现。

实现逻辑:

第一步:首先,Hand在激活的时候重复不断的调用UpdateHovering方法,该方法即处理手悬浮带Interactable组件的物体

        protected virtual void OnEnable()
        {
            inputFocusAction.enabled = true;

            // Stagger updates between hands
            float hoverUpdateBegin = ((otherHand != null) && (otherHand.GetInstanceID() < GetInstanceID())) ? (0.5f * hoverUpdateInterval) : (0.0f);
            InvokeRepeating("UpdateHovering", hoverUpdateBegin, hoverUpdateInterval);
            InvokeRepeating("UpdateDebugText", hoverUpdateBegin, hoverUpdateInterval);
        }

 第二步:在UpdateHovering方法中判断哪个Interactable物体时和手最近的,判断的方法为CheckHoveringForTransform,在这个方法中会遍历子物体中的所有Collider,然后获取该Collider的父物体上的Interactable组件,如果不为Null,则比较与手的距离,找到最近的那一个,并将其Interactable的实例化对象赋值给Hand中的hoveringInteractable;

第三步:在Hand中hoveringInteractable为Interactable类型的属性,当该属性被赋值时会进行广播消息处理,所有继承了

public Interactable hoveringInteractable
        {
            get { return _hoveringInteractable; }
            set
            {
                if (_hoveringInteractable != value)
                {
                    if (_hoveringInteractable != null)
                    {
                        if (spewDebugText)
                            HandDebugLog("HoverEnd " + _hoveringInteractable.gameObject);
                        _hoveringInteractable.SendMessage("OnHandHoverEnd", this, SendMessageOptions.DontRequireReceiver);

                        //Note: The _hoveringInteractable can change after sending the OnHandHoverEnd message so we need to check it again before broadcasting this message
                        if (_hoveringInteractable != null)
                        {
                            this.BroadcastMessage("OnParentHandHoverEnd", _hoveringInteractable, SendMessageOptions.DontRequireReceiver); // let objects attached to the hand know that a hover has ended
                        }
                    }

                    _hoveringInteractable = value;

                    if (_hoveringInteractable != null)
                    {
                        if (spewDebugText)
                            HandDebugLog("HoverBegin " + _hoveringInteractable.gameObject);
                        _hoveringInteractable.SendMessage("OnHandHoverBegin", this, SendMessageOptions.DontRequireReceiver);

                        //Note: The _hoveringInteractable can change after sending the OnHandHoverBegin message so we need to check it again before broadcasting this message
                        if (_hoveringInteractable != null)
                        {
                            this.BroadcastMessage("OnParentHandHoverBegin", _hoveringInteractable, SendMessageOptions.DontRequireReceiver); // let objects attached to the hand know that a hover has begun
                        }
                    }
                }
            }
        }

MonoBehaviour的脚本中定义了 OnHandHoverBegin和OnHandHoverEnd方法的都将被执行;

最后:在该案例的脚本InteractableExample中实现OnHandHoverBegin和OnHandHoverEnd方法。

2.2、抛射物体Throwable

        如图2所示为手抓取物体然后进行抛射的过程,当手抓取物体时会变换为手刚好握住物体的姿态且手指都为静态的,

图2

当手释放掉物体的时候又恢复到原来的状态,并且物体抛出去之后具有一定的速度。

 实现逻辑:

第一步:同样需要添加核心交互组件Interactable,并且需要添加SteamVR_Skeleton_Poser、Rigidbody以及Throwable(或子类);

第二步:编辑SteamVR_Skeleton_Poser中所需要的手部姿势

1)、如图3所示,点击Create创建一个新的姿势,所有的姿势都是SteamVR_Skeleton_Pose的ScriptableObject,保存后为.asset为后缀的文件,可以通过Resources.Load方法直接加载或者直接拖到面板上使用。

标题3

2)、 如图4所示,勾选Show Right Preview 即对手势进行编辑,此时可以看到在物体的附近有一个手的模型,如果想在模板的基础上编辑可以选择Reference Pose:选择之后手即可变成模板的样子。该手部编辑模型会作为子物体出现在,

图4

但是紧紧时在编辑模式下出现,作为编辑使用,编辑完之后需要取消 Show Right Preview的勾选方可正常显示和使用。

直接调整手的位置和关键,使其达到符合要求的握住物体的样子即可,然后勾选Show Left Preview此时下面的Copy Left pose to Right Hand和Copy Right pose to Right Hand会被激活。注意:因为刚刚编辑的时Right的,因此点右边下面的Copy Left pose to Right Hand按钮,将右边的镜像处理得到 左边的数据并覆盖当前左边的,点击之后即可看到两只手都以同样的姿势握住物体。这里一定要点对,不然前面的工作会被覆盖而需要重新做。最后,点击Save Pose 即可。    

第三步、Throwable编辑,在Throwable脚本中同样实现了OnHandHoverBegin和OnHandHoverEnd方法,处理握住物体的逻辑。并且还实现了HandHoverUpdate方法,在该方法中首先判断当前手的按键类型,只要不是抓取的按键触发就握

        protected virtual void HandHoverUpdate( Hand hand )
        {
            GrabTypes startingGrabType = hand.GetGrabStarting();

            if (startingGrabType != GrabTypes.None)
            {
				hand.AttachObject( gameObject, startingGrabType, attachmentFlags, attachmentOffset );
                hand.HideGrabHint();
            }
		}

住物体,该方法在Hand的Update中被广播,另外,还有HandAttachedUpdate方法。

        protected virtual void Update()
        {
            UpdateNoSteamVRFallback();

            GameObject attachedObject = currentAttachedObject;
            if (attachedObject != null)
            {
                attachedObject.SendMessage("HandAttachedUpdate", this, SendMessageOptions.DontRequireReceiver);
            }

            if (hoveringInteractable)
            {
                hoveringInteractable.SendMessage("HandHoverUpdate", this, SendMessageOptions.DontRequireReceiver);
            }
        }

在Throwable中实现HandAttachedUpdate方法,代码如下:该方法每帧都执行,判断手的按键已经释放掉了该物体的时

        protected virtual void HandAttachedUpdate(Hand hand)
        {


            if (hand.IsGrabEnding(this.gameObject))
            {
                hand.DetachObject(gameObject, restoreOriginalParent);

                // Uncomment to detach ourselves late in the frame.
                // This is so that any vehicles the player is attached to
                // have a chance to finish updating themselves.
                // If we detach now, our position could be behind what it
                // will be at the end of the frame, and the object may appear
                // to teleport behind the hand when the player releases it.
                //StartCoroutine( LateDetach( hand ) );
            }

            if (onHeldUpdate != null)
                onHeldUpdate.Invoke(hand);
        }

执行手放弃物体的操作 hand.DetachObject(gameObject, restoreOriginalParent);,然后Hand的DetachObject方法里调用广播函数广播OnDetachedFromHand,最终在Throwable脚本中的实现的OnDetachedFromHand方法里处理了最后被扔出去的逻辑。

public void DetachObject(GameObject objectToDetach, bool restoreOriginalParent = true
 {       
            ...
        
            if (attachedObjects[index].attachedObject != null)
                {
                    if (attachedObjects[index].interactable == null ||                     
                 (attachedObjects[index].interactable != null && 
              attachedObjects[index].interactable.isDestroying == false))
                        attachedObjects[index].attachedObject.SetActive(true);

                    attachedObjects[index].attachedObject.SendMessage("OnDetachedFromHand", 
              this, SendMessageOptions.DontRequireReceiver);
                }
            ...
}

2.3、 线性驱动LinearDrive

         如图5所示为线性驱动的效果示意图,手捂住操作的物体保持姿势不动,移动手柄,手捂住的物体跟随运动,但是

图5

只保持在横向的线性位置移动,手握住的物体不会超过该线性区域。

第一步 :核心组件Interactable当然比不可少,然后实现HandHoverUpdate和HandAttachedUpdate以及OnDetachedFromHand方法,编辑握住物体所需的手势;

第一步:获取手部捂住物体之后手移动的参数,计算方法为:获取手现在的位置和线性起点的位置组成的向量A和终点到起点的向量B,得到向量A和B的点积,然后将这个值作为线性插值的变化因子

 protected virtual void HandAttachedUpdate(Hand hand)
{
       UpdateLinearMapping(hand.transform);

       if (hand.IsGrabEnding(this.gameObject))
      {
         hand.DetachObject(gameObject);
       }
}
protected void UpdateLinearMapping( Transform updateTransform )
		{
			prevMapping = linearMapping.value;
			linearMapping.value = Mathf.Clamp01( initialMappingOffset + CalculateLinearMapping( updateTransform ) );

			mappingChangeSamples[sampleCount % mappingChangeSamples.Length] = ( 1.0f / Time.deltaTime ) * ( linearMapping.value - prevMapping );
			sampleCount++;

			if ( repositionGameObject )
			{
				transform.position = Vector3.Lerp( startPosition.position, endPosition.position, linearMapping.value );
			}
		}

        protected float CalculateLinearMapping( Transform updateTransform )
		{
			Vector3 direction = endPosition.position - startPosition.position;
			float length = direction.magnitude;
			direction.Normalize();

			Vector3 displacement = updateTransform.position - startPosition.position;

			return Vector3.Dot( displacement, direction ) / length;
		}

2.4、环形驱动CircularDrive

        如图6所示,其处理逻辑和线性驱动类似,只是在计算物体旋转上有所差别

 

图6

2.5、 悬浮按钮Hover Button

       如图6所示,手悬浮在按钮上,然后向下压物体可以实现物体按下效果。实现的逻辑和前面的简单交互类似。

图6

 2.6、射箭

        如图7所示为双手射箭的操作,这个交互应该是SteamVR2.6.1这个版本中最复杂的一部分。同样需要添加

图7

Interactable组件。重点是ItemPackageSpawner组件,该组件实现了手用弓箭的所有逻辑。

1)、ItemPackageSpawner实现了HandHoverUpdate方法,并且在面板上勾选了requireGrabActionToTake,因此在手触碰到弓并且按下抓取的扳机键的时候调用SpawnAndAttachObject生成一些列后续操作所需要的包并且这只手抓住弓

		private void HandHoverUpdate( Hand hand )
		{
            ...

			if ( requireGrabActionToTake )
			{
                GrabTypes startingGrab = hand.GetGrabStarting();

				if (startingGrab != GrabTypes.None)
				{
					SpawnAndAttachObject( hand, GrabTypes.Scripted);
				}
			}
		}

 SpawnAndAttachObject方法里先根据ItemPackageType类型来清空手上的东西,然后重新生成一个itemPackage里面的itemPrefab,然后让手抓住它,这个时候生成的物体为Longbow,是带握住手势的弓,如图8所示。如果itemPackage的

图8

 otherHandItemPrefab不为空的话也实例化该物体,并且用另外一只手抓住它。这里的otherHandItemPrefab为握住箭的手势ArrowHand,如图9所示,在ArrowHand中初始化只保留了一个握住箭的手势,箭的生成要在其内部

图9

 HandAttachedUpdate方法里实现

private GameObject InstantiateArrow()
		{
			GameObject arrow = Instantiate( arrowPrefab, arrowNockTransform.position,              arrowNockTransform.rotation ) as GameObject;
			arrow.name = "Bow Arrow";
			arrow.transform.parent = arrowNockTransform;
			Util.ResetTransform( arrow.transform );

			arrowList.Add( arrow );

			while ( arrowList.Count > maxArrowCount )
			{
				GameObject oldArrow = arrowList[0];
				arrowList.RemoveAt( 0 );
				if ( oldArrow )
				{
					Destroy( oldArrow );
				}
			}

			return arrow;
		}


		//-------------------------------------------------
private void HandAttachedUpdate( Hand hand )
{
	if ( allowArrowSpawn && ( currentArrow == null ) ) // If we're allowed to
	{
		currentArrow = InstantiateArrow();
		arrowSpawnSound.Play();
	}
}

 2)、放箭的过程在ArrowHand的HandAttachedUpdate方法中实现,当弓被拉握住箭的手柄按键释放的时候,即射出箭

	private void HandAttachedUpdate( Hand hand )
		{
            ...
               // If arrow is nocked, and we release the trigger
			if ( nocked && hand.IsGrabbingWithType(nockedWithType) == false )
			{
				if ( bow.pulled ) // If bow is pulled back far enough, fire arrow, otherwise reset arrow in arrowhand
				{
					FireArrow();
				}
				else
				{
					arrowNockTransform.rotation = currentArrow.transform.rotation;
					currentArrow.transform.parent = arrowNockTransform;
					Util.ResetTransform( currentArrow.transform );
					nocked = false;
                    nockedWithType = GrabTypes.None;
					bow.ReleaseNock();
					hand.HoverUnlock( GetComponent<Interactable>() );
					allowTeleport.teleportAllowed = true;
				}

				bow.StartRotationLerp(); // Arrow is releasing from the bow, tell the bow to lerp back to controller rotation
			}
}

2.7、远程控制

如图10a和10b所示为操作虚拟手柄远程控制物体的案例,这两个案例非常类似,只是处理的过程非常繁琐,这里不再详细展开了,唯一没有让我完全搞清楚的地方是,控制虚拟手柄的手部动作是如何做到动态的。跟前面手指静态的不同,

图10a

这里的手姿势控制未找到SteamVR_Skeleton_Poser的使用。

图10b

三、总结

3.1、交互的核心组件为Interactable,凡是涉及到用手进行交互都需要添加该组件,后面会讲射线与物体交互也会用到该组件;

3.2、手握住物体的姿势为SteamVR_Skeleton_Pose,是一个ScriptableObject的资源类,可以在编辑器中进行编辑并且保存为后缀.asset文件,该文件可以实现动态加载;

3.3、需要在获取手和物体的处理逻辑上一定要实现Hand中广播的方法;

3.4、远程操作的手握住虚拟手柄的姿势可以动手指,目前还不知道怎么编辑或设置。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

Unity+SteamVR开发——交互 的相关文章

  • 利用宏简化Q_PROPERTY动态属性的定义

    目录 写在前面 实现历程 传统定义方式 预想的方式 事实上有一点点区别 测试通过的例程 mainwindow h mainwindow cpp main cpp 执行结果 Qt6 更新 写在前面 上一篇写了pyqt如何更加便利地定义动态属性

随机推荐

  • 【Unity VR开发】VRTK 3.3.0 配置与基本使用

    VRTK3 3 开发日志 2021 11 16更新 半年前第一次接触VR开发 看B站Siki学院的视频做的笔记 今天整理一下 以供没接触过VR开发的人来学习 有些地方没有配图 但个人认为影响不大 按文字说明一步步来操作还是没问题的 笔记参考
  • Python 和Java 哪个更适合做自动化测试?

    很多小伙伴工作在功能测试行业工作了2 3年后 发现自己已经把功能测试做的非常好了 已经到职业发展和薪资发展的瓶颈期了 就想着学点东西 提提升一下技能 而对于功能测试升级来说 一般有这么3个主流的发展方向 一是性能测试 一是接口测试 一是自动
  • 奶酪【BFS】

    题目链接 点从z 0为起点 想跑到z h 只能在球内 或者是球表层上跑 问能否从起点跑到终点 直接暴力bfs判断即可 include
  • linux下nodejs依赖库libuv库,开发环境准备

    nodejs底层使用libuv库实现异步IO 如果对nodejs的回调函数习以为常 而不知libuv 那岂不是很遗憾 libuv在github上托管了自己的源码 但是我要学习的是希望适用于nodejs某一个版本的 这样的代码是可以经过简单处
  • 华为OD机考攻略一文了解!最新岗位带你起飞

    了解透了华为OD是什么 准备好投递动作了 那就来看看整体的推进流程吧 一般经过 机考 线上 技术面试2轮 资格面试 1轮 综合面试 1轮 全程线上搞定全流程 对于异地的朋友很友好 机考准备之后 其他面试都在一周内完成 所以机考的准备尤为重要
  • 网络志愿者微公益行

    活动中 网络志愿者将书包和牛奶作为新年礼物送给孩子们 并走到愿望墙 一对一完成孩子们的小小愿望 发动仪式完毕后 网络志愿者深化贫困学生家中 详细了解他们的生活困难 并活跃协助处理
  • h5逻辑_H5+app 混合开发

    目录 app分类 h5与原生进行交互 1 判断h5页面打开的环境是ios android 2 JS与客户端互相调用 js调用ios的方法 js调用android的方法 error android接收不到参数 js调用ios与android的
  • redis学习

    1 yfk 博客 Redis http blog csdn net yfkiss article category 1059564
  • Cookie与Session机制,以及scrapy中cookiejar的理解

    Http协议 http是一种无状态协议 先说什么是协议 说白了就是通讯过程中的一种规范 要求 那什么是无状态的呢 就是我们在向服务器请求一个页面的过程中 并不是一个持久性的连接 而是客户端发送一个请求 比方说 我打开腾讯网 然后服务器端返回
  • 惠普PC服务器HP DL360G5网卡的安装

    在惠普服务器 HP DL360G5上安装Linux ES4 0的时候 系统不会自动检测到网卡并安装驱动程序 因此需要手动安装网卡的驱动程序 以下是网卡的安装方法 1 rpm ivh bnx2 1 4 43f 1 src rpm 回车 界面如
  • linux图形界面卡死

    项目场景 linux图形界面卡死 问题描述 鼠标能够移动 但是整个图形界面卡住 解决方案 进入linux下共有六个虚拟控台 f2 f6 分别对应这不同的权限 ctrl alt f2 提示登陆 此时登陆root用户 先输入账号 再输入密码 登
  • IP数据包格式

    IP数据包格式如下 版本号 Version 指 IP 协议的版本 通常为 IPv4 或 IPv6 首部长度 Header Length 指 IP 数据包的首部长度 以 32 位字 4 字节 为单位 区分服务 Differentiated S
  • 网站服务器商标属于哪类,网络水晶头属于商标哪个类别

    水晶头是一种能沿固定方向插入并自动防止脱落的塑料接头 俗称 水晶头 专业术语为RJ 45连接器 RJ 45是一种网络接口规范 类似的还有RJ 11接口 就是我们平常所用的 电话接口 用来连接电话线 之所把它称之为 水晶头 是因为它的外表晶莹
  • jmeter学习笔记(三)-性能测试概念

    性能测试相关概念 a 响应时间 是用户提交一个请求 系统从开始呈现到将所有信息都呈现到客户端所需要的时间 以一个web应用的页面响应时间为例 页面的响应时间可分解为 网络传输时间 N1 N2 N3 N4 应用延迟时间 A1 A2 A3 b
  • 如何在你的Android工程中启用K2编译器?

    如何在你的Android工程中启用K2编译器 K2编译器是用于Kotlin代码编译的最新 高效编译器 你现在可以尝试使用了 Kotlin编译器正在为Kotlin 2 0进行重写 新的编译器实现 代号K2 带来了显著的构建速度改进 编译Kot
  • 数学基础:向量求导整理

    0矩阵求导网站 不包括叉乘和点乘求导 http www matrixcalculus org 1标量对向量求导 标量 分子 分别对行 列向量 分母 各元素求导 结果仍为行 列向量 维度与分母一致 定义行向量 y T y
  • 关于QString的常见用法(详细讲解)

    QString类提供一个unicode字符串 在QT软件开发平台中常用到的一种变量类型 其提供了很多方便的应用方法 下面介绍一些关于QString的常见用法 1 字符串末尾追加 example 1 QString str hello str
  • iMazing2023iOS系统设备数据传输与备份工具使用教程

    iMazing需要数据线将你的电脑和iPhone或者是iPad连接 这款软件是itunes的完美替代品 有用iPhone或iPad的朋们友推荐下载使用 只要在同一网络下 就可以轻松管理你的iPhone 可以说是非常的方便 平时在传输文件资料
  • Vue SEO解决方案

    目录 SEO是什么 SEO目的 Vue中seo的解决方案 SEO是什么 seo是一种网站优化技术 也被叫做搜索引擎优化 可以利用搜索规则提高网站上有关搜索的自然排名 主要表现为微博热搜控榜等 通过seo技术 可以实现一系列的商业行为 对产品
  • Unity+SteamVR开发——交互

    一 前言 本文使用两个工具为Unity2018 4 26和SteamVR2 6 1 SteamVR2 6 1相比之前的版本有了很大的改变 其中在交互上有了很大的提升 SteamVR2 6 1上给出的案例中提供了抛射物体 线性驱动 环形驱动以