クラスター開発者ブログ

Unityで視線にいい感じについてくるVR向けUIを実装する

こんにちは、じょんそん(@johnson65t)です。

cluster.をOculus Riftで使う場合、視線の動きに対してついてくるUIの挙動がイマイチなので研究中です。

具体的にやりたいことは下記の通りです。

  • UIの要素を選択している間は動かない
  • UIの要素を選択しておらず、プレイヤーが同じ位置を注視していれば、そちらを正面にUIが動く
  • 視線を動かしている間は動かない

デモ

サンプルプロジェクトはこちら ClusterVR/following_gui_sample。Unityのバージョンは5.3.3p1です。

CameraFollowingGUIをアタッチした空オブジェクトを親として、UIを配置します。 UIのカメラからの相対位置はlocalPositionで設定します。

以下、実装の要点を紹介します。

UIの要素を選択している状態の判定

今回はUIの要素として3Dオブジェクトを置いているので、カメラからRaycastを飛ばして判定しています。 UIを選択している間は、UIが回転し始めることがないようにします。

CameraFollowingGUI.cs(抜粋)
1
2
3
4
5
bool isPlayerFocusingToUI()
{
    Ray ray = new Ray(mainCamera.transform.position, mainCamera.transform.forward);
    return Physics.Raycast(ray, rayLength, targetLayers);
}

注視している状態の判定

「注視している」状態を取るために、カメラの向きの分散を計算しています。 プレイヤーが同じ位置を注視していると判定する手順は下記の通りです。

  1. カメラのY軸回転の情報を過去60フレーム分保持する
  2. 60フレーム分のY軸回転の分散を計算する
  3. 分散が閾値未満である状態が30フレーム続いたら注視していると判定する
CameraFollowingGUI.cs(抜粋)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
void Start()
{
    mainCamera = Camera.main;

    this.UpdateAsObservable()
        .Select(_ => mainCamera.transform.eulerAngles.y)
        .Buffer(60, 1)
        .Select(samples => !isRecentering && !isPlayerFocusingToUI() && isPlayerStaring(samples))
        .Buffer(30, 1)
        .Where(samples => samples.All(b => b))
        .Subscribe(_ =>
        {
            yTargetAngle = mainCamera.transform.eulerAngles.y;
            isRecentering = true;
        });
}

bool isPlayerStaring(IList<float> samples)
{
    var average = samples.Average();
    var dispersion = samples.Select(sample => Mathf.Pow(sample - average, 2F)).Average();
    return dispersion < sensitivity;
}

UIがカメラの正面を向く

プレイヤーが向いている方向にUIが回転を始める条件は下記の通りです。

  • UIを選択していない
  • プレイヤーが同じ位置を注視している

Mathf.SmoothDampAngleを使ってUIをスムーズに回転させています。

CameraFollowingGUI.cs(抜粋)
1
2
3
4
5
6
7
8
this.UpdateAsObservable()
    .Where(_ => isRecentering)
    .Select(_ => Mathf.SmoothDampAngle(transform.eulerAngles.y, yTargetAngle, ref yAngleVelocity, smoothTime))
    .Subscribe(y => transform.rotation = Quaternion.Euler(0F, y, 0F));

this.UpdateAsObservable()
    .Where(_ => Mathf.Abs(yAngleVelocity) < 0.1F)
    .Subscribe(_ => isRecentering = false);

参考

このエントリーをはてなブックマークに追加