相信很多VR开发者都用过Nolo开发过6dofVR应用,但是今天我想别处心裁,说一下如何用Nolo开发普通桌面应用。
首先,我们需要一套Nolo CV1 Pro(注意:只有这套设备才支持普通桌面应用开发哦,其他几款暂不支持);
然后,我们需要下载一个官方支持程序Nolo Home,此程序安装的时候会相应的安装一些驱动,安装时选择默认安装路径就可以。
软件安装完成之后去GitHub上下载Unity的SDK,下载完成之后解压,然后新建工程,打开解压后的文件夹,找到Nolo-Nomal文件夹里的UnityPackage中的package包,并将其导入新建的Unity工程。新建一个脚本TwoDSceneTest,然后写下相关的交互逻辑,代码中涉及到世界坐标转画布坐标,放大缩小,UI拖动等,因涉及到公司项目,不能上传整个工程,在此将主要源码奉上。
using NoloClientCSharp;
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
public class TwoDSceneTest : MonoBehaviour
{
public Camera uiCamera;
public NoloVR_SimplePointer rightPointer;
public NoloVR_SimplePointer leftPointer;
public Transform rightHandTrans, leftHandTrans;
[SerializeField]
private Image rightHand;
[SerializeField]
private Image leftHand;
[SerializeField]
private Sprite leftHandReleaseSprite, leftHandGripSprite, rightHandReleaseSprite, rightHandGripSprite, rightHandClickSprite;
public List imagItemList;
public RectTransform uiCanvas;
public Transform rightHandParent;
public Transform uiRoot;
private float touchX = 0;
private float delayTime = 0;
// Start is called before the first frame update
void Start()
{
}
// Update is called once per frame
void Update()
{
try
{
if (NoloVR_System.GetInstance().realTrackDevices == 3)
{
Debug.Log("NoloVR_System.GetInstance().realTrackDevices: " + NoloVR_System.GetInstance().realTrackDevices);
}
else
{
if (rightPointer != null)
{
float x = rightPointer.pointer.transform.position.x;
float y = rightPointer.pointer.transform.position.y;
float z = rightPointer.pointer.transform.position.z;
Vector2 screenPos = WorldToUgui(uiCanvas, new Vector3(x,y,z));
Debug.Log(WorldToUgui(uiCanvas, rightPointer.pointer.transform.position).x+"**********"+ WorldToUgui(uiCanvas, rightPointer.pointer.transform.position).y);
//Vector2 screenPos = WordSpaceToCanvas(uiCanvas, rightPointer.pointerTip.transform.position);
Debug.LogError(screenPos.x + "****************" + uiCanvas.sizeDelta.x / 2);
if (screenPos.x > uiCanvas.sizeDelta.x / 2 || screenPos.x < -uiCanvas.sizeDelta.x / 2)
{
if (screenPos.y > uiCanvas.sizeDelta.y / 2 || screenPos.y < -uiCanvas.sizeDelta.y / 2) return;
rightHandParent.GetComponent().anchoredPosition = new Vector2(rightHandParent.GetComponent().anchoredPosition.x, screenPos.y);
}
else if (screenPos.y > uiCanvas.sizeDelta.y / 2 || screenPos.y < -uiCanvas.sizeDelta.y / 2)
{
if (screenPos.x > uiCanvas.sizeDelta.x / 2 || screenPos.x < -uiCanvas.sizeDelta.x / 2) return;
rightHandParent.GetComponent().anchoredPosition = new Vector2(screenPos.x, rightHandParent.GetComponent().anchoredPosition.y);
}
else if (screenPos.x < uiCanvas.sizeDelta.x / 2 && screenPos.x > -uiCanvas.sizeDelta.x / 2 && screenPos.y < uiCanvas.sizeDelta.y / 2 && screenPos.y > -uiCanvas.sizeDelta.y / 2)
{
rightHandParent.GetComponent().anchoredPosition = screenPos;
//Vector2 a=Vector2.zero;
//rightHandParent.GetComponent().anchoredPosition = Vector2.SmoothDamp(rightHandParent.GetComponent().anchoredPosition, screenPos, ref a, 5*Time.deltaTime);
}
}
if (NoloVR_Controller.GetDevice(NoloDeviceType.RightController).GetNoloButtonDown(NoloButtonID.Trigger))
{
for (int i = 0; i < imagItemList.Count; i++)
{
if (imagItemList[i].IsStaying() == true)
{
//imagItemList[i].transform.SetSiblingIndex(imagItemList.Count-1);
imagItemList[i].GetComponent().isOn = true;
}
}
}
if (NoloVR_Controller.GetDevice(NoloDeviceType.RightController).GetNoloButtonPressed(NoloButtonID.Trigger))
{
Debug.Log("右手柄扳机键长按");
rightHand.sprite = rightHandGripSprite;
for (int i = 0; i < imagItemList.Count; i++)
{
if (imagItemList[i].IsStaying() == true && imagItemList[i].GetComponent().isOn/* == true && imagItemList[i].transform.GetSiblingIndex()==imagItemList.Count-1*/)
{
//imagItemList[i].transform.localPosition = Vector3.Lerp(imagItemList[i].transform.localPosition, rightHandTrans.localPosition, 0.9f);
imagItemList[i].transform.SetParent(rightHandParent);
imagItemList[i].transform.SetAsFirstSibling();
}
}
}
if (NoloVR_Controller.GetDevice(NoloDeviceType.RightController).GetNoloButtonUp(NoloButtonID.Trigger))
{
rightHand.sprite = rightHandReleaseSprite;
for (int i = 0; i < imagItemList.Count; i++)
{
imagItemList[i].isStaying = false;
if (imagItemList[i].GetComponent().isOn == true)
{
imagItemList[i].GetComponent().isOn = false;
//imagItemList[i].isStaying = false;
imagItemList[i].transform.SetParent(uiRoot);
imagItemList[i].transform.SetAsLastSibling();
}
}
}
if (NoloVR_Controller.GetDevice(NoloDeviceType.RightController).GetNoloTouchDown(NoloTouchID.TouchPad) && !NoloVR_Controller.GetDevice(NoloDeviceType.RightController).GetNoloButtonPressed(NoloButtonID.Trigger))
{
for (int i = 0; i < imagItemList.Count; i++)
{
if (imagItemList[i].IsStaying() == true)
{
imagItemList[i].GetComponent().isOn = true;
imagItemList[i].transform.SetAsLastSibling();
}
}
delayTime = 0;
touchX = NoloVR_Controller.GetDevice(NoloDeviceType.RightController).GetAxis(NoloTouchID.TouchPad).x;
}
if (NoloVR_Controller.GetDevice(NoloDeviceType.RightController).GetNoloTouchPressed(NoloTouchID.TouchPad) && !NoloVR_Controller.GetDevice(NoloDeviceType.RightController).GetNoloButtonPressed(NoloButtonID.Trigger))
{
delayTime += Time.deltaTime;
if (delayTime <= 0.2f) return;
float touchPosX = NoloVR_Controller.GetDevice(NoloDeviceType.RightController).GetAxis(NoloTouchID.TouchPad).x - touchX;
if (touchPosX > 0)
{
for (int i = 0; i < imagItemList.Count; i++)
{
if (imagItemList[i].IsStaying() == true && imagItemList[i].transform.GetSiblingIndex()==imagItemList.Count-1/*GetComponent().isOn == true*/)
{
if (imagItemList[i].GetComponent().sizeDelta.x * (1 + touchPosX) * 0.8f <= 5120)
{
imagItemList[i].transform.localScale = Vector3.one * (1 + touchPosX) * 0.8f;
}
}
}
}
else
{
for (int i = 0; i < imagItemList.Count; i++)
{
if (imagItemList[i].IsStaying() == true && imagItemList[i].transform.GetSiblingIndex() == imagItemList.Count - 1/*GetComponent().isOn == true*/)
{
if (imagItemList[i].GetComponent().sizeDelta.y * (1 + touchPosX) * 0.8f >= 30)
{
imagItemList[i].transform.localScale = Vector3.one * (1 + touchPosX) * 0.8f;
}
}
}
}
//Debug.LogError(touchPosX);
}
if (NoloVR_Controller.GetDevice(NoloDeviceType.RightController).GetNoloTouchUp(NoloTouchID.TouchPad) && !NoloVR_Controller.GetDevice(NoloDeviceType.RightController).GetNoloButtonPressed(NoloButtonID.Trigger))
{
for (int i = 0; i < imagItemList.Count; i++)
{
imagItemList[i].isStaying = false;
if (/*imagItemList[i].IsStaying() == true && */imagItemList[i].GetComponent().isOn == true)
{
imagItemList[i].GetComponent().sizeDelta *= imagItemList[i].transform.localScale;
imagItemList[i].GetComponent().size *= imagItemList[i].transform.localScale;
imagItemList[i].transform.localScale = Vector3.one;
imagItemList[i].GetComponent().isOn = false;
return;
}
}
}
}
}
catch (System.Exception e)
{
Debug.Log("Catch" + e.Message);
throw;
}
}
private Vector2 WordSpaceToCanvas(RectTransform canvas, Vector3 objPos)
{
Vector2 canvasSize = canvas.sizeDelta;
Vector3 viewPortPos3d = uiCamera.WorldToViewportPoint(objPos);
Vector2 viewPortRelative = new Vector2(viewPortPos3d.x - 0.5f, viewPortPos3d.y - 0.5f);
Vector2 objScreenPos = new Vector2(viewPortRelative.x * canvasSize.x, viewPortRelative.y * canvasSize.y);
return objScreenPos;
}
///
/// 将世界坐标转换为UI坐标
///
/// 画布
/// 坐标
///
public Vector2 WorldToUgui(RectTransform canvas, Vector3 position)
{
Vector2 screenPoint = uiCamera.WorldToScreenPoint(position);//世界坐标转换为屏幕坐标
Vector2 screenSize = new Vector2(Screen.width, Screen.height);
screenPoint -= screenSize / 2;//将屏幕坐标变换为以屏幕中心为原点
Vector2 anchorPos = screenPoint / screenSize * canvas.sizeDelta;//缩放得到UGUI坐标
return anchorPos;
}
///
/// 世界坐标转UI坐标
///
/// UI画布
/// 世界坐标
///
public static Vector2 WorldToCanvasPoint(Canvas canvas, Vector3 worldPos)
{
Vector2 pos;
RectTransformUtility.ScreenPointToLocalPointInRectangle(canvas.transform as RectTransform,
Camera.main.WorldToScreenPoint(worldPos), canvas.worldCamera, out pos);
return pos;
}
}
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
public class ImageItem : MonoBehaviour
{
public GameObject selectObj;
private bool isToggleOn;
public bool isStaying;
public int layer = 0;
// Start is called before the first frame update
void Start()
{
GetComponent().onValueChanged.AddListener(OnToggleSwitch);
}
private void OnToggleSwitch(bool isOn)
{
isToggleOn = isOn;
selectObj.SetActive(isOn);
}
public bool IsToggleOn()
{
return isToggleOn;
}
public bool IsStaying()
{
return isStaying;
}
private void OnTriggerStay2D(Collider2D collision)
{
//if(collision.gameObject.name=="RightHand")
//{
// //Debug.Log("手在" + gameObject.name + "上");
// isStaying = true;
//}
}
private void OnTriggerExit2D(Collider2D collision)
{
Debug.Log("手离开了" + gameObject.name );
//isStaying = false;
}
// Update is called once per frame
void Update()
{
}
}
以下为UI触发事件,之前也试过2D碰撞体,但由于多张图片重叠的时候会碰撞穿透,因此换成了射线检测。
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class HandControl : MonoBehaviour
{
private bool isStaying;
public List imageList;
private void FixedUpdate()
{
//参数为:起点坐标,方向向量
//Ray2D ray = new Ray2D(transform.position, Vector2.zero);
//或者直接用点坐标,方向
RaycastHit2D info = Physics2D.Raycast(transform.position,Vector3.zero,0.1f)/*Physics2D.Raycast(transform.position, Vector2.zero)*/;
if (info.collider != null)
{
for (int i = 0; i < imageList.Count; i++)
{
if (info.collider.gameObject==imageList[i].gameObject)
{
imageList[i].isStaying = true;
}
else
{
imageList[i].isStaying = false;
}
}
}
else
{
imageList.ForEach(m => { m.isStaying = false; });
}
}
}
功能完成之后,就需要测试了,首先将Nolo的头部定位器连接到电脑上,然后开启基站开关和手柄开关,运行工程,发现一切都很顺利,然后打包测试。打包测试之后才发现,拖动图片从屏幕中间往两边移动的时候速度会越来越快,特别是屏幕分辨率越大,这种情况越严重,经过排查,发现是相机原因,由于相机是一个视锥角,所以越往两边,移动的位置在屏幕上的投影就越大,因此我将相机模式改为正交,Culling Mask改为UI,将画布改为屏幕相机模式,然后问题就完美解决了。
如有大佬有更好的解决方案,可以加我QQ共同探讨:741343816