takataka430’s blog

.NET系を中心に勉強したことのまとめを書きます

MRTKで手の動きを記録して再生する

MRTKのドキュメントを眺めていたらこんな機能を見つけました。

docs.microsoft.com

頭と手の動きを記録して再生できるらしいです。これは面白そう!
早速試してみましょう。

開発環境

Unity 2020.3.0f1
MRTK 2.7.2

スクリプトの作成

記録用のスクリプトは「InputRecordingControls」がMRTKに含まれています。

再生用のスクリプトは以下のようになります。

using Microsoft.MixedReality.Toolkit;
using Microsoft.MixedReality.Toolkit.Input;
using System.IO;
using System.Linq;
using UnityEngine;

public class LoadAnimation : MonoBehaviour
{

    //記録したアニメーションを再生するためのフィールドを取得
    private InputPlaybackService playbackService = null;
    private InputPlaybackService PlaybackService
    {
        get
        {
            if (playbackService == null)
            {
                playbackService = CoreServices.GetInputSystemDataProvider<IMixedRealityInputPlaybackService>() as InputPlaybackService;
            }

            return playbackService;
        }
    }

    //再生したファイルを保存したフォルダの最新のファイルを取得して再生
    public void PlayUserInput()
    {
        if (PlaybackService != null)
        {
            var dirInfo = new DirectoryInfo("[記録したアニメーションファイルの保存フォルダ]");
            string filePath = dirInfo.GetFiles().OrderByDescending(f => f.LastWriteTime).FirstOrDefault().FullName;
            PlaybackService.LoadInputAnimation(filePath);
            PlaybackService.Play();
        }
        
    }

    void Start()
    {
        
    }

    void Update()
    {
        
    }
}

InputRecordingControlsで記録したファイルは、PCで実行した場合はC:\Users\[ユーザー名]\AppData\LocalLow\DefaultCompany\[アプリケーション名]という場所のフォルダに保存されます。これをnew DirectoryInfoの引数に指定します。

UIの作成

記録用と再生用の2つのボタン(PressableButtonHoloLens2)を作ります。 それぞれButton_RecordButton_Loadに名前を変えています。
次にそれぞれのボタンにスクリプトを張り付けて設定を行います。

記録用のボタン

Add Componentから「InputRecordingControls」を探して追加します。
On Recording Stopped()で+ボタンを押してイベントを追加します。左下の空欄にはこのボタンを挿入します。右上のNo Functionと書いてあるところにはInputRecordingControls→SaveRecordedInputを選択します。
以上の操作によって記録が終了したときにファイルが保存されます。
f:id:takataka430:20210904145622p:plain

次にInteractableのOnClick()を設定します。左下のNone(Object)のこのボタンを挿入します。右上のNo Functionと書いてあるところにはInputRecordingControls→ToggleRecordingを選択します。こうすると、ボタンを押すごとに記録の開始、終了の操作をすることができます。 f:id:takataka430:20210904145646p:plain

再生用のボタン

Add Componentから先ほど作成した再生用のスクリプトを挿入します。(今回はLoadAnimationという名前にしています)
次にInteractableのOnClick()を設定します。左下のNone(Object)のこのボタンを挿入します。右上のNo Functionと書いてあるところにはLoadAnimation→PlayUserInputを選択します。 f:id:takataka430:20210904145715p:plain

Unityのエディタ上で実行

f:id:takataka430:20210904145025g:plain 左のボタンを押すと記録が開始され、もう一度押すと記録終了です。右側のボタンを押すと記録された手の動きが再生されます。

Azure Communication Servicesのクイックスタート(チャット)をやってみた

チャットアプリを作ってみたいと思ったので、Azure Communication Servicesのチャットについて調べてみました。今回はコンソールアプリを用いたクイックスタートを使ってみます。

開発環境

Microsoft Visual Studio Community 2019 Version 16.10.3
Azure.Communication.Chat 1.0.1
Azure.Communication.Identity 1.0.1

AzureポータルでCommunication Servicesのリソース作成

手順についてはドキュメントに詳細が載っているので割愛します。 docs.microsoft.com

リソースを作成したら、左メニューからキーをクリックして、エンドポイント接続文字列をメモしておきます。(この後で使います)

f:id:takataka430:20210730234951p:plain

クライアントアプリの開発

Visual Studioでコンソールアプリを作成し、Program.csに以下のように記述します。
以下の2つの公式ドキュメントを参考に組み合わせました。

docs.microsoft.com

docs.microsoft.com

using Azure;
using Azure.Communication;
using Azure.Communication.Chat;
using Azure.Communication.Identity;
using System;

namespace ConsoleApp1
{
    class Program
    {

        static async System.Threading.Tasks.Task Main(string[] args)
        {
            // エンドポイントの設定
            Uri endpoint = new Uri("[エンドポイント]");

            var client = new CommunicationIdentityClient("[接続文字列]");
                
            //IDの作成
            var identityResponse = await client.CreateUserAsync();
            var identity = identityResponse.Value;
                
            //アクセストークンの取得
            var tokenResponse = await client.GetTokenAsync(identity, scopes: new[] { CommunicationTokenScope.Chat });
            var token = tokenResponse.Value.Token;

            //チャットクライアントの作成
            CommunicationTokenCredential communicationTokenCredential = new CommunicationTokenCredential(token);
            ChatClient chatClient = new ChatClient(endpoint, communicationTokenCredential);

            //参加者の作成
            var chatParticipant = new ChatParticipant(identifier: new CommunicationUserIdentifier(id: identity.Id))
            {
                DisplayName = "User01"
            };

            //スレッドの作成
            CreateChatThreadResult createChatThreadResult = await chatClient.CreateChatThreadAsync(topic: "Hello world!", participants: new[] { chatParticipant });
            ChatThreadClient chatThreadClient = chatClient.GetChatThreadClient(threadId: createChatThreadResult.ChatThread.Id);

            //メッセージの送信
            SendChatMessageResult sendChatMessageResult = await chatThreadClient.SendMessageAsync(content: "hello world", type: ChatMessageType.Text, senderDisplayName: chatParticipant.DisplayName);
            string messageId = sendChatMessageResult.Id;

            //メッセージの表示
            AsyncPageable<ChatMessage> allMessages = chatThreadClient.GetMessagesAsync();
            await foreach (ChatMessage message in allMessages)
            {
                Console.WriteLine($"{message.Id}:{message.Content.Message} by {message.SenderDisplayName}");
            }
        }
    }
}

これを実行すると以下のようになります。

1627655746772:hello world by User01
1627655746299: by
1627655746269: by

message Id、メッセージ、ユーザー名の順に表示しています。
メッセージは1回しか送っていないのに結果は3つありますね。なぜなんでしょうか?

とりあえず動きはなんとなくわかったので、今後は2つのアプリ間でチャットをできるようにする方法を調べてみようと思います。

Blazor WebAssemblyを使ってみた

以前から気になっていたBlazorの勉強を始めました。今回はプロジェクトを新規に作成して動かすところまでをやってみたいと思います。

環境

Visual Studio 2019

プロジェクトの作成手順

Visual Studio 2019を開き、「新しいプロジェクトの作成」をクリックします。

f:id:takataka430:20210717162909p:plain

「Blazor WebAssembly」で検索を行うと「Blazor WebAssemblyアプリ」が出てくるので、これを選択して右下の「次へ」をクリックします。

f:id:takataka430:20210717162938p:plain

プロジェクト名と保存場所を設定して「次へ」をクリックします。

f:id:takataka430:20210717163000p:plain

追加情報を設定できますが、今回は何も変更せずに「作成」をクリックします。

f:id:takataka430:20210717163016p:plain

プロジェクトが作成されます。画面上部の「IIS Express」をクリックするとアプリケーションが起動します。

f:id:takataka430:20210717163034p:plain

実行すると以下のようなアプリを利用することができます。ホームのページ以外にも、ボタンを押したら数字が増えるページや表が掲載されているページが実装されています。 f:id:takataka430:20210717163053g:plain

コードを見てみる

動きが分かったところで「Counter」ページのコードを見てみましょう。

f:id:takataka430:20210717163344p:plain

上半分が画面のレイアウト、下半分がコードになっています。
コードの部分はcurrentCountが画面に数字を表示するためのフィールドで、IncrementCountを実行するとcurrentCountが増えていきます。
画面のレイアウトでは、@onclickでボタンを押したときのイベントを定義しており、IncrementCountを実行します。currentCountの値が変更されると、5行目の@currentCountに反映される、という動きになっているようです。

使ってみた感想

C#経験はあるけどあまりフロントエンドをやったことがない自分にはとても親しみやすいと感じました。もっとたくさん使っていこうと思います。

ASP.NET Coreで接続文字列を使う

ASP.NET Coreを使っていて、接続文字列の扱いがよくわからなかったので調べてみました。

背景

  • ASP.NET Core でWeb APIを作りたい
  • Azure SQL Databseにつなぎたいので接続文字列が必要
  • 開発はローカルで行い、本番はAzureのApp Serviceへデプロイしたい
  • 接続文字列は
    • ソースコードに書きたくない
    • ローカルと App Serviceへのデプロイ後で別のものを使いたい

調べた結果

接続文字列を取得するコードは、例えば以下のようになります。

using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Configuration;

namespace WebApplication1.Controllers
{
    [Route("api/test")]
    [ApiController]
    public class TestController : ControllerBase
    {
        private IConfiguration _configuration;

        public TestController(IConfiguration configuration)
        {
            _configuration = configuration;
        }

        [HttpGet]
        public string Get()
        {
            //「test」という名前の接続文字列がsに入る
            var s = _configuration.GetConnectionString("test");
            return s;
        }
    }
}

では、接続文字列はどこに記載すればいいのでしょうか。

ローカルの場合

プロジェクト内にあるappsettings.jsonに以下のように記述すると「これはローカルです」という文字列が取得できます。

{
  "ConnectionStrings": {
    "test": "これはローカルです" 
  }
}

また、もう一つの方法としてsecrets.jsonを使う方法があります。 Visual Studio 2019であれば、プロジェクトを右クリックして「ユーザーシークレットの管理」をクリックすると開くことができます。
使い方はappsettings.jsonと同じです。 このファイルはプロジェクトとは別の場所(C:\Users\[username]\AppData\Roaming\Microsoft\UserSecrets\[id]\secrets.json)にあります。

secret.jsonの方がソースコードに含まれないので安心して使えると思います。

App Service(Web Apps)の場合

「構成」を開き、画面下の「接続文字列」という場所があります。「新しい接続文字列」から名前と値を登録しましょう。
こうすると、AppService側でローカルとは異なる接続文字列を設定することができます。

f:id:takataka430:20210529155144p:plain

以上により、ローカル環境とAppService環境で接続文字列を使い分けることができます。

参考

Azure Web Apps の環境変数の管理の基本 ( Key Vault と User Secrets ) - BEACHSIDE BLOG

HoloLensなしでもMRを楽しめるツール・サービス

MRで遊びたいけどHoloLens 2がない、という場合でも案外使えるものがあるのでまとめてみました。

Mixed Reality Toolkit

MRアプリを作るためのUIツールです。クロスプラットフォームなのでHoloLens以外にも対応しています。

以下はOculus Quest用の設定方法です。自分はOculus Quest 2で試してみましたが、楽しいのでおすすめです。

docs.microsoft.com

iOSAndroidにも対応しているようです。(こちらは未検証)

docs.microsoft.com

Azure Spatial Anchors

アンカー情報をデバイス間で共有するためのサービスです。
以下のチュートリアルを進めていき、5番目にiOSAndroidでビルドする方法が書かれています。

docs.microsoft.com

Azure Remote Rendering

高品質な3Dオブジェクトをクラウドレンダリングするサービスです。これによって端末のスペック以上の表現が可能になります。

docs.microsoft.com

以下のサンプルはUnityエディター上で動かすことができます。面白いです。
docs.microsoft.com

Xamarin.Formsで写真を撮る

久々のXamarinの記事です。Xamarin.Formsで写真を撮影し、撮った画像を表示する方法を調査しました。
※手元にiPhoneがないため、動作確認できているのはAndroidのみとなります。

開発環境

Visual Studio 2019
Xamarin.Forms 5.0.0.2012
Xamarin.Essential 1.6.1

利用するメソッドについて

写真を撮るためには名前空間Xamarin.EssentialsMediaPickerクラスのCapturePhotoAsyncメソッドを利用します。戻り値はTask<FileResult>です。
CapturePhotoAsyncを使って撮った画像は端末内に保存されます。保存場所はFileResultクラスのFullPathプロパティで取得可能です。今回は画像が端末内にたまらないよう、削除するようにしてみました。

コード

画面は以下のようにします。ボタンと画像を表示するだけのシンプルなものです。(レイアウトはお好みで)

<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             x:Class="XF_CustomVisionStudy.MainPage">
    <StackLayout>
        <Button Text="test" 
                Clicked="TakePhoto" 
                HorizontalOptions="Center"/>
        <Image  x:Name="image"/>
    </StackLayout>
</ContentPage>

次にコードビハインドは以下のようにします。

using System;
using Xamarin.Essentials;
using Xamarin.Forms;

namespace XF_CustomVisionStudy
{
    public partial class MainPage : ContentPage
    {
        public MainPage()
        {
            InitializeComponent();
        }

        private async void TakePhoto(object sender, EventArgs e)
        {
            try
            {
                //写真をとる
                var photo = await MediaPicker.CapturePhotoAsync();
                
                if(photo !=null)
                {
                    //撮った写真を画面に表示する
                    var stream = await photo.OpenReadAsync();
                    image.Source = ImageSource.FromStream(() => stream);
                    
                    //画像の保存場所を取得
                    var imagePath = photo.FullPath;
                    //画像を削除する
                    System.IO.File.Delete(imagePath);
                }
                
            }
            catch (Exception ex)
            {
                Console.WriteLine(ex.Message);
            }
        }
    }
}

最後にOSごとの設定を行います。

Androidの場合

AndroidManifest.xmlの中に以下を追記します。(<manifest>の内側です)

<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.CAMERA" />

Android11の場合は以下も必要です。(無いとエラーになります)

<queries>
  <intent>
    <action android:name="android.media.action.IMAGE_CAPTURE" />
  </intent>
</queries>

iOSの場合

info.plistに以下を追記します。

<key>NSCameraUsageDescription</key>
<string>This app needs access to the camera to take photos.</string>
<key>NSMicrophoneUsageDescription</key>
<string>This app needs access to microphone for taking videos.</string>
<key>NSPhotoLibraryAddUsageDescription</key>
<string>This app needs access to the photo gallery for picking photos and videos.</string>
<key>NSPhotoLibraryUsageDescription</key>
<string>This app needs access to photos gallery for picking photos and videos.</string>

以上で完了です。実行すると、ボタンクリックで写真を撮ることができ、撮影した画像がボタンの下に表示されます。

参考ページ

Xamarin.Essentials: Media Picker - Xamarin | Microsoft Docs
MediaPicker.CapturePhotoAsync(MediaPickerOptions) Method (Xamarin.Essentials) | Microsoft Docs
FileResult Class (Xamarin.Essentials) | Microsoft Docs
Xamarin Froms で写真を撮影する - rksoftware

HTTPリクエストでAzure VMを操作する

AzureのVMを起動するのに毎回WEBブラウザからAzureポータルに行くのが面倒だったので、HTTPリクエストでVMを起動する方法を調べましたが、実現するまでにちょっと苦労したので手順をまとめます。

手順

Azure ADでアプリの登録を行う

Azure portalのAzure Active Directoryの画面左側のメニューで「アプリの登録」→「新規登録」をクリックします。

f:id:takataka430:20201005153959p:plain  
f:id:takataka430:20201005154248p:plain

「アプリケーションの登録」画面で名前を入力し、登録ボタンを押します。

f:id:takataka430:20201005154442p:plain

登録が済んだらアプリケーション画面に遷移するので、左側のメニューから「証明書と シークレット」を選択します。また、この画面で表示されている「ディレクトリ(テナント)ID」「アプリケーション (クライアント) ID」は後ほど利用するのでメモしておきます。

f:id:takataka430:20201005154626p:plain

「新しいクライアントシークレット」をクリックして「クライアントシークレットの追加」画面で説明を記入します。有効期限を選択して「追加」をクリックします。

f:id:takataka430:20201005154732p:plain

f:id:takataka430:20201005154855p:plain

するとクライアントシークレットが生成されます。この値は後ほどHTTPリクエストを送るときに使うのでどこかにメモしておきます。(この値は初回のみ表示され、次からは確認できなくなります)

 

     

HTTPリクエストでAzureのVMの起動を行う

今回はPostmanを使ってAzure VMの起動を行います。

1.トークンの取得

以下のエンドポイントへHTTPリクエストを送り、アクセストークンの取得を行います。

メソッド : POST
URL : https://login.microsoftonline.com/{ディレクトリ(テナント)ID}/oauth2/token
Body : PostmanではBodyタブの「x-www-form-urlencoded」を選択して以下を入力します。
 grant_type : "client_credentials"
 client_id : アプリケーション (クライアント) ID
 client_secret : アプリの登録の際に作成したクライアントシークレット
 resource : "https://management.core.windows.net/"

f:id:takataka430:20201005155429p:plain

これを実行すると以下のようなレスポンスが返ってきます。この後、"access_token"の値を使います。  
f:id:takataka430:20201005155621p:plain

 

2. Azure VMの起動

メソッド : POST
URL : https://management.azure.com/subscriptions/{subscriptionId}/resourceGroups/{resourceGroupName}/providers/Microsoft.Compute/virtualMachines/{vmName}/start?api-version=2020-06-01

「subscriptionId」「resourceGroupName」「vmName」の値はAzure VMのトップ画面から確認できます。

リクエストを送り、「Status 202 Accepted」が返ってきたら成功です。VMが起動されます。

ドキュメント

Azure REST API Reference | Microsoft Docs
Virtual Machines - Start (Azure Compute) | Microsoft Docs
Azure AD Service to Service Auth using OAuth2.0 | Microsoft Docs