20200322のC#に関する記事は16件です。

【Unity(C#)】TrailRendererの頂点座標を取得する方法

TraliRenderer

TrailRendererを使えば簡単なお絵描き機能がサクッと実装できます。

【参考リンク】:【Unity(C#)】ハンドトラッキングで簡易版VRお絵かきアプリ

今回はTrailRendererの頂点座標を取得していきます。

最終的には頂点座標をシリアライズしてJson形式で保存、
セーブデータとして外部リソースに保存したのち、
デシリアライズしてロード、、、までやります。

デモ

今回行ったデモはこちらです。

PaintTest.gif

中央のボタンを押すと、各TrailRendererの頂点にオブジェクトが生成されます。

コード

using System.Collections.Generic;
using UnityEngine;

/// <summary>
/// お絵描き機能 適当なオブジェクトにアタッチ
/// </summary>
public class Paint : MonoBehaviour
{
    [SerializeField] private Transform _paintObjParent;

    [SerializeField] private GameObject _paintObjPrefab;

    [SerializeField] private GameObject _vertCube;

    private GameObject _tmpPaintObj;

    private void Update()
    {
        //スクリーン座標をワールド座標に変換
        Vector3 worldClickPos = Camera.main.ScreenToWorldPoint(Input.mousePosition +  Camera.main.transform.forward);

        //マウス押した瞬間
        if (Input.GetMouseButtonDown(0))
        {
            _tmpPaintObj = Instantiate(_paintObjPrefab, worldClickPos, Quaternion.identity);
            _tmpPaintObj.transform.parent = _paintObjParent;
        }

        //マウス押し続けている間
        if (Input.GetMouseButton(0))
        {
            _tmpPaintObj.transform.position = worldClickPos;
        }
    }

    /// <summary>
    /// 全てのTrailRendererの全頂点を取得 Buttonに登録
    /// </summary>
    public void GetVert()
    {
        //Paintオブジェクト(TrailRenderer)のリストを作成
        List<TrailRenderer> trList = new List<TrailRenderer>();

        foreach (Transform child in _paintObjParent.transform)
        {
            TrailRenderer tr = child.GetComponent<TrailRenderer>();

            if (tr != null)
            {
                trList.Add(tr);
            }
        }

        //リストのPaintオブジェクト(TrailRenderer)それぞれから頂点を取得
        List<Vector3[]> posList = new List<Vector3[]>();

        foreach (TrailRenderer element in trList)
        {
            int posCount = element.positionCount;
            Vector3[] posArray = new Vector3[posCount];

            //全ての頂点を取ってくる
            int vertCount =  element.GetPositions(posArray);

            //描画した頂点座標を確認
            for (int i = 0; i < vertCount; i++)
            {
                Debug.Log(posArray[i]);
                Instantiate(_vertCube, posArray[i], Quaternion.identity);
            }
        }
    }
}

今回の記事内においては本質ではないので詳細には触れませんが、
クリック時にTrailRederer付きゲームオブジェクトを生成しています。

そのせいで、Listを使って若干複雑になっていますが、
下記リンク内で詰まっていた、
消しゴム機能(Undo,Redo)の実装時に役立ちます。

Save,Load時に活用します。

【参考リンク】:【Unity(C#)】ハンドトラッキングで簡易版VRお絵かきアプリ

GetPositions

TrailRendererコンポーネントに実装された、
GetPositionsメソッドを使用して頂点を取得しています。
引数に頂点座標分の数だけ要素が存在する配列を渡してあげれば、
すべての頂点を取得できます。

まとめ

今回の頂点の取得により、TrailRendererのシリアライズの準備が整いました。

次回はシリアライズから、デシリアライズまで一気に進めようと思います。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Unity】NavMesh でエラー "Failed to create agent because it is not close enough to the NavMesh" が出た時の解決策

概要

UnityでNavMeshを使っていて以下のようなエラーが出ました。

Failed to create agent because it is not close enough to the NavMesh

この解決策が検索してもなかなか見つからなかったため、記録しておきます。

特に Agent Type を Humanoid 以外にしている場合はこの記事と同じ原因である可能性が高いです。

環境

Unity 2019.3.0a5
Windows10

解決策

このエラーを直訳すると、「エージェントがナビメッシュに十分に近くないため生成に失敗している」ですが、この原因として以下の2つが考えられます。

  1. エラー文通り、エージェントとナビメッシュが離れている
  2. エージェントのTypeに合ったナビメッシュが生成されていない

それぞれについて解決策を説明します。

1. エラー文通り、エージェントとナビメッシュが離れている

この場合、NavMeshAgentのコンポーネントのBase Offsetの値を変えることで直ることがあります。
Scene上にNavMeshAgentの円柱が出ているので高さが合うように調整してください。

2. エージェントのTypeに合ったナビメッシュが生成されていない

今回のメインはこちらです。
NavMeshはAgent Type を複数作ることが可能です。これはキャラクターごとに異なる高さや太さを設定するための機能です。しかし、現状Unityでは、NavigationウィンドウでBakeした場合、デフォルトのHumanoidにしか適用されません。

つまり、各Agent TypeごとにナビメッシュをBakeする必要があります。
これをするには、ステージとなるオブジェクトに、Nav Mesh Surfaceコンポーネントを追加します。そしてAgent Typeの値を指定してBakeすることで、そのAgent Typeのエージェントが動くようになります。

image.png

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

月末最後の営業日の曜日 (paizaランク B 相当)

問題文
YouTube解説動画

        static void Main(string[] args)
        {
            int X = int.Parse(Console.ReadLine().Trim(' '));
            int all = 0;
            int[] lastBusinessDays = new int[7]; //日、月、火、水、木、金、土

            DateTime date = DateTime.Now;
            DateTime endDate = date.AddYears(400 * 7);
            while(date < endDate)
            {
                DateTime lastDate = new DateTime(date.Year, date.Month, DateTime.DaysInMonth(date.Year, date.Month));
                if(lastDate.DayOfWeek == DayOfWeek.Saturday)
                {
                    lastDate = lastDate.AddDays(-1);
                }else if(lastDate.DayOfWeek == DayOfWeek.Sunday)
                {
                    lastDate = lastDate.AddDays(-2);
                }
                DayOfWeek dow = lastDate.DayOfWeek;
                switch (dow)
                {
                    case DayOfWeek.Monday:
                        lastBusinessDays[1]++;
                        break;
                    case DayOfWeek.Tuesday:
                        lastBusinessDays[2]++;
                        break;
                    case DayOfWeek.Wednesday:
                        lastBusinessDays[3]++;
                        break;
                    case DayOfWeek.Thursday:
                        lastBusinessDays[4]++;
                        break;
                    case DayOfWeek.Friday:
                        lastBusinessDays[5]++;
                        break;
                }
                date = date.AddMonths(1);
                all++;
            }
            double result = (int)(((double)lastBusinessDays[X] / all) * 1000000 ) / 1000000.0;
            Console.WriteLine(result);
        }
メモ

月末最終日は年月の日数をDateTime.DaysInMonth(year,month)メソッドで求め、DateTimeインスタンスをつくる。

int lastDay = DateTime.DaysInMonth(year,mont)
DateTime day = new DateTime(year,month,lastDay)
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

[C#]DebugとReleaseで別々のconfigファイルを適用する

config配下にApp.Debug.configとApp.Release.configを配置して、実行時にそれぞれ適用する際に詰まったので記録。

環境:
Windows10
VS express2017

こちらのサイトを参考にしました。

1. .csprojを開く

エクスプローラで該当の .csprojを表示し、メモ帳などで開く。

2. .csprojを編集する

2-1. PropertyGroupを編集

<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' "></PropertyGroup>

の間に

<appConfig>App.Debug.config</appConfig>

を記入。DebugをReleaseに置き換えて同様に記入。

2-2. ItemGroupを編集

<None Include="App.config" />
<None Include="App.Debug.config" />
<None Include="App.Release.config" />

を以下に書き換える。(Releaseも同)

<ItemGroup>
 <Content Include="App.config">
  <SubType>Designer</SubType>
 </Content>
</ItemGroup>
<ItemGroup>
 <Content Include="App.Debug.config">
 <DependentUpon>App.config</DependentUpon>
  <SubType>Designer</SubType>
 </Content>
</ItemGroup>
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

C# で出来ること一覧

更新履歴

  • 2020/03/23
    • IoT について追記
    • その他に帝国兵さんのツイートを追加
    • サーバーレスカテゴリーを追加して AWS Lambda を追加
    • ASP.NET Core Razor Pages を追記

本文

昔ブログにこんな記事を書きました。

C# で何か出来るのか?まとめてみた

あれから 2 年が経って昔からある Windows 専用の .NET Framework に対する新機能の提供が終わり、クロスプラットフォームに対応した .NET Core が今後のメインストリームとして .NET 5 → .NET 6 のように進化していくことが 2019 年 5 月の Build 2019 で発表されました。以下の Blog 記事がアナウンス後に発表されています。

Introducing .NET 5

ということで情勢が変わったので、ここらへんで一度まとめなおしてみようと思います。

そもそも C# とは

2002 年に .NET Framework 1.0 上で開発するための言語として登場。最初見た感想は「命名規約が Java はメソッド名は小文字始まりだけど大文字始まりになってる言語なんだな」でした。
言語仕様的にはプロパティが言語仕様に組み込まれている、デリゲートという今では、ほぼ全ての言語でできるメソッドを変数に代入するための型があって、それをつかってイベントという仕組みも言語仕様に組み込まれてる違いがありましたが、当時の自分はマイクロソフトが作った Java と認識してました。

その後主な機能だけ列挙すると:

  • 2005 年に C# 2.0 でジェネリクス/Nullable 型
  • 2007 年に C# 3.0 で LINQ
  • 2010 年に C# 4.0 で動的型付け
  • 2012 年に C# 5.0 で非同期処理(async/await) (8 年も前から async/await 使えてたの凄い)
  • 2015 年に C# 6.0 で null 条件演算子
  • 2017 年に C# 7.0 でタプルや型スイッチ
  • 2019 年に C# 8.0 で null 許容参照型や switch 式、パターンマッチ系

のように、進化してきて、最初に抱いていた Java クローンという印象はすっかり消えて、自分の中で最高に書きやすい言語になりました。細かい所では TypeScript とか Dart とか、Kotlin とか見てうらやましいなぁと思う言語仕様もあるけれど、これから書く守備範囲の広さと合わせて、しばらくは C# メインでやっていこうと思える感じになってます。

ちなみに、あくまで私個人の認識なので、知識に偏りはあるし、もしかしたら間違った認識をしているものもあるかもしれないので、そういうものはバンバン編集リクエストなりコメントで指摘ください m(_ _)m

コンソールアプリケーション開発

やっぱり、余分な技術的な要素を排除しつつ、やりたいことだけを検証するときとかはコンソールアプリケーションが最強ですよね。.NET Core は、グローバルツールというのがあって dotnet tool install -g dotnetsay のようなコマンドでツールをインストールして、インストール後は CLI からパチパチと叩けたりします。npm とかでやってたようなことが C# でも出来るようになってます。.NET Framework も、もちろんコンソールアプリはいけますね。

プラットフォーム/フレームワーク コメント ドキュメント
.NET Core これでやってれば、とりあえず良さそう .NET Core ツールの管理方法
.NET Framework .NET Framework にしか対応してないものがあるならこっちでも

ここから先の全てに言えることですけど、.NET Framework を検討無しで新規開発に選ぶのは辞めた方がいいと思います。.NET Core でもいけるものなら、今後も新機能追加が行われる .NET Core 系を選ぶほうが個人的にはお勧めです。

塩漬けこそ正義の環境なら .NET Framework もいいかもしれないですが

Web アプリケーション開発

Web アプリは ASP.NET Core/ASP.NET あたりを使えば出来ますね!
今から始めるなら、過去のしがらみとかが無い限りは .NET Core 3.1 で ASP.NET Core MVC とかあたりで作り始めることになると思います。

.NET Core で作っておくと、今後のメインストリームの .NET 5, .NET 6... の流れに乗れるので間違いないです。Windows でも Linux でも動くしね。Azure でも AWS でも GCP でも、何処でもデプロイ可能だし、Visual Studio 2019 を使って最高の環境で開発出来るし、Visual Studio Code で手に馴染んだエディター上でもコード補完などが効いた状態で開発もできて、実際にクラウドへのデプロイも、そこから可能でお手軽に始められる。CI/CD パイプライン組んで自動デプロイとかも楽しい。

プラットフォーム/フレームワーク コメント ドキュメント
ASP.NET Core MVC 第一候補。これでいけるなら行くべきだと思う ASP.NET Core MVC の概要
ASP.NET Core Razor Pages View(cshtml) の書き方は、MVCと同じだが、Viewに対するアクションをコントローラーではなく、cshtmlと1対1に対応した ページクラス(cshtml.cs) に定義していくスタイル。MVC がコントローラーへのアクションが起点なのに対して、こちらはページを起点に考えて作っていくスタイルなので、コントローラーをどう分割するかという問題から解放される。DI などの機能も使えるので、結構良さそう。SPA じゃない Web ページが主体のアプリだと ASP.NET Core MVC よりも作りやすい雰囲気を感じる(自分は、これでしっかり作りこんだことはない) ASP.NET Core での Razor ページの概要
ASP.NET MVC .NET Framework に縛られるなら、これを使ってなるべく ASP.NET MVC への依存を低く作っておくのが良さそう ASP.NET MVC 5 の概要
ASP.NET WebForms 過去の資産がない限りは採用する理由は、あまりないと思う。
ノンコーディングで DB からデータ取ってきて表形式で表示できるとかの強みが活かせるなら強いかもしれないけど、そういう用途なら PowerApps 系とかそっちのほうが今では良さそうに思う。
ASP.NET Web Forms

その他にも Blazor サーバーというものもあって、SPA みたいなのを作れるけどブラウザは HTML/CSS でレンダリングしてるだけで、実態は WebSocket とかでサーバーに繋いでいてボタンを押したときの処理やらステートやらは全部サーバーでもってるというのもあります。

プラットフォーム/フレームワーク コメント ドキュメント
Blazor サーバー 1,000 人クライアントがいたら 1,000 人ぶんのデータをサーバーのメモリ上に持つので、大規模な B2C とかに使うのは、個人的にはちょっと不安がある(検証はしたことないです)。社内アプリとかでは、C# でロジックとかが書けるので幸せになれるかもしれないと思ってる。 ASP.NET Core Blazor の概要

Web API 開発

これも Web アプリケーション開発とほぼ同じです。.NET Core 系が今後のメイン。

プラットフォーム/フレームワーク コメント ドキュメント
ASP.NET Core これが一番機能充実してそう ASP.NET Core を使って Web API を作成する
ASP.NET WebAPI .NET Framework ならこっち ASP.NET Web API 2 (C#) を使ってみる

Web API を作るという観点では、共通の前処理を仕込んだりとか便利機能が豊富な ASP.NET Core が一番開発効率は高いような気がしてますが、サーバーレスプラットフォームの Azure Functions の Http trigger を使っても Web API は作れます。HTTP で受けてメッセージキューにメッセージ投げて、その裏でごにょごにょやるようなアプリとかの場合は、Web API 部分も後述する Azure Functions で作ってもいいと思います。

サーバーレスプラットフォーム上での開発

C# でサーバーレスプラットフォーム上での開発も出来ます。Azure Functions は当然ネイティブで対応しています。C# 以外の言語は、Azure Functions のランタイムとは独立したプロセスとして起動していて、gRPC でプロセス間通信しているのですが、C# のほうはランタイムに組み込まれてるので、C# が書けるなら C# で書いたら何か安心感があります。AWS Lambda も、C# に早くから対応していて .NET Core が発表されて暫くして「AWS Lambda が C# をサポート」のニュースとともにサポートされたことにびっくりしたのを覚えています。

プラットフォーム/フレームワーク コメント ドキュメント
Azure Functions Azure のサーバーレスサービスの代名詞的な Azure Functions で動かしたいならこれ。 Azure Functions の概要
AWS Lambda AWS のサーバーレスの代名詞的な AWS Lambda で動かしたいならこれ。 C# による Lambda 関数のビルド

Web フロントエンド

最近は C# でも Web フロントエンドいけるんですねぇ。JavaScript 系フレームワークの方が実績もあるし、やってる人も多いので、それはそれで正義なのですが C# が好きなチームとかでは、ここら辺のも候補に入るかもしれない。

プラットフォーム/フレームワーク コメント ドキュメント
Blazor WebAssembly 正式リリースされてないけど、WebAssembly 対応のが正式版としてリリースされたら、恐らく最強。
WebAssembly 系の奴ら全般にあることだろうけど、初回起動時の遅さが懸念事項。
ASP.NET Core Blazor の概要
Uno Platform これは UWP アプリのコードをビルドしてクロスプラットフォームで動くようにするコンセプトのプラットフォームです。WebAssembly にも対応しています。MS 製品ではありませんが、MS ともコミュニケーションをとってやってるようです。GitHub のページのコントリビューターに Microsoft アイコンがあるので。
2020年3月時点では、安定した盤石なプラットフォームだと思って触らないのが吉です。面白いプラットフォームではありますが、日進月歩でバグフィックス、新機能実装が行われています。
What is the Uno Platform?

モバイルアプリケーション開発

Android や iOS といったモバイル OS 用のアプリ開発です。何も考えずに C# でやりたいなら Xamarin を選ぶといいでしょう。次点で Web フロントエンドでも紹介した Uno Platform です。ワンチャン Unity。

プラットフォーム/フレームワーク コメント ドキュメント
Xamarin ネイティブ Android/iOS の API を、ほぼそのまま全て C# でラップしてあるので、いざとなったら Android/iOS のネイティブアプリ開発向け情報を参考に C# に翻訳して開発もできるし、Xamarin のドキュメントにも結構色々書いてある。
各プラットフォームで出来ることは、多分ほぼ全て出来る上に基本的に全て C# で書けます。Java, Kotlin, Swift, Objective-C を書かないといけないシーンは、他のクロスプラットフォーム系の製品に比べて極端に少ないと思います。それこそ、既存の自社製 or OSS のネイティブ向けライブラリーを Xamarin から使いたい時とかに見るくらいだと思います。
最近は、Uno Platform や Mobile Blazor Bindings の Android/iOS 対応は Xamarin 上で動くようになっているので、安心感があります。
Xamarin とは
Xamarin.Forms Xamarin ネイティブとまとめて紹介しようか悩んだけどわけました。Xamarin ネイティブの上に各プラットフォーム共通の UI コントロールや API 群を提供しています。うまいことやればワンソースで Android/iOS/UWP に対応できます。うまいことやればね。現実は Xamarin.Forms の API や .NET Standard の API では出来ないことをしたいこともあるので、その時は Xamarin ネイティブの方に頼って各 OS ネイティブの API を叩くコードをプラットフォーム毎に書かないといけません。その場合でも C# で書けるのは個人的には神。XAML と呼ばれる Windows アプリ開発者にはなじみ深い XML ベースの言語で画面を作れるのも〇。でも、最近は Flutter などの影響なのかは知らないけど C# で UI を組めるようにする CSharpForMarkup というのも出てきているので、そっちはそっちで楽しみ。 Xamarin.Forms とは
Uno Platform UWP アプリのソースを Andorid/iOS/WebAssembly/macOS 向けアプリとしてビルド出来るようにしようという、とても野心的なプラットフォームです。OSS で開発されています。まだこなれてないけど、そのぶん進化が早くて追いかけるのが楽しいです。 What is the Uno Platform?
Mobile Blazor Bindings これは、完全に名前しか知らないけど Blazor で開発したものがモバイルでも動くんだって。しかも基盤は Xamarin らしい。まだ実験段階なので、こういうのがあるんだな~という位には知っておけばいいと思います。もちろん興味がある人は触ってみよう! Announcing Experimental Mobile Blazor Bindings
Unity ゲーム開発用途が主ですが、AR 系アプリとか、ワンチャン画面アプリとかもいけるかも。個人的にはモバイル用の普通の画面を作るならUIWidgetsを使うと Flutter と同じノリで画面が作れるのでお勧め。 Unity

Windows アプリケーション開発

デスクトップ アプリですね。Windows 7 のサポートも終わり、Windows 8.1 を使っている人が少なめとうことで、運がいいと最近は Windows 10 のみをターゲットとして開発できるようになってきました。UWP or WPF |超えられない壁| WinForms の順番でお勧めかなぁ。

プラットフォーム/フレームワーク コメント ドキュメント
UWP Windows 10 が出たときに出てきたプラットフォーム。昔は、これに非ずんば Windows 10 アプリに非ず。とも受け取れるくらいごり押しをしてきたけど、最近は落ち着いて他のプラットフォームでもデスクトップアプリ開発してもらって大丈夫みたいなメッセージを感じる。
一番セキュアな環境で動かせるので、ユーザーとしてはメリットが大きいのですが、開発者目線では、昔は出来たあれが出来ないとか、別の方法を使わないといけない、使う機能を事前に示的に宣言しないといけない、配布するには証明書が必要(オレオレでもいいけど、その場合はユーザーにオレオレ証明書を入れてもらわないといけない)という点で不自由を感じる。
でも、一番今のところ今風の UI を作りやすいプラットフォーム。私は好き。ユーザーの意図に反する動きが作りにくいように出来てるので、例えばユーザーがウィンドウを最小化しようとしたら止めることは出来ないし、ユーザーが閉じようとしたら基本的には閉じられる。個人的にはあるべき姿だと思うけど、業務アプリでユーザーが間違ったことしようとしても力ずくで止めるようなものを実装するのがシンドイ。
一部新機能の API は、UWP からしか呼べないものもある。
ユニバーサル Windows プラットフォーム (UWP) アプリとは
WPF on .NET Core .NET Core 3.0 以降で WPF がサポートされた。実行速度も早い、自由度も高い、配布形式も柔軟に選べる(従来通りの方法もいけるし、MSIX にパッケージングして UWP っぽい感じに配布も出来る)。最近は Windows 10 向け API も NuGet パッケージ追加するだけでサポートされているものは呼べるようになったり、UI コントロールも XAML Islands などを使って UWP の UI コントロールのホストも可能。個人的には一番バランスが取れてるところにいるのではないかと思う 概要 (WPF)(.NET Core 向けの WPF リファレンスとかは無い)
Migrating WPF apps to .NET Core が .NET Framework の WPF から .NET Core の WPF へのマイグレーションのドキュメント
WPF on .NET Framework 基盤が .NET Framework であることを除くと WPF on .NET Core と同じ。.NET 6 くらいの時代になると、段々と使えない新機能が出てきだすのではないかと個人的には思ってる。 概要 (WPF)
Windows Forms on .NET Core
&
Windows Forms on .NET Framework
めんどくさいので纏めた。古き良き世代の .NET での Windows アプリ開発のフレームワーク。正直、高 DPI とか Per monitor DPI とか、今風のレイアウトのリストとか出そうとすると労力が高くなるので辛い。Windows Forms で提供されているコントロールとサードパーティー製コントロール内で収まるように設計して作れば普通に使えるとは思うけど、新規では Windows Forms 以外を選ぼう。 Windows フォーム

macOS 向けアプリケーション開発

Xamarin.Mac でいけるらしい。実際に freee さんで実績があるけど、自分は macOS がメインじゃないので触ったことがない。でも、C# で作れちゃうの凄い。

プラットフォーム/フレームワーク コメント ドキュメント
Xamarin.Mac よく知らない Xamarin.Mac の概要

機械学習系

ML.NET で行ける。これも触り程度しか使ったことがないのでコメントできるほど知らないけど、こういうのがあるよっていうのは知っておいて、いざというときに覗いてみよう。もし機械学習をやることになったら ML.NET で勉強すると Python の学習と機械学習系の勉強の二重苦にならずに、C# は知ってるので機械学習系の勉強にフォーカス出来る。

もし、Python 系のでやらないといけなくなったら、ML.NET で軽く雰囲気掴んでから Python 系のほうを触ってみるというラーニングパスもありかもしれない。

プラットフォーム/フレームワーク コメント ドキュメント
ML.NET よく知らない ML.NET のドキュメント

ゲーム開発 & MR(VR/AR) 開発

Unity 一強の世界ですなぁ。(開発言語を C# に絞らなければ、他もありますけど)

プラットフォーム/フレームワーク コメント ドキュメント
Unity 昔 DirectX の本をみたときは、本で数ページに渡るコードを書いて実行した結果がティーポッドが出るだけ、みたいな世界だったんですが、今ではポトペタと、Behaviour を作って GameObject にアタッチして動作をカスタムできるという、とっても簡単に作れるような世界になってるので最初に触った時は感動しました。
でも、UI を作ろうとすると、個人的には辛い…、あと Unity と利用ライブラリー間のバージョン合わせチャレンジが辛い点が解消される未来が来たら、より幸福度が上がりそうだと思ってます。
Unity

IoT 開発

Azure IoT Edge で C# を使った開発が出来ます。(瀬尾さんに教えてもらいました!)

プラットフォーム/フレームワーク コメント ドキュメント
Azure IoT Edge Azure IoT Edge のモジュール開発に C# が使えます。Docker イメージにして展開する形になります。私は、やったことないのですが、Docker 強いですね。
さらに、パブリックプレビューだけど Azure Functions が動くのも凄い
Azure IoT Edge とは

その他

何に入れていいか迷ったので、その他をここに。

プラットフォーム/フレームワーク コメント ドキュメント
gRPC サーバー/クライアント .NET Core 3.0 から gRPC がサポートされています。マイクロサービスのサービス間を gRPC で繋いでとかいうのもよく聞く話なので、そこにも C# を仲間に入れてやってください。.NET Framework でも前からライブラリー使えば出来たけど、ASP.NET Core に含まれたのが大きいです。.NET Framework の WCF の移行先としても案内されています。 チュートリアル: ASP.NET Core で gRPC のクライアントとサーバーを作成する
マイクロサービス 旬ですよね。Azure アーキテクチャーセンターにドキュメントも上がってます。なんとなく、この記事に書くような粒度の話じゃないと思ったのですが、その他カテゴリーを後で追加したので、備忘録的に書いておきます。 .NET マイクロサービス: コンテナー化された .NET アプリケーションのアーキテクチャ
Azure App Service の開発 Microsoft Azure の App Service の製品チームの帝国兵さんから App Service も、ほとんど C# で作ってるとコメント貰いました。

まとめ

C# は、相変わらず色々なことが出来て言語的にも他の最新言語と比較して遜色ない状態をキープし続けてくれてる素敵な言語だなぁと思いました。

システムを作る上で、システム内の○○はXX言語、△△はYY言語のような複数言語が乱立するよりも 1 つの言語で統一できるといいなぁと思ってる人には C# は結構いい選択なのではと思ってます。
マイクロサービスの文脈では、各々のチームが一番適してると思った言語で開発すればいいという流れもありますがケースバイケース?

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

再入門C#:コンカレントコレクション(1)

非同期処理に使える様々なコレクションがありますので、そのコレクションの使い方や考え方を調査したいと思います。

コンカレントコレクション

コンカレントコレクションは、非同期用のスレッドセーフに実装されたコレクションで、非同期のユースケースに対応できるようなメソッドがあります。

System.Collections.Concurrent の namespace にはスレッドセーフでスケーラブルないくつかのコレクションクラスがあります。マルチスレッドの状態でも安全にアイテムを追加したり、更新したり、削除したりすることができます。

コレクションの名前 概要                         
BlockingCollection IProducerConsumerCollectionのバウンディングや、ブロッキングの機能をもった実装。Blocking Collection Overview参照
ConcurrentDictionary スレッドセーフの Dictionary
ConcurrentQueue スレッドセーフの Queue
ConcurrentStack スレッドセーフの Stack
ConcurrentBag スレッドセーフのコレクションの実装
IProducerConsumerCollection BlockingCollectionで使われるインターフェイス

全ての内容を理解していませんが、一つ一つ調査しようと思います。

コンカレントコレクションをつわないとどうするか?

通常のQueueと、ConcurrentQueueを比較してみましょう。

Queue

        static async Task QueueAsync()
        {
            var orders = new Queue<string>();
            Task order1 = Task.Run(() => PlaceOrderQueue(orders, "ushio"));
            Task order2 = Task.Run(() => PlaceOrderQueue(orders, "yamada"));
            Task order3 = Task.Run(() => PlaceOrderQueue(orders, "higuchi"));
            Task order4 = Task.Run(() => PlaceOrderQueue(orders, "taro"));
            Task.WaitAll(order1, order2, order3, order4);

            while(orders.Count != 0)
            {
                Console.WriteLine($"order: {orders.Dequeue()}");
            }
        }

        private static void PlaceOrderQueue(Queue<string> order, string name)
        {
            for (int i = 0; i < 5; i++)
            {
                Thread.Sleep(TimeSpan.FromSeconds(1));
                order.Enqueue($"{name} wants surface pro {i + 1}");
            }
        }

実行結果

通常のコレクションを使うと、スレッドセーフではないので、同じタイミングで更新をしたケースでは、データが欠落しているようです。
ちなみに、ConcurrentDictionaryの代わりに Dictionaryを使うと、Null Reference Exception がでて、ちゃんと実行すらも出来ませんでした。

order: taro wants surface pro 1
order:
order: yamada wants surface pro 1
order: higuchi wants surface pro 1
order:
order:
order:
order: ushio wants surface pro 2
order:
order: yamada wants surface pro 3
order: taro wants surface pro 3
order: ushio wants surface pro 3
order: ushio wants surface pro 4
order:
order: yamada wants surface pro 4
order: higuchi wants surface pro 4
order: higuchi wants surface pro 5
order: yamada wants surface pro 5
order: taro wants surface pro 5
order: ushio wants surface pro 5

Task.Run と Async メソッド

余談ですが、最初のコードはこんな感じで書いていました。ちなみに、PlaceOrderQueueAsyncが1行1行実行されます。まるで同期メソッドのように。なぜかというと、PlaceOrderQueueAsyncは本来Asyncではなくても動くものなので、ふつーに実行されてしまうとのこと。当初は、Taskで返すのと、最後にTask.WaitAllしてるんだから、並列に動くのではと思ったけど、そうなりませんでした。

        private static async Task PlaceOrderQueueAsync(Queue<string> order, string name)
        {
            Console.WriteLine($"Into the method. {name}.");
            for (int i = 0; i < 5; i++)
            {
                Thread.Sleep(TimeSpan.FromSeconds(1));
                order.Enqueue($"{name} wants surface pro {i + 1}");
            }
        }
        static async Task QueueAsync()
        {
            var orders = new Queue<string>();
            Task order1 =  PlaceOrderQueueAsync(orders, "ushio");
            Task order2 = PlaceOrderQueueAsync(orders, "yamada");
            Task order3 = PlaceOrderQueueAsync(orders, "higuchi");
            Task order4 = PlaceOrderQueueAsync(orders, "taro");
            Task.WaitAll(order1, order2, order3, order4);

            while (orders.Count != 0)
            {
                Console.WriteLine($"order: {orders.Dequeue()}");
            }
        }

結果

例では、Into the methodのところが、しばらくまってから、一つ、一つ実行されます。並列実行されるときは、ここは一気に出てくるはずですが、そうはなりません。

Into the method. ushio.
Into the method. yamada.
Into the method. higuchi.
Into the method. taro.
order: ushio wants surface pro 1
order: ushio wants surface pro 2
order: ushio wants surface pro 3
order: ushio wants surface pro 4
order: ushio wants surface pro 5
order: yamada wants surface pro 1
order: yamada wants surface pro 2
order: yamada wants surface pro 3
order: yamada wants surface pro 4
order: yamada wants surface pro 5
order: higuchi wants surface pro 1
order: higuchi wants surface pro 2
order: higuchi wants surface pro 3
order: higuchi wants surface pro 4
order: higuchi wants surface pro 5
order: taro wants surface pro 1
order: taro wants surface pro 2
order: taro wants surface pro 3
order: taro wants surface pro 4
order: taro wants surface pro 5

解決策

Asyncメソッドが「なんちゃって」なのが問題なのであれば、なんちゃって出無くすと良いです。こしてあげて、本当のAsyncメソッドにしてあげて、待ち受けが実際に発生するようにすると、想定の動作になります。

        private static async Task PlaceOrderQueueAsync(Queue<string> order, string name)
        {
            Console.WriteLine($"Into the method. {name}.");
            for (int i = 0; i < 5; i++)
            {
                await Task.Delay(TimeSpan.FromSeconds(1));
                order.Enqueue($"{name} wants surface pro {i + 1}");
            }
        }

たまたまうまく動いてくれちゃっていますが、、、。何回か実行すると失敗しました。もしくは、Task.Run で実行すると確実に非同期になりますので、なんちゃって、Asyncメソッドは、Asyncでなくしてしまっても良いでしょう。

Into the method. ushio.
Into the method. yamada.
Into the method. higuchi.
Into the method. taro.
order: taro wants surface pro 1
order: yamada wants surface pro 1
order: higuchi wants surface pro 1
order: ushio wants surface pro 1
order: higuchi wants surface pro 2
order: taro wants surface pro 2
order: yamada wants surface pro 2
order: ushio wants surface pro 2
order: higuchi wants surface pro 3
order: taro wants surface pro 3
order: ushio wants surface pro 3
order: yamada wants surface pro 3
order: taro wants surface pro 4
order: higuchi wants surface pro 4
order: ushio wants surface pro 4
order: yamada wants surface pro 4
order: higuchi wants surface pro 5
order: taro wants surface pro 5
order: ushio wants surface pro 5
order: yamada wants surface pro 5

コンカレントキュー

スレッドセーフのキューを使うと問題解決です。インターフェイスも普通のQueueと同じですが、TryDequeue() メソッドが使われます。他のスレッドが、Dequeueを行う可能性があり、失敗する可能性があるのでこのインターフェイスを使います。失敗した場合、戻り値がfalseになります。(ここでは処理を書いていませんが)Dequeueの場合は failの場合、普通にループを抜けると良いでしょう。このロジックでは、他のスレッドで終了いるので、このままで問題ありません。

        static async Task ConcurrentQueueAsync()
        {
            var orders = new ConcurrentQueue<string>();
            Task order1 = Task.Run(() => PlaceOrderConQueue(orders, "ushio"));
            Task order2 = Task.Run(() => PlaceOrderConQueue(orders, "yamada"));
            Task order3 = Task.Run(() => PlaceOrderConQueue(orders, "higuchi"));
            Task order4 = Task.Run(() => PlaceOrderConQueue(orders, "taro"));
            Task.WaitAll(order1, order2, order3, order4);

            while (orders.Count != 0)
            {
                string message;
                orders.TryDequeue(out message);
                Console.WriteLine($"order: {message}");
            }
        }

        private static void PlaceOrderConQueue(ConcurrentQueue<string> order, string name)
        {
            for (int i = 0; i < 5; i++)
            {
                Thread.Sleep(TimeSpan.FromSeconds(1));
                order.Enqueue($"{name} wants surface pro {i + 1}");
            }
        }

結果

order: higuchi wants surface pro 1
order: yamada wants surface pro 1
order: taro wants surface pro 1
order: ushio wants surface pro 1
order: higuchi wants surface pro 2
order: taro wants surface pro 2
order: ushio wants surface pro 2
order: yamada wants surface pro 2
order: yamada wants surface pro 3
order: ushio wants surface pro 3
order: higuchi wants surface pro 3
order: taro wants surface pro 3
order: ushio wants surface pro 4
order: yamada wants surface pro 4
order: higuchi wants surface pro 4
order: taro wants surface pro 4
order: higuchi wants surface pro 5
order: yamada wants surface pro 5
order: ushio wants surface pro 5
order: taro wants surface pro 5

通常のQueueでなんとかする方法

通常のQueueで何とかしたければ、自分でロックをかければよいです。

        private static object LockObj = new object(); 
        private static void PlaceOrderQueueLock(Queue<string> order, string name)
        {
            for (int i = 0; i < 5; i++)
            {
                Thread.Sleep(TimeSpan.FromSeconds(1));
                lock (LockObj)
                {
                    order.Enqueue($"{name} wants surface pro {i + 1}");
                }
            }
        }

        static async Task QueueLockAsync()
        {
            var orders = new Queue<string>();
            Task order1 = Task.Run(() => PlaceOrderQueueLock(orders, "ushio"));
            Task order2 = Task.Run(() => PlaceOrderQueueLock(orders, "yamada"));
            Task order3 = Task.Run(() => PlaceOrderQueueLock(orders, "higuchi"));
            Task order4 = Task.Run(() => PlaceOrderQueueLock(orders, "taro"));
            Task.WaitAll(order1, order2, order3, order4);

            while (orders.Count != 0)
            {
                Console.WriteLine($"order: {orders.Dequeue()}");
            }
        }

ConcurrentDictionary

コンカレントディクショナリはよく使われるそうで、コンカレントなListのかわりなどにも使われるようです。さて、通常のDictionaryとAPIの差異を確認してみましょう。

Dictionary

        private static void DictionarySample()
        {
            var order = new Dictionary<int, string>()
            {
                {1, "Surface Pro 4"},
                {2, "Surface Book 2"}
            };
            Console.WriteLine($"Current Order number {order.Count}");

            order.Add(3, "Microsoft Mouse");
            order[4] = "Keyboard";
            order.Remove(1);
            foreach (var kv in order)
            {
                Console.WriteLine($"Key: {kv.Key} Value: {kv.Value}");
            }
        }

ConcurrentDictionary

状態の取得、変更の陰には常に別スレッドで変更が行われている可能性が含まれます。ですので基本的にTry...のメソッドになります。
下記の例でも、同じキーのものを追加しようとしたら失敗します。通常のディクショナリの場合だと、気にせず、order[3] = "Microsoft Mouse" とかやりますが、本来ここではお勧めできません。キーがあるかないかもわからないのであれば、AddOrUpdateを使います。AddOrUpdateの第一引数は、キーで、その次の引数が、キーがなかった時に追加される値、そして最後のFunctionは、現在の値がわたってくるので、それを参考にする、もしくは、新しい値をセットするためのものになっています。これも、他のスレッドが更新してないよね?という配慮になります。

        private static void ConcurrentDictionarySample()
        {
            var order = new ConcurrentDictionary<int, string>();
            var success = order.TryAdd(1, "Surface Pro 4");
            Console.WriteLine($"Order 1: succeeded?: {success}");
            success = order.TryAdd(1, "Surface Book 2");
            Console.WriteLine($"Order 2: succeeded?: {success}");
            order[3] = "Microsoft Mouse";
            string message = order.AddOrUpdate(1, "New Order",
                (key, oldValue) => $"{oldValue} is replaced by surface book 3");
            Console.WriteLine($"New Value: {message}");
            string product;
            success = order.TryRemove(3, out product);
            if (success)
                Console.WriteLine($"key3: value: {product} is removed");
            foreach(var kv in order)
            {
                Console.WriteLine($"{kv.Key}: {kv.Value}");
            }

        }

結果

Order 1: succeeded?: True
Order 2: succeeded?: False
New Value: Surface Pro 4 is replaced by surface book 3
key3: value: Microsoft Mouse is removed
1: Surface Pro 4 is replaced by surface book 3

各メソッドでどういう処理を行うべきだろうか?

ConcurrentDictionaryでメソッドが失敗した場合どうしたらいいだろう? マルチスレッドでこれらは起こりうることなので、Exceptionが発生して処理が止まるのがまずいケースもおおいだろう。その場合にTry系のメソッドが役に立つ

TryAdd

これは別メソッドが、既にそのキーを登録しているということなので、別に無視してよければ、何もしなくても良いかもしれない。そうでないばあいは、AddOrUpdateでキーの有無に関係なく登録できるようにするのがよいだろうか?

AddOrUpdate

キーがあるか無いかわからないときは、とにかくこれが良いだろう。

TryRemove

削除しようとしたけど、既にないというケースなので、これも場合によっては無視してしまってもいいだろう。

TryGet

取得しようとしたけど、既になくなっているケースかもしれない。これはまずいので例外か、既に削除されている通知をする必要がありそう。

GetOrAdd

取得しようとして、なければ、値を作る。第二引数は、関数を渡す。キーが渡ってくるので、キーが無い時のバリューをしていして、値をえる
キーが無い時に対処したい場合はこちらだろうか?

var value = order.GetOrAdd(1, (key) => $"New value for {key}");

おわりに

まずは初回オーバービューを書いてみた。次はもっとディープなところや、パターンについても慣れてコンカレントプログラミングに慣れていきたい。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

13日の金曜日(4桁) (paizaランク C 相当)

問題文
YouTube解説動画

    class Program
    {
        static void Main(string[] args)
        {
            DateTime date = new DateTime(2020,3,13);
            DateTime endDate = date.AddYears(400 * 7);
            int all = 0;
            int friday13 = 0;
            while(date.CompareTo(endDate) == -1) //-1で以前、0で同じ、1で以後
            {
                if (date.Day == 13 && date.DayOfWeek.Equals(DayOfWeek.Friday)){
                    friday13++;
                }
                date = date.AddMonths(1);
                all++;
            }
            double p = (double)friday13 / all;
            double result = ((int)(p * 10000)) / 10000.0;
            Console.WriteLine(result);
        }
    }
メモ

日付の比較はDatetime構造体のCompareToメソッドを使う。
戻り値はint型で引数より前で-1、同じで0、より後で1を返す。

日付の加算はDatetime構造体のAddMonthsAddDaysメソッドを使う

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

FIDOキーをWindowsデスクトップアプリで使うためのライブラリ CTAPcs

はじめに

ずいぶん前に WebAuthnぽいことができるWinデスクトップアプリ用ライブラリWebAuthnModokiDesktopβ を作ったんですけど、それを全体的に見直して作り替えました。
この記事ではCTAPcsの基本的な使い方を紹介したいと思います。

gebogebogebo/CTAPcs
https://github.com/gebogebogebo/CTAPcs

  • CTAP(シータップ)です。
  • 本ライブラリ・デモプログラムはWebAuthn、CTAPを勉強しながら作成した検証プログラムです。
  • サンプルレベルの品質なので重要なクレデンシャル情報が入っているFIDOキーは使わないことをお勧めします。
  • 本ライブラリ・デモプログラムを利用することによって生じるいかなる問題についても、その責任を負いません。

環境

  • Windows10 1903,1909
  • Visual Studio 2017,2019
  • C# Desktop Applicaion
  • .Net Framework 4.6.1

どんなものか

  • YubikeyなどのFIDOキーをデスクトップアプリから使うためのDllです。
    • g.FIDO2.dll
    • g.FIDO2.CTAP.dll
    • g.FIDO2.CTAP.HID.dll
    • g.FIDO2.CTAP.NFC.dll
    • g.FIDO2.CTAP.BLE.dll
    • g.FIDO2.Util.dll
  • FIDOキーはFIDO2対応しているものだけ使えます。U2Fは使えません。
  • JavaScriptのWebAuthnみたいなもので、似たようなことをデスクトップアプリからできます。
  • FIDOキーとの通信はCTAPの仕様で行っています。
    • HID(USB)
    • NFC
    • BLE
  • CTAP以外に、FIDOサーバー的な機能も実装しています。
    • チャレンジ生成
    • 登録結果(Attestation)の署名検証
    • 認証結果(Assertion)の署名検証

ソース

とりあえず使ってみる

  • HID(USB)タイプのFIDOキーを使います。
  • 以下のAPIを使います。
    • GetInfoAsync - FIDOキーの情報の取得
    • MakeCredentialAsync - 登録
    • GetAssertionAsync - 認証
    • ClientPINgetRetriesAsync - PINリトライ回数を調べる
    • WinkAsync - FIDOキーのLEDを光らせる
  • PIN認証でResidentKey無しのシンプルな利用です。

注意事項

USBタイプのFIDOキーはHID(ヒューマンインタフェースデバイス)でWindowsに認識されるので、専用のドライバが不要ですべてのアプリから取り扱い可能、という点が良いところだったのですが、Windowsさんはいつの間にか(たぶん1903から)制限をかけるようになり、アクセスにAdministratorの権限が必要になりました。これは手軽に使うことができなくなったという点で非常に残念ですが、Windows Helloなどセキュリティに関わる部分のポリシーに基づく対応なのかもしれません。この権限に関わる仕様変更について、Microsoftのドキュメント等の公式情報は見つけられていないのですが、そういう制限があるのでご注意ください。この時点で一般のアプリではつかえねーもの化しちゃっているのではと個人的には思います。

プロジェクト作成

プロジェクトを新規作成する方法をみてプロジェクトを作成します。

GetInfoAsync - FIDOキーの情報を取得する

FIDOキーをUSBに挿して以下のコードを実行します。

using System;
using System.Linq;
using System.Threading.Tasks;
using System.Windows;
using g.FIDO2.CTAP.HID;
using g.FIDO2.Util;
using g.FIDO2;

namespace HIDTest02
{
    public partial class MainWindow : Window
    {
        private HIDAuthenticatorConnector con;

        public MainWindow()
        {
            InitializeComponent();
            con = new HIDAuthenticatorConnector();
        }

        private async void ButtonGetInfo_Click(object sender, RoutedEventArgs e)
        {
            var res = await con.GetInfoAsync();
            if (res.DeviceStatus == g.FIDO2.CTAP.DeviceStatus.Unauthorized) {
                MessageBox.Show("Excute Administrator ?");
            } else if (res.DeviceStatus == g.FIDO2.CTAP.DeviceStatus.NotConnected) {
                MessageBox.Show("FIDO Key Not Connected");
            } else if (res.DeviceStatus == g.FIDO2.CTAP.DeviceStatus.Ok) {
                MessageBox.Show($"GetInfoAsync\r\n- Status = {res.CTAPResponse.Status}\r\n- StatusMsg = {res.CTAPResponse.StatusMsg}"); ;
            } else {
                MessageBox.Show("Error");
            }
        }
    }
}

処理概要

  • HIDAuthenticatorConnector クラスの GetInfoAsync メソッドでUSB FIDOキーの情報を取得します。
  • 正常に終了すると ResponseGetInfo クラスが戻り値で返ってきます。
  • ResponseGetInfo クラスは DeviceStatus メンバと CTAPResponse メンバで構成されていて、DeviceStatus にHIDレイヤのステータス、CTAPResponse にCTAPレイヤのステータスを持っています。
  • FIDOキーを正常に認識してCTAPコマンドが通った場合は DeviceStatusg.FIDO2.CTAP.DeviceStatus.Ok がセットされ、CTAPResponse.Status0 がセットされます。

ResponseGetInfoについて

さて、FIDOキーの情報ですが、以下のような情報が取れます。

  • Versions
    • FIDOキーが対応しているバージョンです。U2F_V2,FIDO_2_0などどセットされてきます。
    • 本DllはU2Fには対応していないので、FIDO_2_0 がないと正常に動作しません。
  • Aaguid
    • Authenticator Attestation GUID。FIDOキーの製品を識別するためのIDです。個体識別情報ではありませんので注意。
  • Option_rk
    • Resident Key機能を持っているかどうか。通常 present_and_set_to_true です。
  • Option_up
    • User Presence機能を持っているかどうか。通常 present_and_set_to_true です。
  • Option_clientPin
    • PINの機能を持っているかどうか。PINがセットされているかどうかもわかります。
      • PINの機能を持っていないってことはないので、absent はありません。
      • PINがまだ設定されていない場合、present_and_set_to_false となります。
      • PINが設定されていれば、present_and_set_to_true となります。
  • Option_uv
    • User Verification機能(生体認証機能)を持っているかどうか。Feitian BioPassは指紋センサが付いているので present_and_set_to_true となりますが、Yubikeyは absent となります。

MakeCredentialAsync - 登録

FIDOキーにユーザー情報を登録します。

using System;
using System.Linq;
using System.Threading.Tasks;
using System.Windows;
using g.FIDO2.CTAP.HID;
using g.FIDO2.Util;
using g.FIDO2;

namespace HIDTest02
{
    public partial class MainWindow : Window
    {
        private HIDAuthenticatorConnector con;
        private byte[] creid;
        private string pubkey;

        public MainWindow()
        {
            InitializeComponent();
            con = new HIDAuthenticatorConnector();
            con.KeepAlive += OnKeepAlive;
        }

        private void OnKeepAlive(object sender, EventArgs e)
        {
            // MakeCredentialAsync()、GetAssertionAsync()で
            // PIN認証が通ってFIDOキーのタッチ待ちになるとこのイベントが発生します
        }

        private async void ButtonMakeCredential_Click(object sender, RoutedEventArgs e)
        {
            string rpid = "test.com";
            var challenge = AttestationVerifier.CreateChallenge();
            var param = new g.FIDO2.CTAP.CTAPCommandMakeCredentialParam(rpid, challenge);
            var res = await con.MakeCredentialAsync(param, "1234");
            if (res.DeviceStatus == g.FIDO2.CTAP.DeviceStatus.NotConnected) {
                // FIDOキーが接続されていない場合
                return;
            } else if (res.DeviceStatus == g.FIDO2.CTAP.DeviceStatus.Timeout) {
                // FIDOキーのタッチ待ちでTimeoutした場合
                return;
            } else if (res.DeviceStatus == g.FIDO2.CTAP.DeviceStatus.Ok) {
                string verifyResult = "";
                if (res.CTAPResponse.Status == 0) {
                    if (res.CTAPResponse.Attestation != null) {
                        // verify
                        var v = new AttestationVerifier();
                        var verify = v.Verify(rpid,challenge, res.CTAPResponse.Attestation);
                        verifyResult = $"- Verify = {verify.IsSuccess}\r\n- CredentialID = {Common.BytesToHexString(verify.CredentialID)}\r\n- PublicKey = {verify.PublicKeyPem}";
                        if (verify.IsSuccess) {
                            // store
                            creid = verify.CredentialID.ToArray();
                            pubkey = verify.PublicKeyPem;
                        }
                    }
                }
                MessageBox.Show($"MakeCredentialAsync\r\n- Status = {res.CTAPResponse.Status}\r\n- StatusMsg = {res.CTAPResponse.StatusMsg}\r\n{verifyResult}");
            }
        }

    }
}

処理概要

  • HIDAuthenticatorConnector クラスの MakeCredentialAsync メソッドでUSB FIDOキーの登録を実行します。
  • MakeCredentialAsync には以下の情報を渡します。
    • RpId(今回はtest.com)を指定します。これは登録のキーとなります。
    • 応答検証のためのチャレンジを渡します。チャレンジは AttestationVerifier.CreateChallenge() で生成して渡してやればOKです。
    • 認証要素としてPIN(今回は1234)を指定します。
  • FIDOキー側でタッチ待ち状態になるとOnKeepAlive()イベントが発生しますので、アプリ側でガイドメッセージを出すようなことが可能です。ただしこのイベントはUIスレッドではないので注意してください。
  • 正常に終了すると ResponseMakeCredential クラスが返ってきます。

ResponseMakeCredentialの検証

  • ResponseMakeCredential.CTAPResponse.Attestation が登録結果情報です。
  • Attestation には電子署名が付いていて、これを検証(Verify)することで なりすまし情報の改ざんがない事を確認することができます。
  • AttestationVerifier クラスの Verify メソッドで検証します。このときRpIdと最初に生成したチャレンジを渡してやる必要があります。
  • Verifyの結果IsSuccesstrueであれば検証OKです、おめでとうございます。CredentialIDPublicKeyPemを保存しておきましょう。

GetAssertionAsync - 認証

using System;
using System.Linq;
using System.Threading.Tasks;
using System.Windows;
using g.FIDO2.CTAP.HID;
using g.FIDO2.Util;
using g.FIDO2;

namespace HIDTest02
{
    public partial class MainWindow : Window
    {
        private HIDAuthenticatorConnector con;
        private byte[] creid;
        private string pubkey;

        public MainWindow()
        {
            InitializeComponent();
            con = new HIDAuthenticatorConnector();
            con.KeepAlive += OnKeepAlive;
        }

        private void OnKeepAlive(object sender, EventArgs e)
        {
            // MakeCredentialAsync()、GetAssertionAsync()で
            // PIN認証が通ってFIDOキーのタッチ待ちになるとこのイベントが発生します
        }

        private async void ButtonGetAssertion_Click(object sender, RoutedEventArgs e)
        {
              var rpid = "test.com";
            var challenge = AttestationVerifier.CreateChallenge();
            var param = new g.FIDO2.CTAP.CTAPCommandGetAssertionParam(rpid, challenge, creid);
            param.Option_up = true;

            var res = await con.GetAssertionAsync(param, "1234");
            if (res.DeviceStatus == g.FIDO2.CTAP.DeviceStatus.NotConnected) {
                // FIDOキーが接続されていない場合
                return;
            } else if (res.DeviceStatus == g.FIDO2.CTAP.DeviceStatus.Timeout) {
                // FIDOキーのタッチ待ちでTimeoutした場合
                return;
            } else if (res.DeviceStatus == g.FIDO2.CTAP.DeviceStatus.Ok) {
                string verifyResult = "";
                if ( res.CTAPResponse.Assertion != null) {
                    // verify
                    var v = new AssertionVerifier();
                    var verify = v.Verify(rpid,pubkey,challenge, res.CTAPResponse.Assertion);
                    verifyResult = $"- Verify = {verify.IsSuccess}";
                }
                MessageBox.Show($"GetAssertionAsync\r\n- Status = {res.CTAPResponse.Status}\r\n- StatusMsg = {res.CTAPResponse.StatusMsg}\r\n{verifyResult}");
            }          
        }

    }
}

処理概要

  • HIDAuthenticatorConnector クラスの GetAssertionAsyncメソッドでUSB FIDOキーの認証を実行します。
  • GetAssertionAsyncには以下の情報を渡します。
    • キーとして、RpId(今回はtest.com)を指定します。登録の時に指定したものと同じものを指定する必要があります。
    • もう一つのキーとして、登録時にGetしたCredentialIDを指定します。
    • 応答検証のためのチャレンジを渡します。チャレンジは AssertionVerifier.CreateChallenge() で生成した値を渡してやればOK。
    • 認証要素としてPIN(今回は1234)を指定します。
  • FIDOキー側でタッチ待ち状態になるとOnKeepAlive()イベントが発生しますので、アプリ側でガイドメッセージを出すようなことが可能です。ただしこのイベントはUIスレッドではないので注意してください。
  • 正常に終了すると ResponseGetAssertion クラスが返ってきます。

ResponseGetAssertionの検証

  • ResponseGetAssertion.CTAPResponse.Assertion が認証結果情報です。
  • Assertionには電子署名が付いていて、これを検証(Verify)することで なりすまし情報の改ざんがない事を確認することができます。
  • AssertionVerifier クラスの Verify メソッドで検証します。このときRpIdと登録時に生成した公開鍵、最初に生成した challenge を渡してやる必要があります。
  • Verifyの結果IsSuccesstrueであれば検証OKです。認証OKということでログインさせてやりましょう。

ClientPINgetRetriesAsync - PINリトライ回数を調べる

FIDOキーでPINを何度も間違うとロックがかかります。ロックがかかるという事はすなわち詰みです。FIDOキーをリセットして出荷状態に戻さないと使えません。
以下のコードでPINリトライカウンタを調べることができます。リトライカウンタはPINを間違えると減り、PINが通ると初期値に戻ります。ゼロになるとロック状態になります。

private async void ButtonClientPINgetRetries_Click(object sender, RoutedEventArgs e)
{
    var con2 = new HIDAuthenticatorConnector();
    var res = await con2.ClientPINgetRetriesAsync();
    if (res.DeviceStatus == g.FIDO2.CTAP.DeviceStatus.Ok) {
        MessageBox.Show($"ClientPINgetRetriesAsync\r\n- Status = {res.CTAPResponse.Status}\r\n- StatusMsg = {res.CTAPResponse.StatusMsg}\r\n- PIN Retry Count = {res.CTAPResponse.RetryCount}");
    }
}

WinkAsync - FIDOキーのLEDを光らせる

どうでもいい機能として、FIDOキーのLEDを光らせることができます。ただ光らせるだけです。

private async void ButtonWink_Click(object sender, RoutedEventArgs e)
{
    var con = new HIDAuthenticatorConnector();
    for (int intIc = 0; intIc < 5; intIc++) {
        var ret = await con.WinkAsync();
        await Task.Delay(1000);
    }
}

登録と認証のシーケンス

今回のサンプルプログラムは簡単な利用例ですが、実際はクライアント側とサーバー側で役割を分ける構成を想定しています。

Registration

pic

Authentication

pic

その他

ほかには以下のような機能があります。
- NFCタイプのFIDOキーが使えます。
- BLEタイプのFIDOキーが使えます。
- 指紋センサが付いたFIDOキーでPINではなく、指紋認証をすることができます。
- キーにデータを記録することができます。(Resident Key)
- PINの登録、変更をすることができます。

以下のことはできません。
- 指紋の登録。ベンダー提供の専用ツールで行ってください。
- キーのリセット。ベンダー提供の専用ツールで行ってください。

おつかれさまでした

需要なさそうなものを謎のモチベーションで作ってしまいました。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

再入門C#:yield

非同期処理が出てくる中で、yieldが出てきて、はて、C#でどうだっけ?と思ったので書いてみます。yieldは JavaScriptとかで出てきました。ちなみに、アメリカに住んでいると、信号機のところにYeildって書いてあって、その意味は状況を見て曲がっていいよです。

C# の yield

C#のYield は、IEnumerable<T>や、IEnumerator<T>が戻り値になるようなメソッドで使用可能です。簡単な例を示すとこんな感じです。

こういうメソッドを書いておいて、

        static IEnumerable<int> Generate()
        {
            for (int i = 0; i < 10; i++)
            {
                yield return i; 
                Thread.Sleep(TimeSpan.FromSeconds(1));
            }
        }

こんな風に使います

        static void Display()
        {
            foreach (int i in Generate())
            {
                Console.WriteLine($"Number: {i}");
            }
        }

すると、1秒ごとに

Number: 0
Number: 1
  :

といった感じで表示されていきます。IEnumerable<T>は、シンプルなイテレーターを表すインターフェイスで、実装クラスはGetEnumerator()を実装する必要があります。IEnumerator は、Dispose(), MoveNext(), Reset() の三つのメソッドをもっていて、yield のところで待ち受けをしているのですが、MoveNext() が呼ばれたタイミングで、yieldのところで値が返ります。

この仕組みはLinqでも使われているものです。

これと似たメソッドは下記のように書けますが、1秒ごとに待たせるというのは難しくて、メソッドが呼ばれた時点で、表示がかかる前に、このメソッドが10秒時間がかかってから、一気にすべての行が表示されます。

        static IEnumerable<int> GenerateWithCollection()
        {
            var numbers = new List<int>();
            for (int i = 0; i < 10; i++)
            {
                numbers.Add(i);
                Thread.Sleep(TimeSpan.FromSeconds(1));
            }

            return numbers; 
        }

Map を実装する

これを使って何か実装してみましょう。昔私はScalaを書いていて、MapやFlatMapが懐かしいなと思っていました。C#だと、Linqで書けるのですが、あえてMapを自分で実装してみましょう。うお、めっちゃ簡単や!

    public static class Extensions
    {
        public static IEnumerable<U> Map<T, U>(this IEnumerable<T> self, Func<T, U> func)
        {
            foreach (var item in self)
            {
                var converted = func(item);
                yield return converted;
            }
        }
    }

じゃあ使ってみよう。ちなみに、これを実行すると何も起こりませんw。なぜなら、yieldで書いたものは遅延評価になるからです。Linqが遅延評価なのも同じインターフェイスを使っているからです。

        private static void TestOriginalMap()
        {
            var names = new List<string>
            {
                "ushio",
                "yamada",
                "kim"
            };

            var namesWithRespect = names.Map(item =>
            {
                var withRespect = $"{item} sama";
                Console.WriteLine(withRespect);
                return withRespect;
            });
        }

じゃあ、しっかり要素にアクセスしてみましょう。

        private static void TestOriginalMap()
        {
            var names = new List<string>
            {
                "ushio",
                "yamada",
                "kim"
            };

            var namesWithRespect = names.Map(item =>
            {
                var withRespect = $"{item} sama";
                Console.WriteLine(withRespect);
                return withRespect;
            });

            foreach (var name in namesWithRespect)
            {
                Console.WriteLine($"{name} sama de gozaimasu ne.");
            }
        }

実行結果

ushio sama
ushio sama sama de gozaimasu ne.
yamada sama
yamada sama sama de gozaimasu ne.
kim sama
kim sama sama de gozaimasu ne.

非同期型のIEnumerable

非同期型のものもあります。IAsyncEnumerable<T>を返す型を定義すると、asyncメソッドの中でyieldを使うことができます。その際、イテレータは、foreachにawaitキーワードをつけてループを回すことができます。

        static async IAsyncEnumerable<int> GenerateAsync()
        {
            for (int i = 0; i < 10; i++)
            {
                yield return i;
                await Task.Delay(TimeSpan.FromSeconds(1));
            }
        }

        static async Task DisplayAsync()
        {
            await foreach (var i in GenerateAsync())
            {
                Console.WriteLine($"NumberAsync: {i}");
            }
        }

参考

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

13日の金曜日(3桁) (paizaランク C 相当)

問題文
YouTubeのYouTubeの解説動画を視て書いたコード

static void Main(string[] args)
        {
            int all = 0;
            int friday = 0;
            Random random = new Random(123456);
            for (int i = 0; i < 1000000; i++)
            {
                int year = random.Next(DateTime.MinValue.Year,DateTime.MaxValue.Year + 1); //無作為に選ぶ
                int month = random.Next(1, 13); //無作為に選ぶ
                //year年month月13日が金曜日ですか?
                DateTime date = new DateTime(year, month, 13);
                if(date.DayOfWeek.Equals(DayOfWeek.Friday) )
                {
                    friday++;
                }
                all++;
            }
            double result = (int)(((double)friday / all) * 1000) / 1000.0;
            Console.WriteLine(result);

        }
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

再入門:非同期処理・ロック

普段Asyncのプログラムは書いていて、非同期処理のコンセプトもふわっとしっているので、いざとなれば調べたらトラブルシュートもできるのだが、プログラマとしてレベルを上げるために、複雑な非同期処理を何も見ることなくがっつりコードが書けるようになりたいと思い立ち、基礎の基礎からコードを書いてみることにしました。基本的にいつもお世話になっている岩永先生のサイトや、他で学んだ内容を自分なりに理解しながら、整理するだけですので、本家のサイトのほうがいろいろ有用と思います。

一つだけこのシリーズで気を付けたいのは、「コンファタブル」なレベルをキープすることです。例えば、私は最初にコンカレントキューから始めようと思ったのですが、通常のキューとの、Enqueueの挙動の違いが気になってコードを読んで、volatile というキーワードが出てきたり、他のライブラリが出てきたりして一向に終わりませんでした。今の自分は達人ではないので、達人のように一気になろうとするのは問題です。だから、自分が楽に理解できるレベルをキープしながら一歩一歩進めたら、きっと、コンカレントライブラリの実装も全部読めるようになるでしょう。でもそれまでは、自分が難しいと思ったらそこには何か問題があるはずので、自分が楽に理解できるレベルをキープすることを念頭においてやってみたいと思います。

今回は

マルチスレッドが必要な理由

マルチスレッドはコンピューティングの様々な場面で必要です。代表的なものがGUIのプログラミングです。何か時間がかかる処理、例えばダウンロードを行っている間に、GUIが一切反応しなくなってしまったら相当利便性が低くなります。そういった時に、コンピュータが並列で処理ができると、ダウンロードを行っている間に、キーボードからの入力を受け付けて、さらに、バックグラウンドで別のクリーンアップ処理を走らせておくといったことが可能になります。特に実行時間が長くかかるような処理は、平行で処理をして、終了を待っている間、別の処理やイベントを済ませておく方が良いでしょう。そういったときに使えるテクニックです。

スレッドとは

スレッドは1連の処理の流れのことです。プログラムが上から下まで実行されるときのイメージです。マルチスレッドはそれが複数同時に進行するイメージになります。

スレッドプールとは

スレッドプールとは、スレッドが必要なたびに新しいスレッドを作っていると、リソースが枯渇してしまったり、効率が悪かったりします。そこで、スレッドをプールする仕組みがあると良いのですが、これを自分でコードを書くのはめんどくさいでしょう。C#ではこの辺をよしなにやってくれるTaskというクラスがありますが、これはまた次の機会に。

レースコンディション

マルチスレッドで問題になるのが、レースコンディションという問題です。プログラムの処理の結果が予期しない状態になるのdせうが、その原因が、プログラミングのパーツが依存関係をもっていて、イベントや処理実行の順番の違いなどで発生します。あまり深く考えずにマルチスレッドで、ステートのあるコードを書くと簡単します。

レースコンディションが発生する例

下記の例では Parallel.Forは、マルチスレッドで指定した数で平行にActionの処理を実行してくれます。次の例では、0 -> 19 まで、並列で、アクションを実行してくれます。ちなみに、並列数を制御したいとか、CancellationToken を使いたいとかいう場合は、ParallelOptionsというオプションを使えるようになるオーバーロードがあります。また戻り値のオブジェクトでは、実行が正しく実行されたかのフラグを保持しています。

さて、この場合は i => { } のアクションの部分が並列で実行されますが、平行で、numを更新にいっています。このnumが同時に更新されるために、本来実行結果では、20 * 20 の 400 の数だけカウントされるはずが、平行実行でそれぞれが更新する結果、実行結果が毎回変わります。

        static  void MultiThread()
        {
            const int ThreadNum = 20;
            const int LoopNum = 20;
            int num = 0;

            Parallel.For(0, ThreadNum, i =>
            {
                for (int j = 0; j < LoopNum; j++)
                {
                    int tmp = num;
                    Thread.Sleep(1);
                    num = tmp + 1; 
                }
            });
            Console.Write($"{num} ({ThreadNum * LoopNum})");
        }

結果

80 (400)

ロックを使用する

解決の方法としては、ここは、平行で実行すると困るという箇所を、クリティカルセクションと呼ぶのですが、そのクリティカルセクションに対して、ロックをかけます。基本的な書き方は、ロックオブジェクトというオブジェクトをつくります。ここではsyncObjがそれに該当します。
そして、クリティカルセクションをlock(syncObj)で囲います。すると、syncObjにロックがかかって、1つのスレッドしか同時にロックができなくなります。つまり同時に1つのスレッドしかこのセクションを実行できません。1つのスレッドがセクションの実行を終えると、ロックが解放されて、他のスレッドが使用可能になります。

        static void MultiThreadWithLock()
        {
            const int ThreadNum = 20;
            const int LoopNum = 20;
            int num = 0;
            var syncObj = new object(); 
            Parallel.For(0, ThreadNum, i =>
            {
                for (int j = 0; j < LoopNum; j++)
                {
                    lock (syncObj)
                    {
                        int tmp = num;
                        Thread.Sleep(1);
                        num = tmp + 1;
                    }
                }
            });
            Write($"{num} ({ThreadNum * LoopNum})");
        }

実行結果は正しくなります。

400 (400)

実行時間

ロックをかけると、当然実行時間にインパクトが出ます。ベンチマークをとってみました。あたりまえですが、ロックが無いのが最速です(答え間違っていますが)ロックをすると極端に実行時間が増えます。ちなみに2個目のMontorLockは、Monitor.Enter()/Minotor.Exec()を使ったロックで、元のブログに載っていたサンプルを実行した結果です。lockは、それのシンタックスシュガーですので、似たような実行結果になります。

80 (400) True The first(no lock): Elapsed Time: 1442 ms
400 (400)The second(Monitor lock): Elapsed Time: 6189 ms
400 (400)The third(lock): Elapsed Time: 6231 ms
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

再入門C#:非同期処理・ロック

普段Asyncのプログラムは書いていて、非同期処理のコンセプトもふわっとしっているので、いざとなれば調べたらトラブルシュートもできるのだが、プログラマとしてレベルを上げるために、複雑な非同期処理を何も見ることなくがっつりコードが書けるようになりたいと思い立ち、基礎の基礎からコードを書いてみることにしました。基本的にいつもお世話になっている岩永先生のサイトや、他で学んだ内容を自分なりに理解しながら、整理するだけですので、本家のサイトのほうがいろいろ有用と思います。

一つだけこのシリーズで気を付けたいのは、「コンファタブル」なレベルをキープすることです。例えば、私は最初にコンカレントキューから始めようと思ったのですが、通常のキューとの、Enqueueの挙動の違いが気になってコードを読んで、volatile というキーワードが出てきたり、他のライブラリが出てきたりして一向に終わりませんでした。今の自分は達人ではないので、達人のように一気になろうとするのは問題です。だから、自分が楽に理解できるレベルをキープしながら一歩一歩進めたら、きっと、コンカレントライブラリの実装も全部読めるようになるでしょう。でもそれまでは、自分が難しいと思ったらそこには何か問題があるはずので、自分が楽に理解できるレベルをキープすることを念頭においてやってみたいと思います。

今回は

マルチスレッドが必要な理由

マルチスレッドはコンピューティングの様々な場面で必要です。代表的なものがGUIのプログラミングです。何か時間がかかる処理、例えばダウンロードを行っている間に、GUIが一切反応しなくなってしまったら相当利便性が低くなります。そういった時に、コンピュータが並列で処理ができると、ダウンロードを行っている間に、キーボードからの入力を受け付けて、さらに、バックグラウンドで別のクリーンアップ処理を走らせておくといったことが可能になります。特に実行時間が長くかかるような処理は、平行で処理をして、終了を待っている間、別の処理やイベントを済ませておく方が良いでしょう。そういったときに使えるテクニックです。

スレッドとは

スレッドは1連の処理の流れのことです。プログラムが上から下まで実行されるときのイメージです。マルチスレッドはそれが複数同時に進行するイメージになります。

スレッドプールとは

スレッドプールとは、スレッドが必要なたびに新しいスレッドを作っていると、リソースが枯渇してしまったり、効率が悪かったりします。そこで、スレッドをプールする仕組みがあると良いのですが、これを自分でコードを書くのはめんどくさいでしょう。C#ではこの辺をよしなにやってくれるTaskというクラスがありますが、これはまた次の機会に。

レースコンディション

マルチスレッドで問題になるのが、レースコンディションという問題です。プログラムの処理の結果が予期しない状態になるのdせうが、その原因が、プログラミングのパーツが依存関係をもっていて、イベントや処理実行の順番の違いなどで発生します。あまり深く考えずにマルチスレッドで、ステートのあるコードを書くと簡単します。

レースコンディションが発生する例

下記の例では Parallel.Forは、マルチスレッドで指定した数で平行にActionの処理を実行してくれます。次の例では、0 -> 19 まで、並列で、アクションを実行してくれます。ちなみに、並列数を制御したいとか、CancellationToken を使いたいとかいう場合は、ParallelOptionsというオプションを使えるようになるオーバーロードがあります。また戻り値のオブジェクトでは、実行が正しく実行されたかのフラグを保持しています。

さて、この場合は i => { } のアクションの部分が並列で実行されますが、平行で、numを更新にいっています。このnumが同時に更新されるために、本来実行結果では、20 * 20 の 400 の数だけカウントされるはずが、平行実行でそれぞれが更新する結果、実行結果が毎回変わります。

        static  void MultiThread()
        {
            const int ThreadNum = 20;
            const int LoopNum = 20;
            int num = 0;

            Parallel.For(0, ThreadNum, i =>
            {
                for (int j = 0; j < LoopNum; j++)
                {
                    int tmp = num;
                    Thread.Sleep(1);
                    num = tmp + 1; 
                }
            });
            Console.Write($"{num} ({ThreadNum * LoopNum})");
        }

結果

80 (400)

ロックを使用する

解決の方法としては、ここは、平行で実行すると困るという箇所を、クリティカルセクションと呼ぶのですが、そのクリティカルセクションに対して、ロックをかけます。基本的な書き方は、ロックオブジェクトというオブジェクトをつくります。ここではsyncObjがそれに該当します。
そして、クリティカルセクションをlock(syncObj)で囲います。すると、syncObjにロックがかかって、1つのスレッドしか同時にロックができなくなります。つまり同時に1つのスレッドしかこのセクションを実行できません。1つのスレッドがセクションの実行を終えると、ロックが解放されて、他のスレッドが使用可能になります。

        static void MultiThreadWithLock()
        {
            const int ThreadNum = 20;
            const int LoopNum = 20;
            int num = 0;
            var syncObj = new object(); 
            Parallel.For(0, ThreadNum, i =>
            {
                for (int j = 0; j < LoopNum; j++)
                {
                    lock (syncObj)
                    {
                        int tmp = num;
                        Thread.Sleep(1);
                        num = tmp + 1;
                    }
                }
            });
            Write($"{num} ({ThreadNum * LoopNum})");
        }

実行結果は正しくなります。

400 (400)

実行時間

ロックをかけると、当然実行時間にインパクトが出ます。ベンチマークをとってみました。あたりまえですが、ロックが無いのが最速です(答え間違っていますが)ロックをすると極端に実行時間が増えます。ちなみに2個目のMontorLockは、Monitor.Enter()/Minotor.Exec()を使ったロックで、元のブログに載っていたサンプルを実行した結果です。lockは、それのシンタックスシュガーですので、似たような実行結果になります。

80 (400) True The first(no lock): Elapsed Time: 1442 ms
400 (400)The second(Monitor lock): Elapsed Time: 6189 ms
400 (400)The third(lock): Elapsed Time: 6231 ms
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Fixed Joint機能について

Fixed Jointについて

今回はFixed Jointの機能を使ってオブジェクト同士をくっつける方法を簡単に説明していきたいと思います。

今回は以下の2つのオブジェクトを使用して説明していきたいと思います.

bandicam 2020-03-17 16-10-05-677.jpg
bandicam 2020-03-17 20-33-22-708.jpgbandicam 2020-03-17 20-33-29-156.jpg

CubeにこんなScriptをつけてみました

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class CubeScript : MonoBehaviour
{
    private float one = 0.05f;
    // Start is called before the first frame update
    void Start()
    {   
    }
    // Update is called once per frame
    void Update()
    {
        transform.position += new Vector3(one, 0, 0);
    }
}

すると,
ダウンロード1.gif

もちろんCapsuleは動かず,Cubeだけ進んでいきます

そこで使うのがFixed Jointです

 Fixed Jointの機能

bandicam 2020-03-17 20-48-39-112.jpg
- Connected Body:くっつけたいオブジェクトを入れる場所
- Break Force:くっつけたオブジェクトを離すために必要な力
- Break Torque:くっつけたオブジェクトを離すために必要なトルク
- Enable Collision:チェックをつけるとくっつけたオブジェクト同士が衝突をすることができる
- Enable Preprocessing:チェックをつけると不可能な動きをした時に安定した動きを取るようにする
- Mass Scale:質量の大きさ
- Connected Mass Scale:くっつけたオブジェクトの質量の大きさ

今回はScriptでFixed Jointの解除を行う予定なので,Connected BodyにCapsuleを入れるだけでOKです!

bandicam 2020-03-17 21-18-15-625.jpg

(くっつけるオブジェクトには必ずRigidBodyがついていることを確認してください)

Connected BodyにCapsuleを入れ,実行すると...

ダウンロード2.gif

このままだとCapsuleが引きずられている風になっているので,CubeのRigidbodyを開いて,ConstraintからFreeze Rotationnoのzにチェックを入れます

*bandicam 2020-03-17 15-54-55-578.jpg

Z軸のRotationを固定したので...

ダウンロード3.gif

これで完成です!

付記

if文とDestroyを使えば簡単にComponentの解除や追加をできるので,この機会にぜひFixed Jointを使ってみてください

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class CubeScript : MonoBehaviour
{
    private float one = 0.05f;
    // Start is called before the first frame update
    void Start()
    {   
    }
    // Update is called once per frame
    void Update()
    {

        transform.position += new Vector3(one, 0, 0);
       if (Input.GetMouseButtonDown(0))
        {
            FixedJoint component = this.gameObject.GetComponent<FixedJoint>();
            Destroy(component);
        }

    }
}

ダウンロード4.gif

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Unityでシーンを読み込むたびに静的フィールドを初期化するたったひとつの冴えたやりかた

この記事で述べていること

  • Unityでシーンを読み込むたびに、静的フィールドの初期化メソッドを一度だけ呼び出したい。
  • そんなときは静的コンストラクタSceneManager.sceneLoaded に初期化メソッドを追加すればよい。
  • この方法に辿り着く前に検討した方法と、採用しなかった理由はこういうものです。
  • もっといいやり方あったら情報ください。

使用バージョン

Unity 2018.4.5f1 Personal

説明

Unityでは静的フィールドの中身はシーンが変わっても保持されます。
しかし、時には「シーンを読み込むたびに初期化したい!」「でも静的フィールドにはしておきたい!」と思うときもあると思います。少なくとも私はありました。
そんなときは、以下の方法を使えばシーンを読み込むたびに静的フィールドの初期化メソッドを一度だけ呼び出すことができます。

1.まず、以下のようにクラスの静的コンストラクタを用意します。
これは、静的フィールドを初めて参照した際、参照する直前に呼び出されるコンストラクタです。これを使えば静的フィールドが参照される前に一度だけ、処理を行うことができます。

静的コンストラクタ
public class Example{
    private static int staticField_;

    static Example(){

    }
}

2.次に初期化用のメソッドを用意し、そのメソッドを静的コンストラクタ内でSceneManager.sceneLoadedに追加します。
これはUnityAPI側で用意されているイベントで、シーンが読み込まれた際にここに追加されたメソッドが呼び出されます。
第一引数には読み込まれたシーン、第二引数にはシーンの読み込みモードが入ります。この引数は追加するイベントに合わせて付ける必要があるため、省くことはできないことに注意してください。
また、SceneManagerクラスはUnityEngine.SceneManagement名前空間にあるため、インポートする必要があることにも注意してください。

シーンロード時のイベント
using UnityEngine.SceneManagement;

public class Example{
    private static int staticField_;

    static Example(){
      SceneManager.sceneLoaded += init;
    }

    private static void init(Scene loadingScene, LoadSceneMode loadSceneMode){
        staticField_ = 0;
    }
}

実行タイミング

「シーンを読み込んだ際に呼び出される」と書きましたが、その実行タイミングは
1.Awake
2.sceneLoadedに追加したメソッド(ここで実行される)
3.Start
となっているらしいです。つまりこの方法でも、自他のスクリプトのAwakeで静的フィールドを参照するようなコードを書くと、未初期化の値を参照してしまう恐れがあるので、その点は注意してください。

こうしなかった理由

これを考えつく前に検討した方法と、そうしなかった理由です。

1.わざわざ初期化メソッドを追加しなくても、静的コンストラクタで初期化すればいいんじゃないの?

静的コンストラクタは、初めて静的フィールドを参照した際に一度だけ実行されます。
「ならそれで初期化すればよくない?」と思われるかもしれませんが、Unityでは静的フィールドの値が保持される関係で、最初の一回のみ「初めて参照した」と判断されるらしいです。つまるところこれで初期化してしまうと、最初の一回以外はいくらシーンを切り替えようがゲームを終了しない限りはもう二度と初期化されません。

2. Awakeで初期化すればいいんじゃない?

このスクリプトが1つしか張り付いてない・確実にシーンにそのスクリプトが張り付いているオブジェクトがアクティブで存在している保証があるならまあそれでもいいんですが…いくつか以下のようなデメリットが出る場合があるので採用しませんでした。

デメリット1:Awakeは各インスタンス毎に実行されるため、必然的に複数張り付いていればそれだけ初期化メソッドが実行されます。 初期化が重ければ、それだけシーンのロードが重くなってしまいます。

デメリット2:「なら静的フィールドのフラグを作って一度だけ実行されるように管理すれば…」と思われるかもしれませんが、それだけの理由でフィールドが1つ増えてしまうことになりますし、そのフラグをStartなりでfalseにしてやる手間が増える(シーンの最初で再度初期化を走らせるため)ので、好ましくありません。

デメリット3:Awakeは「オブジェクトがアクティブのとき、インスタンスがロードされたタイミング」に実行されます。つまり、何か理由があって「オブジェクトを最初は非アクティブにしておき、後でアクティブにする」ということをしていた場合、初期化メソッドが走るタイミングはシーンの最初ではなく、そのタイミングになってしまいます。(オブジェクトがアクティブならスクリプトが非アクティブでもAwakeは実行されます)

デメリット4:そのスクリプトが張り付いているオブジェクトがなければ、必然Awakeも実行されません。つまりそのスクリプトが張り付いているオブジェクトが存在しないシーンでその静的フィールドを参照した場合、未初期化の値が取得されてしまいます。

最後に

これ本当に一番いい方法なんでしょうか?もし「違うよ」って人がいましたら連絡ください。

参考サイト

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

[Unity] UniversalRenderPipelineのMSAAをC#から変更する

はじめに

下記の続きです。
https://qiita.com/jnhtt/items/4814d629bcd98dc28a38

UniversalRenderPipelineのMSAAを実行時にC#から変更したいのでコードを書きました。
URPを使っている人向けの情報なので、URPのセットアップは説明は省略します。
ソースコードはここです。

↓結果画面
msaa.gif

目的

実行時にC#からMSAAを変更して見え方を確認したできるようにします。

Editor操作で変更

インスペクタ上でMSAAを変更する場合、使っているUniversalRenderPipelineAssetのインスペクタを開きます。
Quality > AntiAliasing(MSAA)のボタンを押すとMSAAのサンプル数が変更されます。
スクリーンショット 2020-03-22 0.58.17.png

C#から変更

GraphicsSettings.currentRenderPipelineでUniversalRenderPipelineAssetを取得すれば、あとはmsaaSampleCountにアクセスできます。
Default値は1です。

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Events;
using UnityEngine.Rendering;
using UnityEngine.Rendering.Universal;
using UnityEngine.UI;

public class MSAA : MonoBehaviour
{
    [SerializeField]
    private Slider renderScaleSlider;
    [SerializeField]
    private Button msaaButton;
    [SerializeField]
    private TMPro.TMP_Text text;

    private static string[] MSAA_TEXT = new string[] { "NONE", "x2", "x4", "x8" };
    private UniversalRenderPipelineAsset pipelineAsset;
    private int msaaValue;

    private void Start()
    {
        pipelineAsset = GraphicsSettings.currentRenderPipeline as UniversalRenderPipelineAsset;
        if (pipelineAsset != null)
        {
            msaaButton.onClick.AddListener(OnPushMSAA);
            msaaValue = (int)Mathf.Log(pipelineAsset.msaaSampleCount, 2);
            renderScaleSlider.onValueChanged.AddListener(OnValueChanged);
            SetMSAAText();
            renderScaleSlider.value = pipelineAsset.renderScale;
        }
    }

    private void OnValueChanged(float value)
    {
        if (pipelineAsset != null)
        {
            pipelineAsset.renderScale = value;
        }
    }

    private void OnPushMSAA()
    {
        if (pipelineAsset != null)
        {
            msaaValue = (msaaValue + 1) % 4;
            pipelineAsset.msaaSampleCount = 1 << msaaValue;
            SetMSAAText();
        }
    }

    private void SetMSAAText()
    {
        text.text = "MSAA " + MSAA_TEXT[msaaValue % 4];
    }
}

さいごに

URPのMSAAを変更できるようになりました。
UniversalRenderPipelineAssetの他のパラメーターも変更できます。

URPの使い方をもっと知りたい方はこちらをどうぞ。
技術書典8 新刊
Unity実践UniversalRenderPipeline
技術書典応援祭
BOOTH

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

[Unity] UniversalRenderPipelineのMSAA設定をC#から変更する

はじめに

下記の続きです。
https://qiita.com/jnhtt/items/4814d629bcd98dc28a38

UniversalRenderPipelineのMSAAを実行時にC#から変更したいのでコードを書きました。
URPを使っている人向けの情報なので、URPのセットアップは説明は省略します。
ソースコードはここです。

↓結果画面
msaa.gif

目的

実行時にC#からMSAAを変更して見え方を確認したできるようにします。

Editor操作で変更

インスペクタ上でMSAAを変更する場合、使っているUniversalRenderPipelineAssetのインスペクタを開きます。
Quality > AntiAliasing(MSAA)のボタンを押すとMSAAのサンプル数が変更されます。
スクリーンショット 2020-03-22 0.58.17.png

C#から変更

GraphicsSettings.currentRenderPipelineでUniversalRenderPipelineAssetを取得すれば、あとはmsaaSampleCountにアクセスできます。
Default値は1です。

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Events;
using UnityEngine.Rendering;
using UnityEngine.Rendering.Universal;
using UnityEngine.UI;

public class MSAA : MonoBehaviour
{
    [SerializeField]
    private Slider renderScaleSlider;
    [SerializeField]
    private Button msaaButton;
    [SerializeField]
    private TMPro.TMP_Text text;

    private static string[] MSAA_TEXT = new string[] { "NONE", "x2", "x4", "x8" };
    private UniversalRenderPipelineAsset pipelineAsset;
    private int msaaValue;

    private void Start()
    {
        pipelineAsset = GraphicsSettings.currentRenderPipeline as UniversalRenderPipelineAsset;
        if (pipelineAsset != null)
        {
            msaaButton.onClick.AddListener(OnPushMSAA);
            msaaValue = (int)Mathf.Log(pipelineAsset.msaaSampleCount, 2);
            renderScaleSlider.onValueChanged.AddListener(OnValueChanged);
            SetMSAAText();
            renderScaleSlider.value = pipelineAsset.renderScale;
        }
    }

    private void OnValueChanged(float value)
    {
        if (pipelineAsset != null)
        {
            pipelineAsset.renderScale = value;
        }
    }

    private void OnPushMSAA()
    {
        if (pipelineAsset != null)
        {
            msaaValue = (msaaValue + 1) % 4;
            pipelineAsset.msaaSampleCount = 1 << msaaValue;
            SetMSAAText();
        }
    }

    private void SetMSAAText()
    {
        text.text = "MSAA " + MSAA_TEXT[msaaValue % 4];
    }
}

さいごに

URPのMSAAを変更できるようになりました。
UniversalRenderPipelineAssetの他のパラメーターも変更できます。

URPの使い方をもっと知りたい方はこちらをどうぞ。
技術書典8 新刊
Unity実践UniversalRenderPipeline
技術書典応援祭
BOOTH

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む