※ これは 2016/05/31 Unity HoloLens Technical Preview ver.5.4.0beta14(Windows版) 時点の情報です
最新版では動作が異なる可能性がありますのでご注意ください
前回 は Button へのタップ操作を検出できるようになりました
ただ、Button への注視に物理衝突判定用の Colider を利用しているところが普通の uGUI の使い方と異なっており冗長なので、Colider を利用しない方法を試してみます
まずはコード修正
using UnityEngine; using UnityEngine.UI; using UnityEngine.VR.WSA.Input; public class VRUI : MonoBehaviour { /// <summary> /// ボタンの属する Canvas /// </summary> [SerializeField] private Canvas layoutRoot; /// <summary> /// ボタンのラベルテキスト /// </summary> [SerializeField] private UnityEngine.UI.Text buttonLabel; /// <summary> /// ターゲッテイング用カーソル /// </summary> [SerializeField] private GameObject cursor; /// <summary> /// ジェスチャ検知 /// </summary> private GestureRecognizer gesture; /// <summary> /// 視線オーバーフラグ /// </summary> private bool isOver = false; /// <summary> /// ホールドジェスチャフラグ /// </summary> private bool isHold = false; /// <summary> /// タップカウント /// </summary> private int count = 0; /// <summary> /// 初期処理 /// </summary> public void Awake() { this.gesture = new GestureRecognizer(); this.gesture.SetRecognizableGestures(GestureSettings.Hold | GestureSettings.Tap); this.gesture.HoldStartedEvent += this.OnButtonHold; this.gesture.HoldCompletedEvent += this.OnButtonHoldEnd; this.gesture.HoldCanceledEvent += this.OnButtonHoldEnd; this.gesture.TappedEvent += this.OnButtonClick; this.gesture.StartCapturingGestures(); } /// <summary> /// 破棄処理 /// </summary> public void OnDestroy() { if (this.gesture != null && this.gesture.IsCapturingGestures()) { this.gesture.StopCapturingGestures(); this.gesture.HoldStartedEvent -= this.OnButtonHold; this.gesture.HoldCompletedEvent -= this.OnButtonHoldEnd; this.gesture.HoldCanceledEvent -= this.OnButtonHoldEnd; this.gesture.TappedEvent -= this.OnButtonClick; } } /// <summary> /// 定期的な処理 /// </summary> public void FixedUpdate() { RaycastHit hit; if (Raycast(this.layoutRoot, new Ray(Camera.main.transform.position, Camera.main.transform.forward), out hit)) { this.cursor.transform.position = hit.point; this.cursor.transform.rotation = Quaternion.Euler(hit.normal); this.cursor.SetActive(true); this.isOver = true; } else { this.isOver = false; this.cursor.SetActive(false); } } /// <summary> /// グラフィックをもとに衝突判定する /// </summary> /// <param name="canvas">親の Canvas</param> /// <param name="ray">視線の位置と方向</param> /// <param name="hit">衝突結果</param> /// <returns>衝突したグラフィックがあった場合 true、それ以外は false</returns> public bool Raycast(Canvas canvas, Ray ray, out RaycastHit hit) { hit = new RaycastHit(); var results = false; var depth = -1; // なぜか GetEnumelator が実装されてないので for で反復 var graphics = GraphicRegistry.GetGraphicsForCanvas(canvas); for(var i = 0; i < graphics.Count; i++) { var graphic = graphics[i]; if (graphic.depth == -1 || graphic.gameObject == this.cursor) { continue; } Vector3 position; if (RayIntersectsRectTransform(graphic.rectTransform, ray, out position)) { var screenPosition = Camera.main.WorldToScreenPoint(position); if (graphic.Raycast(screenPosition, Camera.main) && depth < graphic.depth) { hit.point = position; results = true; } } } return results; } /// <summary> /// UI と Ray の交点を求める /// </summary> /// <param name="rectTransform">UI の RectTransform</param> /// <param name="ray">位置と方向</param> /// <param name="position">交点のワールド座標</param> /// <returns>交点があった場合 true、それ以外は false</returns> public static bool RayIntersectsRectTransform(RectTransform rectTransform, Ray ray, out Vector3 position) { position = Vector3.zero; var corners = new Vector3[4]; rectTransform.GetWorldCorners(corners); var dummy = new Plane(corners[0], corners[1], corners[2]); float enter; if (!dummy.Raycast(ray, out enter)) { // UI平面を通過しない場合 return false; } // UI平面との交点 var intersection = ray.GetPoint(enter); // UI矩形内に視線が収まっていればその交点ワールド座標を返す var bottom = corners[3] - corners[0]; var left = corners[1] - corners[0]; var bottomDot = Vector3.Dot(intersection - corners[0], bottom); var leftDot = Vector3.Dot(intersection - corners[0], left); if (bottomDot < bottom.sqrMagnitude && leftDot < left.sqrMagnitude && bottomDot >= 0 && leftDot >= 0) { position = corners[0] + leftDot * left / left.sqrMagnitude + bottomDot * bottom / bottom.sqrMagnitude; return true; } return false; } ~ 中略 ~ }
Oculus Rift 用の VR UI サンプル を参考に、これまでは Physics.Raycast を利用していたところを別途メソッドを増やして衝突判定するようにしました
Physics.Raycast は本来 3D オブジェクト用なので、Colider を利用しますが、Button のような UI は単純な判定範囲となるので普通に座標計算するようになっています
コードを修正したら Unity Editor 上で少々変更
Button の Box Colider のチェックを外して無効化してしまって
Button の親の Canvas を VRUI の Layout Root にドラッグドロップで設定します
さてこれでビルド実行
注視を検出できてはいますが・・・前と同じように範囲が二倍になっているような
これは Unity Editor 側の設定がまずそうな予感?!