在VR虚拟场景搭建的项目中,需要实现,三维物体部分放大的效果,展示不同组成部分的介绍功能,同时便于观察。
我设想通过手柄触碰目标物体,扣动扳机触发事件,目标物体放大,其他部分隐藏,关闭渲染。在放大的物体上扣动扳机,物体返回原样。同时,在物体放大时,无论用户在什么位置,物体显示在用户的实现朝向的方向,即用户面前。
编写脚本,继承VRTK_InteractableObject类(steamVR插件VRTK中,用来控制手柄和物体交互的脚本)。
设置参数,例如移动后的高度,距离camera Rig位置面前的距离,移动后的scale大小,移动的时间等等。
枚举物体的状态,我设置了三种 idle moving placed
//楼层状态 private enum State { IDLE, MOVING, PLACED }
移动的过程中moving,初始状态idle,移动后放大等效果为placed。
定义变量,开始时为IDLE状态,记录物体开始时的初始位置originPosition。Pivot差值。头显的数据等等。
void Start(){ state = State.IDLE; originPosition = transform.position; pivot = new Vector3 (0, Pivot, 0); scaleDelta = new Vector3 (Scale, Scale, Scale) / SpeedFrame; headset = VRTK_DeviceFinder.HeadsetTransform (); }
VRTK_DeviceFinder类:用于在场景中孕照左右手柄,头显,返回硬件编号,位置信息等。
除HeadsetTransform等,其他重要的API:
获取左右手柄的游戏物体
VRTK_DeviceFinder.GetControllerRightHand(); VRTK_DeviceFinder.GetControllerLiftHand();
hu'o 获得左右手柄对应的硬件编号
扫描二维码关注公众号,回复:
18079 查看本文章
VRTK_DeviceFinder.GetControllerIndex(rightHand)
继承VRTK_InteractableObject类,重构StartUsing方法,按下Trigger是进行状态转换
public override void StartUsing(GameObject currentUsingObject) { base.StartUsing(currentUsingObject); switch (state) { case State.IDLE: { StartCoroutine(moveToPlayArea()); } break; case State.MOVING: break; case State.PLACED: { StartCoroutine(moveBack()); } break; } }
将物体移动到用户的面前:
private IEnumerator moveToPlayArea() { foreach (ModelInteractableObject o in models) { if (o.state == State.PLACED) { StartCoroutine (o.moveBack ()); } else if (o.state == State.MOVING) { yield return new WaitWhile(() => o.state == State.MOVING); } } state = State.MOVING; Vector3 position = headset.position; position.y = Height; position += new Vector3(headset.forward.x, 0, headset.forward.z) * Distance; Vector3 dirDelta = (position - (transform.position + pivot)) / SpeedFrame; for(int i = 0; i < SpeedFrame; i++) { transform.Translate (dirDelta , Space.World); transform.localScale -= scaleDelta; yield return new WaitForEndOfFrame(); } state = State.PLACED; }
物体三种状态的转换,根据头显的位置,确定物体移动的位置。高度,距离变量控制。speedFrame控制时间。通过Translate方法移动。
将物体移动回原来的位置:
private IEnumerator moveBack() { state = State.MOVING; Vector3 dirDelta = (originPosition - transform.position) / SpeedFrame; for(int i = 0; i < SpeedFrame; i++) { transform.Translate (dirDelta , Space.World); transform.localScale += scaleDelta; yield return new WaitForEndOfFrame(); } state = State.IDLE; }
与上面方法同理。
源码:
using System.Collections; using System.Collections.Generic; using UnityEngine; using VRTK; using VRTK.Highlighters; public class ModelInteractableObject : VRTK_InteractableObject { [Header("Model Interactable Object")] [Tooltip("移动面前的距离")] public float Distance = 3f; [Tooltip("移动面前的高度")] public float Height = 1f; [Tooltip("移动面前的Scale")] public float Scale = 0.5f; [Tooltip("移动到目标位置的时间,以帧为单位")] public float SpeedFrame = 30.0f; [Tooltip("Pivot差值")] public float Pivot = 0; [Tooltip("FloorInfoPanel用于显示楼层信息")] public ModelInfoPanel FloorInfoPanel; [Tooltip("FloorInfoPanel中显示的信息")] public string Info; private enum State { IDLE, MOVING, PLACED } private State state; private Vector3 originPosition; private Vector3 pivot; private Vector3 scaleDelta; private Transform headset; private GameObject textPanel; private static ModelInteractableObject[] _models; private static ModelInteractableObject[] models { get { if (_models == null) { _models = FindObjectsOfType<ModelInteractableObject> (); } return _models; } } void Start(){ state = State.IDLE; originPosition = transform.position; pivot = new Vector3 (0, Pivot, 0); scaleDelta = new Vector3 (Scale, Scale, Scale) / SpeedFrame; headset = VRTK_DeviceFinder.HeadsetTransform (); } //按下Trigger时进行状态转换 public override void StartUsing(GameObject currentUsingObject) { base.StartUsing(currentUsingObject); switch (state) { case State.IDLE: { StartCoroutine(moveToPlayArea()); } break; case State.MOVING: break; case State.PLACED: { StartCoroutine(moveBack()); } break; } } //触摸时打开高亮 public override void OnInteractableObjectTouched (InteractableObjectEventArgs e) { if (state == State.PLACED || state == State.MOVING) { ToggleHighlight (false); } base.OnInteractableObjectTouched (e); } //开始触摸,显示手柄的高亮和Tooltips public override void StartTouching (GameObject currentTouchingObject) { base.StartTouching (currentTouchingObject); VRTK_ControllerActions action = currentTouchingObject.GetComponent<VRTK_ControllerActions> (); action.ToggleHighlightTrigger (true, Color.yellow); action.SetControllerOpacity (0.5f); } //停止触摸,关闭手柄高亮和Tooltips public override void StopTouching (GameObject previousTouchingObject) { base.StopTouching (previousTouchingObject); ///FloorInfoPanel.gameObject.SetActive (false); VRTK_ControllerActions action = previousTouchingObject.GetComponent<VRTK_ControllerActions> (); action.ToggleHighlightTrigger (false); action.SetControllerOpacity (1f); } private IEnumerator moveToPlayArea() { foreach (ModelInteractableObject o in models) { if (o.state == State.PLACED) { StartCoroutine (o.moveBack ()); } else if (o.state == State.MOVING) { yield return new WaitWhile(() => o.state == State.MOVING); } } state = State.MOVING; Vector3 position = headset.position; position.y = Height; position += new Vector3(headset.forward.x, 0, headset.forward.z) * Distance; Vector3 dirDelta = (position - (transform.position + pivot)) / SpeedFrame; for(int i = 0; i < SpeedFrame; i++) { transform.Translate (dirDelta , Space.World); transform.localScale -= scaleDelta; yield return new WaitForEndOfFrame(); } state = State.PLACED; } private IEnumerator moveBack() { state = State.MOVING; Vector3 dirDelta = (originPosition - transform.position) / SpeedFrame; for(int i = 0; i < SpeedFrame; i++) { transform.Translate (dirDelta , Space.World); transform.localScale += scaleDelta; yield return new WaitForEndOfFrame(); } state = State.IDLE; } }