• VR电脑模拟实现


     

    一、概述

    1.实现的基本操作是:

      1)用手柄抓住黄色的方块代表手抓住鼠标。

      2)通过移动手柄模拟鼠标移动,电脑屏幕上的光标跟着移动。

      3)当光标移动到一个Button上时,Button高亮,离开时Button取消高亮,点击Button触发点击事件。

      4)当点击Button之后,打开一个画图程序,可以用光标在颜色选择区选择一种颜色,然后在画图区根据光标的移动轨迹,画出选择颜色的光标移动路径的曲线;

    2.脚本

      1)ComputerController挂在代表电脑的Canvas上,本例挂在Computer上;

      2)MouseController挂在一个代表鼠标的物体上,本例挂在Mouse上;

      3)ComputerCursorController 挂在表示光标的一个Image上,本例挂在Cursor上;

      4)  ComputerClickable挂在所有可点击的应用程序图标上,在本例中只有一个应用程序,挂在PaintProgramIcon上;

      5)PaintProgram一个画图程序,在本例中挂在PaintProgram这个Panel上

    3.场景的Hierachy面板

     二、实现

    1.手柄的操作的鼠标设置:

    由于VRTK这个插件集成了很好的物理交互功能,所以就手柄与场景物体交互方面选择用VRTK这个插件。

    下面是代表鼠标的黄色的Cube的设置

    首先需要挂上如上面图中的所有组件:

      1)Rigidbody需要设置约束:禁止Y方向的移动,以及任意方向的旋转;

      2)将VRTK_TrackObjectGrabAttach这个脚本拖到VRTK_InteractableObject的Grab Attach Mechanic上;

      3)将Grab Override Button选择一个手柄上不存在键,HTC VIVE中这个键是Button One,我们将在代码中设置抓取;

      4)将VRTK_InteractableObject这个组件上的IsGrabable和IsUsable打上勾;

      5)将VRTK_Interact Controller Apperance的Hide Controller On Grab打勾;

    Mouse Controller这个脚本就是控制鼠标移动的;

    using System;
    using UnityEngine;
    using VRTK;
    
    [RequireComponent(typeof(VRTK_InteractableObject))]
    public class MouseController : MonoBehaviour
    {
        public Transform mousePad;//鼠标垫
    
        public Action ClickDown;//当手柄上的use键按下的时候,引发的事件(Trigger键);
        public Action ClickUp;//当手柄上的use键抬起的时候,引发的事件;
    
        private Rect mappingRect;//这个鼠标的移动范围(用来映射电脑屏幕上的光标的位置)
        private Vector2 mouseReletiveToMousePadPostion;//鼠标相对于鼠标垫的位置
    
        Rigidbody rig;
        BoxCollider boxCollider;
        VRTK_InteractableObject mouse;
        VRTK_InteractGrab controller;//当前正在使用鼠标的手柄
    
        bool isUsedToGrab;//鼠标是不是被手柄抓住
        float releaseDistance;//手柄离开鼠标的距离(不再抓住鼠标了)
    
        void Start()
        {
            //初始化一些数据 
            mappingRect = GetMatchRect(mousePad);
            CalculateMouseReletivePos();
            mouse = GetComponent<VRTK_InteractableObject>();
            rig = GetComponent<Rigidbody>();
            boxCollider = GetComponent<BoxCollider>();
            releaseDistance = boxCollider.bounds.size.y * 2f;
    
            //监听手柄触碰到鼠标的事件
            mouse.InteractableObjectTouched += Mouse_InteractableObjectTouched;
            //监听手柄不再触碰鼠标事件
            mouse.InteractableObjectUntouched += Mouse_InteractableObjectUntouched;
        }
    
        /// <summary>
        ///  //当手柄触碰到鼠标时,执行的事件
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void Mouse_InteractableObjectTouched(object sender, InteractableObjectEventArgs e)
        {
            //监听手柄使用键按下时的事件
            mouse.InteractableObjectUsed += Mouse_InteractableObjectUsed;
        }
    
        /// <summary>
        /// 当手柄use键按下时,执行的事件
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void Mouse_InteractableObjectUsed(object sender, InteractableObjectEventArgs e)
        {
            isUsedToGrab = true;//设置抓取住了鼠标
            controller = e.interactingObject.GetComponent<VRTK_InteractGrab>();//设置当前抓取鼠标的手柄
            controller.AttemptGrab();//强制手柄抓住鼠标
            mouse.InteractableObjectUsed -= Mouse_InteractableObjectUsed;//不再监听use键按下事件
            //监听当前操作的手柄use键按下时的事件
            controller.GetComponent<VRTK_InteractUse>().UseButtonPressed += MouseController_UseButtonPressed;
            //监听当前操作的手柄use键抬起时的事件
            controller.GetComponent<VRTK_InteractUse>().UseButtonReleased += MouseController_UseButtonReleased;
        }
    
        private void MouseController_UseButtonReleased(object sender, ControllerInteractionEventArgs e)
        {
            //引发事件
            if (ClickUp != null)
            {
                ClickUp();
            }
        }
    
        private void MouseController_UseButtonPressed(object sender, ControllerInteractionEventArgs e)
        {
            if (ClickDown != null)
            {
                ClickDown();
            }
        }
    
        private void Mouse_InteractableObjectUntouched(object sender, InteractableObjectEventArgs e)
        {
            rig.velocity = Vector3.zero;//一旦手柄不再和鼠标有碰撞,这时要让鼠标的速度为0
            if (isUsedToGrab)//如果之前是抓住了鼠标的
            {
                isUsedToGrab = false;
                controller.GetComponent<VRTK_InteractUse>().UseButtonPressed -= MouseController_UseButtonPressed;
                controller.GetComponent<VRTK_InteractUse>().UseButtonReleased -= MouseController_UseButtonReleased;
                controller.ForceRelease();//强制松开
            }
            else
            {
                mouse.InteractableObjectUsed -= Mouse_InteractableObjectUsed;
            }
            controller = null;
        }
    
        void Update()
        {
            if (isUsedToGrab)//只有当鼠标被抓住时才执行
            {
                //根据鼠标移动的速度,手柄相应程度的振动
                VRTK_ControllerReference controllerReference = VRTK_ControllerReference.GetControllerReference(controller.gameObject);
                float force = VRTK_SDK_Bridge.GetControllerVelocity(controllerReference).sqrMagnitude;
                VRTK_SDK_Bridge.HapticPulse(controllerReference, force / 3);
    
                CalculateMouseReletivePos();
                //判定当前手柄是不是离开了鼠标
                if ((transform.position - controller.transform.position).sqrMagnitude > releaseDistance * releaseDistance)
                {
                    controller.ForceRelease();//强制松开
                }
            }
        }
    
        public Vector2 MouseReletiveToTablePos
        {
            get
            {
                return mouseReletiveToMousePadPostion;
            }
        }
    
        /// <summary>
        /// 计算鼠标相对于鼠标垫的位置,x和y都是0-1 ;
        /// </summary>
        void CalculateMouseReletivePos()
        {
            float x = Mathf.InverseLerp(mappingRect.xMin, mappingRect.xMax, transform.position.x);
            float y = Mathf.InverseLerp(mappingRect.yMin, mappingRect.yMax, transform.position.z);
            mouseReletiveToMousePadPostion.Set(x, y);
        }
    
        /// <summary>
        /// 设置鼠标的移动范围
        /// </summary>
        /// <param name="content"></param>
        /// <returns></returns>
        public Rect GetMatchRect(Transform content)
        {
            Vector3 contentSize = content.GetComponent<MeshRenderer>().bounds.size;
            Vector3 selfSize = GetComponent<MeshRenderer>().bounds.size;
            //让Rect的position是在鼠标垫的左下角
            Vector2 pos = new Vector2(content.localPosition.x - contentSize.x * 0.5f + selfSize.x * 0.5f, content.localPosition.z - contentSize.z * 0.5f + selfSize.z * 0.5f);
            //设置Rect的长度和宽度(应该减去鼠标自身的长宽)
            Vector2 size = new Vector2(contentSize.x - selfSize.x, contentSize.z - selfSize.z);
            Rect rect = new Rect(pos, size);
            return rect;
        }
    
    }

    2.电脑屏幕光标的设置:

    需要注意的是:锚点在左下角,pivot在自身矩形的左上角(鼠标的尖端的位置)

    ComputerCursorController是实现鼠标和光标位置映射的类

    using System;
    using UnityEngine;
    
    public class ComputerCursorController : MonoBehaviour
    {
        public MouseController mouseController;
    
        public Action OnMoved;
        public Action ClickedDown;
        public Action ClickedUp;
    
        private Rect rect;//表示电脑屏幕范围的Rect
        private RectTransform rectTransform;//这个光标的RectTransform
    
        void Start()
        {
            //代表电脑屏幕范围的Rect的位置计算
            rect = transform.parent.GetComponent<RectTransform>().rect;
            rect.position += new Vector2(rect.width / 2f, rect.height / 2f);
            rectTransform = transform as RectTransform;
        }
    
        void OnEnable()
        {
            mouseController.ClickUp += MouseController_ClickUp);
            mouseController.ClickDown += MouseController_ClickDown;
        }
    
        void OnDisable()
        {
            mouseController.ClickUp -= MouseController_ClickUp;
            mouseController.ClickDown -= MouseController_ClickDown;
        }
    
        private void MouseController_ClickDown()
        {
            if (ClickedDown != null)
            {
                ClickedDown();
            }
        }
    
        private void MouseController_ClickUp()
        {
            if (ClickedUp != null)
            {
                ClickedUp();
            }
        }
    
        void Update()
        {
            Vector2 parameter = mouseController.MouseReletiveToTablePos;
            Vector2 vector = new Vector2(Mathf.Lerp(rect.xMin, rect.xMax, parameter.x), Mathf.Lerp(rect.yMin, rect.yMax, parameter.y));
    
            //当鼠标映射的位置和光标的位置不等的时候,说明这个时候鼠标是在移动的
            if (rectTransform.anchoredPosition != vector && OnMoved != null)
            {
                OnMoved();
            }
            rectTransform.anchoredPosition = vector;
        }
    }

    3.响应光标的点击事件

    在电脑中点击桌面上的一个图标的时候,是可以进入应用程序的,在VR中直接单击进入程序就好,ComputerController这个类用来控制光标引发的一些事件

    using System;
    using UnityEngine;
    
    public class ComputerController : MonoBehaviour
    {
        public ComputerClickable[] clickables;//桌面上所有可点击的应用程序图标
        public ComputerCursorController cursorController;//光标
    
        private ComputerClickable currentClickable;//当前光标所在图标
        private ComputerClickable cacheClickedClickable;//缓存的图标
    
        [SerializeField]
        private Canvas canvas;//代表Computer的画布
    
        public Action Clicked;
        public Action UnClicked;
    
        void OnEnable()
        {
            cursorController.OnMoved += CheckClickablesIsHoverByCursor;
            cursorController.ClickedDown += ClickDown;
            cursorController.ClickedUp += ClickUp;
        }
    
    
        void OnDisable()
        {
            cursorController.OnMoved -= CheckClickablesIsHoverByCursor;
            cursorController.ClickedDown -= ClickDown;
            cursorController.ClickedUp -= ClickUp;
        }
    
        /// <summary>
        /// 检查光标是不是移动到应用程序图标上
        /// </summary>
        private void CheckClickablesIsHoverByCursor()
        {
            for (int i = 0; i < clickables.Length; i++)
            {
                if (clickables[i].CheckHoverByCursor(CursorPosition))
                {
                    currentClickable = clickables[i];
                    return;
                }
            }
            currentClickable = null;
        }
    
        private void ClickDown()
        {
            if (currentClickable != null)
            {
                currentClickable.Click();
                cacheClickedClickable = currentClickable;
                currentClickable = null;
            }
            if (Clicked != null)
            {
                Clicked();
            }
        }
    
        private void ClickUp()
        {
            if (cacheClickedClickable != null)
            {
                cacheClickedClickable.UnClick();
                cacheClickedClickable = null;
            }
            if (UnClicked != null)
            {
                UnClicked();
            }
        }
    
        public Canvas Canvas
        {
            get
            {
                return canvas;
            }
        }
    
        /// <summary>
        /// 光标的位置
        /// </summary>
        public Vector2 CursorPosition
        {
            get
            {
                return RectTransformUtility.WorldToScreenPoint(canvas.worldCamera, cursorController.transform.position); 
            }
        }
    }

    4.可点击的应用程序图标

    using UnityEngine;
    using UnityEngine.UI;
    using UnityEngine.EventSystems;
    
    public class ComputerClickable : MonoBehaviour
    {
        RectTransform rectTransform;
        Canvas canvas;
        Button button;
        bool isHighlighter;//当前Button是否高亮
    
        void Start()
        {
            //初始化字段 
            rectTransform = transform as RectTransform;
            canvas = GetComponentInParent<Canvas>();
            button = GetComponent<Button>();
            button.onClick.AddListener(() => Debug.Log("Clicked"));
        }
    
        /// <summary>
        /// 光标是否移动到自身上
        /// </summary>
        /// <param name="cursorPos">光标的位置</param>
        /// <returns>True 当前光标在自身上</returns>
        public bool CheckHoverByCursor(Vector2 cursorPos)
        {
            //检查一个RectTransform是不是包含一个点
            bool isHorver = RectTransformUtility.RectangleContainsScreenPoint(rectTransform, cursorPos, canvas.worldCamera);
            PointerEventData eventData = new PointerEventData(EventSystem.current);
            if (isHorver && !isHighlighter)//如果包含,且当前Button没有高亮
            {
                //引发Button高亮
                ExecuteEvents.Execute(gameObject, eventData, ExecuteEvents.pointerEnterHandler);      
                isHighlighter = true;
            }
            else if (!isHorver && isHighlighter)//如果没有包含,但是Button高亮,说明光标已经离开
            {
                isHighlighter = false;
                ExecuteEvents.Execute(gameObject, eventData, ExecuteEvents.pointerUpHandler);
                ExecuteEvents.Execute(gameObject, eventData, ExecuteEvents.pointerExitHandler);
                ExecuteEvents.Execute(gameObject, eventData, ExecuteEvents.deselectHandler);
            }
            return isHorver;
        }
    
        public void Click()
        {
            ExecuteEvents.Execute(gameObject, new PointerEventData(EventSystem.current), ExecuteEvents.pointerDownHandler);  
            button.onClick.Invoke();
        }
    
        public void UnClick()
        {
            ExecuteEvents.Execute(gameObject, new PointerEventData(EventSystem.current), ExecuteEvents.pointerUpHandler);
           // button.OnPointerUp(new PointerEventData(EventSystem.current));
        }
    }

    至此基本功能已经实现,接下来可以写一个小程序来试试;

    三、简单的画图小程序 

    画图小程序主要包含3个部分

      1)颜色选择区(ColorPick)

      2)颜色选择展示区(SelectColor)

      3)画图区(PaintImage)

    脚本功能实现:

    using UnityEngine.UI;
    using UnityEngine;
    using System;
    using System.Linq;
    public class PaintProgram : MonoBehaviour
    {
        public ComputerController computer;
    
        public int pictureWidth;//画图区的宽度
        public int pictureHeight;//画图区的高度
        public RawImage pictureImage;//画图区
        public RawImage colorPickImage;//颜色选择区
        public Image selectedColorDisplay;//颜色选择展示区
    
        private Texture2D pictureTex;//赋值给画图区的Texture
        private Texture2D colorPickTex;//颜色选择区的Texture
        private Rect pictureRect;//画图区区域
        private Rect colorPickerRect;//颜色选择区区域
     
        private bool canOperate;//当前是否可以操作(画图或者选择颜色)
    
        private bool isInDrawArea;//光标是否在画图区
        private bool isInPickArea;//光标是否在颜色选择区域
        private Color selectedColor = Color.red;//当前从颜色选择区选择的颜色 
    
        //在本例中是给每个像素赋值,用这两个参数,可以给一个区域内的像素赋值
        private Color[] c;//色块的颜色 
        private Vector2 lineSize;//画图区线的大小
    
    
        void Awake()
        {
            pictureTex = new Texture2D(pictureWidth, pictureHeight);
            pictureTex.filterMode = FilterMode.Point;
            pictureTex.wrapMode = TextureWrapMode.Clamp;
            pictureImage.texture = pictureTex;
            ResetPixes(Color.white);
    
            //设置画图区域矩形的位置
            pictureRect = pictureImage.GetComponent<RectTransform>().rect;
            pictureRect.position = computer.Canvas.transform.InverseTransformPoint(pictureImage.transform.position);
            pictureRect.center = pictureRect.position;
            //设置颜色选择区域矩形的位置
            colorPickerRect = colorPickImage.GetComponent<RectTransform>().rect;
            colorPickerRect.position = computer.Canvas.transform.InverseTransformPoint(colorPickImage.transform.position);
            colorPickerRect.center = colorPickerRect.position;
    
            colorPickTex = colorPickImage.texture as Texture2D;
    
        }
    
        void Start()
        {
            lineSize = 5 * new Vector2(pictureRect.width / (float)pictureWidth, pictureRect.height / (float)pictureHeight);
            c = new Color[(int)lineSize.x * (int)lineSize.y];
            c = Enumerable.Repeat<Color>(selectedColor, c.Length).ToArray<Color>();
        }
    
        void OnEnable()
        {
            computer.Clicked += OnMouseClick;
            computer.UnClicked += OnMouseUnClick;
        }
    
        void OnDisable()
        {
            computer.Clicked -= OnMouseClick;
            computer.UnClicked -= OnMouseUnClick;
        }
    
        void Update()
        {
            Vector2 relativeDrawPosition = GetRelativePosition(pictureRect);
            Vector2 relativePickPosition = GetRelativePosition(colorPickerRect);
            isInDrawArea = relativeDrawPosition.x >= 0f && relativeDrawPosition.x < 1f && relativeDrawPosition.y >= 0f && relativeDrawPosition.y < 1f;
            isInPickArea = relativePickPosition.x >= 0f && relativePickPosition.x < 1f && relativePickPosition.y >= 0f && relativePickPosition.y < 1f;
            if (isInDrawArea)//如果在画图区
            {
                if (canOperate)//鼠标点击了
                {
                    SetPixel(relativeDrawPosition.x, relativeDrawPosition.y);
                }
            }
            else if (isInPickArea)//如果在颜色选择区
            {
                if (canOperate)//鼠标点击了
                {
                    PickColor(relativePickPosition.x, relativePickPosition.y);
                }
            }
            else
            {
                if (canOperate)
                {
                    canOperate = false;
                }
            }
        }
    
        /// <summary>
        /// 选取颜色
        /// </summary>
        /// <param name="x"></param>
        /// <param name="y"></param>
        private void PickColor(float x, float y)
        {
            selectedColor = colorPickTex.GetPixel((int)(x * colorPickTex.width), (int)(y * colorPickTex.height));//获取选择的颜色 
            selectedColorDisplay.color = selectedColor;//把选择的颜色展示出来
            c = Enumerable.Repeat<Color>(selectedColor, c.Length).ToArray<Color>();
        }
    
        /// <summary>
        /// 获取光标相对于指定rect的位置
        /// </summary>
        /// <param name="rect">指定的rect</param>
        /// <returns></returns>
        private Vector2 GetRelativePosition(Rect rect)
        {
            Vector2 cursorPos = computer.Canvas.transform.InverseTransformPoint(computer.cursorController.transform.position);
            Vector2 result = cursorPos - rect.position;
            result.x /= rect.width;
            result.y /= rect.height;
            return result;
        }
    
        /// <summary>
        /// 鼠标点击 
        /// </summary>
        private void OnMouseClick()
        {
            Vector2 relativeDrawPosition = GetRelativePosition(pictureRect);
            Vector2 relativePickPosition = GetRelativePosition(colorPickerRect);
            isInDrawArea = relativeDrawPosition.x >= 0f && relativeDrawPosition.x < 1f && relativeDrawPosition.y >= 0f && relativeDrawPosition.y < 1f;
            isInPickArea = relativePickPosition.x >= 0f && relativePickPosition.x < 1f && relativePickPosition.y >= 0f && relativePickPosition.y < 1f;
            if (isInPickArea || isInDrawArea)
            {
                canOperate = true;
            }
        }
    
        /// <summary>
        /// 鼠标点击后抬起
        /// </summary>
        private void OnMouseUnClick()
        {
            canOperate = false;
        }
    
        /// <summary>
        /// 是否打开画图程序 
        /// </summary>
        /// <param name="isActive"></param>
        public void Show(bool isActive)
        {
            gameObject.SetActive(isActive);
        }
    
        /// <summary>
        /// 画图
        /// </summary>
        /// <param name="relX"></param>
        /// <param name="relY"></param>
        private void SetPixel(float relX, float relY)
        {
            Color[] pixels = pictureTex.GetPixels();
            int num = Mathf.Clamp((int)(relX * (float)pictureWidth), 0, pictureWidth - 1);
            int num2 = Mathf.Clamp((int)(relY * (float)pictureHeight), 0, pictureHeight - 1);
            if (pixels[num2 * pictureWidth + num] != selectedColor)
            {
                pixels[num2 * pictureWidth + num] = selectedColor;
                pictureTex.SetPixels(pixels);
                pictureTex.Apply();
            }
          //pictureTex.SetPixels(num, num2, (int)lineSize.x, (int)lineSize.y, c);
        }
    
        /// <summary>
        /// 重置图片
        /// </summary>
        /// <param name="color"></param>
        private void ResetPixes(Color color)
        {
            Color[] array = new Color[pictureWidth * pictureHeight];
            for (int i = 0; i < array.Length; i++)
            {
                array[i] = color;
            }
            pictureTex.SetPixels(array);
            pictureTex.Apply();
        }
    }

     最后为PaintProgramIcon的Button组件添加OnClick事件;

    最后附上工程:链接:https://pan.baidu.com/s/1jJkaVEu 密码:mdwh

  • 相关阅读:
    LeetCode> 989. 数组形式的整数加法
    Java> Java核心卷读书笔记
    npx是干嘛的
    typescript教程
    http Get 发送body数据
    59. 螺旋矩阵 II
    使用静态代理模式实现公用的报表导出功能
    win10更新后 sqlserver客户端连接一段时间后报错 CryptUnprotectData: Win32 错误:-2146893813 (Microsoft.SqlServer.RegSvrEnum)或该项不适用于在指定状态下使用
    Docker Compose-Window
    Docker的容器使用与连接-Window
  • 原文地址:https://www.cnblogs.com/marsir/p/8367325.html
Copyright © 2020-2023  润新知