九、pico+Unity交互开发——触碰抓取

ops/2024/10/24 5:32:49/

一、VR交互的类型

  1. Hover(悬停)
    • 定义:发起交互的对象停留在可交互对象的交互区域。例如,当手触摸到物品表面(可交互区域)时,视为触发了Hover。
  2. Grab(抓取)
    • 概念:把物品抓起来。
  3. Use(使用)
    • 基于“抓取”。例如抓取一把枪后,按下枪的扳机发射子弹则视为Use。

三、发起交互的对象(Interactor)

  1. XR Direct Interactor脚本

    • 为了让双手成为发起交互的对象,在LeftHand Controller和RightHand Controller下分别创建Direct Interactor子物体,并添加XR Direct Interactor脚本。
  2. 添加可交互区域

    • XR Direct Interactor需要一个Trigger类型的碰撞体作为可交互区域,且需和它挂载在同一游戏物体上。如在左手和右手的Direct Interactor上分别添加Sphere Collider,调整Radius并勾选Is Trigger。当可交互对象进入该区域后,可进行交互。 ,并设置合适的参数。

在这里插入图片描述

四、可交互的对象(Interactable)创建一个3d对象,比如立方体

  1. 添加刚体

    • 因为手与物品的交互基于物理效果,所以要为可交互物体添加刚体。
    • 在这里插入图片描述
  2. XR Simple Interactable脚本

    • 为方块添加XR Simple Interactable脚本,但是它没有自带抓取的功能。
    • 物体有刚体后该脚本才生效,且挂载脚本的物体还需一个碰撞体。如果是创建的3d对象不需要额外加,如果是自定义模型的话,别忘了手动添加碰撞体
      在这里插入图片描述

2.1、可交互事件

  • 实现功能:
    • 手触碰到方块时,方块颜色改变(模拟Hover,即悬停在可交互区域)。
    • 触碰方块后按下手柄Grip键,方块颜色变成蓝色。
    • 触碰方块后按住Grip键再按Trigger键,方块颜色变回红色。
  • 实现方法:
    • 使用XR Simple Interactable脚本中的Interactable Events
    • 其中Hover Entered对应开始悬停在可交互区域触发的事件,可手动设置更改方块材质。
    • Select EnteredActivated事件分别对应上述第二、三个功能
    • 在Inspector面板中手动绑定事件,Select动作绑定“按下Grip键抓取键”,Activate动作绑定“按下Trigger键扳机键”。 Activate动作以Select动作为前提,Interactable Events中的所有事件以“与可交互对象发生交互”为前提。
      在这里插入图片描述
  1. XR Grab Interactable脚本、和XR Simple Interactable脚本脚本类似,但是有抓取效果;所以需要移除刚刚的XR Simple Interactable脚本

    • Movement Type 移动类型

      • Instantaneous:物体移动位置和姿态完全跟随手的移动,无延迟但无物理刚体效果,物体可穿过碰撞体且碰撞效果非物理。
      • Kinematic:通过Kinematic Rigidbody移动,有延迟,移动中不受力和碰撞作用,但可对其他刚体施加物理效果。
      • Velocity Tracking:通过设置刚体速度和角速度移动,有延迟,有刚体物理效果,可与碰撞体碰撞并对其他刚体产生力的效果。
        在这里插入图片描述
    • Attach Transform 抓取点

      • XR Grab Interactable脚本中有Attach Transform变量可赋值作为抓取点,若未赋值,默认以物体position为抓取点。如抓取枪时,抓取点应在枪柄;
      • 实现功能:可创建枪的子物体Attach Point并赋值给Attach Transform,然后调整其Position位置Rotation以优化抓取效果。但仅设置Attach Transform会有穿模现象,若要实现精细抓取,可参考相关教程。
  2. 代码实现Use功能(制作简易手枪)

    • 核心脚本

      • 创建GunController脚本挂载到枪的游戏物体上。获取XRGrabInteractable中的activated事件,通过AddListener绑定FireBullet函数。
    • 制作子弹预制体

      • 创建子弹Prefab(需刚体和碰撞体),并将子弹Rigidbody的Collision Detection设为Continous Dynamic,防止高速运动时检测不到碰撞。
        在这里插入图片描述
    • 制作子弹发射位置

      • 创建枪的子物体Spawn Point作为子弹生成位置,其z轴箭头方向对应子弹发射方向,将子弹和Spawn Point赋给Gun Controller。
        在这里插入图片描述
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.XR.Interaction.Toolkit;// 枪支控制器类
public class GunController : MonoBehaviour
{// 子弹预制体public GameObject bullet;// 子弹发射位置的变换组件public Transform spawnPoint;// 子弹发射速度public float fireSpeed = 40;void Start(){// 获取当前物体上的 XRGrabInteractable 组件XRGrabInteractable grabbable = GetComponent<XRGrabInteractable>();// 为 grabbable 的 activated 事件添加监听器,当该事件触发时调用 FireBullet 方法grabbable.activated.AddListener(FireBullet);}private void FireBullet(ActivateEventArgs arg){// 在 spawnPoint 的位置和旋转处实例化子弹预制体GameObject spawnBullet = Instantiate(bullet, spawnPoint.position, spawnPoint.rotation);// 设置实例化出的子弹的刚体速度,使其沿着 spawnPoint 的前方以 fireSpeed 的速度飞行spawnBullet.GetComponent<Rigidbody>().velocity = spawnPoint.forward * fireSpeed;// 在 5 秒后销毁这个子弹对象Destroy(spawnBullet, 5);}
}
  1. 优化一:左右手抓取(判断哪只手与物体交互
  • 方法一(不推荐)
    • GunController脚本中,给XRGrabInteractableSelectEntered事件绑定ChangeAttachTransform函数,通过判断Interactor是左手还是右手控制器来切换Attach Transform。
      也就是创建两个抓取点,并进行监听ChangeAttachTransform判断左右手、来重新设置抓取点
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.XR.Interaction.Toolkit;// 枪支控制器类
public class GunController : MonoBehaviour
{// 子弹游戏对象public GameObject bullet;// 子弹发射位置的变换组件public Transform spawnPoint;// 子弹发射速度public float fireSpeed = 40;// 左手抓取点的变换组件private Transform leftHandAttachPoint;// 右手抓取点的变换组件private Transform rightHandAttachPoint;// 当前物体上的可抓取交互组件private XRGrabInteractable grabbable;void Start(){// 在当前物体下查找名为"LeftHand Attach Point"的子物体,获取其变换组件leftHandAttachPoint = transform.Find("LeftHand Attach Point");// 在当前物体下查找名为"RightHand Attach Point"的子物体,获取其变换组件rightHandAttachPoint = transform.Find("RightHand Attach Point");// 获取当前物体上的 XRGrabInteractable 组件grabbable = GetComponent<XRGrabInteractable>();// 为可抓取交互组件的 selectEntered 事件添加监听器,当该事件触发时调用 ChangeAttachTransform 方法grabbable.selectEntered.AddListener(ChangeAttachTransform);// 为可抓取交互组件的 activated 事件添加监听器,当该事件触发时调用 FireBullet 方法grabbable.activated.AddListener(FireBullet);}private void ChangeAttachTransform(SelectEnterEventArgs arg){// 获取触发 selectEntered 事件的交互器的变换组件Transform interactor = arg.interactorObject.transform;// 如果交互器的父物体名称为"Left Controller",表示是左手控制器if (interactor.transform.parent.name == "Left Controller"){// 将当前物体的抓取点设置为左手抓取点grabbable.attachTransform = leftHandAttachPoint;}else if (interactor.transform.parent.name == "Right Controller"){// 如果交互器的父物体名称为"Right Controller",表示是右手控制器// 将当前物体的抓取点设置为右手抓取点grabbable.attachTransform = rightHandAttachPoint;}}private void FireBullet(ActivateEventArgs arg){// 在 spawnPoint 的位置和旋转处实例化子弹游戏对象GameObject spawnBullet = Instantiate(bullet, spawnPoint.position, spawnPoint.rotation);// 获取实例化出的子弹的刚体组件Rigidbody bulletRigidbody = spawnBullet.GetComponent<Rigidbody>();// 设置子弹的速度,使其沿着 spawnPoint 的前方以 fireSpeed 的速度飞行bulletRigidbody.velocity = spawnPoint.forward * fireSpeed;// 在 5 秒后销毁这个子弹对象Destroy(spawnBullet, 5);}
}
  • 方法二减少耦合性
  • 新建脚本XRGrabInteractableTwoAttach继承XRGrabInteractable,重写OnSelectEntered方法,根据Interactor的Tag判断是左手还是右手,切换相应的Attach Transform
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.XR.Interaction.Toolkit;// 自定义的可抓取交互类,继承自 XRGrabInteractable
public class XRGrabInteractableTwoAttach : XRGrabInteractable
{// 左手的抓取点变换public Transform leftAttachTransform;// 右手的抓取点变换public Transform rightAttachTransform;// 重写 OnSelectEntered 方法,在选择进入时触发protected override void OnSelectEntered(SelectEnterEventArgs args){// 如果交互对象的标签是“Left Hand”(左手)if (args.interactorObject.transform.CompareTag("Left Hand")){// 将当前物体的抓取点设置为左手抓取点attachTransform = leftAttachTransform;}// 如果交互对象的标签是“Right Hand”(右手)else if (args.interactorObject.transform.CompareTag("Right Hand")){// 将当前物体的抓取点设置为右手抓取点attachTransform = rightAttachTransform;}// 调用基类的 OnSelectEntered 方法base.OnSelectEntered(args);}
}

在这里插入图片描述

  • 将枪上的XRGrabInteractable抓取脚本替换为XRGrabInteractableTwoAttach,并在编辑器中为左右手Attach Transform赋值并给Direct Interactor添加Tag

在这里插入图片描述
在这里插入图片描述

  • 第一次抓取或第一次切换抓取位置错误解决方法
    - 方法一
    - 在可抓取物体Gun被抓取物体上添加XRSingleGrabFreeTransformer脚本,游戏运行时会自动添加到XRGrabInteractableTwoAttach脚本。
    在这里插入图片描述

方法二
- 重写刚刚新建的XRGrabInteractableTwoAttach脚本的GetAttachTransform方法,而不是OnSelectEntered方法。根据InteractorTag返回相应的Attach Transform,若Tag不匹配则返回基类的GetAttachTransform结果。

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.XR.Interaction.Toolkit;public class XRGrabInteractableTwoAttach : XRGrabInteractable
{public Transform leftAttachTransform;public Transform rightAttachTransform;public override Transform GetAttachTransform(IXRInteractor interactor){Transform i_attachTransform = null;if (interactor.transform.CompareTag("Left Hand")){i_attachTransform = leftAttachTransform;}if (interactor.transform.CompareTag("Right Hand")){i_attachTransform = rightAttachTransform;}return i_attachTransform != null ? i_attachTransform : base.GetAttachTransform(interactor);}
}

因为之前学的射线交互层级是Everything层级比较高和物体交互层级有重复,所以也能实现射线抓取;然后将与传送有关的 XR Ray Interactor 和 Teleport Area 的 Interaction Layer Mask 改成 Teleport,就可以避免出现这类问题
在这里插入图片描述
在这里插入图片描述

  1. 其他功能一:将与物体接触的地方作为抓取点(Dynamic Attach),也就是碰哪里抓哪里,没有固定抓取点

    • 创建细长Cube作为测试物体,添加碰撞体、刚体和XR Grab Interactable脚本,勾选Use Dynamic Attach后,可根据需求设置Match Position、Match Rotation、Snap To Collider等选项,实现手抓在物体接触部位的效果。

Match Position(匹配位置):当启用相关功能时,确保被抓取物体的位置与抓取点的位置相匹配。例如在使用特定的抓取方式时,可使被抓取物体在被抓取瞬间其位置与抓取点重合,以实现更真实的抓取效果。
Match Rotation(匹配旋转):类似 “匹配位置”,当启用此选项时,被抓取物体的旋转会与抓取点的旋转相匹配。这样可以确保被抓取物体在抓取过程中以合适的角度呈现,增强真实感和交互性。
Snap To Collider(吸附到碰撞体):当开启这个功能后,抓取操作会使被抓取物体吸附到抓取点所在的碰撞体上。这可以使抓取更加精准,并且在抓取过程中物体与抓取点的连接更加紧密,避免出现不合理的位置偏差。

在这里插入图片描述

五、XR Tint Interactable Visual脚本

  1. 功能:可挂载到可交互对象上,当Interactor悬停(Hover)或选中(Select动作触发)可交互对象时,能暂时改变其颜色。
    .在这里插入图片描述2. 设置:调整Tint Color设置颜色,勾选Tint On Hover在Hover时改变颜色,勾选Tint On Selection在Select时改变颜色。注意要把使用的XR Grab Interactable放置到最上层。
    在这里插入图片描述

六、取消身体和可抓取物体的物理碰撞

  1. 设置Layer:将XR Origin的Layer设为Player(仅设置Character Controller所在物体Layer,子物体选No),将所有可抓取物体Layer设为Interactable。
    在这里插入图片描述

  2. 配置Physics:打开Unity编辑器上方菜单栏的Edit编辑 -> Project Settings项目设置-> Physics物理,找到Layer Collision Matrix图层碰撞器,将Player和Interactable的交叉点取消勾选,避免身体与可抓取物体发生物理碰撞。
    在这里插入图片描述

七、XR Interaction Group

  1. 功能:XR Interaction Toolkit 2.3新组件,可管理多个Interactor。当其中一个Interactor生效时,Group内其他Interactor会暂时失效。
    在这里插入图片描述

  2. 应用示例:如在Left/RightHand Controller物体上添加XR Interaction Group组件,将Direct Interactor物体和UI Ray Interactor物体拖到Group中,可实现在抓取物体时让UI射线暂时失效。

八、XR Direct Interactor脚本中的Select Action Trigger

在这里插入图片描述

  1. 参数说明
    • Toggle:以抓取为例,选择Toggle后,靠近物体按下手柄抓取键,物体会被抓在手上,松开抓取键物体仍在手上,下次按下抓取键才会释放。
    • Sticky:按下手柄抓取键物体被抓手上,松开抓取键物体仍在手上,下次按下并松开抓取键物体才会释放。
    • State和State Change的区别(在可抓取物体Select Mode选择Single时)
      • State:可能出现一只手无法接管另一只手抓取权的情况,只有先松开当前抓取手的抓取键才会进行切换抓取。
      • State Change:可以随意切换抓取,推荐在抓取功能上使用State Change。

http://www.ppmy.cn/ops/128020.html

相关文章

室内定位论文整理-20241016

VINS-Mono: 一种稳健且灵活的单目视觉惯性状态估计器 研究问题 如何设计一个稳健且灵活的单目视觉惯性状态估计器,以解决机器人在动态环境中导航和定位的问题? 方法 VINS-Mono 使用单一相机与惯性测量单元(IMU)的数据进行联合处理。该方法结合了来自视觉输入的姿态信息…

【功能超全】基于OpenCV车牌识别停车场管理系统软件开发【含python源码+PyqtUI界面+功能详解】-车牌识别python 深度学习实战项目

背景及意义 车牌识别系统(Vehicle License Plate Recognition&#xff0c;VLPR) 是指能够检测到受监控路面的车辆并自动提取车辆牌照信息&#xff08;含汉字字符、英文字母、阿拉伯数字及号牌颜色&#xff09;进行处理的技术。车牌识别是现代智能交通系统中的重要组成部分之一&…

代码工艺:SQL 优化的细节

1. 巧用 limit 当出现深分页的时候&#xff0c;例如&#xff1a; select id, name, status, detail from product limit 100000, 30; 那么MySQL的执行方式为&#xff1a;一共需要查100030条数据&#xff0c;然后丢弃前面的100000条&#xff0c;只返回后面的30条数据&#xf…

【达梦数据库】两台或多台服务器之间免密登录设置-【dmdba用户】

目录 背景1、服务器A免密登录本机1.1、生成私钥&#xff08;id_rsa&#xff09;和公钥&#xff08;id_rsa.pub&#xff09;1.2、追加公钥到服务器A的密码登录权限管理文件1.3、结果验证 2、服务器A免密登录服务器B2.1、确认服务器B有目的文件夹2.2、服务器A的公钥复制到服务器B…

Python基础08

目录 1.Object-Oriented Programming 2.类 2.1类的定义 2.2实例化对象(构造函数) 2.3self 2.4cls 2.5实例变量(也叫属性) 2.6类属性 2.5初始化方法 2.7类方法 2.8静态方法 3.继承 3.1单继承 3.2多继承 3.3覆盖(Override) 1.Object-Oriented Programming 一切皆…

力扣每日一题3175. 找到连续赢 K 场比赛的第一位玩家

看到数据范围n是1e5, k是1e9就可以知道&#xff0c;这题目暴力模拟肯定会超时。 但我们看&#xff0c;在k比n大情况下&#xff0c;一轮循环下来肯定是没有赢家的&#xff0c;需要多轮。当然&#xff0c;我们肯定不可能去一轮一轮模拟&#xff0c;那我们怎么判断谁是赢家呢&…

gin入门教程(8):渲染与静态文件

目录结构 /hello-gin │ ├── cmd/ │ └── main.go ├── pkg/ │ └── shared_lib.go ├── internal/ │ └── internal_lib.go ├── api/ │ └── routes.go ├── config/ │ └── config.go ├── migrations/ │ └── migration.sql └…

Vue封装组件并发布到npm仓库

前言 使用Vue框架进行开发&#xff0c;组件封装是一个很常规的操作。一个封装好的组件可以在项目的任意地方使用&#xff0c;甚至我们可以直接从npm仓库下载别人封装好的组件来进行使用&#xff0c;比如iview、element-ui这一类的组件库。但是每个公司的业务场景可能不同&…