在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();
获得左右手柄对应的硬件编号
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 ();
}
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 ();
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 ();
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;
}
}