Unity使用SteamVR2.0实现基本功能(瞬移,抓取物品,射线点击,UI交互等)

news/2024/11/23 23:39:47/

基础设置

  •  把SteamVR的Player预制件拖到一个空场景,删掉场景内原本的相机

一.瞬移

  • 新建一个Plane,当做地板
  • 找到SteamVR的人物瞬移控制器 Teleporting ,把它拖到场景里

1. 范围移动

  •  我们需要在可以移动的区域,也就是碰撞器上,挂TeleportArea脚本

  1. 这个脚本会自动修改你的材质球
  2. locked 该区域是否可以移动
  3. markerActive 区域跟随按键显示隐藏

  • 因为这个脚本会更改材质球的属性,所以我们不能直接给地板挂这个脚本
  1. 新建一个Plane,给它改名字TeleportArea
  2. 给TeleportArea对象挂TeleportArea脚本
  3. 将TeleportArea对象调整到合适位置和合适大小
  • 现在运行项目,已经可以通过Teleport瞬移到挂载TeleportArea脚本的区域了
  • 如果我们不想要TeleportArea生成的边框显示出来,那么就把MeshRenderer关掉就好了

2. 定点移动

  • 找到TeleportPoint预制件,将它拖到场景里

  •  把TeleportPoint对象调整到合适的位置,移动按键同上

  • 看看参数解释

  1. 前两项同上述一致
  2. TeleportType:移动类型(MoveToLocation-移动到节点位置,SwitchToNewScene-前往设定场景)
  3. Title: 提示(会在传送点上生成提示文字)
  4. SwitchToScene:场景名字(TeleportType选择切换场景的话,就会前往这里设定的场景)
  5. titleVisibleColor:提示文字默认颜色
  6. titleHighlightedColor:传送点被选中时,文字高亮的颜色
  7. titleLockedColor:传送点不可用,文字颜色
  8. playerSpawnPoint:将当前传送点设置为玩家初始默认点(游戏启动后,会将Player位置重置到此处)

二.模型交互

1. Interactable参数介绍

如果要实现触摸,拾取,就绕不开Interactable组件,Hand的所有交互,都是依赖Interactable组件,所以我们首先必须要清楚这个组件上的所有参数代表的内容,才能开发出符合需求的项目

  • Active Action Set On Attach:在拾取物体时激活的动作集(就是SteamVR Input设置的Actions)
  • Hide Hand On Attach:拾取物体时是否隐藏手
  • Hide Skileton On Attach:拾取物体时是否隐藏骨骼
  • Hide Controller On Attach:拾取物体时是否隐藏控制器
  • Hand Animation On PickUp:拾取物体时手部动画(与SteamVR_Skeleton_Poser配合使用)
  • Set Range Of Motion On PickUp:设置手部拾取的运动状态
  • Use Hand Object Attachment Point:指定使用手的附着物体的位置点,还是直接使用手的位置作为挂载点
  • Attach Ease In:开启缓动
  • Snap Attach Ease In Time:缓动时间
  • Snap Attach Ease In Completed:发送缓动完成事件
  • Hand Follow Transform:手部跟随对象
  • Highlight On Hover:悬停在游戏物体上时,游戏物体高亮(包含所有子物体)
  • Hide Hightlight:不参与高亮的游戏物体列表
  • Hover Priority:层级,数值越大,Hover越优先(当多个物品堆叠在一起时,会优先选中层级高的对象)

2. Interactable函数介绍

打开脚本之后我们能看到的交互事件,有下面这四个

  • OnHandHoverBegin:触摸到
  • OnHandHoverEnd:触摸离开
  • OnAttachedToHand:抓取到
  • OnDetachedFromHand:松手后

如果要在属性面板上,对不同的回调,添加方法,可以添加InteractableHoverEvents组件

因为HandInteractable之间的信息传递是靠SendMessage所以还隐藏着别的事件

  • HandHoverUpdate:持续触摸
  • HandAttachedUpdate:持续抓取

这些函数会在后面的拾取等交互中用到,了解这些,在后面的开发中,思路和逻辑会更加通顺

3. 拾取物品

SteamVR插件内置了基于Interactable组件,拾取的功能

创建一个被拾取的道具

  • 新建一个Cube,Cube需要有Collider碰撞器
  • 给Cube添加Interactable交互组件
  • 添加Throwable拾取组件和RIgidbody刚体组件

现在创建的Cube已经具有被拾取的功能了

 Throwable参数详解

  • attachmentFlags:

这个参数类型是一个多选枚举AttachmentFlags,用来控制不同的抓取效果

public enum AttachmentFlags
{SnapOnAttach = 1 << 0, // 对象应该吸附到手上指定的连接点的位置。  DetachOthers = 1 << 1, // 如果抓到物体的这只手还抓着别的对象,那么旧的对象会被抛开DetachFromOtherHand = 1 << 2, // 如果这个物体在别的手上,会替换到现在抓取它的这只手ParentToHand = 1 << 3, // 该对象被抓取后,会变成手的子对象VelocityMovement = 1 << 4, // 物体将尝试移动以匹配手的位置和旋转。  TurnOnKinematic = 1 << 5, // 这个物体不会对外部物理反应。打开刚体的KinematiTurnOffGravity = 1 << 6, // 这个物体不会对外部物理反应。关闭重力AllowSidegrade = 1 << 7, //该物体能够从捏抓器切换到抓握器。 降低了投掷成功的可能性,同时也降低了意外掉落的可能性  
};
  • attachmentOffset:

在抓取后对象相对手的位置和角度,会根据这个Transform信息偏移,就是说,你可以在手的控制器下,自己创建一个,想要被依附的位置

  • catchingSpeedThreshold:

谷歌机翻了一下官方介绍,由于按住扳机而不是按下扳机,该物体必须以多快的速度移动才能附着?-1禁用

看代码,如果cube的刚体速度,大于这个计算出来的值,才可以被抓取

  • releaseVelocityStyle:

松手释放后,物体的速度角度变化,也是一个枚举ReleaseStyle

ShortEstimation和AdvancedEstimation需要添加VelocityEstimator组件

public enum ReleaseStyle
{NoChange,//无变化GetFromHand,//跟随手ShortEstimation,//短计算AdvancedEstimation,//长计算
}
  • releaseVelocityTimeOffset:

releaseVelocityStyle使用FromHand选项释放对象时使用的时间偏移量  

  • scaleReleaseVelocity:释放速度增量,如果你想把对象扔的远一点,就调大点
  • scaleReleaseVelocityThreshold:释放速度增量阈值
  • scaleReleaseVelocityCurve:释放速度增量曲线
  • restoreOriginalParent:对象松手后,返回原父物体下

Throwable监听事件

  • OnPickUp-当拾取到
  • OnDetachFromHand-当放下时
  • OnHeldUpdate-当抓着时(每帧调用)

VelocityEstimator组件

刚刚在介绍 Throwable时,发现releaseVelocityStyle松手时速度变化用到了两个值ShortEstimation和AdvancedEstimation

这个组件就是用来根据位置的变化估计物体的速度,在松手的时候,达到更仿真的效果

Throwable搭配起来使用的

至此,一个可以被拾取的Cube,所需要的所有组件,和组件属性,都可以跟随我们的需求设定了

4.拾取物品后使用

如果我们拾取到手枪,想要再拾取的同时,再加上可以开枪的功能,我写了一个基类,可以参考一下

using UnityEngine;
using UnityEngine.Events;
using Valve.VR;
using Valve.VR.InteractionSystem;public class PropUse : MonoBehaviour
{///使用该道具的事件public SteamVR_Action_Boolean useAction = SteamVR_Actions.default_TestAction;///事件触发public UnityEvent OnUse;///当前道具被抓取中protected virtual void HandAttachedUpdate(Hand hand){if (useAction.GetStateDown(hand.handType)){Debug.Log("按键按下,道具使用");OnUse?.Invoke();}}
}

在道具上添加PropUse组件 

 我们抓取到物品后,按下TestAction的按钮

与道具的基础交互 ,到这一步基本就可以正常开发项目了

三.射线交互

SteamVR_LaserPointer组件详解

 SteamVR内置了一个射线脚本,这个脚本使用的时候挂在Hand上

 属性介绍

  • pose:就是当前的手柄,不需要手添加,运行的时候会自动查找
  • interactWithUI:和UI交互的事件(和UI交互的其它部分需要自己写)
  • active:是否激活射线(这个没有用到,可能是写的时候点瞌睡了)
  • color:默认状态下的颜色
  • thickness:射线的尺寸
  • clickColor:interactWithUI触发时的射线颜色
  • holderpointer:用来画线的
  • addRigidBody:给射线末端添加刚体

 回调介绍

  •  PointerIn:当射线进入
  •  PointerOut:当射线离开
  •  PointerClick:当射线点击

射线与对象交互

我们在使用VRTK开发中, 会在需要射线交互的控制器上,添加VRTK_Pointer组件,现在没有现成的,我们要自己写一个了

交互代码

  • 继承之前的射线脚本,新增了一个点击模型的动作,射线颜色变化改成了VRTK的习惯
using UnityEngine;
using Valve.VR;
using Valve.VR.Extras;public class FT_LaserPointer : SteamVR_LaserPointer
{public SteamVR_Action_Boolean interactWithModel = SteamVR_Input.GetBooleanAction("GrabPinch");bool isActive = false;public event PointerEventHandler PointerClickModel;Transform previousContact = null;public virtual void OnPointerClickModel(PointerEventArgs e){if (PointerClickModel != null)PointerClickModel(this, e);}private void Update(){if (!isActive){isActive = true;this.transform.GetChild(0).gameObject.SetActive(true);}float dist = 100f;Ray raycast = new Ray(transform.position, transform.forward);RaycastHit hit;bool bHit = Physics.Raycast(raycast, out hit);if (previousContact && previousContact != hit.transform){PointerEventArgs args = new PointerEventArgs();args.fromInputSource = pose.inputSource;args.distance = 0f;args.flags = 0;args.target = previousContact;OnPointerOut(args);previousContact = null;}if (bHit && previousContact != hit.transform){PointerEventArgs argsIn = new PointerEventArgs();argsIn.fromInputSource = pose.inputSource;argsIn.distance = hit.distance;argsIn.flags = 0;argsIn.target = hit.transform;OnPointerIn(argsIn);previousContact = hit.transform;}if (!bHit){previousContact = null;pointer.GetComponent<MeshRenderer>().material.color = color;}else {pointer.GetComponent<MeshRenderer>().material.color = clickColor;}if (bHit && hit.distance < 100f){dist = hit.distance;}if (bHit && interactWithUI.GetStateUp(pose.inputSource)){PointerEventArgs argsClick = new PointerEventArgs();argsClick.fromInputSource = pose.inputSource;argsClick.distance = hit.distance;argsClick.flags = 0;argsClick.target = hit.transform;OnPointerClick(argsClick);}if(bHit && interactWithModel.GetStateUp(pose.inputSource)){PointerEventArgs argsClick = new PointerEventArgs();argsClick.fromInputSource = pose.inputSource;argsClick.distance = hit.distance;argsClick.flags = 0;argsClick.target = hit.transform;OnPointerClickModel(argsClick);}if (interactWithUI != null && interactWithUI.GetState(pose.inputSource)){pointer.transform.localScale = new Vector3(thickness * 5f, thickness * 5f, dist);}else{pointer.transform.localScale = new Vector3(thickness, thickness, dist);}pointer.transform.localPosition = new Vector3(0f, 0f, dist / 2f);}
}
  • 被交互的道具挂这个脚本
using UnityEngine;
using UnityEngine.Events;public class FT_InteractableRay : MonoBehaviour
{public bool isActive = true;public UnityEvent OnRayEnter;public UnityEvent OnRayExit;public UnityEvent OnRayClick;public virtual void RayEnter(){OnRayEnter?.Invoke();Debug.Log("射线进入:" + name);}public virtual void RayExit(){OnRayExit?.Invoke();Debug.Log("射线离开:" + name);}public virtual void RayClick(){OnRayClick?.Invoke();Debug.Log("射线点击:" + name);}
}
  •  射线交互控制脚本
using UnityEngine;
using Valve.VR;
using Valve.VR.Extras;public class FT_Pointer : MonoBehaviour
{/// <summary>/// 和模型交互/// </summary>public SteamVR_Action_Boolean interactWithModel = SteamVR_Actions.default_GrabPinch;public FT_LaserPointer vR_LaserPointer;public event PointerEventHandler PointerIn;void Start(){vR_LaserPointer = GetComponent<FT_LaserPointer>();vR_LaserPointer.interactWithModel = interactWithModel;vR_LaserPointer.PointerIn += OnPointerIn;vR_LaserPointer.PointerOut += OnPointerOut;vR_LaserPointer.PointerClick += OnPointerClick;}private void OnPointerIn(object sender, PointerEventArgs e){if (e.target.GetComponent<FT_InteractableRay>()!=null&& e.target.GetComponent<FT_InteractableRay>().isActive){e.target.GetComponent<FT_InteractableRay>().RayEnter();}}private void OnPointerOut(object sender, PointerEventArgs e){if (e.target.GetComponent<FT_InteractableRay>() != null && e.target.GetComponent<FT_InteractableRay>().isActive){e.target.GetComponent<FT_InteractableRay>().RayExit();}}private void OnPointerClick(object sender, PointerEventArgs e){if (e.target.GetComponent<FT_InteractableRay>() != null && e.target.GetComponent<FT_InteractableRay>().isActive){e.target.GetComponent<FT_InteractableRay>().RayClick();}}
}

对象脚本设置

  •  手部控制器上挂FT_LaserPointer和FT_Pointer

  •  交互的对象上挂FT_InteractableRay

  •  看看效果

四.UI交互

UIElement

先看一下SteamVR内置的UI交互组件,需要UIElement、Interactable、Collider

交互的时候需要手柄去碰到这个按钮,而且不支持Slider之类的需要拖动的UI,我们了解一下就好了

UGUI射线交互系统

StubbrnStar大佬写了一套UGUI的射线交互系统,非常好用 

下载地址:SteamVR2.xUGUI交互系统-Unity3D文档类资源-CSDN下载

大佬的博客原文

按照大佬的教程,我们下载到UI交互插件

  • 1.Kvr_UICanvas: 在Canvas添加Kvr_UICanvas组件

  •  2.替换InputModule对象的InputModule组件,使用Kvr_InputModule组件

  • 3.在手部控制器上添加SteamVR_LaserPointer和Kvr_UIPointer

  • 4.看看效果,非常好用,非常丝滑

Tips:射线交互UI和交互模型的功能不冲突,是可以共存的,把SteamVR_LaserPointer组件换成FT_LaserPointer就可以了

 

 

 如果觉得本篇博客有用,可以给博主点个赞和收藏,笔芯ღღღღღღღღღღღღღღღღღღღღღღღღღღღღღღღღღღღღღღღღღღღღღღღღღღღღღღ


http://www.ppmy.cn/news/102561.html

相关文章

嵌入式实操--迁移git仓库至gitlab(二)

本文主要是通过迁移的思维&#xff0c;记录本人初次将git仓库迁移至gitlab进行管理 本文只讲述操作的实际方法&#xff0c;假设gitlab git已安装成功 。 1. 将git仓库进行克隆 由于git仓库用于管理代码&#xff0c;所以当中有很多的分支及tags&#xff0c;为了进行仓库迁移就…

排序算法专题_1_GnomeSort (侏儒排序)——最简单的排序

最简单的排序算法不是冒泡排序…&#xff0c;不是插入排序…&#xff0c;而是Gnome排序&#xff01; The simplest sort algorithm is not Bubble Sort…, it is not Insertion Sort…, it’s Gnome Sort! Gnome Sort is based on the technique used by the standard Dutch G…

华为OD机试真题 Java 实现【递增字符串】【2023Q1 200分】,附详细解题思路

一、题目描述 定义字符串完全由“A’和B"组成&#xff0c;当然也可以全是"A"或全是"B。如果字符串从前往后都是以字典序排列的&#xff0c;那么我们称之为严格递增字符串。 给出一个字符串5&#xff0c;允许修改字符串中的任意字符&#xff0c;即可以将任…

某SRC的渗透测试实战

前言 因为不甘心被称作会只点鼠标的猴子&#xff0c;所以开始了一次某SRC漏洞挖掘&#xff0c;为期一个多星期。文章有点长&#xff0c;但请耐心看完&#xff0c;记录了完整的SRC漏洞挖掘实战 渗透过程 因为选择的幸运儿没有对测试范围进行规划&#xff0c;所以此次范围就是…

斑梨电子Air101开发板LuatOS XT804内核QFN32支持128x160分辨率

spotpear.cn/index/product/detail/id/1332.html detail.tmall.com/item.htm?id719888144249 【产品简介】 [] Air101开发板使用Air101处理器&#xff0c;内置2MFlash和176KLuatOS专属RAM&#xff0c;最高主频可以达到240MHz,采用QFN32封装&#xff0c;18组GPIO可用。开发板…

Linux压缩和归档命令的速查表

在Linux系统中&#xff0c;有多种命令可用于压缩和归档文件和目录。这些命令使我们能够将文件和目录打包成单个文件&#xff0c;并可以选择压缩以节省存储空间。本文将提供一个Linux压缩和归档命令的速查表&#xff0c;帮助您快速查找和了解各种常用命令及其用法。 压缩文件和目…

如何在 Linux 中进行网络地址转换 (NAT)?

网络地址转换&#xff08;Network Address Translation&#xff0c;简称NAT&#xff09;是一种在网络中使用的技术&#xff0c;它允许将私有网络中的IP地址映射到公共网络上&#xff0c;从而实现多个设备共享单个公共IP地址。在Linux系统中&#xff0c;我们可以使用一些工具和配…

实验篇(7.2) 04. 映射服务器到公网IP 远程访问 ❀ Fortinet网络安全专家 NSE4

【简介】由于服务器的IP是内网地址&#xff0c;所以无法从公网直接访问服务器。要想远程访问服务器&#xff0c;最简单的办法就是将服务器映射到公网IP&#xff0c;然后通过公网IP加端口号的方式进行访问。 实验要求与环境 OldMei集团深圳总部部署了一台服务器&#xff0c;用来…