MRが楽しい

MRやVRについて学習したことを書き残す

Unityで基本変形(移動・回転・スケール)のアニメーションを作成する その6(アニメーションの終了を検知する)

本日は Unity の技術調査枠です。
基本変形(移動・回転・スケール)のアニメーションを作成する方法についてまとめます。
前回記事の続きになります。
bluebirdofoz.hatenablog.com

今回は前回設定を行った「一度だけ再生するアニメーション」の終了タイミングを検知する方法です。

アニメーションの終了を検知する

前回作成した[Loop Time]を無効化したアニメーションコントローラを利用します。
以下のように順を追ってアニメーションを行うコントローラとスクリプトを作成しました。
f:id:bluebirdofoz:20181015221611j:plain
・TriggerKick.cs

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

// スクリプトを追加するオブジェクトに必要なコンポーネントの列記
[RequireComponent(typeof(Animator))] 
public class TriggerKick : MonoBehaviour {
    /// <summary>
    /// Animatorコンポーネント
    /// </summary>
    private Animator animator;

    /// <summary>
    /// 起動時の初期化
    /// </summary>
    void Start () {
        // Animatorコンポーネントの取得
        animator = GetComponent<Animator>();
    }

    /// <summary>
    /// 定期実行
    /// </summary>
    void Update()
    {
        // Zキー押下で実行する
        if (Input.GetKey(KeyCode.Z))
        {
            // トリガーを実行する
            animator.SetTrigger("AnimTrigger_Z");
        }
        // Yキー押下で実行する
        if (Input.GetKey(KeyCode.Y))
        {
            // トリガーを実行する
            animator.SetTrigger("AnimTrigger_Y");
        }
        // Xキー押下で実行する
        if (Input.GetKey(KeyCode.X))
        {
            // トリガーを実行する
            animator.SetTrigger("AnimTrigger_X");
        }
        // Rキー押下で実行する
        if (Input.GetKey(KeyCode.R))
        {
            // トリガーを実行する
            animator.SetTrigger("AnimTrigger_Reset");
        }
    }
}

シーンを再生します。
Y キーを押して、最初のアニメーションが開始した直後に X キーを押してみます。
すると以下のようにアニメーションの途中で次のアニメーションが割り込んでしまいます。
f:id:bluebirdofoz:20181015221621g:plain

アニメーションが完了するまで入力を受け付けないようにコードを修正します。
normalizedTime を参照する事で、ループを行わないアニメーションの終了を検知できます。
normalizedTime はアニメーション開始時に0を、1ループ分の再生完了後に1以上の値を返します。
docs.unity3d.com

normalizedTime を利用する際は GetCurrentAnimatorStateInfo で参照するレイヤを指定する必要があります。
今回は以下の通り、Base Layer しか存在しないので GetCurrentAnimatorStateInfo(0) で参照を行います。
f:id:bluebirdofoz:20181015221631j:plain

コードを以下の通り修正します。
EntryState については待機する必要はないので、EntryState のときのみ待機を無視しています。
・TriggerKick.cs

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

// スクリプトを追加するオブジェクトに必要なコンポーネントの列記
[RequireComponent(typeof(Animator))] 
public class TriggerKick : MonoBehaviour {
    /// <summary>
    /// Animatorコンポーネント
    /// </summary>
    private Animator animator;

    /// <summary>
    /// EntryStateのハッシュ値を取得
    /// </summary>
    static int entryState = Animator.StringToHash("Base Layer.EntryState");

    /// <summary>
    /// 起動時の初期化
    /// </summary>
    void Start () {
        // Animatorコンポーネントの取得
        animator = GetComponent<Animator>();
    }

    /// <summary>
    /// 定期実行
    /// </summary>
    void Update()
    {
        // アニメーションが再生完了(normalizedTime 1以上)
        // または EntryState のときのみ、入力を受け付け
        if (animator.GetCurrentAnimatorStateInfo(0).fullPathHash == entryState ||
            animator.GetCurrentAnimatorStateInfo(0).normalizedTime > 1.0f)
        {
            // Zキー押下で実行する
            if (Input.GetKey(KeyCode.Z))
            {
                // トリガーを実行する
                animator.SetTrigger("AnimTrigger_Z");
            }
            // Yキー押下で実行する
            if (Input.GetKey(KeyCode.Y))
            {
                // トリガーを実行する
                animator.SetTrigger("AnimTrigger_Y");
            }
            // Xキー押下で実行する
            if (Input.GetKey(KeyCode.X))
            {
                // トリガーを実行する
                animator.SetTrigger("AnimTrigger_X");
            }
            // Rキー押下で実行する
            if (Input.GetKey(KeyCode.R))
            {
                // トリガーを実行する
                animator.SetTrigger("AnimTrigger_Reset");
            }
        }
    }
}

シーンを再生します。
アニメーションが完了したタイミングで次の入力が受け付けられるようになりました。
f:id:bluebirdofoz:20181015221648g:plain

公式チュートリアル「MR and Azure 305」の記事をまとめる

本日はまとめ枠です。
公式チュートリアル「MR and Azure 305」を実施した記事についてまとめておきます。

Chapter 3 ~ 5

bluebirdofoz.hatenablog.com

Chapter 11 ~ 12

bluebirdofoz.hatenablog.com

公式チュートリアル「MR and Azure 305」の動作確認を試してみる

本日はチュートリアルの実施枠です。
Academyの「MR and Azure 305: Functions and storage」の実施内容をまとめます。
docs.microsoft.com
前回記事の続きです。
bluebirdofoz.hatenablog.com
今回はアプリの動作確認についてまとめます。

1.HoloLens上でアプリを起動します。
 するとキューブに「Loading..」と表示され、Azure Storageのデータを読み込みます。
f:id:bluebirdofoz:20181013033035j:plain

2.初回起動時の場合、Azure Storageにシェイプファイルはありません。
 キューブに「No Shape File!」と表示され、注視によるシェイプの作成が有効になります。
f:id:bluebirdofoz:20181013033213j:plain

3.この状態でキューブに視点を合わせて注視を行います。
 するとシェイプ作成が行われ、ランダムな図形のオブジェクトが生成されます。
 同時にバックグラウンドでAzure Storageに生成したシェイプの情報が保存されます。
f:id:bluebirdofoz:20181013033206j:plain

4.追加でシェイプを生成する場合は、一度キューブから視点を外します。
 注視を外しているときはキューブが赤色になります。
f:id:bluebirdofoz:20181013033223j:plain

5.この状態から再びキューブを注視すると、別のシェイプが生成されます。
f:id:bluebirdofoz:20181013033229j:plain

6.シェイプを作成したら一旦アプリを終了します。
 アプリのウィンドウを閉じるなどして、一度プロセスを完全に終了させる必要があります。
 その後、再びアプリを起動します。
f:id:bluebirdofoz:20181013033237j:plain

7.Azure Storageのデータを読み込みで、今度はシェイプファイルが検出されます。
 前回起動時に生成したシェイプリストが読み込まれ、同じシェイプが生成されます。
 キューブに「Load Complete!」と表示されます。
f:id:bluebirdofoz:20181013033247j:plain

以上で HOLOGRAMS 305 は終了です。

公式チュートリアル「MR and Azure 305 11~12章」を試してみる

本日はチュートリアルの実施枠です。
Academyの「MR and Azure 305: Functions and storage」の実施内容をまとめます。
docs.microsoft.com
前回記事の続きです。
bluebirdofoz.hatenablog.com
今回は「Chapter 11 ~ Chapter 12(最終章)」です。

Chapter 11:Building the UWP Solution

Unity セクションで必要なものは全て完成したので、ビルドを行います。
1.メニューから File -> Build Settings を選択します。
f:id:bluebirdofoz:20181012092814j:plain

2.「Unity C# Projects」をチェックし、「Build」を実行します。
f:id:bluebirdofoz:20181012092822j:plain

3-4.Unity のビルドが完了すると、File Explorer が開きます。
ビルドフォルダを開き、新しいプロジェクトソリューションを開きます。
f:id:bluebirdofoz:20181012092832j:plain

Chapter 12:Deploying your application

アプリケーションを HoloLens に展開します。
1-3.ビルドで出力された sln ファイルを開きます。
構成を「Release」「x86」「リモートコンピュータ(HoloLensのIPアドレス)」に変更します。
f:id:bluebirdofoz:20181012092843j:plain

4-5.メニューから デバッグ -> デバッグなしで実行 でアプリを HoloLens にインストールして実行します。
f:id:bluebirdofoz:20181012092851j:plain

Chapter 11 ~ 12 はここまでです。
アプリの動作確認を次回記事にまとめます。
bluebirdofoz.hatenablog.com

公式チュートリアル「MR and Azure 305 10章」を試してみる

本日はチュートリアルの実施枠です。
Academyの「MR and Azure 305: Functions and storage」の実施内容をまとめます。
docs.microsoft.com
前回記事の続きです。
bluebirdofoz.hatenablog.com
今回は「Chapter 10」です。

Chapter 10:Completing the AzureServices class

全てのスクリプトが揃ったので、AzureServices クラスを完成させます。

1.Script フォルダから AzureServices クラスを開きます。
f:id:bluebirdofoz:20181011073939j:plain

2-6.以下の通り、スクリプトを編集します。
・AzureServices.cs

// 名前空間の追加
using System;
using System.Collections;
using System.Threading.Tasks;
using UnityEngine;
using UnityEngine.Networking;
using Microsoft.WindowsAzure.Storage;
using Microsoft.WindowsAzure.Storage.File;
using System.IO;
using System.Net;

public class AzureServices : MonoBehaviour {
    // インスペクタフィールドの追加
    /// <summary>
    /// Provides Singleton-like behavior to this class.
    /// このクラスをシングルトンと同じように動作させます
    /// </summary>
    public static AzureServices instance;

    /// <summary>
    /// Reference Target for AzureStatusText Text Mesh object
    /// AzureStatusText Text Meshオブジェクトの参照先
    /// </summary>
    public TextMesh azureStatusText;

    // メンバ変数の追加
    /// <summary>
    /// Holds the Azure Function endpoint - Insert your Azure Function Connection String here.
    /// Azure Function のエンドポイントを保持します 
    /// 第二章で作成した Azure Function のエンドポイントをここに挿入します。
    /// Connection String here.
    /// </summary>
    private string azureFunctionEndpoint = "--Insert here you AzureFunction Endpoint--";

    /// <summary>
    /// Holds the Storage Connection String - Insert your Azure Storage Connection String here.
    /// Azure Storage の接続用文字列を保持します
    /// 第一章で作成した Azure Storage の接続用文字列をここに挿入します。
    /// </summary>
    private string storageConnectionString = "--Insert here you AzureStorage Connection String--";

    /// <summary>
    /// Name of the Cloud Share - Hosts directories.
    /// 共有クラウドの名前 - ホストディレクトリ
    /// </summary>
    private const string fileShare = "fileshare";

    /// <summary>
    /// Name of a Directory within the Share
    /// 共有内のディレクトリ名
    /// </summary>
    private const string storageDirectory = "storagedirectory";

    /// <summary>
    /// The Cloud File
    /// クラウドファイル
    /// </summary>
    private CloudFile shapeIndexCloudFile;

    /// <summary>
    /// The Linked Storage Account
    /// リンクされたストレージアカウント
    /// </summary>
    private CloudStorageAccount storageAccount;

    /// <summary>
    /// The Cloud Client
    /// クラウドクライアント
    /// </summary>
    private CloudFileClient fileClient;

    /// <summary>
    /// The Cloud Share - Hosts Directories
    /// クラウド共有 - ホストディレクトリ
    /// </summary>
    private CloudFileShare share;

    /// <summary>
    /// The Directory in the share that will host the Cloud file
    /// クラウドファイルをホストする共有内のディレクトリ
    /// </summary>
    private CloudFileDirectory dir;


    /// <summary>
    /// Called on initialization
    /// 初期化処理
    /// </summary>
    private void Awake()
    {
        instance = this;
    }

    /// <summary>
    /// Runs at initialization right after Awake method
    /// StartメソッドはAwakeメソッドの直後の初期化時に実行されます
    /// </summary>
    private void Start()
    {
        // Disable TLS cert checks only while in Unity Editor (until Unity adds support for TLS)
        // Unity Editor上ではTLS証明書チェックを無効にする(UnityがTLSのサポートを追加するまで)
#if UNITY_EDITOR
        ServicePointManager.ServerCertificateValidationCallback = delegate { return true; };
#endif

        // Set the Status text to loading, whilst attempting connection to Azure.
        // Azureへの接続を試みている間、ステータステキストを[Loading...]に設定します
        azureStatusText.text = "Loading...";

        // Creating the references necessary to log into Azure and check if the Storage Directory is empty
        // Azureにログインし、ストレージディレクトリが空であるかどうかを確認するのに必要な参照を作成する
        CreateCloudIdentityAsync();
    }

    /// <summary>
    /// Call to the Azure Function App to request a Shape.
    /// Azure Function の App を呼び出して図形を要求します
    /// </summary>
    public async void CallAzureFunctionForNextShape()
    {
        int azureRandomInt = 0;

        // Call Azure function
        // Azure function を呼び出す
        HttpWebRequest webRequest = WebRequest.CreateHttp(azureFunctionEndpoint);

        WebResponse response = await webRequest.GetResponseAsync();

        // Read response as string
        // レスポンスを文字列として読み取る
        using (Stream stream = response.GetResponseStream())
        {
            StreamReader reader = new StreamReader(stream);

            String responseString = reader.ReadToEnd();

            // parse result as integer
            // 結果を整数として解析する
            Int32.TryParse(responseString, out azureRandomInt);
        }

        // add random int from Azure to the ShapeIndexList
        // ShapeIndexList に Azure から取得したランダム int を追加する
        ShapeFactory.instance.shapeHistoryList.Add(azureRandomInt);

        ShapeFactory.instance.CreateShape(azureRandomInt, false);

        // Save to Azure storage
        // Azure Storage に保存する
        await UploadListToAzureAsync();
    }

    /// <summary>
    /// Create the references necessary to log into Azure
    /// Azure にログインするのに必要な参照を作成する
    /// </summary>
    private async void CreateCloudIdentityAsync()
    {
        // Retrieve storage account information from connection string
        // 接続文字列からストレージアカウント情報を取得する。
        storageAccount = CloudStorageAccount.Parse(storageConnectionString);

        // Create a file client for interacting with the file service.
        // ファイルサービスと対話するためのファイルクライアントを作成します
        fileClient = storageAccount.CreateCloudFileClient();

        // Create a share for organizing files and directories within the storage account.
        // ストレージアカウント内のファイルとディレクトリを整理するための共有を作成します
        share = fileClient.GetShareReference(fileShare);

        await share.CreateIfNotExistsAsync();

        // Get a reference to the root directory of the share.
        // 共有のルートディレクトリへの参照を取得します
        CloudFileDirectory root = share.GetRootDirectoryReference();

        // Create a directory under the root directory
        // ルートディレクトリの下にディレクトリを作成する
        dir = root.GetDirectoryReference(storageDirectory);

        await dir.CreateIfNotExistsAsync();

        // Check if the there is a stored text file containing the list
        // リストを含む格納されたテキストファイルがあるかどうかをチェックする
        shapeIndexCloudFile = dir.GetFileReference("TextShapeFile");

        if (!await shapeIndexCloudFile.ExistsAsync())
        {
            // File not found, enable gaze for shapes creation
            // ファイルが見つからない場合、図形作成の注視を可能にする
            Gaze.instance.GazeEnabled = true;

            azureStatusText.text = "No Shape\nFile!";
        }
        else
        {
            // The file has been found, disable gaze and get the list from the file
            // ファイルが見つかった場合、注視を無効にしてファイルからリストを取得する
            Gaze.instance.GazeEnabled = false;

            azureStatusText.text = "Shape File\nFound!";

            await ReplicateListFromAzureAsync();
        }
    }

    /// <summary>
    /// Upload the locally stored List to Azure
    /// Azure に、ローカルで保存されたリストをアップロードする
    /// </summary>
    private async Task UploadListToAzureAsync()
    {
        // Uploading a local file to the directory created above
        // 上記で作成したディレクトリにローカルファイルをアップロードする
        string listToString = string.Join(",", ShapeFactory.instance.shapeHistoryList.ToArray());

        await shapeIndexCloudFile.UploadTextAsync(listToString);
    }

    ///<summary>
    /// Get the List stored in Azure and use the data retrieved to replicate 
    /// a Shape creation pattern
    /// Azureに格納されたリストを取得し、取り出されたデータを使用してシェイプ作成パターンを複製する
    ///</summary>
    private async Task ReplicateListFromAzureAsync()
    {
        string azureTextFileContent = await shapeIndexCloudFile.DownloadTextAsync();

        string[] shapes = azureTextFileContent.Split(new char[] { ',' });

        foreach (string shape in shapes)
        {
            int i;

            Int32.TryParse(shape.ToString(), out i);

            ShapeFactory.instance.shapeHistoryList.Add(i);

            ShapeFactory.instance.CreateShape(i, true);

            await Task.Delay(500);
        }

        Gaze.instance.GazeEnabled = true;

        azureStatusText.text = "Load Complete!";
    }
}

f:id:bluebirdofoz:20181011073955j:plain

7.Visual Studio で変更を保存して Unity に戻ります。
f:id:bluebirdofoz:20181011074003j:plain

Chapter 10 はここまでです。
次回は Chapter 11 を実施します。
bluebirdofoz.hatenablog.com

公式チュートリアル「MR and Azure 305 9章」を試してみる

本日はチュートリアルの実施枠です。
Academyの「MR and Azure 305: Functions and storage」の実施内容をまとめます。
docs.microsoft.com
前回記事の続きです。
bluebirdofoz.hatenablog.com
今回は「Chapter 9」です。

Chapter 9:Create the Gaze class

最後に作成するスクリプトは、Gazeクラスです。
このクラスは、メインカメラから前方に Raycast を作成し、ユーザーがどのオブジェクトを見ているかを検出します。
Raycast は、ユーザがシーン内の GazeButton オブジェクトを見ているかどうかを識別し、動作をトリガします。

1.Script フォルダを開きます。
2.フォルダ内で右クリックして、Creapte -> C# Script を選択します。
Script の名称は ShapeFactory に設定します。
f:id:bluebirdofoz:20181010084346j:plain

3.新しいスクリプトをダブルクリックしてVisual Studioで開きます。
4-9.以下の通り、スクリプトを編集します。
・Gaze.cs

// 名前空間の追加
using UnityEngine;

public class Gaze : MonoBehaviour {
    // メンバ変数の追加
    /// <summary>
    /// Provides Singleton-like behavior to this class.
    /// このクラスをシングルトンと同じように動作させます
    /// </summary>
    public static Gaze instance;

    /// <summary>
    /// The Tag which the Gaze will use to interact with objects. Can also be set in editor.
    /// 視線がオブジェクトに作用するために使用するタグ。エディタでも設定できます
    /// </summary>
    public string InteractibleTag = "GazeButton";

    /// <summary>
    /// The layer which will be detected by the Gaze ('~0' equals everything).
    /// 注視によって検出されるレイヤー('~0'は全て)
    /// </summary>
    public LayerMask LayerMask = ~0;

    /// <summary>
    /// The Max Distance the gaze should travel, if it has not hit anything.
    /// 何も衝突していない場合、注視が働くの最大距離
    /// </summary>
    public float GazeMaxDistance = 300;

    /// <summary>
    /// The size of the cursor, which will be created.
    /// 作成されるカーソルのサイズ
    /// </summary>
    public Vector3 CursorSize = new Vector3(0.05f, 0.05f, 0.05f);

    /// <summary>
    /// The color of the cursor - can be set in editor.
    /// カーソルの色はエディタで設定できます
    /// </summary>
    public Color CursorColour = Color.HSVToRGB(0.0223f, 0.7922f, 1.000f);

    /// <summary>
    /// Provides when the gaze is ready to start working (based upon whether
    /// Azure connects successfully).
    /// 注視が機能を開始する準備が整ったときを通知します
    /// (Azureが正常に接続されたかどうかに基づきます)
    /// </summary>
    internal bool GazeEnabled = false;

    /// <summary>
    /// The currently focused object.
    /// 現在フォーカスされているオブジェクト
    /// </summary>
    internal GameObject FocusedObject { get; private set; }

    /// <summary>
    /// The object which was last focused on.
    /// 最後に焦点を合わせたオブジェクト
    /// </summary>
    internal GameObject _oldFocusedObject { get; private set; }

    /// <summary>
    /// The info taken from the last hit.
    /// 最後のヒットから得た情報
    /// </summary>
    internal RaycastHit HitInfo { get; private set; }

    /// <summary>
    /// The cursor object.
    /// カーソルオブジェクト
    /// </summary>
    internal GameObject Cursor { get; private set; }

    /// <summary>
    /// Provides whether the raycast has hit something.
    /// レイキャストが何かにヒットしたかどうかを示します
    /// </summary>
    internal bool Hit { get; private set; }

    /// <summary>
    /// This will store the position which the ray last hit.
    /// レイキャストが最後に当たった位置が保存されます。
    /// </summary>
    internal Vector3 Position { get; private set; }

    /// <summary>
    /// This will store the normal, of the ray from its last hit.
    /// 最後のヒットのレイキャストの法線を保存します。
    /// </summary>
    internal Vector3 Normal { get; private set; }

    /// <summary>
    /// The start point of the gaze ray cast.
    /// 注視線のキャストの開始点
    /// </summary>
    private Vector3 _gazeOrigin;

    /// <summary>
    /// The direction in which the gaze should be.
    /// 凝視するべき方向
    /// </summary>
    private Vector3 _gazeDirection;

    /// <summary>
    /// The method used after initialization of the scene, though before Start().
    /// Start()の前、シーンの初期化後に使用されるメソッド
    /// </summary>
    private void Awake()
    {
        // Set this class to behave similar to singleton
        // このクラスをシングルトンと同じように動作させます
        instance = this;
    }

    /// <summary>
    /// Start method used upon initialization.
    /// 初期化時に使用されるStartメソッド
    /// </summary>
    private void Start()
    {
        FocusedObject = null;
        Cursor = CreateCursor();
    }

    /// <summary>
    /// Method to create a cursor object.
    /// カーソルオブジェクトを作成するメソッド
    /// </summary>
    /// <returns></returns>
    private GameObject CreateCursor()
    {
        GameObject newCursor = GameObject.CreatePrimitive(PrimitiveType.Sphere);
        newCursor.SetActive(false);

        // Remove the collider, so it doesn't block raycast.
        // コライダーを取り外すので、レイキャストはブロックされません
        Destroy(newCursor.GetComponent<SphereCollider>());
        newCursor.transform.localScale = CursorSize;

        newCursor.GetComponent<MeshRenderer>().material = new Material(Shader.Find("Diffuse"))
        {
            color = CursorColour
        };

        newCursor.name = "Cursor";

        newCursor.SetActive(true);

        return newCursor;
    }

    /// <summary>
    /// Called every frame
    /// フレームごとに呼び出されます
    /// </summary>
    private void Update()
    {
        if (GazeEnabled == true)
        {
            _gazeOrigin = Camera.main.transform.position;

            _gazeDirection = Camera.main.transform.forward;

            UpdateRaycast();
        }
    }

    /// <summary>
    /// Raycastを投影し、ヒットターゲットを検出します
    /// </summary>
    private void UpdateRaycast()
    {
        // Set the old focused gameobject.
        // 古い焦点を合わせたゲームオブジェクトを設定します
        _oldFocusedObject = FocusedObject;

        RaycastHit hitInfo;

        // Initialise Raycasting.
        // レイキャストを初期化する。
        Hit = Physics.Raycast(_gazeOrigin,
            _gazeDirection,
            out hitInfo,
            GazeMaxDistance, LayerMask);

        HitInfo = hitInfo;

        // Check whether raycast has hit.
        // レイキャストがヒットしたかどうかを確認します
        if (Hit == true)
        {
            Position = hitInfo.point;

            Normal = hitInfo.normal;

            // Check whether the hit has a collider.
            // ヒットしたコライダーがあるかどうかを確認します
            if (hitInfo.collider != null)
            {
                // Set the focused object with what the user just looked at.
                // フォーカスオブジェクトを、ユーザーが見たものに設定します
                FocusedObject = hitInfo.collider.gameObject;
            }
            else
            {
                // Object looked on is not valid, set focused gameobject to null.
                // 見つかったオブジェクトが有効でない場合、フォーカスオブジェクトをnullに設定します
                FocusedObject = null;
            }
        }
        else
        {
            // No object looked upon, set focused gameobject to null.
            // オブジェクトが見つからない場合、フォーカスオブジェクトをnullに設定します
            FocusedObject = null;

            // Provide default position for cursor.
            // カーソルのデフォルト位置を指定します
            Position = _gazeOrigin + (_gazeDirection * GazeMaxDistance);

            // Provide a default normal.
            // デフォルトの法線を指定します
            Normal = _gazeDirection;
        }

        // Lerp the cursor to the given position, which helps to stabilize the gaze.
        // レイキャストが当たった位置にカーソルを移動させます。注視を安定させるのに役立ちます
        Cursor.transform.position = Vector3.Lerp(Cursor.transform.position, Position, 0.6f);

        // Check whether the previous focused object is this same 
        //    object. If so, reset the focused object.
        // 前回のフォーカスされたオブジェクトが同じオブジェクトであるかどうかを確認します
        // その場合は、フォーカスされたオブジェクトをリセットします
        if (FocusedObject != _oldFocusedObject)
        {
            ResetFocusedObject();

            if (FocusedObject != null)
            {
                if (FocusedObject.CompareTag(InteractibleTag.ToString()))
                {
                    // Set the Focused object to green - success!
                    // フォーカスされたオブジェクトを緑色に設定します
                    FocusedObject.GetComponent<Renderer>().material.color = Color.green;

                    // Start the Azure Function, to provide the next shape!
                    // 次の図形を提供するためにAzure関数を開始してください
                    AzureServices.instance.CallAzureFunctionForNextShape();
                }
            }
        }
    }

    /// <summary>
    /// Reset the old focused object, stop the gaze timer, and send data if it
    /// is greater than one.
    /// 古いフォーカスされたオブジェクトをリセットし、視線タイマーを停止します
    /// データが 1 より大きい場合はデータを送信します
    /// </summary>
    private void ResetFocusedObject()
    {
        // Ensure the old focused object is not null.
        // 古いフォーカスオブジェクトがnullでないことを確認します
        if (_oldFocusedObject != null)
        {
            if (_oldFocusedObject.CompareTag(InteractibleTag.ToString()))
            {
                // Set the old focused object to red - its original state.
                // 古いフォーカスされたオブジェクトを赤色(元の状態)に設定します
                _oldFocusedObject.GetComponent<Renderer>().material.color = Color.red;
            }
        }
    }
}

f:id:bluebirdofoz:20181010084401j:plain

10.Visual Studio で変更を保存して Unity に戻ります。
f:id:bluebirdofoz:20181010084412j:plain

11.Hierarchy パネルの MixedRealityCameraParent を開くと、直下に MixedRealityCamera オブジェクトがあります。
Gaze スクリプトをこの MixedRealityCamera オブジェクトにドラッグして追加します。
f:id:bluebirdofoz:20181010084421j:plain

Chapter 9 はここまでです。
次回は Chapter 10 を実施します。
bluebirdofoz.hatenablog.com

公式チュートリアル「MR and Azure 305 8章」を試してみる

本日はチュートリアルの実施枠です。
Academyの「MR and Azure 305: Functions and storage」の実施内容をまとめます。
docs.microsoft.com
前回記事の続きです。
bluebirdofoz.hatenablog.com
今回は「Chapter 8」です。

Chapter 8:Create the ShapeFactory class

作成する次のスクリプトは、ShapeFactoryクラスです。
このクラスの役割は、要求されたときに新しいシェイプを作成し、履歴リストで作成されたシェイプの履歴を保持することです。
シェイプが作成されるたびに、履歴リストが AzureService クラスで更新され、Azure ストレージに保存されます。
アプリケーションが起動すると、Azure Storage に保存されている履歴リストを取得します。
履歴リストが見つかると、生成されたシェイプがストレージのものか新しいものかを3D Textオブジェクトが通知します。

1.Script フォルダを開きます。
2.フォルダ内で右クリックして、Creapte -> C# Script を選択します。
Script の名称は ShapeFactory に設定します。
f:id:bluebirdofoz:20181009040423j:plain

3.新しいスクリプトをダブルクリックしてVisual Studioで開きます。
4-6.以下の通り、スクリプトを編集します。
・ShapeFactory.cs

// 名前空間の追加
using System;
using System.Collections.Generic;
using UnityEngine;

public class ShapeFactory : MonoBehaviour {
    // メンバ変数の追加
    /// <summary>
    /// Provide this class Singleton-like behaviour
    /// このクラスをシングルトンと同じように動作させます
    /// </summary>
    [HideInInspector]
    public static ShapeFactory instance;

    /// <summary>
    /// Provides an Inspector exposed reference to ShapeSpawnPoint
    /// InspectorにShapeSpawnPointへの参照を提供する
    /// </summary>
    [SerializeField]
    public Transform spawnPoint;

    /// <summary>
    /// Shape History Index
    /// シェイプ履歴のインデックス
    /// </summary>
    [HideInInspector]
    public List<int> shapeHistoryList;

    /// <summary>
    /// Shapes Enum for selecting required shape
    /// 必要な形状を選択するための図形列挙型
    /// </summary>
    private enum Shapes { Cube, Sphere, Cylinder }


    /// <summary>
    /// Called on initialization
    /// 初期化処理
    /// </summary>
    private void Awake()
    {
        instance = this;
    }

    /// <summary>
    /// Runs at initialization right after Awake method
    /// StartメソッドはAwakeメソッドの直後の初期化時に実行されます
    /// </summary>
    private void Start()
    {
        shapeHistoryList = new List<int>();
    }

    /// <summary>
    /// Use the Shape Enum to spawn a new Primitive object in the scene
    /// シェイプ列挙型を使用してシーン内に新しいプリミティブオブジェクトを生成する
    /// </summary>
    /// <param name="shape">Enumerator Number for Shape(図形の列挙子番号)</param>
    /// <param name="storageShape">Provides whether this is new or old(新しいものか古いものか)</param>
    internal void CreateShape(int shape, bool storageSpace)
    {
        Shapes primitive = (Shapes)shape;
        GameObject newObject = null;
        string shapeText = storageSpace == true ? "Storage: " : "New: ";

        AzureServices.instance.azureStatusText.text = string.Format("{0}{1}", shapeText, primitive.ToString());

        switch (primitive)
        {
            case Shapes.Cube:
                newObject = GameObject.CreatePrimitive(PrimitiveType.Cube);
                break;

            case Shapes.Sphere:
                newObject = GameObject.CreatePrimitive(PrimitiveType.Sphere);
                break;

            case Shapes.Cylinder:
                newObject = GameObject.CreatePrimitive(PrimitiveType.Cylinder);
                break;
        }

        if (newObject != null)
        {
            newObject.transform.position = spawnPoint.position;

            newObject.transform.localScale = new Vector3(0.5f, 0.5f, 0.5f);

            newObject.AddComponent<Rigidbody>().useGravity = true;

            newObject.GetComponent<Renderer>().material.color = UnityEngine.Random.ColorHSV(0f, 1f, 1f, 1f, 0.5f, 1f);
        }
    }
}

f:id:bluebirdofoz:20181009040436j:plain

7.Visual Studio で変更を保存して Unity に戻ります。
f:id:bluebirdofoz:20181009040452j:plain

8.Hierarchy パネルの MixedRealityCameraParent を開くと、直下に MixedRealityCamera オブジェクトがあります。
ShapeFactory スクリプトをこの MixedRealityCamera オブジェクトにドラッグして追加します。
f:id:bluebirdofoz:20181009040500j:plain

9.Hierarchy パネルの GazeButton を開きます。
直下にある ShapeSpawnPoint オブジェクトを ShapeFactory スクリプトの SpawnPoint 変数に設定します。
f:id:bluebirdofoz:20181009040513j:plain

Chapter 8 はここまでです。
次回は Chapter 9 を実施します。
bluebirdofoz.hatenablog.com