HoloLens (第 1 世代) と Azure 305: 機能とストレージ


Note

Mixed Reality Academy のチュートリアルは、HoloLens (第 1 世代) と Mixed Reality イマーシブ ヘッドセットを念頭に置いて編成されています。 そのため、それらのデバイスの開発に関するガイダンスを引き続き探している開発者のために、これらのチュートリアルをそのまま残しておくことが重要だと考えています。 これらのチュートリアルが、HoloLens 2 に使用されている最新のツールセットや操作に更新されることは "ありません"。 これらは、サポートされているデバイス上で継続して動作するように、保守されます。 今後、HoloLens 2 向けに開発する方法を示す新しいチュートリアル シリーズが投稿される予定です。 この通知は、それらのチュートリアルが投稿されたときにリンクと共に更新されます。


最終製品 -start

このコースでは、Mixed Reality アプリケーションの中で、Azure Functions を作成して使用し、Azure Storage リソース内にデータを格納する方法について学習します。

Azure Functions は Microsoft のサービスであり、これを使用することで、開発者は Azure で小さなコードである 「関数」 を実行できます。 これにより、ローカル アプリケーションではなく、クラウドに作業を委任することができ、多くの利点を得られます。 Azure Functions は、C#、F#、Node.js、Java、PHP などのいくつかの開発言語をサポートします。 詳細については、Azure Functions の記事を参照してください。

Azure Storage は Microsoft のクラウド サービスであり、これを使用することで開発者は、高い可用性、安全性、耐久性、拡張性、冗長性を保証されたデータを保存することができます。 これは、Microsoft がすべてのメンテナンスと重大な問題の対処を行うということです。 詳細については、Azure Storage の記事を参照してください。

このコースを修了すると、次のことができる Mixed Reality イマーシブ ヘッドセット アプリケーションを手に入れることができます。

  1. ユーザーがシーン内を見渡せるようにします。
  2. ユーザーが 3D の「ボタン」を見つめると、オブジェクトが生成されるようにします。
  3. 生成されるオブジェクトは、Azure Function によって選択されます。
  4. 各オブジェクトが生成されると、アプリケーションによって、Azure Storage にある Azure File にオブジェクトの種類が保存されます。
  5. 2 回目のロード時には、Azure File のデータが取得され、アプリケーションの前のインスタンスの生成アクションを再生するために使用されます。

アプリケーションでは、結果を設計にどのように統合するかはあなた次第です。 このコースは、Azure のサービスを Unity プロジェクトに統合する方法を学べることを目的としています。 このコースで得られた知識を使用して、ご自分の Mixed Reality アプリケーションを強化しましょう。

デバイス サポート

コース HoloLens イマーシブ ヘッドセット
MR と Azure 305:機能とストレージ ✔️ ✔️

注意

このコースでは Windows Mixed Reality イマーシブ (VR) ヘッドセットを中心に取り上げていますが、このコースで習得したことは、Microsoft HoloLens にも応用できます。 このコースに取り組む過程で、HoloLens をサポートするために採用する必要のある変更に関するノートが表示されます。

前提条件

Note

このチュートリアルは、Unity と C# の基本的な使用経験がある開発者を対象としています。 また、このドキュメント内の前提条件や文章による説明は、執筆時 (2018 年 5 月) にテストおよび検証された内容であることをご了承ください。 「ツールのインストール」の記事に記載されているように、お客様は最新のソフトウェアを自由に使用できます。ただし、このコースの情報は、以下に記載されているものよりも新しいソフトウェアで見つかったものと完全に一致するとは限りません。

このコースでは、次のハードウェアとソフトウェアをお勧めします。

開始する前に

このプロジェクトのビルドで問題が発生しないように、このチュートリアルで説明したプロジェクトをルートフォルダーまたはルートに近いフォルダーに作成することを強くお勧めします (フォルダー パスが長いと、ビルド時に問題が発生する可能性があります)。

第 1 章 - Azure portal

Azure Storage サービスを利用するには、Azure portal で Storage アカウントを作成および設定する必要があります。

  1. Azure Portal にログインします。

    Note

    まだ Azure アカウントをお持ちでない方は、作成する必要があります。 このチュートリアルを教室やラボで受講している場合は、インストラクターや監督者に新しいアカウントの設定についてサポートを依頼してください。

  2. ログインしたら、左上隅にある [新規作成] をクリックして、ストレージ アカウントを検索し、Enter をクリックします。

    Azure Storage の検索

    Note

    新しいポータルでは、[新規作成] という文字列が [リソースの作成] に置き換えられることがあります。

  3. 新しいページに、Azure Storage アカウント サービスの説明が表示されます。 このプロンプトの左下にある [作成] ボタンを選択すると、このサービスとの関連付けが作成されます。

    サービスの作成

  4. [作成] をクリックしたら、以下の手順を実行します。

    1. ご自分のアカウントの [名前] を入力します。このフィールドには数字と小文字しか入力できないことに注意してください。

    2. [デプロイ モデル][リソース マネージャー] を選択します。

    3. [アカウントの種類][ストレージ (汎用 v1)] を選択します。

    4. リソース グループの [場所] を決定します (新しいリソース グループを作成する場合)。 この場所は、アプリケーションが実行されるリージョン内が理想的です。 一部の Azure 資産は、特定の地域でしか利用できません。

    5. [レプリケーション] には、[読み取りアクセス geo 冗長ストレージ (RA-GRS)] を選択します。

    6. [パフォーマンス] には [Standard] を選択します

    7. [安全な転送が必須][無効] のままにしておきます。

    8. サブスクリプションを選択します。

    9. [リソース グループ] を選択するか、新規に作成します。 リソース グループは、Azure アセットのコレクションの監視、アクセス制御、プロビジョニング、課金管理を行う方法を提供します。 1 つのプロジェクト (これらのラボなど) に関連するすべての Azure サービスを共通のリソース グループの下に置くことをお勧めします。

      Azure リソース グループの詳細については、リソース グループに関する記事を参照してください。

    10. また、本サービスに適用される利用規約を理解していることを確認する必要があります。

    11. [作成] を選択します

      入力サービス情報

  5. [作成] をクリックしたら、サービスが作成されるのを待つ必要があります。これには 1 分ほどかかります。

  6. サービスのインスタンスが作成されると、ポータルに通知が表示されます。

    Azure portal での新しい通知

  7. 通知をクリックして、新しいサービス インスタンスを確認します。

    リソースに移動する

  8. 通知の[リソースに移動]ボタンをクリックして、新しいサービスインスタンスを探します。 新しいストレージ アカウント サービス インスタンスの概要ページが表示されます。

    アクセス キー

  9. [アクセス キー] をクリックして、このクラウド サービスのエンドポイントを表示します。 [メモ帳] または同様の方法を使用して、後で使用するためにいずれかのキーをコピーします。 また、後で作成する AzureServices クラスで使用するため、接続文字列の値もメモしておきます。

    copy 接続文字列

第 2 章 - Azure Function を設定する

ここでは、Azure Service で AzureFunction を記述します。

Azure Function を使用すると、コード内で従来の関数を使用した場合とほぼ同じ処理を行うことができます。異なるのは、この関数は、Azure アカウントにアクセスするための資格情報を持つすべてのアプリケーションからアクセスできるということです。

Azure 関数を作成するには、以下の手順を実行します。

  1. Azure portal で、左上の [新規作成] をクリックし、Function App と検索して Enter キーを押します。

    関数アプリを作成する

    Note

    新しいポータルでは、[新規作成] という文字列が [リソースの作成] に置き換えられることがあります。

  2. 新しいページには、Azure Function App サービスの説明が表示されます。 このプロンプトの左下にある [作成] ボタンを選択すると、このサービスとの関連付けが作成されます。

    関数アプリ情報

  3. [作成] をクリックしたら、以下の手順を実行します。

    1. アプリ名を指定します。 ここで使用できるのは、英数字のみです (大文字でも小文字でもかまいません)。

    2. 使用する サブスクリプションを選択します。

    3. リソース グループ を選択するか、新規に作成します。 リソース グループは、Azure アセットのコレクションの監視、アクセス制御、プロビジョニング、課金管理を行う方法を提供します。 1 つのプロジェクト (これらのラボなど) に関連するすべての Azure サービスを共通のリソース グループの下に置くことをお勧めします。

      Azure リソース グループの詳細については、リソース グループに関する記事を参照してください。

    4. この演習では、選択する OS として Windows を選択します。

    5. [ホスティング プラン] として [従量課金プラン] を選択します。

    6. リソース グループの [場所] を決定します (新しいリソース グループを作成する場合)。 この場所は、アプリケーションが実行されるリージョン内が理想的です。 一部の Azure 資産は、特定の地域でしか利用できません。 最適なパフォーマンスを得るためには、ストレージ アカウントと同じリージョンを選択します。

    7. ストレージ には、[既存のものを使用] を選択し、ドロップダウン メニューから、以前に作成したストレージを探します。

    8. この演習では、Application Insights をオフにしておきます。

      入力関数アプリの詳細

  4. [作成] ボタンをクリックします。

  5. [作成] をクリックすると、サービスが作成されるまで待つ必要があります。これには1分ほどかかる場合があります。

  6. サービスのインスタンスが作成されると、ポータルに通知が表示されます。

    新しい Azure portal 通知

  7. 通知をクリックして、新しいサービス インスタンスを確認します。

    リソース関数アプリに移動する

  8. 通知の[リソースに移動]ボタンをクリックして、新しいサービスインスタンスを探します。 新しい Function App サービス インスタンスが表示されます。

  9. Function App のダッシュボードで、左側のパネルにある Functions にマウス カーソルを合わせ、+ (プラス) シンボルをクリックします。

    新しい関数を作成する

  10. 次のページで、[Webhook + API] が選択されていることを確認し、[言語の選択] では、このチュートリアルで使用する言語である [CSharp] を選択します。 最後に、[この関数を作成する] ボタンをクリックします。

    Web フック csharp を選択する

  11. コード ページ (run.csx) が表示されます。表示されない場合は、左のパネルにある関数リストから、新しく作成した関数をクリックします。

    新しい関数を開く

  12. 関数に次のコードをコピーします。 この関数は、呼び出されたときに、0 から 2 までのランダムな整数を返します。 既存のコードは無視して問題ないため、上から貼り付けてもかまいません。

        using System.Net;
        using System.Threading.Tasks;
    
        public static int Run(CustomObject req, TraceWriter log)
        {
            Random rnd = new Random();
            int randomInt = rnd.Next(0, 3);
            return randomInt;
        }
    
        public class CustomObject
        {
            public String name {get; set;}
        }
    
  13. [保存] を選択します。

  14. 結果は次の図のようになります。

  15. [関数の URL の取得] をクリックし、表示される エンドポイント をメモしておきます。 これは、このコースで後ほど作成する AzureServices クラスに挿入する必要があります。

    関数エンドポイントを取得する

    関数エンドポイントを挿入する

第 3 章 - Unity プロジェクトを設定する

次に示すのは、Mixed Reality で開発するための一般的な設定であり、他のプロジェクトのテンプレートとして利用できます。

Mixed Reality イマーシブ ヘッドセットをセットアップしてテストします。

Note

このコースでは、モーション コントローラーは必要ありません。 イマーシブ ヘッドセットの設定についてサポートが必要な場合は、Mixed Reality の設定方法に関する記事を参照してください。

  1. Unity を開いて、[新規] をクリックします。

    新しい Unity プロジェクトを作成する

  2. ここで、Unity プロジェクト名を指定する必要があります。 MR_Azure_Functions を挿入します。 プロジェクトの種類が [3D] に設定されていることを確認します。 [Location] (場所) を適切な場所に設定します (ルート ディレクトリに近い方が適しています)。 次に、[プロジェクトの作成] をクリックします。

    新しい Unity プロジェクトに名前を付ける

  3. Unity を開いた状態で、既定のスクリプト エディターVisual Studio に設定されているかどうか確認することをお勧めします。 [Edit] (編集)>[Preferences] (環境設定) に移動し、新しいウィンドウで [External Tools] (外部ツール) に移動します。 [外部スクリプト エディター][Visual Studio 2017] に変更します。 [環境設定] ウィンドウを閉じます。

    Visual Studio をスクリプト エディターとして設定する

  4. 次に、[ファイル の>ビルド設定] に移動し、[プラットフォームの切り替え] ボタンをクリックして、プラットフォームをユニバーサル Windows プラットフォーム切り替えます。

    プラットフォームを UWP に切り替える

  5. [File] (ファイル)>[Build Settings] (ビルド設定) に移動して、次を確認します。

    1. [ターゲット デバイス][任意のデバイス] に設定されている。

      Microsoft HoloLens の場合は、[ターゲット デバイス]HoloLens に設定します。

    2. [ビルドの種類][D3D] に設定されている

    3. [SDK][Latest installed] (最新のインストール) に設定されている

    4. [Visual Studio Version] (Visual Studio のバージョン)[Latest installed] (最新のインストール) に設定されている

    5. [Build and Run] (ビルドと実行)[Local Machine] (ローカル マシン) に設定されている

    6. シーンを保存し、ビルドに追加します。

      1. これを行うには、[Add Open Scenes] (開いているシーンを追加) を選択します。 保存ウィンドウが表示されます。

        開いているシーンを追加する

      2. これと、今後のシーン用の新しいフォルダーを作成し、[新しいフォルダー] ボタンを選択して、新しいフォルダーを作成し、「Scenes」という名前を付けます。

        scenes フォルダーを作成する

      3. 新しく作成した Scenes フォルダーを開き、[ファイル名] テキスト フィールドに「FunctionsScene」と入力して [保存] をクリックします。

        関数シーンを保存する

  6. [Build Settings] (ビルド設定) の残りの設定は、ここでは既定値のままにしておきます。

    既定のビルド設定のままにする

  7. [ビルド設定] ウィンドウで、[プレーヤー設定] ボタンをクリックすると、[インスペクター] が配置されているスペースに関連パネルが表示されます。

    インスペクターのプレーヤー設定

  8. このパネルでは、いくつかの設定を確認する必要があります。

    1. [その他の設定] タブで:

      1. [スクリプト ランタイム バージョン][試験段階 (.NET 4.6 と同等)] である (この場合、エディターの再起動が必要になります)。
      2. [スクリプト バックエンド][.NET] である
      3. [API 互換性レベル][.NET 4.6] である
    2. [公開設定] タブ内の [機能] で、次の内容を確認します。

      • InternetClient

        set 機能

    3. パネルのさらに下にある [XR 設定] ([公開設定] の下) で、[Virtual Reality サポート] をオンにし、Windows Mixed Reality SDK が追加されていることを確認します。

      XR 設定を設定する

  9. [Build Settings] (ビルド設定) に戻ると、"Unity C# プロジェクト" に適用されていた灰色表示が解除されています。この横にあるチェック ボックスをオンにします。

    ティック c# プロジェクト

  10. [ビルド設定] ウィンドウを閉じます。

  11. シーンとプロジェクトを保存します ([FILE](ファイル)>[SAVE SCENE / FILE](シーン/ファイルの保存)>[SAVE PROJECT](プロジェクトの保存))。

第 4 章 - Main Camera を設定する

重要

このコースで 「Unity の設定」 コンポーネントをスキップして、そのままコードに進みたい場合は、この .unitypackage をダウンロードして、カスタム パッケージとしてプロジェクトにインポートしてください。 これには、次の章の DLL も含まれます。 インポート後、第 7 章から手順を進めてださい。

  1. Hierarchy (階層) パネルの中には、[Main Camera](メイン カメラ) という名前のオブジェクトがあります。このオブジェクトは、あなたがアプリケーションの中にいるときの 「頭」 の視点を表しています。

  2. 目の前にある Unity ダッシュボードを使用して、[Main Camera GameObject] を選択します。 [インスペクター] パネル (通常はダッシュボードの右側にあります) には、GameObject のさまざまなコンポーネントが表示されます。これらは、[変換][カメラ]、その他いくつかのコンポーネントの順に表示されています。 Main Camera の Transform (変換) をリセットして、正しい位置に配置する必要があります。

  3. これを行うには、カメラの Transform コンポーネントの隣にある歯車アイコンを選択し、[Reset](リセット) を選択します。

    変換のリセット

  4. 次に、以下のように [Transform](変換) コンポーネントを更新します。

変換 - 位置

X Y Z
0 1 0

変換 - 回転

X Y Z
0 0 0

変換 - スケール

X Y Z
1 1 1

カメラ変換を設定する

第 5 章 - Unity シーンを設定する

  1. [階層パネル]の何もない領域を右クリックし、[3D オブジェクト][平面] の順に選択して追加します。

    新しい平面を作成する

  2. [Plane](平面) オブジェクトを選択した状態で、Inspector (インスペクター) パネルの以下のパラメーターを変更します。

変換 - 位置

X Y Z
0 0 4

変換 - スケール

X Y Z
10 1 10

平面の位置とスケールを設定する

平面のシーン ビュー

  1. Hierarchy (階層) パネルで空の領域を右クリックして、[3D Object](3D オブジェクト) の下から [Cube](キューブ) を追加します。

    1. キューブの名前を「GazeButton」に変更します (キューブを選択した状態で「F2」キーを押します)。

    2. インスペクター パネル[Transform Position]\(位置の変換\) の次のパラメーターを変更します。

      X Y Z
      0 3 5

      視線入力ボタンの変換を設定する

      視線入力ボタンのシーン ビュー

    3. [ タグ ] ドロップダウン ボタンをクリックし、[タグの 追加] をクリックして、[ タグ] & [レイヤー] ウィンドウを開きます

      新しいタグを追加する

      プラスを選択する

    4. + (プラス) ボタンを選択し、New Tag Name (新しいタグ名) フィールドに「GazeButton」と入力して、[Save](保存) を押します。

      新しいタグに名前を付けます

    5. Hierarchy (階層) パネルで GazeButton オブジェクトをクリックし、Inspector (インスペクター) パネル で、新しく作成した GazeButton タグを割り当てます。

      視線入力ボタンを新しいタグに割り当てる

  2. Hierarchy (階層) パネルで GazeButton オブジェクトを右クリックし、空の GameObject を追加します (これは、「」 オブジェクトとして追加されます)。

  3. 新しいオブジェクトを選択し、名前を「ShapeSpawnPoint」に変更します。

    1. インスペクター パネル[Transform Position]\(位置の変換\) の次のパラメーターを変更します。

      X Y Z
      0 -1 0

      図形の生成ポイント変換を更新する

      図形の生成ポイント シーン ビュー

  4. 次に、Azure サービスの状態をフィードバックするための [3D Text](3D テキスト) オブジェクトを作成します。

    Hierarchy (階層) パネルで GazeButton を右クリックし、[3D Object](3D オブジェクト)>[3D Text](3D テキスト) を 「」 として追加します。

    新しい 3D テキスト オブジェクトを作成する

  5. [3D Text](3D テキスト) オブジェクトの名前を「AzureStatusText」に変えます。

  6. AzureStatusText オブジェクトの Transform Position を次のように変更します。

    X Y Z
    0 0 -0.6
  7. AzureStatusText オブジェクトの Transform Scale を次のように変更します。 |X | Y | Z | |:---: |:---: |:---: | |0.1 |0.1 |0.1 |

    Note

    以下のテキスト メッシュ コンポーネントが更新されると修正されるため、今は中心がずれていても問題ありません。

  8. [Text Mesh](テキスト メッシュ) コンポーネントを以下に合わせて変更します。

    テキスト メッシュ コンポーネントを設定する

    ヒント

    ここでは、16 進数カラーで 000000FF を選択していますが、これは自由に選択できます (読みやすい色を選択するようにします)。

  9. [階層] パネルの構造は次のようになります。

    階層内のテキスト メッシュ

  10. シーンは次のようになります。

    シーン ビューのテキスト メッシュ

第 6 章 - Azure Storage for Unity をインポートする

ここでは、Azure Storage for Unity (これ自体も、.Net SDK for Azure を利用しています) を使用します。 この詳細については、Azure Storage for Unity に関する記事を参照してください。

現在、Unity には、インポート後にプラグインを再構成しなければならないという既知の問題があります。 バグが解決された後は、これらの手順 (このセクションの 4 - 7) は不要になります。

SDK を独自のプロジェクトにインポートするには、GitHub から最新の '.unitypackage' をダウンロードしていることを確認します。 次に、以下を実行します。

  1. [アセット]>[パッケージのインポート]>[カスタム パッケージ] メニュー オプションを使用して、.unitypackage ファイルを Unity に追加します。

  2. ポップアップ表示される [Import Unity Package] (Unity パッケージのインポート) ボックスで、[Plugin] (プラグイン)>[Storage] (ストレージ) の下にあるすべてを選択できます。 このコースでは不要なため、その他のすべてをオフにします。

    パッケージへのインポート

  3. [インポート] ボタンをクリックして、各項目をプロジェクトに追加します。

  4. [Project](プロジェクト) ビューの Plugins の下の Storage フォルダーに移動して、以下のプラグインのみを選択します。

    • Microsoft.Data.Edm

    • Microsoft.Data.OData

    • Microsoft.WindowsAzure.Storage

    • Newtonsoft.Json

    • System.Spatial

      [任意のプラットフォーム] をオフにする

  5. "これらの特定のプラグイン" を選択した状態で、[Any Platform] (任意のプラットフォーム)オフにし、[WSAPlayer]オフにして、[Apply] (適用) をクリックします。

    プラットフォーム dll を適用する

    Note

    Unity エディターで使用するためにのみこれらの特定のプラグインをマークしています。 これは、プロジェクトが Unity からエクスポートされた後に使用される、同じプラグインの異なるバージョンが WSA フォルダー内にあるためです。

  6. Storage プラグイン フォルダーで、以下のみを選択します。

    • Microsoft.Data.Services.Client

      set は dll の処理を行わない

  7. [Platform Settings] (プラットフォームの設定) の下の [Don't Process] (処理しない) ボックスをオンにして、[Apply] (適用) をクリックします。

    処理を適用しない

    Note

    Unity アセンブリ パッチャーではこのプラグインを処理するのが困難であるため、このプラグインを [Don't Process](処理しない) としてマークしています。 処理されていなくてもこのプラグインは動作します。

第 7 章 - AzureServices クラスを作成する

最初に作成するクラスは AzureServices クラスです。

AzureServices クラスは、以下の役割を担います。

  • Azure アカウントの資格情報の保管。

  • Azure App 関数の呼び出し。

  • Azure Cloud Storage のデータ ファイルのアップロードとダウンロード。

このクラスを作成するには、以下の手順を実行します。

  1. [Project](プロジェクト) パネルにある Asset フォルダーを右クリックし、[Create](作成)>[Folder](フォルダー) の順にクリックします。 フォルダーに「Scripts」という名前を付けます。

    新しいフォルダーを作成する

    call フォルダー - スクリプト

  2. 先ほど作成したフォルダーをダブルクリックして開きます。

  3. フォルダー内を右クリックし、[Create](作成)>[C# Script](C# スクリプト) を選択します。 スクリプトの名前を「AzureServices」にします。

  4. 新しい AzureServices クラスをダブルクリックして、Visual Studio で開きます。

  5. AzureServices の先頭に次の名前空間を追加します。

        using System;
        using System.Threading.Tasks;
        using UnityEngine;
        using Microsoft.WindowsAzure.Storage;
        using Microsoft.WindowsAzure.Storage.File;
        using System.IO;
        using System.Net;
    
  6. AzureServices クラス内に次の Inspector フィールドを追加します。

        /// <summary>
        /// Provides Singleton-like behavior to this class.
        /// </summary>
        public static AzureServices instance;
    
        /// <summary>
        /// Reference Target for AzureStatusText Text Mesh object
        /// </summary>
        public TextMesh azureStatusText;
    
  7. 次に、AzureServices クラス内に次のメンバー変数を追加します。

        /// <summary>
        /// Holds the Azure Function endpoint - Insert your Azure Function
        /// Connection String here.
        /// </summary>
    
        private readonly string azureFunctionEndpoint = "--Insert here you AzureFunction Endpoint--";
    
        /// <summary>
        /// Holds the Storage Connection String - Insert your Azure Storage
        /// Connection String here.
        /// </summary>
        private readonly 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;
    

    重要

    endpointconnection string の値を、Azure portal にある Azure ストレージの値に置き換えてください。

  8. Awake() メソッドと Start() メソッドのコードを追加する必要があります。 これらのメソッドは、クラスの初期化時に呼び出されます。

        private void Awake()
        {
            instance = this;
        }
    
        // Use this for initialization
        private void Start()
        {
            // Set the Status text to loading, whilst attempting connection to Azure.
            azureStatusText.text = "Loading...";
        }
    
        /// <summary>
        /// Call to the Azure Function App to request a Shape.
        /// </summary>
        public async void CallAzureFunctionForNextShape()
        {
    
        }
    

    重要

    CallAzureFunctionForNextShape() のコードについては、今後の章で説明します。

  9. Update() メソッドは、このクラスでは使用しないので削除します。

  10. Unity に戻る前に、Visual Studio に変更を保存します。

  11. Scripts フォルダーの AzureServices クラスをクリックして、Hierarchy (階層) パネルの Main Camera オブジェクトにドラッグします。

  12. Main Camera (メイン カメラ) を選択し、GazeButton オブジェクトの下にある AzureStatusText 子オブジェクトをつかんで、"Inspector (インスペクター)" 内の AzureStatusText 参照ターゲット フィールド内に配置し、AzureServices スクリプトへの参照を指定します。

    Azure 状態テキスト参照ターゲットを割り当てる

第 8 章 - ShapeFactory クラスを作成する

次に作成するスクリプトは、ShapeFactory スクリプトです。 このクラスの役割は、要求に応じて新しい形状を作成し、作成した形状の履歴を 「Shape History (形状の履歴) リスト」 に格納することです。 形状が作成されるたびに、"Shape History (形状の履歴) リスト" が AzureService クラスで更新され、Azure Storage に格納されます。 アプリケーションの起動時に Azure Storage にファイルが保存されていると、"Shape History (形状の履歴) リスト" が取得および再生され、[3D Text](3D テキスト) オブジェクトに、生成された形状がストレージからのものか、新規のものかが表示されます。

このクラスを作成するには、以下の手順を実行します。

  1. 先ほど作成した Scripts フォルダーに移動します。

  2. フォルダー内を右クリックし、[Create](作成)>[C# Script](C# スクリプト) を選択します。 スクリプトの名前を「ShapeFactory」にします。

  3. 新しい ShapeFactory スクリプトをダブルクリックして、それを Visual Studio で開きます。

  4. ShapeFactory クラスに、次の名前空間が含まれていることを確認します。

        using System.Collections.Generic;
        using UnityEngine;
    
  5. ShapeFactory クラスに以下の変数を追加し、Start()Awake() 関数を以下のように置き換えます。

        /// <summary>
        /// Provide this class Singleton-like behaviour
        /// </summary>
        [HideInInspector]
        public static ShapeFactory instance;
    
        /// <summary>
        /// Provides an Inspector exposed reference to 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 }
    
        private void Awake()
        {
            instance = this;
        }
    
        private void Start()
        {
            shapeHistoryList = new List<int>();
        }
    
  6. CreateShape() メソッドは、指定された "整数" のパラメーターに基づいてプリミティブな形状を生成します。 ブール値パラメーターは、現在生成された形状がストレージからのものか、新規のものかを指定するために使用されます。 以下のコードを ShapeFactory クラス内の、前のメソッドの下に配置します。

        /// <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);
            }
        }
    
  7. Unity に戻る前に、必ず Visual Studio で変更を保存してください。

  8. Unity Editor に戻り、Scripts フォルダーの ShapeFactory クラスをクリックして、Hierarchy (階層) パネルの [Main Camera](メイン カメラ) オブジェクトにドラッグします。

  9. Main Camera を選択しても、ShapeFactory スクリプト コンポーネントに Spawn Point の参照がないことにお気づきになるでしょう。 修正するには、ShapeSpawnPoint オブジェクトを Hierarchy (階層) パネルから "Spawn Point" 参照ターゲットにドラッグします。

    図形ファクトリ参照ターゲットを設定する

第 9 章 - Gaze クラスを作成する

作成する必要がある最後のスクリプトは Gaze クラスです。

このクラスは、レイキャストの作成を担当します。これは、ユーザーがどのオブジェクトを見ているかを検出するために、メイン カメラから前方に投影されます。 このケースでは、レイキャストはユーザーがシーン内の GazeButton オブジェクトを見ているかどうかを識別し、動作をトリガーする必要があります。

このクラスを作成するには、以下の手順を実行します。

  1. 先ほど作成した Scripts フォルダーに移動します。

  2. [Project](プロジェクト) パネルを右クリックし、[Create](作成)>[C# Script](C# スクリプト) を選択します。 スクリプトの名前を「Gaze」にします。

  3. 新しい Gaze スクリプトをダブルクリックして、それを Visual Studio で開きます。

  4. スクリプトの上部に次の名前空間が含まれていることを確認します。

        using UnityEngine;
    
  5. 次に、Gaze クラス内に以下の変数を追加します。

        /// <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).
        /// </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).
        /// </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;
    

重要

これらの変数の一部は、エディターで編集できます。

  1. ここで、Awake() メソッドと Start() メソッドのコードを追加する必要があります。

        /// <summary>
        /// The method used after initialization of the scene, though before Start().
        /// </summary>
        private void Awake()
        {
            // Set this class to behave similar to singleton
            instance = this;
        }
    
        /// <summary>
        /// Start method used upon initialization.
        /// </summary>
        private void Start()
        {
            FocusedObject = null;
            Cursor = CreateCursor();
        }
    
  2. 以下のコードを追加します。ここでは、最初にカーソル オブジェクトの作成が行われ、GazeEnabled ブール値の切り替えが行われる場所でもある、Raycast メソッドを実行する Update() メソッドも作成されます。

        /// <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();
            }
        }
    
  3. 次に、レイキャストを投影し、ヒットしたターゲットを検出する UpdateRaycast() メソッドを追加します。

        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.
                    FocusedObject = null;
                }
            }
            else
            {
                // No object looked upon, set focused gameobject to 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!
                        AzureServices.instance.CallAzureFunctionForNextShape();
                    }
                }
            }
        }
    
  4. 最後に、ResetFocusedObject() メソッドを追加します。これは、GazeButton オブジェクトの現在の色を切り替えることで、新しい形状を作成しているかどうかを示します。

        /// <summary>
        /// Reset the old focused object, stop the gaze timer, and send data if it
        /// is greater than one.
        /// </summary>
        private void ResetFocusedObject()
        {
            // Ensure the old focused object is not 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;
                }
            }
        }
    
  5. Unity に戻る前に、Visual Studio に変更を保存します。

  6. Scripts フォルダーの Gaze クラスをクリックして、Hierarchy (階層) パネルの [Main Camera](メイン カメラ) オブジェクトにドラッグします。

第 10 章 - AzureServices クラスを完了させる

他のスクリプトが揃ったので、AzureServices クラスを完成させることができます。 この流れは以下の通りです。

  1. CreateCloudIdentityAsync() という名前の新しいメソッドを追加し、Azure との通信に必要な認証変数を設定します。

    また、このメソッドは、シェイプ リストを含むファイルが以前に保存されていたかどうかの確認も行います。

    ファイルが見つかった場合、ユーザーの [視線入力] が無効になり、Azure Storage ファイルに保存されている形状のパターンに従って、形状の生成が開始されます。 テキスト メッシュには、形状の生成元に応じて [Storage] または [New] と表示されるため、ユーザーはこれを確認することができます。

    ファイルが見つからない場合、[視線入力] が有効になり、ユーザーがシーン内の GazeButton オブジェクトを見つめると形状が生成されるようになります。

        /// <summary>
        /// Create the references necessary to log into 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();
            }
        }
    
  2. 次のコード スニペットは Start() メソッドの中にあります。ここでは、CreateCloudIdentityAsync() メソッドの呼び出しが行われます。 以下の内容を、現在の Start() メソッドに上書きする形で貼り付けます。

        private void Start()
        {
            // Disable TLS cert checks only while in Unity Editor (until Unity adds support for TLS)
    #if UNITY_EDITOR
            ServicePointManager.ServerCertificateValidationCallback = delegate { return true; };
    #endif
    
            // Set the Status text to loading, whilst attempting connection to Azure.
            azureStatusText.text = "Loading...";
    
            //Creating the references necessary to log into Azure and check if the Storage Directory is empty
            CreateCloudIdentityAsync();
        }
    
  3. メソッド CallAzureFunctionForNextShape() のコードを入力します。 先に作成した Azure Function App を使用して、シェイプ インデックスを要求します。 新しい形状を受け取ると、このメソッドから ShapeFactory クラスにこの形状が送信され、シーンに新しい形状が生成されます。 以下のコードを使用して、CallAzureFunctionForNextShape() の本文を完成させます。

        /// <summary>
        /// Call to the Azure Function App to request a Shape.
        /// </summary>
        public async void CallAzureFunctionForNextShape()
        {
            int azureRandomInt = 0;
    
            // Call 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
            ShapeFactory.instance.shapeHistoryList.Add(azureRandomInt);
    
            ShapeFactory.instance.CreateShape(azureRandomInt, false);
    
            //Save to Azure storage
            await UploadListToAzureAsync();
        }
    
  4. 形状履歴リストに格納されている整数を連結して文字列を作成し、Azure Storage ファイルに保存するメソッドを追加します。

        /// <summary>
        /// Upload the locally stored List to 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);
        }
    
  5. Azure Storage ファイルのファイルに格納されているテキストを取得し、それをリストに "逆シリアル化" するメソッドを追加します。

  6. この処理が完了すると、ユーザーがシーンにさらに形状を追加できるように、このメソッドによって視線入力が再び有効になります。

        ///<summary>
        /// Get the List stored in Azure and use the data retrieved to replicate 
        /// a Shape creation pattern
        ///</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!";
        }
    
  7. Unity に戻る前に、Visual Studio に変更を保存します。

第 11 章 - UWP ソリューションを構築する

ビルド プロセスを開始するには、次の手順を実行します。

  1. [File](ファイル)>[Build Settings](ビルド設定) に移動します。

    アプリをビルドする

  2. [ビルド] をクリックします。 Unity によって [エクスプローラー] ウィンドウが起動されます。そこで、アプリのビルド先のフォルダーを作成して選択する必要があります。 ここでそのフォルダーを作成して、「App」という名前を付けます。 次に、App フォルダーを選択した状態で、[フォルダーの選択] を押します。

  3. Unity で、App フォルダーに対してプロジェクトのビルドが開始されます。

  4. Unity によるビルドが完了すると (多少時間がかかる場合があります)、[エクスプローラー] ウィンドウが開いて、ビルドの場所が表示されます (必ずしも最前面に表示されるとは限らないため、タスク バーを確認してください。新しいウィンドウが追加されたことがわかります)。

第 12 章 - アプリケーションをデプロイする

アプリケーションをデプロイするには、以下の手順を実行します。

  1. 前の章で作成した App フォルダーに移動します。 アプリ名が入った拡張子 [.sln] のファイルが表示されるので、これをダブルクリックして Visual Studio で開きます。

  2. ソリューション プラットフォームで、[x86]、[ローカル マシン] を選択します。

  3. [ソリューション構成] で、[デバッグ] を選択します。

    Microsoft HoloLens の場合は、これを [リモート コンピューター] に設定した方が便利かもしれません。そうすると、お使いのコンピューターに縛られずに済みます。 ただし、次のことも行う必要があります。

    • HoloLens の IP アドレス を把握します。これは 、[設定>ネットワーク] & [インターネット>Wi-Fi>詳細設定オプション] 内にあります。IPv4 は使用する必要があるアドレスです。
    • 開発者モードがオンになっていることを確認します「Settings>Update & Security>For developers」を参照してください

    ソリューションのデプロイ

  4. [ビルド] メニューの [ソリューションの配置] をクリックして、アプリケーションをお使いのコンピューターにサイドロードします。

  5. インストールされたアプリの一覧にお客様のアプリが表示され、起動してテストできる状態になります。

完成した Azure Functions と Storage Application

おめでとうございます。これで、Azure Functions と Azure Storage の両方のサービスを活用した Mixed Reality アプリを構築できました。 このアプリは、保存されたデータを描画し、そのデータに基づいてアクションを提供することができます。

最終製品 -end

ボーナス演習

演習 1

2 番目の生成ポイントを作成し、オブジェクトが作成された生成ポイントを記録します。 データ ファイルを読み込んだら、形状ごとにそれぞれ最初に生成された場所から生成される様子を再生します。

演習 2

毎回アプリを開き直さずに、アプリを再スタートする方法を構築します。 まずは、「シーンの読み込み」から開始してみましょう。 その後、アプリから簡単にリセットできるよう、Azure Storage に保存されたリストを消去する方法を構築します。