PicoNeo3开发VR——小白教程

2023-11-19


不断更新中,欢迎大佬们来指导

纠错!!!

导入PicoVRSDK

1.新创一个Unity工程(Unity版本最好选择2019.4以上版本,以及需配置好安卓环境),然后导入官方picoVRSDK;
2.渲染设置
Graphics APIs暂不支持Vulkan,对于OpenGLES2,OpenGLES3,开发者需要按照需求选择。
在这里插入图片描述
三:对于API Level的设置要求
在这里插入图片描述

Pico开发之无线打包与调试

1.虽然说是无线,但是我们第一次还是需要进行usb连接,打开Pico的开发者模式,与电脑进行连接,连接好了之后,按下Win+R输入Cmd打开命令窗口,输出adb tcpip 5555,进行tcp模式的连接,连接之后会输出重新连接到这个端口,需要保证设备和电脑是同一网络下哦!也就是局域网

  • 如果输出的是error: no devices/emulators found,则说明Pico头盔没有和电脑正确的连接,需要重新连接
  • 如果输出的是无法连接,那么是你的电脑没有adb的插件,且没有配置adb的环境,需要对adb的环境进行配置

2.输出成功连接之后我们需要找到Pico连接的WIFI的IP地址,点击Pico的WIFI或者点击设置进入WIFI找到连接的WIFI,点击左下角的更多,进入WIFI点击网络信息,可以找到我们目前连接WIFI的IP地址

3.记录下这个IP地址,然后输入adb connect 192.168.131.163,这个是我的IP地址可以换成自己设备上的,输入成功之后会输出已经成功连接到这个IP地址

4.这个时候我们的Pico设备就已经和电脑进行无线连接了,可以将usb线拔掉了,这个时候我们返回Unity,点击File找到Build Setting页面,点击Refresh,就可以到当前设备名字+ip地址,证明连接成功了,这个时候我们打包的时候就可以进行无线传输了

5.接下来我们进行的是调试方面的设置,由于Unity中的Debug打包到Pico上是无法进行输出的,这对我们的调试造成了极大的不便,这个我们只需要勾选几个选项,就可以在Pico运行的时候我们在Unity的输出窗口也能够看到调试信息,我们需要将Development Bulid勾选上,这个是开发者模式构建,第二个就是Script Debugging这个是可以让你的Debug信息进行输出

6.然后我们进行打包测试,我们进行输出的就是Pico头盔的状态,通过UPvr_GetPsensorState()得到,为0的则是戴上的状态,为1则为表示远离,当我们打包在头盔上运行后,打开Unity的Console面板,点击Editor,展开后会发现AndroidPlayer +一串IP地址,点击这个就看到输出结果.大功告成!

试玩接口验证错误(61001)

61001的错误的出现是因为你的应用程序没有上传到Pico Developer Platform上。

因为受限于Pico neo3,它会把这个apk文件当作在Pico Store上发布的应用程序,所以需要进行“用户权限检查”。

然而,没有在Pico Store上发布的作品,“用户权限检查”也没法通过。

因此有两种办法:

1.第一种方法

直接把“用户权限检查”给关闭掉,这个方式直接、简单。【推荐】

具体操作:Pvr_UnitySDK -> Platform settings ->User Entitlement Check 把这个叉掉就行了

2.第二种方法

可以使用设备的SN来模拟“用户权限检查”。

至于设备的SN可以在Pico neo3上获得:设置 -> 通用 -> 设备序列号

当然,为了方便复制,可以直接在有线串流模式下,打开cmd,运行代码adb devices就可以得到SN号。
在这里插入图片描述
接着在Platform Settings中输入App ID(也就是登录这个网址后https://developer.pico-interactive.com/账号的Publisher ID)和设备的SN号。

PicoNeo3手柄发射射线交互UI

1:先将场景中的MainCamera删除,然后根据下面所示路径找到Assets>PicoMobileSDK>Pvr_UnitySDk>Prefabs>Pvr_UnitySDK预置体,将其拖放到场景中。
此时运行就可以在PicoNeo3眼镜中看到此场景了。
2:这时我们会发现,我们现在还控制不了场景(缺少控制器,手柄),所以我们要添加一个手柄。
将Asset>PicoMobileSDK>Pvr_Controller>Prefabs>ControllerManager预置体添加到场景中,放到Pvr_UnitySDK下,和Head同级,如下
在这里插入图片描述
在ControllerManager下有PvrController0和PvrController1两个物体,分别对应两个手柄
在这里插入图片描述
dot:手柄发射的线段的顶端,一个小圆点
ray_alpha:手柄发射的可视化线段
controller:其上挂载着Pvr_ControllerInit脚本,负责控制手柄的初始化,上面有三种手柄的模型可供选择。
此时,再运行,就可以再场景中看到我们的手柄了。
3:在Pvr_UnitySDK上新建一个空物体为子物体,与Head同级,命名为HeadController,再新建一个空物体作为HeadController的子物体,并为这个子物体挂载Pvr_UIPointer这个脚本。
4:Event上挂载Pvr_InputModule脚本。
在这里插入图片描述
默认是按下摇杆键与UI进行交互,Pvr_InputModule中ConfirmBtn参数可修改与控制UI交互的手柄按键

5:创建一个Canvas(Scale建议设为(0.005,0.005,0.001)),RenderMode设为WorldSpace,将EventSystem删除,再将这两个脚本Pvr_UICanvas、Pvr_ControllerDemo挂载到Canvas上,并且指定部分值,如下图所示:
在这里插入图片描述
6:此时,UI已经可以和手柄完成正常交互检测了,InputField中键盘异常的问题有待进一步解决。
PS:UI面板的Z轴最好为零,否则将会出现UI无法交互的情况,原因可能是ui交互的碰撞体是添加在canvas上的,所以当我们的ui面板z轴上的偏移过大时,canvas上的碰撞体就无法覆盖到我们的面板上了。

官方搜狗VR输入法

相对于Unity的InputField来说,这款输入法没有光标,不能选中输入的内容,只能从已输入内容的最后一位开始操作,所以相对来说略微有点不太人性,优点就是它的语音系统(需设备已连接网络),识别度还挺高的。

1:在UI可以正常交互的基础上,导入官方搜狗插件(下载地址:链接:https://pan.baidu.com/s/1mxriL3DQVQozGqhp2GCDrw 提取码:1234),然后再Canvas上添加一个组件:Pvr_ControllerDemo_VRInput。
2:找到路径Assets–SGVRInput–Prefabs路径下的两个预制体,将其拖入层级面板中,然后创建一个Text,然后添加TextHandler组件;
在这里插入图片描述
3:相应脚本赋值如下图所示:
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
4:打包测试:用射线点击我们创建的Text,就会弹出搜狗键盘,然后就可以正常输入了。键盘的位置和大小可以根据自己喜欢来设置。

2D物体的射线拖拽

1:需要被射线拖拽的2d物体上挂载Pvr_UIDraggableItem脚本以及CanvasGroup组件,即可实现在Canvas内随意拖动,切记一定要添加CanvasGroup组件,否则只能实现一次拖拽,第二次拖拽将不起作用。
2:特定范围内拖拽的实现:
需拖拽的物体挂载脚本和组件与第一步相同,不同的是特定范围的拖拽需要将Pvr_UIDraggableItem脚本上的两个复选框√上。然后在可拖动的范围物体上挂载Pvr_UIDropZone脚本,最后将被拖拽的物体作为其中一个挂载了Pvr_UIDropZone脚本的范围物体上的子物体。如下图所示:
在这里插入图片描述

瞬移功能的实现

1:创建一个可供移动的Plane,给其指定层级,如下图所示:
在这里插入图片描述
2:创建一个空物体,命名为Point,新建一个名为Teleport脚本,并挂载在Point上,脚本如下:

using Pvr_UnitySDKAPI;
using System.Collections;
using UnityEngine;

public class Teleport : MonoBehaviour
{
    public static Pvr_KeyCode TOUCHPAD = Pvr_KeyCode.TOUCHPAD;
    public static Pvr_KeyCode TRIGGER = Pvr_KeyCode.TRIGGER;
    public float fadeTime = 0.2f;
    public bool IsBezierCurve = false;
    public bool IsScreenFade = false;
    public Material LineMat;
    public GameObject PointGo;
    public Material PointGoMat;
    private GameObject cube;
    private GameObject currentController = null;
    private Vector3 currentHitPoint = Vector3.zero;
    private Color fadeColor = new Color(0.9f, 0.9f, 0.9f, 0f);
    private Material fademat;

    private LineRenderer line;
    private Ray ray;
    private GameObject sdkManagerGo;

   
    public GameObject CurrentController
    {
        get
        {
            if (currentController == null)
                currentController = FindObjectOfType<Pvr_ControllerDemo>().currentController;
            return currentController;
        }
    }

    public static Vector3[] GetBeizerPathPointList(Vector3 startPoint, Vector3 controlPoint, Vector3 endPoint, int pointNum)
    {
        Vector3[] BeizerPathPointList = new Vector3[pointNum];
        for (int i = 1; i <= pointNum; i++)
        {
            float t = i / (float)pointNum;
            Vector3 point = GetBeizerPathPoint(t, startPoint,
                controlPoint, endPoint);
            BeizerPathPointList[i - 1] = point;
        }
        return BeizerPathPointList;
    }

    private static Vector3 GetBeizerPathPoint(float t, Vector3 p0, Vector3 p1, Vector3 p2)
    {
        return (1 - t) * (1 - t) * p0 + 2 * t * (1 - t) * p1 + t * t * p2;
    }
    /// <summary>
    /// 瞬移功能按键控制功能  如需改变控制瞬移功能的按键  在此处修改即可
    /// </summary>
    /// <returns></returns>
    private static bool GetTeleportKey()
    {
        return //Controller.UPvr_GetKey(0, TOUCHPAD) ||
            Controller.UPvr_GetKey(1, TOUCHPAD) ||
            //Controller.UPvr_GetKey(0, TRIGGER) ||
            //Controller.UPvr_GetKey(1, TRIGGER) ||
            Input.GetMouseButton(0);
    }

    private static bool GetTeleportKeyUp()
    {        
        return //Controller.UPvr_GetKeyUp(0, TOUCHPAD) ||
            Controller.UPvr_GetKeyUp(1, TOUCHPAD) ||
            //Controller.UPvr_GetKeyUp(0, TRIGGER) ||
            //Controller.UPvr_GetKeyUp(1, TRIGGER) ||
            Input.GetMouseButtonUp(0);
    }

    private void DrawLine()
    {
        Vector3 startPoint = CurrentController.transform.Find("start").position;
        Vector3 endPoint = CurrentController.transform.Find("dot").position;
        Vector3 controllerPoint = CurrentController.transform.Find("controller").position;
        if (!IsBezierCurve)
        {
            line.positionCount = 2;
            line.SetPosition(0, startPoint);
            line.SetPosition(1, endPoint);
        }
        else
        {
            float distance = Vector3.Distance(startPoint, endPoint);
            Vector3 controlPoint = startPoint + (startPoint - controllerPoint).normalized * distance / 1.6f;

            Vector3[] bcList = GetBeizerPathPointList(startPoint, controlPoint, endPoint, 30);
            line.positionCount = bcList.Length + 1;
            line.SetPosition(0, startPoint);
            for (int i = 0; i < bcList.Length; i++)
            {
                Vector3 v = bcList[i];
                line.SetPosition(i + 1, v);
            }
        }
    }

    private bool HitFloor(ref RaycastHit hit)
    {
        return 1 << hit.transform.gameObject.layer == LayerMask.GetMask("TransparentFX");
    }

    private void LineInit()
    {
        if (GetComponent<LineRenderer>())
            line = GetComponent<LineRenderer>();
        else
            line = gameObject.AddComponent<LineRenderer>();
        line.material = LineMat;
        line.startWidth = 0.02f;
        line.numCapVertices = 5;
    }

    private void MoveCameraPrefab(Vector3 target)
    {
        if (GetTeleportKeyUp())
        {
            if (IsScreenFade)
                StartCoroutine(ScreenFade(target));
            else
                sdkManagerGo.transform.position = new Vector3(target.x, target.y + 1.67f, target.z);
        }
    }

    private IEnumerator ScreenFade(Vector3 target)
    {
        float ShowTimer = 0.0f;
        float HideTimer = 0.0f;
        fademat.color = fadeColor;
        cube.SetActive(true);
        Color color = fadeColor;
        while (ShowTimer < fadeTime)
        {
            yield return new WaitForEndOfFrame();
            ShowTimer += Time.deltaTime;
            color.a = Mathf.Clamp01(ShowTimer / fadeTime);
            if (color.a > 0.8f)
                break;
            fademat.color = color;
        }
        sdkManagerGo.transform.position = new Vector3(target.x, target.y + 1.67f, target.z);
        while (HideTimer < fadeTime)
        {
            yield return new WaitForEndOfFrame();
            HideTimer += Time.deltaTime;
            color.a = 0.8f - Mathf.Clamp01(HideTimer / fadeTime);
            if (color.a < 0.01f)
                break;
            fademat.color = color;
        }
        cube.SetActive(false);
    }

    private void Start()
    {
        LineInit();
        sdkManagerGo = FindObjectOfType<Pvr_UnitySDKManager>().gameObject;

        fademat = new Material(Shader.Find("Sprites/Default"));
        cube = GameObject.CreatePrimitive(PrimitiveType.Cube);
        cube.GetComponent<MeshRenderer>().material = fademat;
        cube.transform.position = sdkManagerGo.transform.position;
        cube.transform.parent = sdkManagerGo.transform;
        cube.SetActive(false);

        if (PointGoMat != null)
            PointGo.GetComponent<MeshRenderer>().material = PointGoMat;
        PointGo.SetActive(false);

        ray = new Ray();
    }

    // Update is called once per frame

    private void Update()
    {
        if (CurrentController != null && GetTeleportKey())
        {
            line.enabled = true;
            //sdkManagerGo = currentController.transform.parent.gameObject;
            ray.direction = CurrentController.transform.Find("dot").position - CurrentController.transform.Find("start").position;
            ray.origin = CurrentController.transform.Find("start").position;

            RaycastHit hit;
            if (Physics.Raycast(ray, out hit))
            {
                currentHitPoint = hit.point;
                if (HitFloor(ref hit) && hit.point != null)
                {
                    PointGo.transform.position = hit.point;

                    PointGo.SetActive(true);
                    //CurrentController.transform.Find("dot").position = hit.point;
                }
            }
            else { PointGo.SetActive(false); }

            DrawLine();
        }
        else
        {
            if (currentHitPoint != Vector3.zero)
            {
                if (PointGo.activeInHierarchy)
                {
                    MoveCameraPrefab(currentHitPoint);

                    currentHitPoint = Vector3.zero;
                    PointGo.SetActive(false);
                }
            }

            if (line.enabled == false)
                return;
            line.enabled = false;
        }



      
    }
}

脚本会有一个报错,只需将Pvr_ControllerDemo脚本中的currentController变量设为public类型的即可。
然后,再创建一个Capsule作为Point的子物体,建议将其Scale设为(0.1,0.01,0.1),并且将其隐藏,切记不能移除其碰撞体。

PS:如在瞬移前没有实现UI交互功能,需要找个物体挂载Pvr_ControllerDemo脚本,并进行相应赋值,详细请看上文添加UI交互部分
在这里插入图片描述
关于瞬移方面,效果方面我个人做了一些小的优化,具体效果如下图所示:
在这里插入图片描述

上面瞬移脚本存在一个问题,就是当移动光圈移动到某个可瞬移位置后,然后将射线指向一个不可瞬移的位置,此时也能瞬移过去,只需要将上面脚本做下图所示修改即可:
在这里插入图片描述

射线远距离抓取物体

1:在角色控制器上创建两个物体附着运动的焦点(两个焦点的位置信息保持一致即可),如下图所示:
在这里插入图片描述
2:再新建一个脚本,命名为AttachTest,将这个脚本挂载在需要被抓取的物体身上(被抓取物体身上应该要有碰撞体),脚本如下:

using System.Collections;
using System.Collections.Generic;
using Pvr_UnitySDKAPI;
using UnityEngine;

public class AttachTest : MonoBehaviour
{
    // Start is called before the first frame update

    //The focus of the object's "attach" movement(left hand / right hand)
    [Header("物体“附着”运动的焦点(左手/右手)")]
    public Transform node0;
    public Transform node1;

    //Controller(left hand / right hand)
    public GameObject controller0;
    public GameObject controller1;

    //The speed of the "attach" process
    public float attachSpeed;

    //The speed of throwing objects
    public float throwSpeed = 5;

    //Controller in use
    private GameObject currentController;

    //The focus of the object's "attach" movement
    private Transform currentNode;

    private int mainHandNess;
    
    private Ray ray;
    private RaycastHit hit;

    //The material in the highlighted state of the object
    //[SerializeField]
    private Material attachMaterial;
    //[SerializeField]
    private Material normalMaterial;

    //The key is pressed or not pressed
    private bool noClick = true;

    //The current state of motion of the object
    private bool moveState = false;

    //private Vector3 currentPosition;
    //private Vector3 lastPosition;
    //private Vector3 movementDirection;

    private Vector3 angularVelocity;
    private Vector3 linearVelocity;

    private Vector3 angularVelocityGetKey;
    private Vector3 angularVelocityAverage;

    void Start()
    {
        ray = new Ray();
        hit = new RaycastHit();

        attachMaterial = Resources.Load<Material>("Materials/Custom_AttachMaterial");
        normalMaterial = Resources.Load<Material>("Materials/Custom_NormalMaterial");
    }

    // Update is called once per frame
    void Update()
    {
        //Determined whether the handle is connected
        if (Controller.UPvr_GetControllerState(0) == ControllerState.Connected || Controller.UPvr_GetControllerState(1) == ControllerState.Connected)
        {
            //Get the current master control controller index
            mainHandNess = Pvr_UnitySDKAPI.Controller.UPvr_GetMainHandNess();

            if (mainHandNess == 0)
            {
                currentController = controller0;
                currentNode = node0;
            }

            if (mainHandNess == 1)
            {
                currentController = controller1;
                currentNode = node1;

            }
          
            
            ray.direction = currentController.transform.forward - currentController.transform.up * 0.25f;
            ray.origin = currentController.transform.Find("start").position;

            //Determine whether the ray interacts with this object
            if (Physics.Raycast(ray, out hit) && (hit.transform == transform))
            {
                if (noClick)
                {
                    transform.GetComponent<MeshRenderer>().material = attachMaterial;
                }

                {
                    //Judging whether the "Trigger" is pressed or not
                    if (Input.GetKey(KeyCode.Space) || Pvr_UnitySDKAPI.Controller.UPvr_GetKey(mainHandNess, Pvr_UnitySDKAPI.Pvr_KeyCode.TRIGGER))
                    {
                        moveState = true;
                        noClick = false;
                        transform.GetComponent<MeshRenderer>().material = normalMaterial;

                        //Completed the attach effect
                        transform.position = Vector3.Lerp(transform.position, currentNode.position, Time.deltaTime * attachSpeed);
                        transform.rotation = Quaternion.Lerp(transform.rotation,currentNode.rotation,Time.deltaTime *attachSpeed);
                        transform.SetParent(currentNode);
                        GetComponent<Rigidbody>().isKinematic = true;

                        //The reason for using "Input.GetKey" is to get a more accurate motion trend in 2 frams.
                        angularVelocityGetKey = Pvr_UnitySDKAPI.Controller.UPvr_GetAngularVelocity(mainHandNess);

                    }

                }
            }
            else
            {
                transform.GetComponent<MeshRenderer>().material = normalMaterial;
            }

            //Checking whether the "Trigger" is lifted or not
            if (Input.GetKeyUp(KeyCode.Space) || Pvr_UnitySDKAPI.Controller.UPvr_GetKeyUp(mainHandNess, Pvr_UnitySDKAPI.Pvr_KeyCode.TRIGGER))
            {
                if (moveState)
                {
                    noClick = true;

                    transform.SetParent(null);
                    GetComponent<Rigidbody>().isKinematic = false;

                    angularVelocity = Pvr_UnitySDKAPI.Controller.UPvr_GetAngularVelocity(mainHandNess);
                    angularVelocityAverage = (angularVelocityGetKey + angularVelocity) / 2;
                    linearVelocity = Pvr_UnitySDKAPI.Controller.UPvr_GetVelocity(mainHandNess);

                    GetComponent<Rigidbody>().angularVelocity = angularVelocityAverage * 0.0001f * throwSpeed;
                    GetComponent<Rigidbody>().velocity = linearVelocity * 0.0001f * throwSpeed;
                    
                    moveState = false;
                }
                
            }

            
        }
    }
}

脚本相关变量、参数赋值如下图所示:
在这里插入图片描述
在Resources文件夹下新建一个名为Materials的文件夹,然后创建两个材质球,命名如下图所示,其中将NormalMaterial赋值给被抓取的物体即可。
在这里插入图片描述

获取手柄按键输入

要获取PicoNeo3的手柄按键输入,首先应该在脚本里引用命名空间(using Pvr_UnitySDKAPI),然后按照以下格式:
Controller.UPvr_GetKeyDown(int hand,Pvr_KeyCode.xxx);
其中0代表左手手柄,1代表右手手柄 具体按键所对应的名称如下图所示:
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
其他比较常用的手柄输入方式获取代码如下:

判断控制器是否连接可以使用 0代表左手 1代表右手
Controller.UPvr_GetControllerState(1) == ControllerState.Connected

手柄振动(振动强度:1 持续时间:500毫秒 右手柄)Controller.UPvr_VibrateController(1, 500, 1);

判断Joystick键是否向上:UPvr_GetJoystickUp(其他方向修改成对应的方向单词即可获取)

获取摇杆的拨动值:UPvr_GetAxis2D

获取手柄的角速度:UPvr_GetAngularVelocity

获取手柄的线速度:UPvr_GetVelocity(返回一个Vector3的值,这个值是以手柄正前方为z轴,右边为x轴,上面为y轴建立的一个三维坐标系值)

下表是指一体机头显上的按键与Unity里的键值对应关系。

HMD按键 Unity输入键
返回键 KeyCode.Escape(Unity中使用:Input .GetKeyDown(KeyCode.Joystick1Button0))
确认键 KeyCode.JoystickButton0(unity中获取,同上所示)
Home键 KeyCode.Home(系统占用,默认不开放)

想要了解更为详细的API接口函数,可取官网浏览,网址如下:
http://sdk.picovr.com/docs/UnitySDK/cn/chapter_seven.html

如何使用自定义的手柄模型

方法一:

1:如果想要使用自定义的手柄模型(比如手枪、弹弓、魔杖、剑等道具),需要勾选此项(哪个手柄需要就勾选哪个)
在这里插入图片描述:2:将你的自定义手柄模型放到ControllerManager——PvrController0(1)——controler下,作为其子物体,然后调整合适的位置和旋转即可。
在这里插入图片描述
PS:此种方法在实际项目的打包发布中存在一个问题,就是当你按下手柄Home键后再次进入游戏,会出现手柄模型丢失这一问题。下面要说的方法二就不存在这个问题!

方法二:

1:可以将PvrController0和PvrController1下的controller 删除掉,换成自己的的模型就行。这里需要注意的是,用此种方法可以不用勾选PvrController0和PvrController1的IsCustomModel。将模型调整合适的位置和角度即可。

如何使用手柄抓取物体

此种抓取是一次性抓取,即抓取之后不能放下

1:首先给需要抓取的手添加刚体(不勾选重力,勾选IsKinematic)和碰撞体(碰撞体大小可以参考我下图所示进行调整,碰撞体为触发器),如下图所示:
在这里插入图片描述
2:参考自定义手柄模型部分,将需要被抓取的物体放在抓取手Controller下,作为其子物体,并将其隐藏。然后新建一个脚本,将下面所示方法复制进脚本中:

void OnTriggerStay(Collider collider)
{       
    if (collider.name.Equals("PM40"))
    {
        if (Controller.UPvr_GetKeyDown(0, Pvr_KeyCode.TRIGGER))
        {
            collider.gameObject.SetActive(false);
            //需要隐藏的模型(一般情况下是手的模型)
            transform.GetChild(0).gameObject.SetActive(false);
            //拾取的模型
            transform.GetChild(1).gameObject.SetActive(true);
        }           
    }
}

并将该脚本添加到这个controller上即可。
在这里插入图片描述

3:复制一个一模一样的抓取物体,放在场景中合适的位置,然后在这个被抓取的物体上添加刚体和碰撞体。
在这里插入图片描述

这里就另一方法也做下说明讲解,这种方法是可以实现多次抓取和放下的,但并不是很完美,有时会出现抓取之后不能放下的情况,而且不能通过一个按键控制抓取和放下。具体的实现和上面第一种方法基本一样,就是不需要在controller下放一个一模一样的被抓取物,只需要将脚本内容换成如下所示即可:

  Pvr_ControllerModuleInit conmodinit;
    Rigidbody ri;
    FixedJoint joint;
    void Start()
    {
        conmodinit = this.transform.parent.GetComponent<Pvr_ControllerModuleInit>();
    }

    private void Update()
    {
        //按下Y和B键放下物体
        if (Controller.UPvr_GetKeyDown(0, Pvr_KeyCode.Y) || Controller.UPvr_GetKeyDown(1, Pvr_KeyCode.B))
        {
            joint = GetComponent<FixedJoint>();
            Destroy(joint);
        }
    }

    void OnTriggerStay(Collider collider)
    {

        //扣下扳机拾取物体
        if (Controller.UPvr_GetKeyDown(0, Pvr_KeyCode.TRIGGER) ||
            Controller.UPvr_GetKeyDown(1, Pvr_KeyCode.TRIGGER))
        {
            joint = gameObject.AddComponent<FixedJoint>();
            joint.connectedBody = collider.GetComponent<Rigidbody>();
        }

    }

有兴趣的朋友可自行优化
Demo加V(有偿)-----MrChen8-13

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

PicoNeo3开发VR——小白教程 的相关文章

  • 【华为OD统一考试B卷

    华为OD统一考试A卷 B卷 新题库说明 2023年5月份 华为官方已经将的 2022 0223Q 1 2 3 4 统一修改为OD统一考试 A卷 和OD统一考试 B卷 你收到的链接上面会标注A卷还是B卷 请注意 根据反馈 目前大部分收到的都是

随机推荐

  • oracle清除试图的数据,ORACLE大表中删除部分数据最佳方案

    如果业务无法停止的话 主要有这三种思路 思路1 根据ROWID分片 再利用Rowid排序 批量处理 回表删除 在业务无法停止的时候 选择这种方式 的确是最好的 一般可以控制在每一万行以内提交一次 不会对回滚段造成太大压力 我在做大DML时
  • Python+Selenium安装及环境配置

    转自 https www cnblogs com sandysun p 7838113 html 一 Python安装 Window系统下 python的安装很简单 访问python org download 下载最新版本 安装过程与其他w
  • 摄像头网络模组的使用

    摄像头和网络模组尾线如上图所示 利用上面两个模组 打算自己做一个简单的网络摄像头 在上面的模组网站上找到对应模组的接口定义资料 因为我们要自己将对应的网线接口等接好 本模组的接口如下 具体的该模组的接线如上所示 该网络模组尾线总共有 根 根
  • B07_NumPy 高级索引(整数数组索引,布尔索引,花式索引)

    NumPy高级索引 NumPy 比一般的 Python 序列提供更多的索引方式 除了之前看到的用整数和切片的索引外 数组可以由整数数组索引 布尔索引及花式索引 整数数组索引 以下实例获取数组中 0 0 1 1 和 2 0 位置处的元素 实例
  • 文储研习社第17期

    文储研习社是文储区块链技术人员自发组织的学习交流社区 旨在于追踪区块链时下最新热点 解码热点蕴含的未知领域 享受思想交流的碰撞 欢迎志同道合的小伙伴加入我们 共同学习与成长 第17期 为了提高考证通过率 不小心搭了条链 作者 Bingo 你
  • java springboot 8080端口号冲突时 修改当前项目端口号

    背景 springboot 项目启动时报错 Web server failed to start Port 8080 was already in use 报错原因 端口被占用 解决方案 修改项目application properties
  • SpringCloud整合Eureka出现“Error creating bean with name ‘configurationPropertiesBeans‘ defined in......“

    笔者在实现SpringCloud整合Eureka注册中心时出现如下报错 org springframework beans factory BeanCreationException Error creating bean with nam
  • C++ 类:类相关的非成员函数、构造函数

    前提 仍有 Sales data 类的代码 struct Sales data std string isbn const return bookNo 返回 isbn 编号 Sales data combine const Sales da
  • cpplint在VS Code中的安装及使用

    目录 前言 Python环境的配置 在VS Code中安装相应插件 补充 如何将VS Code默认的格式化风格改为Google风格 相关链接 前言 cpplint是一款Google的代码检查工具 确定一种编码风格对于我们有非常大的帮助 也可
  • Qt视频播放器[QMediaPlayer+QVideowidget]

    目录 参考 一 安装K Lite 解码器 二 Qt代码结构 VideoPlayer pro main cpp videoplayer h 播放器 videoplayer cpp 播放器 videoplayer ui 播放器 playersl
  • Python爬虫抓取经过JS加密的API数据的实现步骤

    随着互联网的快速发展 越来越多的网站和应用程序提供了API接口 方便开发者获取数据 然而 为了保护数据的安全性和防止漏洞 一些API接口采用了JS加密技术这种加密技术使得数据在传输过程中更加安全 但也给爬虫开发带来了一定的难度 在面对经过J
  • 常用集成运放电路合集(简洁易懂,附Multisim仿真文件)

    电赛初试培训整理的常用的集成运放电路集合 尽可能地追求全面 本文省略繁琐的推导过程 直接给出电路及其功能 以便读者使用时进行查阅 由于每个电路的介绍可能相对简略 实际使用或学习时可在站内查阅资料 网上资料丰富 在此不再赘述 本文目的是提供一
  • 树莓派体验3 - SSH登录树莓派

    如果没有HDMI转接线 显示器 USB转TTL串口线 那么可以通过网络SSH远程登录的方式访问树莓派 无显示器使用SSH访问 开启SSH服务 首先 通过镜像版本号确认一下SSH是否默认开启 树莓派官网的release note中说明 201
  • SQL的使用规范

    高程序运行效率 优化应用程序 在SP编写过程中应该注意以下几点 a SQL的使用规范 i 尽量避免大事务操作 慎用holdlock子句 提高系统并发能力 ii 尽量避免反复访问同一张或几张表 尤其是数据量较大的表 可以考虑先根据条件提取数据
  • 小程序主体为个人的教育类小程序备案——教育APP备案问题

    今天微信小程序后台发了通知 看了一下 我的小程序选择了 教育分类 思考了一下 虽然我选择了 教育 但是我的小程序是属于字典工具类的 与教育备案没什么关联 解决的办法 删除教育分类 老老实实备案 我选择了 因为我想了解下个人主体的小程序能不能
  • VS2019下的GAMES101作业环境配置

    序 很久很久以前 好像看过这个 GAMES101 现代计算机图形学入门 闫令琪 哔哩哔哩 bilibili 里面好像还有一个实验 当时只是看了看视频里的热闹 并没有写实验 现在想想 还是写一写的好 万一以后用上了呢 虽然是个24K纯小白 估
  • 解决Logback日志不会每天生成新文件的问题

    logback配置文件 问题现象 上图是最初的logback的配置 线上发现经常不会按日生成日志文件 而是一个日志越来越大 只有在项目重启之后才会生成当天的日志文件 原因分析 该配置使用了基于时间的滚动切割策略 TimeBasedRolli
  • 离散数学模拟微信红包算法升级版

    可以自定义红包总金额 总包数 每包最小金额
  • 关于springboot的序列化和反序列化问题

    在springboot中有时候需要将Datatime的格式进行格式化 有时候Long型的数据超过16位传值到前端js中会丢失精度 解决这个问题需要将Long型先转为String类型在传值到前端 所以可以编写实现类继承ObjectMapper
  • PicoNeo3开发VR——小白教程

    不断更新中 欢迎大佬们来指导 纠错 导入PicoVRSDK 1 新创一个Unity工程 Unity版本最好选择2019 4以上版本 以及需配置好安卓环境 然后导入官方picoVRSDK 2 渲染设置 Graphics APIs暂不支持Vul