LoginSignup
4
5

More than 5 years have passed since last update.

GoogleVRのカーソル表示処理のScriptを解読

Posted at

はじめに

GoogleVRのサンプルのカーソル(焦点)の色を変更から始まるVRで画面中央に表示されるカーソル(Reticle)をカスタマイズしていくシリーズ?です。
最終的には以下のようなものにしたい…
参考イメージ
↑参考イメージ(HanGame,チョコットランドより)

前回、とりあえずカーソルを表示しているGameObjectとScriptを特定し色の変更だけ行いましたので、今回はScriptの中身を把握していきたいと思います。

コメントを超追加

Reticleに追加されているScript、CvrReticle.csにて表示の仕組みが書かれている。
すごい人は絶対やらないだろうがソースに自分はコメントを書きまくる↓
これで理解した気分になっているうちにいつの間にか理解してたり実行してたら勘違いしてたことに気付く…
(以下コードはGoogleVRのライブラリに入っているGoogleのソースです。)

GvrReticle.cs
    using UnityEngine;

    //ユーザーが注目しているオブジェクトに円形の焦点を描画します。
    /// Draws a circular reticle in front of any object that the user gazes at.
    //オブジェクトが選択可能であれば円が拡張します。
    /// The circle dilates if the object is clickable.
    [AddComponentMenu("GoogleVR/UI/GvrReticle")]
    [RequireComponent(typeof(Renderer))]

    /************************************************
     * GvrReticle : カーソルの焦点クラス
     ************************************************/
    public class GvrReticle : MonoBehaviour, IGvrGazePointer {
        //焦点の円を作るセグメント(要素?)数 ※多分これが多い方が高画質?
        /// Number of segments making the reticle circle.
        public int reticleSegments = 20;

        //焦点が大きくなる速度(大きいほど早く,小さいほど遅くなる)
        /// Growth speed multiplier for the reticle.
        public float reticleGrowthSpeed = 8.0f;

        /// Private members
        private Material materialComp; //焦点として表示するマテリアル
        private GameObject targetObj; //焦点を合わせているオブジェクト

        //焦点の現在の内角(角度)
        /// Current inner angle of the reticle (in degrees).
        private float reticleInnerAngle = 0.0f;
        //焦点の現在の外角(角度)
        // Current outer angle of the reticle (in degrees).
        private float reticleOuterAngle = 0.5f;
        //焦点の現在の距離(メートル)
        /// Current distance of the reticle (in meters).
        private float reticleDistanceInMeters = 10.0f;

        //焦点の最小内角(角度)
        /// Minimum inner angle of the reticle (in degrees).
        private const float kReticleMinInnerAngle = 0.0f;
        //焦点の最小外角(角度)
        /// Minimum outer angle of the reticle (in degrees).
        private const float kReticleMinOuterAngle = 0.5f;
        //選択可能なオブジェクトと交差する時、焦点を拡大する角度。
        /// Angle at which to expand the reticle when intersecting with an object.
        private const float kReticleGrowthAngle = 1.5f;

        //焦点の最小距離(メートル)
        /// Minimum distance of the reticle (in meters).
        private const float kReticleDistanceMin = 0.45f;
        //焦点の最大距離(メートル)
        /// Maximum distance of the reticle (in meters).
        private const float kReticleDistanceMax = 10.0f;

        //現在の距離の乗算で使う内径と外径(内径…内側の直径,外径…外側の直径)
        /// Current inner and outer diameters of the reticle, before distance multiplication.
        private float reticleInnerDiameter = 0.0f;
        private float reticleOuterDiameter = 0.0f;

        /*------------------------------------------*
         * Start : クラスが生成された時に呼ばれる
         *------------------------------------------*/
        void Start () {
            //焦点にカーソルを生成
            CreateReticleVertices();
            //焦点として表示するマテリアルを読み込み
            materialComp = gameObject.GetComponent<Renderer>().material;
        }

        /*------------------------------------------*
         * OnEnable : オブジェクトが有効になった時
         *------------------------------------------*/
        void OnEnable() {
            //焦点のオブジェクトとして自身を登録?
            GazeInputModule.gazePointer = this;
        }

        /*------------------------------------------*
         * OnDisable : オブジェクトが無効になった時
         *------------------------------------------*/
        void OnDisable() {
            //焦点のオブジェクトが自身の時、登録を解除?
            if (GazeInputModule.gazePointer == this) {
                GazeInputModule.gazePointer = null;
            }
        }

        /*------------------------------------------*
         * Update : フレーム毎に呼ばれる
         *------------------------------------------*/
        void Update() {
            //焦点カーソルの円の変更
            UpdateDiameters();
        }

        //「BaseInputModule」システムを有効になった時に呼び出されます。
        /// This is called when the 'BaseInputModule' system should be enabled.
        /*------------------------------------------*
         * OnGazeEnabled : BaseInputModuleが有効になった時
         *------------------------------------------*/
        public void OnGazeEnabled() {

        }

        //「BaseInputModule」システムを無効になった時に呼び出されます。
        /// This is called when the 'BaseInputModule' system should be disabled.
        /*------------------------------------------*
         * OnGazeDisabled : BaseInputModuleが無効になった時
         *------------------------------------------*/
        public void OnGazeDisabled() {

        }

        //ユーザーが3DやUI要素で選択可能なオブジェクトを発見した時に呼ばれます。
        /// Called when the user is looking on a valid GameObject. This can be a 3D or UI element.
        //cameraはイベントカメラです、targetObjectはユーザーが見ているオブジェクトです、そしてintersectionPositionがカメラの視点とオブジェクトの交差点の座標です。
        /// The camera is the event camera, the target is the object the user is looking at, and the intersectionPosition is the intersection point of the ray sent from the camera on the object.
        /*------------------------------------------*
         * OnGazeStart : 選択可能なオブジェクトを選択開始
         *------------------------------------------*/
        public void OnGazeStart(Camera camera, GameObject targetObject, Vector3 intersectionPosition, bool isInteractive) {
            //オブジェクトとの位置でカーソルの表示を変える
            SetGazeTarget(intersectionPosition, isInteractive);
        }

        //ユーザーが3DやUI要素で選択可能なオブジェクトを見続けている限り毎フレーム呼ばれます。
        /// Called every frame the user is still looking at a valid GameObject. This can be a 3D or UI element.
        //cameraはイベントカメラです、targetObjectはユーザーが見ているオブジェクトです、そしてintersectionPositionがカメラの視点とオブジェクトの交差点の座標です。
        /// The camera is the event camera, the target is the object the user is looking at, and the intersectionPosition is the intersection point of the ray sent from the camera on the object.
        /*------------------------------------------*
         * OnGazeStay : 選択可能なオブジェクトを選択中
         *------------------------------------------*/
        public void OnGazeStay(Camera camera, GameObject targetObject, Vector3 intersectionPosition, bool isInteractive) {
            //オブジェクトとの位置でカーソルの表示を変える
            SetGazeTarget(intersectionPosition, isInteractive);
        }

        //ユーザーが以前見ていたオブジェクトから目を離した時に呼ばれる。
        /// Called when the user's look no longer intersects an object previously intersected with a ray projected from the camera.
        //これはOnGazeDisabledの前に呼び出されます、そしてnull値である可能性があります。
        /// This is also called just before **OnGazeDisabled** and may have have any of the values set as **null**.
        //cameraはイベントカメラでtargetObjectは以前見ていたオブジェクトです。
        /// The camera is the event camera and the target is the object the user previously looked at.
        /*------------------------------------------*
         * OnGazeExit : 選択可能なオブジェクトを選択解除
         *------------------------------------------*/
        public void OnGazeExit(Camera camera, GameObject targetObject) {
            //焦点情報を戻す
            reticleDistanceInMeters = kReticleDistanceMax;
            reticleInnerAngle = kReticleMinInnerAngle;
            reticleOuterAngle = kReticleMinOuterAngle;
        }

        //選択時のイベント発生時に呼び出されます。
        /// Called when a trigger event is initiated.
        //ユーザーが選択し実行する直前です。
        /// This is practically when the user begins pressing the trigger.
        /*------------------------------------------*
         * OnGazeTriggerStart : 選択でのイベント開始時
         *------------------------------------------*/
        public void OnGazeTriggerStart(Camera camera) {
            //ここにあなたの焦点の選択開始時の処理をいれてね(^^)
            // Put your reticle trigger start logic here :)
        }

        //選択時のイベント終了時に呼び出されます。
        /// Called when a trigger event is finished.
        //ユーザーが選択し実行から解放された時です。
        /// This is practically when the user releases the trigger.
        /*------------------------------------------*
         * OnGazeTriggerEnd : 選択でのイベント終了時
         *------------------------------------------*/
        public void OnGazeTriggerEnd(Camera camera) {
            //ここにあなたの焦点の選択終了時の処理をいれてね(^^)
            // Put your reticle trigger end logic here :)
        }

        /*------------------------------------------*
         * GetPointerRadius : 内側の半径と外側の半径を取得??
         *------------------------------------------*/
        public void GetPointerRadius(out float innerRadius, out float outerRadius) {
            float min_inner_angle_radians = Mathf.Deg2Rad * kReticleMinInnerAngle;
            float max_inner_angle_radians = Mathf.Deg2Rad * (kReticleMinInnerAngle + kReticleGrowthAngle);

            innerRadius = 2.0f * Mathf.Tan(min_inner_angle_radians);
            outerRadius = 2.0f * Mathf.Tan(max_inner_angle_radians);
        }

        /*------------------------------------------*
         * CreateReticleVertices : 焦点に表示するカーソルの頂点を生成
         *------------------------------------------*/
        private void CreateReticleVertices() {
            //Mesh(オブジェクトを成形する頂点、辺、面の集合)の作成
            Mesh mesh = new Mesh();
            gameObject.AddComponent<MeshFilter>();
            GetComponent<MeshFilter>().mesh = mesh;

            //Meshを構成するセグメント(要素?)数
            int segments_count = reticleSegments;
            int vertex_count = (segments_count+1)*2;

            #region Vertices
            /*---以下、円の頂点を指定してMeshにカーソルを生成しているっぽい(完全に把握できていない)---*/

            //頂点座標配列
            Vector3[] vertices = new Vector3[vertex_count];
            //円周率×2
            /*
             * Mathf.PI
             * 円周率
             */
            const float kTwoPi = Mathf.PI * 2.0f;
            //配列の追加箇所
            int vi = 0;
            for (int si = 0; si <= segments_count; ++si) {
                //すべての円セグメントのための2つの頂点を追加します:
                // Add two vertices for every circle segment:
                //角柱の開始時に1、
                // one at the beginning of the prism,
                //角柱の終わりに1。
                // and one at the end of the prism.
                //要素数を360度円状に敷き詰める時のsi番目の角度
                float angle = (float)si / (float)(segments_count) * kTwoPi;
                //Sin,Cosで円ができる?
                /*
                 * Mathf.Sin(a)
                 * aの角度のサイン(三角比)を返す
                 */
                float x = Mathf.Sin(angle);
                /*
                 * Mathf.Cos(a)
                 * aの角度のコサイン(三角比)を返す
                 */
                float y = Mathf.Cos(angle);

                //1pxの厚みを持たせて円の外側と内側の座標を追加
                vertices[vi++] = new Vector3(x, y, 0.0f); // Outer vertex.
                vertices[vi++] = new Vector3(x, y, 1.0f); // Inner vertex.
            }
            #endregion

            #region Triangles
            /*---以下、三角形の頂点を指定してMeshにカーソルを生成しているっぽい(完全に把握できていない)---*/
            int indices_count = (segments_count+1)*3*2;
            int[] indices = new int[indices_count];

            int vert = 0;
            int idx = 0;
            for (int si = 0; si < segments_count; ++si) {
                indices[idx++] = vert+1;
                indices[idx++] = vert;
                indices[idx++] = vert+2;

                indices[idx++] = vert+1;
                indices[idx++] = vert+2;
                indices[idx++] = vert+3;

                vert += 2;
            }
            #endregion

            //Meshにカーソルを成形する情報を与える
            mesh.vertices = vertices;//Meshを構成する円情報の座標を与える
            mesh.triangles = indices;//Meshを構成する三角形情報の座標を与える
            mesh.RecalculateBounds();//頂点のバウンディングボリュームを再計算と書いてあったがなんのこっちゃ
            mesh.Optimize();//Meshの面と頂点の順番変更を制御しパフォーマンスを最適化して出力メッシュを生成らしい(とりあえず描画)
        }

        /*------------------------------------------*
         * UpdateDiameters : 焦点カーソルの直径の更新
         *------------------------------------------*/
        private void UpdateDiameters() {
            /*
             * Mathf.Clamp(a,b,c)
             * aの値をb以上c以下の値にして返してくれる
             */

            //焦点の現在の距離(メートル)を調整
            reticleDistanceInMeters = Mathf.Clamp(reticleDistanceInMeters, kReticleDistanceMin, kReticleDistanceMax);

            //焦点の現在の内角(角度)を最小値以下に合わせる
            if (reticleInnerAngle < kReticleMinInnerAngle) {
                reticleInnerAngle = kReticleMinInnerAngle;
            }
            //焦点の現在の外角(角度)を最小値以下に合わせる
            if (reticleOuterAngle < kReticleMinOuterAngle) {
                reticleOuterAngle = kReticleMinOuterAngle;
            }

            /*
             * Mathf.Deg2Rad
             * 値を度数法と弧度法で変換
             */
            //度数法(Angle)から弧度法(Radian)に変換
            float inner_half_angle_radians = Mathf.Deg2Rad * reticleInnerAngle * 0.5f;
            float outer_half_angle_radians = Mathf.Deg2Rad * reticleOuterAngle * 0.5f;

            /*
             * Mathf.Tan(a)
             * aの角度のタンジェント(三角比)を返す
             */
            //直径の外側,内側を出す?
            float inner_diameter = 2.0f * Mathf.Tan(inner_half_angle_radians);
            float outer_diameter = 2.0f * Mathf.Tan(outer_half_angle_radians);

            /*
             * Mathf.Lerp(a,b,c)
             * cが0ならaの値が,1ならbの値が,0.5と中間ならaとbの中間の値が返る
             */
            //現在の距離の内径と外径
            reticleInnerDiameter = Mathf.Lerp(reticleInnerDiameter, inner_diameter, Time.deltaTime * reticleGrowthSpeed);
            reticleOuterDiameter = Mathf.Lerp(reticleOuterDiameter, outer_diameter, Time.deltaTime * reticleGrowthSpeed);

            //マテリアルのサイズを変更することで焦点カーソルの円のサイズを変更する
            materialComp.SetFloat("_InnerDiameter", reticleInnerDiameter * reticleDistanceInMeters);
            materialComp.SetFloat("_OuterDiameter", reticleOuterDiameter * reticleDistanceInMeters);
            materialComp.SetFloat("_DistanceInMeters", reticleDistanceInMeters);
        }

        /*------------------------------------------*
         * SetGazeTarget : 焦点を合わせているオブジェクトとの距離
         *------------------------------------------*/
        private void SetGazeTarget(Vector3 target, bool interactive) {
            //オブジェクトの座標取得
            Vector3 targetLocalPosition = transform.InverseTransformPoint(target);
            //オブジェクトとの位置取得
            reticleDistanceInMeters = Mathf.Clamp(targetLocalPosition.z, kReticleDistanceMin, kReticleDistanceMax);

            //現在の内角,外角の取得
            if (interactive) {
                reticleInnerAngle = kReticleMinInnerAngle + kReticleGrowthAngle;
                reticleOuterAngle = kReticleMinOuterAngle + kReticleGrowthAngle;
            } else {
                reticleInnerAngle = kReticleMinInnerAngle;
                reticleOuterAngle = kReticleMinOuterAngle;
            }
        }
    }

とても頭の悪そうなソースになってしまったが教科書みたいに一回してみた。
慣れたソースだとコメント無いほうがいいが最初はこうしてしまう…

処理部分のソース要約

上のソースだと理解し辛いかと思うので要点だけまとめます。

float reticleGrowthSpeed
・カーソルの点が円に変わる速度(少ないほど遅い)
・publicなのでUnityのInspectorからも編集可能

void CreateReticleVertices()
・Start()の初期化処理で呼ばれる
・ここで円のMesh(カーソル)を生成する

void UpdateDiameters():ここでカーソルの円の大きさを更新
・Meshの円のサイズを更新する
・通常は選択していない時は円が小さいので点に見える

3Dの知識のない私からしてみたらMeshを円から別の形に自由に変えて生成するのは難しい。
なのでカーソルになるGameObjectはソース内で生成ではなくあらかじめ生成しておく。
位置やサイズに関してはコードから操作してみる。(3DでMesh生成でやれる人は頑張ってみて…そして教えて…)

次回はこのコードに手を加えて自由な形のカーソルを表示してみたいと思います。

4
5
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
4
5