使用 Raycast 代替 Gaze Pointer [英] Using Raycast instead of Gaze Pointer

查看:27
本文介绍了使用 Raycast 代替 Gaze Pointer的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我是 VR 开发的新手,我正在使用 oculus 集成,所以我用线渲染器制作了一条光线线,但现在我需要让这条线与注视指针(对象、UI、滚动条等)交互..),, 我正在 Oculus Go 上测试我的项目

I'm new to VR development,I'm using oculus integration so I made a ray line with the line renderer , but now I need to make this line interactive with everything like a gaze pointer (objects,UI, Scrolls..),, I'm testing my project on Oculus Go

我尝试了很多教程,但没有人关注这一点.

I have tried a lot of tutorials but no one focuses on this point.

这是我对代码的尝试:

RaycastHit hit;
if(Physics.Raycast(transform.position,transform.forward ,out hit,0.8f))
{
    if(hit.collider != null)
    {
        if (OVRInput.Get(OVRInput.Button.PrimaryIndexTrigger)) 
        { 
            go = hit.transform.gameObject;
            go.SetActive(false);
        }
    }
}

这适用于对象,但不适用于像 GazePointer 那样工作的 UI.

This works for objects but not with UI like how a GazePointer works.

推荐答案

我也对这样的事情很感兴趣,所以我对提到的SteamVR 激光笔菜单.

I was also interessted in something like this so I got a bit deeper into the mentioned SteamVR Laser Pointer Menus.

总体思路很简单:

  • 在所有可交互的 UI 上使用 BoxCollider
  • 然后你可以简单地坚持Physics.Raycast
  • 如果指向 UI 元素,则另外触发相应的 EventSystems.ExecuteEvents

实际上,这看起来有点复杂.我基于 steamvr_laserpointer.cs 创建了自己的脚本,但不得不对其进行大量更改以涵盖大部分交互.

In reality this looks a bit more complex. I created my own script based on the steamvr_laserpointer.cs but had to change it a lot in order to cover most of the interactions.

它还没有完全工作 - 虽然它基本上已经非常适合 3D 对象和简单的按钮交互,但我在让 拖动 正常工作时遇到了一些麻烦.但我希望这对每个尝试类似事情的人来说都是一个很好的起点:

It is not fully working yet - while it basicaly already works perfect for 3D objects and simple button interactions I have a little trouble getting the dragging to work properly. But I hope this is a good startpoint for everyone trying something similar:

(最初基于steamvr_laserpointer.cs)

这是附加到您的控制器游戏对象.它将创建一个子对象LaserHolder",它也有一个子LaserPointer".LaserPointer"基本上只是一个经过缩放的立方体,看起来像一束激光.-

This is attached to your controller GameObject. It will create a child object "LaserHolder" which also has a child "LaserPointer". The "LaserPointer" is basically simply a cube which is scaled so it looks like a laser beam.-

#define USE_CUSTOM_EDITOR

using System.Linq;
using UnityEngine;
using UnityEngine.EventSystems;
#if UNITY_EDITOR && USE_CUSTOM_EDITOR
using UnityEditor;
#endif

public class LaserPointerController : MonoBehaviour
{
    #region Inspector

    [Header("Pointer Settings")]
    [Tooltip("Should the LaserPointer origin have an offset to the controller position (in local coordinates)?")]
    [SerializeField] private bool useOffset;
    [SerializeField] private Vector3 offset = Vector3.zero;

    [Tooltip("The maximal distance of the laser pointer (in Unity Units)")]
    [SerializeField] private float maxPointerDistance = 5f;

    [Tooltip("Should the laser have a Collider attached?
Useful for e.g. directly using OnTriggerEnter

Note: This will use Physics.RaycastAll which is less efficient.")]
    [SerializeField] private bool useCollider;
    [Tooltip("Should the laser Collider be a trigger?
Has to be disabled to e.g. use OnCollisionEnter")]
    [SerializeField] private bool colliderIsTrigger;
    [Tooltip("Should the laser additionally have a RigidBody?
Required for some types of collisions.")]
    [SerializeField] private bool useRigidBody;

    [Header("Style Settings")]
    [Tooltip("Should the laser change its Color when pressed")]
    [SerializeField] private bool changeColor;
    [Tooltip("Should the laser change its thickness when pressed")]
    [SerializeField] private bool changeThickness;
    [Tooltip("Color of the Laser in idle")]
    [SerializeField] private Color idleColor = Color.red;
    [Tooltip("Color of the beam when pressed")]
    [SerializeField] private Color pressedColor = Color.green;
    [Tooltip("Thickness of the laser beam")]
    [SerializeField] private float idleThickness = 0.005f;
    [Tooltip("Thickness of the laser beam when pressed")]
    [SerializeField] private float pressedThickness = 0.007f;

    [Header("Debug")]
    [SerializeField] private bool isPressed;
    [SerializeField] private Vector3 pointerPosition3D;
    [SerializeField] private Vector2 pointerPosition2D;

#if UNITY_EDITOR && USE_CUSTOM_EDITOR
    [CustomEditor(typeof(LaserPointerController))]
    private class LaserPointerControllerEditor : Editor
    {
        private SerializedProperty useOffset;
        private SerializedProperty offset;

        private SerializedProperty maxPointerDistance;

        private SerializedProperty useCollider;
        private SerializedProperty colliderIsTrigger;
        private SerializedProperty useRigidBody;

        private SerializedProperty changeColor;
        private SerializedProperty changeThickness;
        private SerializedProperty idleColor;
        private SerializedProperty pressedColor;
        private SerializedProperty idleThickness;
        private SerializedProperty pressedThickness;

        private SerializedProperty _isPressed;
        private SerializedProperty pointerPosition3D;
        private SerializedProperty pointerPosition2D;

        private void OnEnable()
        {
            useOffset = serializedObject.FindProperty("useOffset");
            offset = serializedObject.FindProperty("offset");

            maxPointerDistance = serializedObject.FindProperty("maxPointerDistance");
            useCollider = serializedObject.FindProperty("useCollider");
            colliderIsTrigger = serializedObject.FindProperty("colliderIsTrigger");
            useRigidBody = serializedObject.FindProperty("useRigidBody");

            changeColor = serializedObject.FindProperty("changeColor");
            changeThickness = serializedObject.FindProperty("changeThickness");
            idleColor = serializedObject.FindProperty("idleColor");
            pressedColor = serializedObject.FindProperty("pressedColor");
            idleThickness = serializedObject.FindProperty("idleThickness");
            pressedThickness = serializedObject.FindProperty("pressedThickness");

            _isPressed = serializedObject.FindProperty("isPressed");
            pointerPosition3D = serializedObject.FindProperty("pointerPosition3D");
            pointerPosition2D = serializedObject.FindProperty("pointerPosition2D");
        }

        public override void OnInspectorGUI()
        {
            serializedObject.Update();

            DrawScriptField();

            EditorGUILayout.PropertyField(useOffset);
            if (useOffset.boolValue)
            {
                EditorGUILayout.PropertyField(offset);
                EditorGUILayout.Space();
            }

            EditorGUILayout.PropertyField(maxPointerDistance);

            EditorGUILayout.PropertyField(useCollider);
            if (useCollider.boolValue)
            {
                EditorGUILayout.PropertyField(colliderIsTrigger);
                EditorGUILayout.PropertyField(useRigidBody);
            }
            else
            {
                colliderIsTrigger.boolValue = true;
                useRigidBody.boolValue = false;
            }

            EditorGUILayout.PropertyField(changeColor);
            EditorGUILayout.PropertyField(changeThickness);

            EditorGUILayout.PropertyField(idleColor);
            if (changeColor.boolValue) EditorGUILayout.PropertyField(pressedColor);

            EditorGUILayout.PropertyField(idleThickness);
            if (changeThickness.boolValue) EditorGUILayout.PropertyField(pressedThickness);

            if (EditorApplication.isPlayingOrWillChangePlaymode)
            {
                EditorGUILayout.PropertyField(_isPressed);

                EditorGUI.BeginDisabledGroup(true);
                {
                    EditorGUILayout.PropertyField(pointerPosition3D);
                    EditorGUILayout.PropertyField(pointerPosition2D);
                }
                EditorGUI.EndDisabledGroup();
            }

            serializedObject.ApplyModifiedProperties();
        }

        private void DrawScriptField()
        {
            EditorGUI.BeginDisabledGroup(true);
            {
                EditorGUILayout.ObjectField("Script", MonoScript.FromMonoBehaviour((LaserPointerController)target), typeof(LaserPointerController), false);
            }
            EditorGUI.EndDisabledGroup();

            EditorGUILayout.Space();
        }
    }
#endif

    #endregion Inspector


    #region Public Members

    public Vector3 PointerPosition3D
    {
        get { return pointerPosition3D; }
    }

    public Vector2 PointerPosition2D
    {
        get { return pointerPosition2D; }
    }

    public bool IsPressed
    {
        get { return isPressed; }
    }

    public Transform CurrentlyHoveredTransform
    {
        get { return _currentlyHovered; }
    }

    #endregion Public Members


    #region Private Members

    private Transform _holder;
    private Transform _laser;
    private Transform _currentlyHovered;
    private Material _laserMaterial;

    private PointerEventData _pointerEventData;
    private Vector2 _lastPointerPosition;
    private bool _isDragging;
    private bool _lastPressed;
    private Camera _camera;

    #endregion Private Members


    #region MonoBehaviour Messages
    private void Awake()
    {
        _holder = new GameObject("LaserHolder").transform;
        _holder.parent = transform;
        _holder.localScale = Vector3.one;
        _holder.localPosition = useOffset ? offset : Vector3.zero;
        _holder.localRotation = Quaternion.identity;

        _laser = GameObject.CreatePrimitive(PrimitiveType.Cube).transform;
        var collider = _laser.GetComponent<Collider>();

        if (!useCollider)
        {
            Destroy(_laser.GetComponent<Collider>());
        }
        else
        {
            collider.isTrigger = colliderIsTrigger;
        }

        if (useRigidBody)
        {
            var rigidBody = _laser.gameObject.AddComponent<Rigidbody>();
            rigidBody.useGravity = false;
            rigidBody.isKinematic = true;
        }

        _laser.name = "LaserPointer";
        _laser.transform.parent = _holder;
        _laser.transform.localScale = new Vector3(idleThickness, idleThickness, maxPointerDistance);
        _laser.transform.localPosition = new Vector3(0f, 0f, maxPointerDistance / 2f);
        _laser.transform.localRotation = Quaternion.identity;

        _laserMaterial = new Material(Shader.Find("Unlit/Color")) { color = idleColor };
        _laser.GetComponent<MeshRenderer>().material = _laserMaterial;

        _camera = Camera.main;

        _pointerEventData = new PointerEventData(EventSystem.current)
        {
            // most Unity comonents e.g. the ScrollRect expect this to be Left
            // otherwise they ignore pointer input
            button = PointerEventData.InputButton.Left
        };
    }

    private void OnEnable()
    {
        _holder.gameObject.SetActive(true);
    }

    private void OnDisable()
    {
        _holder.gameObject.SetActive(false);
    }

    // Update is called once per frame
    private void Update()
    {
        //TODO uncomment this in order to get the isPressed from the according controller
        // curently I only set it via the Inspector
        //isPressed = OVRInput.Get(OVRInput.Button.PrimaryIndexTrigger);

        // Initially the dist is the maximum pointer distance
        var dist = maxPointerDistance;

        // create the ray from the laserpointer origin
        var ray = new Ray(_holder.position, _holder.forward);

        var hit = new RaycastHit();

        // If using a Collider we have to ignore the Collider of the LaserPointer itself
        if (useCollider)
        {
            var hits = Physics.RaycastAll(ray, maxPointerDistance);
            hit = hits.FirstOrDefault(h => h.transform != _laser.transform);
        }
        else
        {
            Physics.Raycast(ray, out hit, maxPointerDistance);
        }

        // Are we hitting something?
        if (hit.transform)
        {
            // ignore if still pressing the trigger
            if (!isPressed)
            {
                // Are we hitting something different to what we hit before?
                if (_currentlyHovered && _currentlyHovered != hit.transform)
                {
                    ExecuteEvents.ExecuteHierarchy(_currentlyHovered.gameObject, _pointerEventData, ExecuteEvents.pointerExitHandler);

                    _currentlyHovered = null;
                }

                // Are we hitting something new?
                if (_currentlyHovered != hit.transform)
                {
                    ExecuteEvents.ExecuteHierarchy(hit.transform.gameObject, _pointerEventData, ExecuteEvents.pointerEnterHandler);

                    _currentlyHovered = hit.transform;
                }
            }

            if (_currentlyHovered == hit.transform)
            {
                // If we are hitting something correct the dist value
                if (hit.distance < maxPointerDistance)
                {
                    dist = hit.distance;
                }

                if (isPressed)
                {
                    HandlePointerDown(hit.transform);
                }
                else
                {
                    HandlePointerUp(hit.transform);
                }
            }
        }
        else
        {
            if (!isPressed && _currentlyHovered)
            {
                HandlePointerUp(_currentlyHovered, true);

                ExecuteEvents.ExecuteHierarchy(_currentlyHovered.gameObject, _pointerEventData, ExecuteEvents.pointerExitHandler);

                _currentlyHovered = null;
            }
        }

        // Apply changes in the thickness and set the laser dimensions using the dist
        var thickness = !changeThickness || !isPressed ? idleThickness : pressedThickness;
        _laser.transform.localScale = new Vector3(thickness, thickness, dist);
        _laser.transform.localPosition = new Vector3(0f, 0f, dist / 2f);

        // Apply color changes
        if (changeColor) _laserMaterial.color = isPressed ? pressedColor : idleColor;

        // Update the pointerEventData
        pointerPosition3D = _laser.transform.position + _laser.transform.forward * dist / 2f;
        pointerPosition2D = _camera.WorldToScreenPoint(pointerPosition3D);
        _pointerEventData.position = pointerPosition2D;
        _pointerEventData.delta = _pointerEventData.position - _lastPointerPosition;
        _lastPointerPosition = _pointerEventData.position;

        _lastPressed = isPressed;
    }

    #endregion MonoBehaviour Messages


    #region Private Methods

    private void HandlePointerDown(Component target)
    {
        if (!target) return;

        // only call once if IsPressed changed its value this frame
        if (_lastPressed != isPressed)
        {
            // Try pointerClickHandler, only use submitHandler as fallback
            // e.g. UI.Button implements both ... would lead to duplicate clicks
            var done = ExecuteEvents.ExecuteHierarchy(target.gameObject, _pointerEventData, ExecuteEvents.pointerClickHandler);
            if (!done) ExecuteEvents.ExecuteHierarchy(target.gameObject, _pointerEventData, ExecuteEvents.submitHandler);

            ExecuteEvents.ExecuteHierarchy(target.gameObject, _pointerEventData, ExecuteEvents.pointerDownHandler);
            ExecuteEvents.ExecuteHierarchy(target.gameObject, _pointerEventData, ExecuteEvents.selectHandler);
            ExecuteEvents.ExecuteHierarchy(target.gameObject, _pointerEventData, ExecuteEvents.initializePotentialDrag);
        }
        else
        {
            if (!_isDragging)
            {
                // in the first frame use beginDragHandler
                ExecuteEvents.ExecuteHierarchy(target.gameObject, _pointerEventData, ExecuteEvents.beginDragHandler);
                _isDragging = true;
                _pointerEventData.dragging = true;
            }
            else
            {
                // later use dragHandler
                ExecuteEvents.ExecuteHierarchy(target.gameObject, _pointerEventData, ExecuteEvents.dragHandler);
            }
        }
    }

    private void HandlePointerUp(Component target, bool forceInvoke = false)
    {
        if (!target) return;

        // only call once if IsPressed changed its value this frame
        // or forceInvoke is set
        if (_lastPressed != isPressed || forceInvoke)
        {
            ExecuteEvents.ExecuteHierarchy(target.gameObject, _pointerEventData, ExecuteEvents.pointerUpHandler);
            ExecuteEvents.ExecuteHierarchy(target.gameObject, _pointerEventData, ExecuteEvents.deselectHandler);
            ExecuteEvents.ExecuteHierarchy(target.gameObject, _pointerEventData, ExecuteEvents.endDragHandler);
            ExecuteEvents.ExecuteHierarchy(target.gameObject, _pointerEventData, ExecuteEvents.dropHandler);

            _isDragging = false;
            _pointerEventData.dragging = false;
        }
    }

    #endregion Private Methods
}

VRUIItem.cs

(基于 VRUIItem.cs 但添加了 Update 如此动态的 UI,例如 ScrollBarHandles> 在 ScrollRect 中更新它的碰撞器)

VRUIItem.cs

(Based on VRUIItem.cs but added the Update so dynamic UI like e.g. the Handles of the ScrollBar in the ScrollRect gets its Collider updated)

这基本上必须放置在每个可以与激光交互的 UI 元素上

This has to be placed on basically every UI element that should be interactable for the laser

using UnityEngine;

[RequireComponent(typeof(RectTransform))]
public class VRUIItem : MonoBehaviour
{
    private BoxCollider _boxCollider;
    private RectTransform _rectTransform;

    private void OnEnable()
    {
        ValidateCollider();
    }

    private void Update()
    {
        ValidateCollider();
    }

    private void OnValidate()
    {
        ValidateCollider();
    }

    private void ValidateCollider()
    {
        if (!_rectTransform) _rectTransform = GetComponent<RectTransform>();

        if (!_boxCollider) _boxCollider = GetComponent<BoxCollider>();
        if (!_boxCollider) _boxCollider = gameObject.AddComponent<BoxCollider>();

        _boxCollider.size = _rectTransform.rect.size;
    }
}

Interactable3D.cs

如何使用 IPointerXYHandler 接口创建交互式 3D 游戏对象的示例

Interactable3D.cs

An example of how you could use the IPointerXYHandler interfaces in order to create interactive 3D GameObjects

using System;
using UnityEngine;
using UnityEngine.EventSystems;

public class Interactable3D : MonoBehaviour, IPointerEnterHandler, IPointerExitHandler, IPointerClickHandler
{
    public Color normalColor;
    public Color hoverColor;

    private Renderer renderer;

    private void Awake()
    {
        renderer = GetComponent<Renderer>();
        renderer.material.color = normalColor;
    }

    public void OnPointerEnter(PointerEventData eventData)
    {
        Debug.Log(" ENTEREd!");
        transform.localScale *= 1.2f;
        renderer.material.color = hoverColor;
    }

    public void OnPointerExit(PointerEventData eventData)
    {
        Debug.Log("EXIT!");
        transform.localScale /= 1.2f;
        renderer.material.color = normalColor;
    }

    public void OnPointerClick(PointerEventData eventData)
    {
        Debug.Log("CLICKED!");
    }
}

<小时>

演示

如前所述,滚动还不能完全正确,但是您可以看到它基本上是可能的 - 也许我通过 _pointerEventData.position 传入的指针坐标有问题 - 但是所有其他交互都已经起作用了:


Demo

As said the scrolling doesn't work quite correct yet, however you can see it is basically possible - maybe just something is wrong about the pointer coordinates I pass in via the _pointerEventData.position - but all other interactions already work:

这篇关于使用 Raycast 代替 Gaze Pointer的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆