VR硬件SDK开发基础第二天

763 阅读16分钟

一、关闭窗口弹出问题

image.png

image.png

二、自定义手部抓取姿态手枪案例(Skeleton Poser)

1.给游戏对象挂载对应的组件和脚本

这里我们给枪挂载上对应的三个脚本,其中涉及到手部姿势修改的是Steam VR_Skeleton_Poser这个脚本 image.png

image.png image.png

SteamVR_Skeleton_Poser 组件

image.png

image.png

image.png

image.png 给枪挂载以下脚本,注意要关联对应的枪扳机按钮,还有调整最终扳机的旋转角度将值修改到TriggerDownRotation中

image.png

using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.XR;
using Valve.VR;
using Valve.VR.InteractionSystem;
using Hand = Valve.VR.InteractionSystem.Hand;

public class fire : MonoBehaviour
{
    //手枪扳机组件 
    public Transform gunTriggerTrans;
    //扳机完全按下的角度 
    public Vector3 TriggerDownRotation;
    //扳机的初始角度
    private Quaternion TriggerOriginRotation;
    //获取游戏对象上挂载的intercable组件
    private Interactable interactable;

    void Start()
    {
        TriggerOriginRotation = gunTriggerTrans.localRotation;//获取手枪初始扳机的角度 
        interactable = GetComponent<Interactable>();//获取interactable组件
        if (interactable!=null)
        {
            interactable.onAttachedToHand += attachToHand;//给抓取时的动作添加一个事件
            interactable.onDetachedFromHand += DetachedFromHand; //当松开时要移除对Squeeze动作的监听
        }


    }

    private void DetachedFromHand(Hand hand)
    {
        SteamVR_Actions.default_Squeeze.RemoveOnAxisListener(onSqueezeAxis, hand.handType);//移除对Squeeze动作的监听  
    }

    private void attachToHand(Hand hand)
    {
        SteamVR_Actions.default_Squeeze.AddOnAxisListener(onSqueezeAxis, hand.handType);//对Squeeze动作进行监听
    }
    //这里newAxis表示当前Trigger按下的程度从0到1进行变化浮点数,newDelta表示当前动作是松开还是按下,按下是正数,松开是负数 
    private void onSqueezeAxis(SteamVR_Action_Single fromAction, SteamVR_Input_Sources fromSource, float newAxis, float newDelta)
    {
        //使用lerp方法,让扣动扳机时参数有一个线性变化的过程
        gunTriggerTrans.localRotation = Quaternion.Lerp(TriggerOriginRotation,Quaternion.Euler(TriggerDownRotation),newAxis);
    }

}

三、 Skeleton Poser(骨骼姿态)

Skeleton Poser 系统有一个简单的目的:当拿起物理对象时,你在游戏中的手应该变形为拿着对象的姿势。 这些稳固的姿势可以直接在 Unity 编辑器中创作和调整,以便随着游戏的进行快速迭代。 您可以在姿势之上应用奇特的效果,例如附加的每指动画和动态抓握,以及多姿势混合。

该系统的价值来自于简化的工作流程。 姿势不是处理导入的动画和噩梦般的动画图,而是存储为紧凑的资源,动画会根据与您所持物体相关联的姿势自动应用。 这允许在较小的时间预算内进行更复杂的手部行为。

image.png

手部姿态

这些是这些工具的基本功能。它们优于 Unity 动画的地方在于,姿势是在场景视图中创建的,复杂的行为可以通过轻按几个开关堆叠起来。

要将手部姿势添加到游戏中的任何对象,只需向其中添加SteamVR_Skeleton_Poser脚本即可。 Poser 脚本有两个部分,我们将在下面的内容中介绍这两部分。

image.png

SteamVR_Skeleton_Poser 组件

3.1 The Basics(基本需求)

SteamVR_Skeleton_Poser脚本旨在独立于SteamVR交互系统运行,并可添加到您自己的系统中,但 SteamVR 交互系统开箱即用,是快速试用的好方法。

SteamVR_Skeleton_Poses是包含特定手部姿势和一些每指动画信息的ScriptableObject

可以通过SteamVR_Skeleton_Poser脚本及其方便的用户界面添加和修改多个姿势。 您可以为一个 Poser 添加任意数量的姿势,但您可能最多只需要几个。 使用姿势编辑器中的按钮,可以创建新姿势,可以在姿势之间复制姿势数据,可以镜像姿势数据,可以将姿势重置为各种基础,并且可以将场景视图中的骨架更改保存为 改变姿势。 您还可以通过手指移动下拉菜单添加每个手指的附加动画。 这让手指可以根据骨架输入移动,同时保持姿势的约束。 有几种类型的手指运动:

  • Static:没有手指移动。 只使用姿势。
  • Free:手指自由移动。 忽略姿势。
  • Extend:手指可以抬起到完全伸展的位置,但不能比姿势的位置弯曲得更远
  • Contract:手指可以卷曲到完全收缩的位置,但不能张开超过姿势所在的位置。

使用 Poser 的混合编辑器选项卡,您可以设置混合行为,以复杂的方式混合和堆叠多个姿势。 将混合编辑器视为动画控制器,将姿势视为动画。 您可以添加三种类型的混合行为:手动、模拟操作或布尔操作。 手动行为必须由代码驱动,只需简单调用:

**

Poser.SetBlendingBehaviourValue(string behaviourName, float value)

另一方面,模拟和布尔动作行为由选定的动作自动驱动。 平滑值将是应用于它们的平滑速度,0 表示无。 模拟动作不需要平滑,但推荐用于布尔驱动的行为,因为它们看起来会更平滑。

image.png

示例场景中的手部姿态

3.2 Using the Skeleton Poser with the SteamVR Interaction system(将 Skeleton Poser 与 SteamVR 交互系统结合使用)

Poser 组件将自动与 SteamVR 交互系统一起工作。 您需要做的就是将 SteamVR_Skeleton_Poser 脚本添加到可交互的游戏对象中。 交互上有几个设置,您应该确保更改:

  • 禁用 Interactable.HideHandOnAttach。 如果你留下它,这会隐藏你美丽的姿势!
  • 启用 Interactable.HideControllerOnAttach。 这将确保您的控制器不会妨碍您的手部姿势。
  • 如果您正在创建一个希望能够拿起的可交互对象,请向其中添加 Throwable 脚本。
  • 如果您希望投掷物不穿过环境,只需设置以下附件标志:SnapOnAttach、DetachFromOtherHand、VelocityMovement、TurnOffGravity
    这就是得到一个简单的可交互的 poser 支持所需要的全部。

如前所述,SteamVR_Skeleton_Poser 脚本旨在独立于 SteamVR 交互系统运行。 如果您使用 SteamVR_Behaviour_Skeleton 脚本来为您的手设置动画,您可以通过调用 BlendToPoser() 告诉它混合到特定姿势器的输出。

如果您使用不同的解决方案来为您的骨骼设置动画,Poser 可以按照 SteamVR_Skeleton_PoseSnapshot 数据类的格式根据命令生成姿势,该数据类保存所有骨骼的对象偏移和位置/旋转。 调用 poser.GetBlendedPose,传递 SteamVR_Behaviour_SkeletonSteamVR_Action_SkeletonSteamVR_Input_Sources 手部标识符。 这将根据该特定姿势的各种行为和选项为您提供完全合成的姿势,您可以自由地将其应用于您的骨骼。

3.3 Pose Editor(姿态编辑器)

姿势编辑器用于创建和编辑姿势(Steamvr_Skeleton_Pose),可以将其作为脚本对象保存到您的项目中,并用于该对象或游戏中的任何其他对象。

image.png

姿态编辑器

当您第一次将脚本添加到游戏对象上时,在 Inspector 面板会看到一个选项,可以从项目中选择一个姿势,或者创建一个新姿势。 image.png

创建新的手部姿态

点击创建(Create)后,Unity 会在 Cube 下生成相应的手部模型的克隆体(Clone):

image.png

Cube 下的手部模型克隆体

要预览您正在创作的姿势,请单击 “左手” 和 “右手” 部分中的手形图标以在场景中打开和关闭预览。 这些预览骨骼在它们的变换中保存了您的所有修改,因此请记住不要禁用已经进行修改的 Hand,除非它们已使用 “Save Pose” 按钮保存。

image.png

保存创建的手部姿态

执行此操作时在场景中实例化的手是临时的,只要脚本正确跟踪它们,就会在游戏运行时销毁它们。 在应用于预制件之前禁用双手预览是一种很好的做法,因为预制件中的骨架是凌乱、大且不必要的。

当只启用一个姿势时,最容易编辑姿势,但要使此选项卡中的某些按钮起作用,您需要启用两只预览手。 如果按钮变灰,您可能需要启用一个或两个骨架来激活它。

如果您想修改骨骼的姿势,只需打开可交互对象下方的层次结构。 你可以看到已经添加了一个 vr 手套骨架,你可以进去编辑这些骨骼的变换来形成你的姿势。

image.png

手部模型克隆体的层次结构

点击选择手部骨骼的各个节点,旋转节点可以调整手部姿态。

image.png

通过旋转自定义手部姿态

如果你想做出不对称的姿势,比如说一个不对称的物体——你可以为右手和左手创作一个不同的姿势。 但是,对于简单或对称的对象,您可能希望双手具有相同的姿势,因此您可以使用 Copy x Pose to y hand 按钮将您所做的任何单手修改复制到另一只手上。 当姿势被复制时,手会自动镜像到你的对象上,并且通常会给出完美的结果。 小心此操作,因为它会永久覆盖另一只手的姿势。

image.png

将设置好的手部姿态复制到另一个手上

修改完成后,点选 Show Left Preview,查看左右手的预览。

image.png

左右手预览

预览完成后,点击 Copy Right Pose To Left Hand,使得双手姿态一致。点击 Save Pose 保存修改。

image.png

双手姿态保持一致

记得将 Interactable 脚本下的 HIde Hand On Attach 取消勾选,勾选了则代表手抓握物体时隐藏手,如果不取消勾选,运行时看不到手部模型。我们可以根据不同物体定制其握持动作。

image.png

定制化手部姿态

要向对象添加更多可用姿势,或创建新姿势,请点击顶部姿势列表旁边的小加号按钮。 您将看到创建了一个新选项卡,默认情况下未选择任何姿势,您可以再次从项目中选择一个姿势或创建一个新姿势。 添加 SteamVR_Skeleton_Poser 的姿势将成为稍后可用于混合的姿势。 除了标记为 (MAIN) 的第一个姿势之外,这些顺序无关紧要,被标记为(MAIN)的姿势将是基本姿势。

image.png

添加手部姿态

在每个手形图标下方,您可能已经注意到手指移动的所有选项。 这是用于附加动画,您希望骨骼系统的单个手指动画应用到您创建的姿势之上。 默认情况下,这将设置为静态,但还有其他三个选项。

  • Static 静态:无附加动画 。
  • Free 自由:如果你不希望你的姿势适用于手指,在这种情况下,它只会听从骨骼系统。
  • Extend 伸展:如果手中的姿势是手指的最紧握姿势,则在玩家抬起手指时,手指就会抬起。 这可能是最常见的,因为姿势主要是围绕物体。
  • Contract 收缩:与伸展相反,手指处于任何姿势都是手指的最大伸展值,并且只允许进一步向拳头姿势收缩。

image.png

手指姿态设置选项

3.4 Blending Editor(混合编辑器)

混合编辑器用于创建更加复杂的行为,即在多个姿势之间混合。

image.png

混合编辑器

点击底部的加号按钮来添加一个新的混合行为,默认情况下称为 new Behaviour。 您可以启用和禁用行为,它们有一个 Influence 滑块,如果您不想在运行时严格启用和禁用它们,您可以在其中关闭和打开它们并使用更多渐变(中间值)。

image.png

influence

他们有一个目标姿势,默认情况下,他们将混合到主要姿势。 因为主要姿势是基础,所以这不会做任何事情。 相反,您需要将其设置为已添加到姿势编辑器列表中的次要姿势之一。

共有三种不同类型的混合行为:

Manual:如果您希望此混合由脚本控制或仅在此处使用此值滑块在 Inspector 中设置,您将使用什么。Analog:允许您将此混合行为权重映射到项目中的模拟操作之一。 平滑速度可让您对此进行一些平滑处理。 0 意味着没有平滑,任何高于零的都将是缓慢的,随着值的增加,平滑变得越来越快。 一个合适的值应该在 10 到 30 之间,尽管您可能根本不想要任何平滑,因为这是一个模拟动作。Boolean:这与模拟动作非常相似,不同之处在于它可以映射到项目中的布尔动作,例如按下按钮。 在这种行为类型中,平滑可能更重要一点,因为如果您没有任何平滑,它将立即跳转。 同样,建议使用 10 到 30 之间的值。

image.png

三种混合行为

如果将混合行为的类型选择为后两种,我们可以为混合行为设置 Action。Analog Action类型对应 Action_single,Boolean Action类型对应 Action_bool。

image.png

Boolean 类型

我们可以添加多个行为,从而在 Blending Behaviour 设置实现不同 Action 绑定不同的手势。但需要注意的是,混合行为需要我们为物体设置多个 Pose。这里我们以示例场景中的 Squishy 物体为例,演示如何设置并绑定。

image.png

示例场景中的小球

如上图所示,Squishy 有两个 Pose,如下所示:

image.pngimage.png

其中示例场景中的 Blending Editor 中已经为副姿势设置了 Action 对应于我们手柄的扳机键,这里我们对另一个 Pose 添加 Pose。我们指定动作(Action)为 Grip,在手柄中我们设置了 Grip 动作对应于手柄的侧边键。

image.png

对主副姿态进行设置

平滑速度对应于 Pose 切换的平滑程度,0 意味着没有平滑,瞬间切换。

四、双手持握道具交互:Item Package 模块使用详解

1.模块解析

image.png

2.实现棍棒和盾牌的捆绑抓取

image.png

image.png

image.png

image.png

image.png

image.png

image.png

image.png

五、开关门交互实例:CircularDrive组件的使用

1.给要旋转的对象添加CircularDrive脚本

image.png

2.CircularDrive中的一些参数

image.png

image.png

3.添加门把手的碰撞器

image.png

4.设置CircularDrive参数

image.png

六、旋转阀门交互实例:LinearMapping与LinearDisplacement组件

1.实现阀门旋转需要挂载的三个脚本

image.png

image.png

2.CircularDrive的参数设置

image.png

3.LinearDisplacement的参数设置

image.png

七、拖拉抽屉交互实例:LinearDrive组件的使用

image.png LinearMapping: 一个脚本组件,用于输出该物体在起止点上的比例,数值为0-1 Reposition Game Object: 游戏物体重定位,即不勾选时,Sphere不可移动,但还会输出数值到LinearMapping

Maintain Momemntum: 物体在滑动时是否具有惯性,即勾选后,停下滑动Sphere,Sphere还会根据惯性继续向前移动一段距离

Momemutum Dampen Rate: 勾选上个选项后的阻力值。越大,物体停下越快

八、凝视UI交互效果

1.凝视UI效果

在游戏场景中,人物会在头盔的位置发出一条射线,碰撞到对应的UI组件上,有一个悬停效果,悬停时会出现一个进度圈,当进度圈旋转到最大值时能够进行场景跳转等的交互效果,这里我们不需要用到手柄控制器,仅仅通过头盔移动发出射线,来进行UI的交互,我们称这种交互叫凝视UI交互。

2.给交互对象添加UIElement脚本

image.png

3.进度圈原理

image.png

4.代码实现逻辑

using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
using Valve.VR.InteractionSystem;

public class headRaycast : MonoBehaviour
{
    //关联头盔的位置  
    public Transform head;
    //悬停的时间 
    public float activeTime = 3;
    //记录悬停的时间  
    public float stareTime;
    //获取变化的小圆圈的Image ui对象
    public Image progressImage;
    //获取取消激活的小圆圈的Image ui对象
    public Image cursor;
    //获取button组件 
    private Button button;
    //射线 
    private Ray ray;
    //怎么能够知道我是一直悬停着,是不是可以判断碰撞的前后的物体是不是同一个   
    private GameObject currentObject;
    private GameObject lastObject;
    //创建一个布尔值  isEnabled  表示是否要开启凝视ui的效果 
    public bool isEnabled = false;
    //创建一个变量来存碰撞的物体
    private RaycastHit hit;
    //射线的长度
    public float rayLength = 50;
    //表示筛选的图层
    public LayerMask layerMask;
    private void Awake()
    {
        isEnabled = true;

    }


    void Update()
    {
        //发射射线    
        if (head != null) startRaycast();
    }

    private void startRaycast()
    {
        //获取头的位置 
        Vector3 headRaycastPoint = head.position;
        //生成射线 
        ray = new Ray(headRaycastPoint,head.forward);
        //发射射线 
        if (Physics.Raycast(ray,out hit,rayLength,layerMask))  
        {
            //拿到当前碰撞到的物体 
            currentObject = hit.transform.gameObject;
            if (currentObject != lastObject) dective();
           
            //判断一下当前的碰撞的物体是否有button组件 
            button = currentObject.GetComponent<Button>();
            if (button == null) return;
            //显示cursor 
            if (cursor!=null)
            {
                cursor.transform.gameObject.SetActive(true);
                cursor.transform.position = hit.point;
                cursor.transform.rotation = head.rotation;
            }
            if (currentObject&&currentObject!=lastObject)
            {
                //准备进行悬停的效果 
                stareTime = 0;
                //调用悬停的方法
                InputModule.instance.HoverBegin(currentObject);
                if (progressImage != null) progressImage.fillAmount = 0;

            }else if (currentObject==lastObject)
            {
                //记录时间  
                stareTime += Time.deltaTime;
                //改变progressImage.fillAmount的值 
                if (progressImage != null) progressImage.fillAmount = stareTime / activeTime;
                if (stareTime > activeTime)
                {
                    stareTime = 0;
                    isEnabled = false;
                    //场景切换了  触发按钮点击的效果 
                    InputModule.instance.Submit(currentObject);

                }
            }
            //更新lastobject的值  
            lastObject = currentObject;

        }
    }

    private void dective()
    {
        stareTime = 0;
        if (progressImage != null) progressImage.fillAmount = 0;
        if (cursor != null) cursor.transform.gameObject.SetActive(false);
    }
}

九、使用射线进行UI交互

1.给两个手柄控制器对象挂上SteamVRLaserPointer脚本

image.png

2.给控制器挂上对应的脚本

using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.EventSystems;
using Valve.VR.Extras;

public class demo : MonoBehaviour
{
    //关联LaserPointer组件
    private SteamVR_LaserPointer laserPointer;
    //当前交互的UI元素 
    private GameObject UIElement;
    //是否开启激光交互 
    public bool isEnabled = true;

    private void Awake()
    {
        //获取LaserPointer组件
        laserPointer = GetComponent<SteamVR_LaserPointer>();
        if (laserPointer!=null)
        {
            if (!isEnabled)
            {
                laserPointer.enabled = false;//不显示激光指针 
                return;
            }
            else
            {
                //绑定对应的鼠标移入移出的方法  
                laserPointer.PointerIn += LaserOnPointerIn;  
                laserPointer.PointerOut += LaserOnPointerOut;
                laserPointer.PointerClick += LaserOnPointerClick;

            }
        }
    }

    private void LaserOnPointerIn(object sender, PointerEventArgs e) //e表示事件的参数集合  
    {
        IPointerEnterHandler pointerEnter = e.target.GetComponent<IPointerEnterHandler>();
        if (pointerEnter!=null)
        {
            pointerEnter.OnPointerEnter(new PointerEventData(EventSystem.current)  );//这里其实就是事件系统的鼠标移入相关的操作  
        }
    }

    private void LaserOnPointerOut(object sender, PointerEventArgs e)
    {
        IPointerExitHandler pointerExit= e.target.GetComponent<IPointerExitHandler>();
        if (pointerExit!=null)
        {
            pointerExit.OnPointerExit(new PointerEventData(EventSystem.current));
        }
    }

    private void LaserOnPointerClick(object sender, PointerEventArgs e)
    {
        IPointerClickHandler pointerClick = e.target.GetComponent<IPointerClickHandler>();
        if (pointerClick!=null)
        {
            pointerClick.OnPointerClick(new PointerEventData(EventSystem.current)) ;
        }
    }


}

十、SteamVR loadLevel场景跳转

1.实现场景跳转中显示加载场景的效果

image.png

2.解决跳转场景后Player位置的问题

我们知道每次进行场景跳转,在新场景中会创建一个新的player实例,位置和上一个场景player的位置是一样的,那这样就会出现一个问题,万一新场景的player出现的位置和上一个场景的位置不一样,怎么办?其实很简单,我们可以通过Teleport point来解决这个问题

image.png

3.我们在场景跳转的时候会出现两个audio

1.我们可以创建一个空场景,让其自动跳转到开始场景中

注意我们这里只给第一个空场景添加一个Player,后面在其他场景就只会生成一个Player,在进行非空场景的切换的时候就不会出现多余的Player,在进行场景的返回的时候,可能位置会有问题,所以我们还需要用到Teleport point来设置跳转场景后Player显示的位置 image.png

4.实现返回上一场景的操作,这里我们模拟通过按下trigger键来触发回到上一个场景

using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using Valve.VR;
using UnityEngine.SceneManagement;
public class back : MonoBehaviour
{
    private void Start()
    {
        SteamVR_Actions.default_GrabPinch.onStateUp += backScene;//给Trgger键添加一个按键抬起的事件

    }

    private void backScene(SteamVR_Action_Boolean fromAction, SteamVR_Input_Sources fromSource)
    {
        //跳转到上一个场景 
        SceneManager.LoadScene("demo02");
    }
}