20191023のC#に関する記事は13件です。

[C#] Linqの中でawaitさせたいとき

もくじ
https://qiita.com/tera1707/items/4fda73d86eded283ec4f

やりたいこと

下記のようなコードで、Selectを使ってテーブルの値を加工したい。

Selectでテーブル編集.cs
int[] table = { 1,2,3,4,5,6,7,8,9,10 };

private void Button_Click2(object sender, RoutedEventArgs e)
{
    var ans = table.Select(x => Add10(x));
}
private int Add10(int a)
{
    return a + 10;
}

このとき、Add10メソッドが非同期メソッドだった場合に、どのように書けばよいかわからなかった。

Selectでテーブル編集.cs
int[] table = { 1,2,3,4,5,6,7,8,9,10 };

private async void Button_Click2(object sender, RoutedEventArgs e)
{
    // どうやって書けばいい?
    // ここでansを計算して、awaitで待って、この後にansを表示させるなどしたい。
    var ans = await??? table.Select(async???(x) => ???);
}
private async<int> Add10(int a)
{
    // 非同期版の計算メソッド
    await Task.Delay(1000);
    return a + 10;
}

やりかた

Linqの部分を、Task.WhenAllで包んで、それをawaitする。

サンプルコード

Selectでテーブル編集(非同期版).cs
int[] table = { 1,2,3,4,5,6,7,8,9,10 };

private async void Button_Click(object sender, RoutedEventArgs e)
{
    Debug.WriteLine("処理開始");

    // ★★数値tableに100を足す計算をSelectで行い、それをTask.WhenAllで包む★★
    var ans = await Task.WhenAll(table.Select(async (x) => await Add100(x)));

    Debug.WriteLine($"処理終了");
}

// 100加算して返す(実験的に5秒待つ)
private async Task<int> Add100(int a)
{
    Debug.WriteLine($"input is {a}. Start Calc...");
    await Task.Delay(1000);
    Debug.WriteLine($"input is {a}. End Calc.");

    return a + 100;
}

上記を実行すると、下のような出力となる。
処理全体としては、SelectでAdd100メソッドをよぶ処理が10件全部平行して行われるので、1秒(1000ms)経過した時点で処理が完了する。
※1~10までの加算処理の完了は、順番は保証されない。

出力.txt
処理開始
input is 1. Start Calc...
input is 2. Start Calc...
input is 3. Start Calc...
input is 4. Start Calc...
input is 5. Start Calc...
input is 6. Start Calc...
input is 7. Start Calc...
input is 8. Start Calc...
input is 9. Start Calc...
input is 10. Start Calc...
input is 3. End Calc.
input is 2. End Calc.
input is 1. End Calc.
input is 10. End Calc.
input is 8. End Calc.
input is 9. End Calc.
input is 7. End Calc.
input is 6. End Calc.
input is 5. End Calc.
input is 4. End Calc.
処理終了

参考

ラムダ内でasyncを書き、結果はIEnumerable>となるので、配列に戻してやるためにTask.WhenAllとセットで使っていくのが基本となります。
http://neue.cc/2013/12/04_435.html

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

C#のコンパイルエラーの対処例を書いてみる

まえおき

C#布教活動しているなかで、初心者ごろしなのがコンパイルエラーな気がしてきたので、トラブルシューティング的なものを書いてみたい
結果、思ってたのと違う記事が出来上がった。
というわけで、読み物としてお楽しみください。

やってること

わざとコンパイルエラーを通らないコードを書いてコンパイルエラーを潰していく様を書いてみる。
※この記事全体でいえることですが、別なコードを書いている場合は、同じコンパイルエラーでも、とるべき対処法は変わります。

結局のところ

場当たり的にコンパイルエラー潰すより、文法を学ぶなり、まっとうなサンプルコードを見るなりしたほうが、トータルでみると早くて、まともな設計ができるようになると思うので、この記事は意味がない。ちゃんと勉強しましょう。

環境

Windows10 で、コマンドプロンプトで実行。
この記事では、パスが通せていることを前提にしています。

where cscの結果:
C:\Windows\Microsoft.NET\Framework64\v4.0.30319\csc.exe

本題に入る前に - Visual Studio向けのソースコードをもってきたケース

'InitializeComponent' は現在のコンテキスト内に存在しません
というコンパイルエラーがでる場合。

InitializeComponentは、通常、Visual Studioを使っている前提のソースコードです。
(Formのコードなどがツールで自動生成されたものです。)
コマンドプロンプト環境でコンパイルするのはハードル高いです。
この記事を見ても解決できません。ごめんなさい。

本題 - Step.0 - べた書き

Hello Worldをやりたくて下記みたいなコードを書いたとします。
(ふつうはコンパイル通るコードをコピペしてHello Worldやると思いますが。)

BadExample.cs
WriteLine(HelloWorld);

csc BadExample.csと打ってコンパイルすると、下記のようにおこられます。

BadExample.cs(1,1): error CS0116: A namespace cannot directly contain members such as fields or methods

■日本語の場合:
badExample.cs(1,1): error CS0116: 名前空間にフィールドやメソッドのようなメンバーを直接含めることはできません。

C#で処理を書くときはクラスで囲めということです。
※「うっかり」以外でこのエラーが出るようなコーディングをしている場合は、いますぐコンパイル作業をやめてC#の文法を学びましょう。
(まずは、長くても数十行くらいのコンパイルが通るサンプルコードを持ってきて、なんとなくの書き方を知るのがよいかと。)

Step.1 - コンパイルエラーが増えることもある

Step.0に対してクラスで囲んで対処。

BadExample.cs
class {
    WriteLine(HelloWorld);
}

↓コンパイルエラー

BadExample.cs(1,7): error CS1001: Identifier expected
BadExample.cs(2,5): error CS1520: Method must have a return type
BadExample.cs(2,25): error CS1001: Identifier expected

■日本語の場合:
BadExample.cs(1,7): error CS1001: ID がありません。
BadExample.cs(2,5): error CS1520: メソッドは戻り値の型を持たなければなりません。
BadExample.cs(2,25): error CS1001: ID がありません。
`ファイル名.cs(行番号,文字位置): error CS1518: Expected class, delegate, enum, interface, or struct`

むしろコンパイルエラーが増えました。1つ潰すとコンパイルエラーが増えることはよくあります。

なお、慣れないうちはコンパイルエラーは先頭から潰しましょう。

エラーメッセージのフォーマットは、
ファイル名(行番号,文字位置): error エラー番号: エラーの説明 です。
特に「行番号」は大きな手掛かりになります。

Step.2

Step.1への対策として、ID(いわゆる識別子)を付ける。

BadExample.cs
class BadExample {
    WriteLine(HelloWorld);
}

↓コンパイルエラー

BadExample.cs(2,5): error CS1520: Method must have a return type
BadExample.cs(2,25): error CS1001: Identifier expected

■日本語の場合:
BadExample.cs(2,5): error CS1520: メソッドは戻り値の型を持たなければなりません。
BadExample.cs(2,25): error CS1001: ID がありません。

Step.3 - コンパイラの解釈とプログラマの意図がずれている

Step.2のコンパイルエラーは、
このコードを書いた人が「BadExampleクラスがWriteLineメソッドを持っているコードを書きたい。」と解釈してエラーメッセージをだしています。

コンパイラが解釈している、Step.2の正しいコード(あくまでイメージです)
class BadExample {
    戻り値の型 WriteLine(引数) {
    
    }
}

実際にやりたいことは、WriteLineを呼び出して「Hello World」を表示したい。(この記事ではそういう設定と思ってください。)
なので、下記のように修正してみます。

BadExample.cs
class BadExample {
    void BadMethod() {
        WriteLine(HelloWorld);
    }
}

↓コンパイルエラー

BadExample.cs(3,9): error CS0103: The name 'WriteLine' does not exist in the current context
BadExample.cs(3,19): error CS0103: The name 'HelloWorld' does not exist in the current context

■日本語の場合:
BadExample.cs(3,9): error CS0103: 名前 'WriteLine' は現在のコンテキスト内に存在しません。
BadExample.cs(3,19): error CS0103: 名前 'HelloWorld' は現在のコンテキスト内に存在しません。

エラーが、「〇〇は現在のコンテキスト内に存在しません。」だけになりました。
ようやく構文エラーが消えました。
構文エラーの場合、直接の原因でない箇所でもコンパイルエラーがでたりするので、まずは構文エラーを潰すのが定石だと思います。
(括弧の閉じ忘れとか、識別子が1個抜けてるとか、セミコロンを忘れているとかとか。
Visual Studio Codeとか、構文をある程度解釈して色分け表示してくれるようなエディタや統合開発環境を選べば、うっかり系の構文エラーはかなり減らせます。)

Step.4 - クラスを探す(Microsoft Docsがおすすめ)

Step.3では、コンパイラがWriteLineがないと言っているので、検索キーワードwriteline site:microsoft.comとかでググるなりします。
(今回のように、クラスを探すだけならsite:microsoft.comを付けるのがおすすめです。)

「Console.WriteLine - Microsoft Docs」
https://docs.microsoft.com/ja-jp/dotnet/api/system.console.writeline?view=netframework-4.8
が見つかるはずなので、開きます。(Consoleを知っているというテイで、ご容赦ください。。)

名前空間:System
Assemblies:System.Console.dll, mscorlib.dll, netstandard.dll

とかかれているはずです。(別のクラスにもWriteLineがあるので注意。)
というわけで、名前空間をつなげてSystem.Console.WriteLineとしてあげると、コンパイラがこれを特定できます。
(なお、using 名前空間;と宣言しておくと、名前空間.は省略できます。
Consoleはクラス名なので省略できません1。)

BadExample.cs
class BadExample {
    void BadMethod() {
        System.Console.WriteLine(HelloWorld);
    }
}

↓コンパイルエラー

BadExample.cs(3,34): error CS0103: 名前 'HelloWorld' は現在のコンテキスト内に存在しません。

Step.5 - 文字列について(ちょっと脱線)

文字列は""で囲む。(記事を書いている途中で、このミスは「わざとらしすぎる」ので、最初から"HelloWorld"にしようかと思ったが、警告内容が変わってめんどくさいので、そのままにした。)

ちなみに@"xxx"とすると、エスケープ文字を処置しなくて済むので、ファイルパスとかを埋め込むときとか正規表現書くときはこっちのほうが楽。

ダメな例:"c:\tmp.txt" (※\tはタブ文字と解釈される。\tに限らず、基本的にエスケープをミスっててもコンパイルは通ってしまうケースが多い。)
OKな例:"c:\\tmp.txt" もしくは @"c:\tmp.txt"

エスケープ文字や"を含まない文字列については、@付きの表記のほうがミスしにくいのでお勧めしたい。

BadExample.cs
class BadExample {
    void BadMethod() {
        System.Console.WriteLine(@"HelloWorld");
    }
}

↓コンパイルエラー

error CS5001: Program 'c:\(中略)\BadExample.exe' does not contain a static 'Main' method
 suitable for an entry point

■日本語の場合:
error CS5001: プログラム 'c:\(中略)\BadExample.exe' は、エントリ ポイントに適切な静的
 'Main' メソッドを含んでいません

Step.6 - Mainメソッドがないとプログラムの入口がわからない

Mainメソッドがないよ。
※C言語ユーザーは違和感あるかもしれませんが、mainではなくMainです。

ググってC#のMainの書き方を調べたことにしてください。
[STAThread]つけ忘れてはまると可哀想なので、つけておきます。

BadExample.cs
class BadExample {
    [STAThread]
    static void Main() {
        System.Console.WriteLine(@"HelloWorld");
    }
}

これでいけるでしょ。完成!
↓コンパイルエラー

BadExample.cs(2,6): error CS0246: The type or namespace name 'STAThread' could not be found (are you missing a using
        directive or an assembly reference?)
BadExample.cs(2,6): error CS0246: The type or namespace name 'STAThreadAttribute' could not be found (are you missing a
        using directive or an assembly reference?)

■日本語の場合:
BadExample.cs(2,6): error CS0246: 型または名前空間名 'STAThread' が見つかりませんでした。using
        ディレクティブまたはアセンブリ参照が不足しています。
BadExample.cs(2,6): error CS0246: 型または名前空間名 'STAThreadAttribute' が見つかりませんでした。using
        ディレクティブまたはアセンブリ参照が不足しています。

Step.7

ディレクティブまたはアセンブリ参照が不足しています。は、名前空間の指定が足りていなくて、コンパイラが見つけられていないケースが多いです。
※コンパイルオプションが必要な場合もあります。2

site:microsoft.com をつけてググります。

STAThreadAttribute Class
名前空間:System
Assemblies:System.Runtime.dll, mscorlib.dll, netstandard.dll

System に存在しているので、

BadExample.cs
using System;

class BadExample {
    [STAThread]
    static void Main() {
        Console.WriteLine(@"HelloWorld");
    }
}

という風にすればコンパイル通ります。
下記でも問題ないですが、STAThreadSystemつけているのはあまり見ないスタイル(※個人の感想です)。

class BadExample {
    [System.STAThread]
    static void Main() {
        System.Console.WriteLine(@"HelloWorld");
    }
}

BadExample.exeの実行結果:

HelloWorld

もうBadExampleじゃないけど、名前変えるとミスしそうなのでこのまま。

・・・見てくれた方はありがとうございます。お疲れさまでした!

おまけ - cscの出力メッセージを英語にする。

コンパイラのメッセージを英語と日本語の両方を載せたいと思い、調べた。

コマンドプロンプトで chcp 437 を実行するとcscの警告内容も英語に変えられる。日本語(Shift_JIS)はchcp 932
参考:
http://pocketstudio.jp/log3/2012/02/13/change_language_and_chcp/
https://www.ipentec.com/document/windows-codepage-list


  1. 「省略できません」はちょっと嘘ですが、この記事を読む段階では知らないほうがよい気がします。(using static使えば、staticなクラスについては省略可能になる。Mathクラスとか使いまくる場合は要り様になるかもしれない。) 

  2. csc/r:DLLへのパスオプションでアセンブリ参照を追加しないといけないケースがあります。(Excel使う場合とか。大体はC:\Windows\assembly\以下をdir /s 名前空間名.dllすればでてくると思います。参考: https://www.atmarkit.co.jp/fdotnet/dotnettips/846extratfromgac/extratfromgac.html

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

脱出ゲームの作り方 6 アイテム使用による電球のONOFF

これまでの作業で、

■A.DrawerPnelで引き出しがクリックされたら電球画像を表示し、
ItemBoxに電球を取得する処理を作成した

1.DrawerPnelで引き出しがクリックされたら、電球画像を表示する
脱出ゲームの作り方 2
◇TriggerButtonを押したら電球パネルとテキストを表示

DrawerManager.rb
public void OnClickTrigger()
    {
        LightBulbSetActive(true);
    }

2.上記1.と同時に、ItemBoxに電球の画像を表示する
脱出ゲームの作り方 4
◇itemに電球のデータが取得されたら、電球の画像を表示する

DrawerManager.rb
public void OnClickTrigger()
    {
        LightBulbSetActive(true);
        itemBoxManager.SetItem(ITEM.LIGHT_BULB);
    }

■B.LightstandPanelで、ItemBoxの1つ目と2つ目をクリックして電球をON/OFFするテストをした
脱出ゲームの作り方  5
テスト
Unityエディタで
1つ目のItemBoxの On Click() にLightSwitch()をtrueで設定 :電球が点灯
2つ目のItemBoxの On Click() にLightSwitch()をfalseで設定 :電球が消灯

※設定を解除するときは、On Click()のマイナス(-)ボタンで削除する

アイテムボックスに取得した電球を使うと、電球が点灯する処理を作成する

◇ItemBoxManagerに処理を追加する

アイテムを使用する関数UseItem()に、
アイテムボックスに電球があったら、電球を使用してライトスタンドを点灯する処理

LightStandManagerスクリプトを使えるようにする

:interrobang:
LightStandManager(型)、 lightStandManager(変数)??
※エディタでItemBoxPnel(オブジェクト)を選択すると、InspecterのItemBoxManagerスクリプトに
LightStandManagerが出る
スクリプトを変数のように扱って、他のスクリプトで使えるようにする?

ItemBoxManager.cs
   [SerializeField] LightStandManager lightStandManager;

アイテムが電球だったら、
lightstandManagerスクリプトのLightSwitch()関数をtrueで実行する
それ以外は何もしない

ItemBoxManager.cs
    public void UseItem(int index)
    {
        switch (itemList[index])            //アイテムが電球だったら、
        {
            case ITEM.LIGHT_BULB:
                lightstandManager.LightSwitch(true);     //LightSwitch()をtrue
                break;
            default:                                    //それ以外は何もしない
                break;
        }
        itemList[index] = ITEM.NONE; // index番目のアイテムを使用したので空にする;
        itemBoxImages[index].sprite = null; //index番目に何も表示しない
    }

※UseItem(int index)の引数は、itemBoxの番号に対応している

◇Unityエディタで

ItemBoxPnel(オブジェクト)を選択し、InspecterでのItemBoxManagerスクリプトに
LightStandManager
が出ているので、ItemBoxPnel(オブジェクト)をセットする

その下の、4つのItemBoxを全部選択し、
InspectorでItemBoxのButtonコンポーネントの On Click() にItemBoxPanel(オブジェクト)をセットして、
ItemBoxManagerに表示される関数をからUseItem(int)を設定する。
4つの各ItemBoxについて、UseItem(int)の引数(0,1,2,3)を設定して、
ItemBoxと使うアイテムを対応させる

:interrobang:
どのオブジェクトにどのスクリプトをアタッチしたかを一覧で見る方法?
ボタンコンポーネントに、スクリプトを作成した関数を選択するためにオブジェクトを設定するときにわかりにくくなってしまう

:interrobang:自分の課題
各オブジェクト
オブジェクトにアタッチしたスクリプト
スクリプトで作成した変数、関数
Inspectorで変数や関数を設定する
これらのつながりを整理する

教材

Unityゲームスタジオ スタジオしまづ
【Unity】初心者からの脱出!? 脱出ゲームの作り方 その6 アイテム使用による電球のONOFF
https://youtu.be/Z4uJWBRvGhk

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

Azure Cognitive Services × C# で画像内の顔を検出するアプリを作成する

今回は Face API を使って、画像内の顔を検出するアプリを作成します。Face API は日に日に精度や機能が追加されていますが、REST API があったりなど使いこなしやすいので便利です。

Azure Portal で Face API をデプロイする

まずはアプリケーションを作成する際に使用する、Face API Key をデプロイします。Azure Portal で Face と検索すると出てきます。任意のリソースグループ名とリソース名を入れてデプロイします。今回使うものはフリープランでも大丈夫です。
image.png

作成したら Face API の Key をコピーしておきましょう。
image.png

サンプルコードをデプロイする

Microsoft の Document にある Github 公開しているコードを自分の開発環境上に Clone します。コマンドプロンプトから以下を実行するとデプロイできます。次いでに Visual Studio を開くところまで下記コードで実行できます。

git clone https://github.com/Azure-Samples/Cognitive-Face-CSharp-sample.git
cd Cognitive-Face-CSharp-sample/FaceTutorialCS
FaceTutorialCS.sln

Face API Key の追加

そのままデプロイすると当たり前ですが以下のエラーが発生します。
image.png

Visual Studio が自動で場所を推測していくれるので、以下画像の場所に最初にコピーしたEndpoint と Key を入力します。
image.png
実はこれだけではできません。Face API の Nuget Package をダウンロードします。
image.png
これでやると、Key の String に不適切な値が入っているとエラーが発生します。少しですが以下のように書き換えます。(MSのGithub のコードが少し間違ってましたね)
image.png

これでデプロイしてみます。
image.png
大谷翔平はスマイル100%でした!

参考URL&Tips

チュートリアル:画像内の顔データを表示する WPF アプリの作成
https://docs.microsoft.com/ja-jp/azure/cognitive-services/face/tutorials/faceapiincsharptutorial

Face API の Key でできなかった場合は、Cognitive Services の Key でデプロイしてみましょう。デプロイは通っているけどうまく分析できない場合はそれで解決することがあります。最近 Cognitive Services にまとめれてきているため、Endpoint に無駄な「/faces」とか入っているとうまくAPI をたたけない場合があります。

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

【.NetCore】ViewComponentで共通画面の作成【C#】

ViewComponentとは?

多分、画面の部品とかそういう感じの機能です。
WebFormsでいうascxに近い役割だと勝手に思ってます。
いろんな画面で共通の項目を表示したい時など、その共通部分をViewComponentとして作成しておくと流用が効きます。

最近、かなり多用しているので、基本的な使い方をシェアしておきます。

簡単に作成してみる。

HomeController
 public class HomeController : Controller
    {
        public IActionResult Index()
        {
            return View();
        }
    }

    [ViewComponent(Name = "TestView")]
    public class MenuView : Microsoft.AspNetCore.Mvc.ViewComponent
    {

        public IViewComponentResult Invoke(string text)
        {
            ViewBag.Text = "ビューバッグ";
            TestClass test = new TestClass();
            test.TestName = text;
            return View(test);
        }
    }

    public class TestClass
    {
        public string TestName { get;set;}
    }
index.cshtml
@{
    ViewData["Title"] = "Home Page";
    Layout = null;
}

<p>メインビューだよ</p>
@await Component.InvokeAsync("TestView","田中太郎") 
default.cshtml
<div>
    <p> ビューコンポーネントだよ</p>
    <p>@ViewBag.Text</p>
    <p>@Model.TestName</p>
</div>

解説

ViewComponent用のクラスを作成する

Controllerの2クラス目のMenuView、これがViewComponentのクラスです。
ViewComponentを呼び出すとInvokeメソッドが実行され、default.cshtmlにモデルが渡されてビューが出来ます。
Invokeメソッドは基本的にアクションメソッドと変わりません。

ViewComponentを呼び出す

index.cshtmlの@await Component.InvokeAsync("TestView","田中太郎")でViewComponentのInvokeメソッドを実行できます。

構成と結果

キャプチャ.PNG
1.PNG

こんな感じで、かなり簡単な手順で画面の一部分を別処理で出力することができます。
これだけでも便利ですが、ただの部品の共通化以外にもAjaxと組み合わせることで画面の非同期更新が非常に簡単に行えます。
そのやり方はいずれ書こうと思います。

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

C#で様々な線を描写する

C#でウィンドウに線を描く基本が学べます。簡単な直線から、線のスタイルやラインキャップなどを変えた線までを描写します。

前半は基本から始めるので、C#で何かを描写したことがある方は読み飛ばしてください。

描写の準備

Graphicsオブジェクトの生成

フォームに何かを描写する場合、最初にGraphicsオブジェクトを生成する。
これは身近なものに例えるとキャンバスを用意することと同義である。
今回は現在のForm1をgraphicsオブジェクトに指定するので、以下のように記述します。

Graphics g = Graphics.FromHwnd(this.Handle);

これでForm1が認識されました。

この後フォームを初期化します。

g.Clear(this.BackColor);

この時、Clearメソッドの引数にフォームの背景色を指定しています。
これで初期化完了です。

Penオブジェクトの生成

Graphicsオブジェクトを生成したら次にPenオブジェクトを生成する。
これは身近なものに例えると正しくペンを準備するのと同義である。
この二つを用意するとようやく線を書く準備が整ったことになる。

以下の例では、コンストラクタにペンの色と幅を指定している。

Pen p = new Pen(Color.Red, 10);

Penに対しては様々なプロパティが指定できる。詳しくはPen Classを参照されたい。
以下ではPenのプロパティのうち代表的なものを紹介する。

リソースの管理

通常のプログラムにおいてリソースの寿命に気を使う必要はないが、知っておくに越したことはないでしょう。
頻繁なオブジェクトの生成・消滅が行われる場合や、画像の処理を行う場合は明示的に開放を行うとリソースを有効に活用できます。
明示的にリソースの寿命をシステムに伝えるとシステムに与える負荷を減らすことができます。
プログラムでは一般的に、usingやDisoposeメゾットが用いられます。
使用例については最下部のプログラムを参照してください。

様々なペンのプロパティ

ここでは様々な線のプロパティの種類について説明します。

線のスタイルの指定

線のスタイル一覧

スタイル   内容                     
Custom ユーザー定義のカスタム線種          
Dash 破線                     
DashDot 破線と点線で構成される線           
DashDotDot 破線と点線の繰り返しのパターンで構成される線 
Dot 点線                     
Solid 実践                     

指定方法の例

p.DashStyle = DashStyle.Solid ;

より細かいプロパティには、DashCapプロパティDashPatternプロパティなどがあります。

ラインキャップの指定

ラインキャップの種類一覧

スタイル         内容       
Flat 平坦なキャップ    
Round 丸いキャップ    
Triangle 三角形のキャップ      
ArrowAnchor 矢印型のアンカー    
DiamondAnchor 菱型のアンカー      
RoundAnchor 円いのアンカー    
SquareAnchor 四角いアンカー    

詳細は、LineCap Enum を参照されたい。

//Pen pに対してLienCapを指定する場合
p.StartCap = LineCap.ArrowAnchor;
p.EndCap = LineCap.RoundAnchor;

直線を書く

直線を書く場合Graphics.DrawLineメソッドを用いる。
様々なパラメータのして方法があるがここでは一例を紹介する。
以下の例は、Graphics g に対して Pen p で (10,20) から (30,40) に直線を描写する例である。

g.DrawLine(p,10,20,30,40) ;

他のパラメータ指定方法は、Graphics.DrawLine Method を参照されたい。
下部の実装例では、ポインタによる指定方法を用いている。

直線描写の実装

Form1
using System;
using System.Drawing;
using System.Windows.Forms;

namespace DrawLine
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
        }

        private void Form1_Click(object sender, EventArgs e)
        {
            //Graphicsの準備
            using (Graphics g = Graphics.FromHwnd(this.Handle))
            {
                g.Clear(this.BackColor);
                //Pointの指定
                Point ptS = new Point(10, 50);
                Point ptE = new Point(this.Width - 20, 50);

                //Penの準備
                Pen p = new Pen(Color.Black, 5);

                //線の描写
                p.DashStyle = System.Drawing.Drawing2D.DashStyle.Dash;
                g.DrawLine(p, ptS, ptE);

                ptS.Y += 20;
                ptE.Y = ptS.Y;
                p.DashStyle = System.Drawing.Drawing2D.DashStyle.DashDot;
                g.DrawLine(p, ptS, ptE);

                ptS.Y += 20;
                ptE.Y = ptS.Y;
                p.DashStyle = System.Drawing.Drawing2D.DashStyle.DashDotDot;
                g.DrawLine(p, ptS, ptE);

                ptS.Y += 20;
                ptE.Y = ptS.Y;
                p.DashStyle = System.Drawing.Drawing2D.DashStyle.Dot;
                g.DrawLine(p, ptS, ptE);

                ptS.Y += 20;
                ptE.Y = ptS.Y;
                p.DashStyle = System.Drawing.Drawing2D.DashStyle.Solid;
                g.DrawLine(p, ptS, ptE);

                ptS.Y += 20;
                ptE.Y = ptS.Y;
                p.StartCap = System.Drawing.Drawing2D.LineCap.Flat;
                p.EndCap = System.Drawing.Drawing2D.LineCap.Round;
                g.DrawLine(p, ptS, ptE);

                ptS.Y += 20;
                ptE.Y = ptS.Y;
                p.StartCap = System.Drawing.Drawing2D.LineCap.ArrowAnchor;
                p.EndCap = System.Drawing.Drawing2D.LineCap.DiamondAnchor;
                g.DrawLine(p, ptS, ptE);

                ptS.Y += 20;
                ptE.Y = ptS.Y;
                p.StartCap = System.Drawing.Drawing2D.LineCap.RoundAnchor;
                p.EndCap = System.Drawing.Drawing2D.LineCap.SquareAnchor;
                g.DrawLine(p, ptS, ptE);
            }
        }
    }
}

実行結果

クリックすると以下のように表示された。

キャプチャ.PNG

最後に

DrawLineやPenには様々なプロパティがあり、今回はその一部のみしか紹介できませんでした。その他については、各自でそれぞれ調べてみてください。

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

脱出ゲームの作り方  5 電球のON/OFF

スクリプトの作成

LightstandManagerを作成し、LightstandPanel(オブジェクト)に張り付ける。

:page_facing_up:LightstandManager.cs

using UnityEngine.UI;を追加する。

lightON、lightOFF、の画像を用意
image型の変数image(画像を入れる)

[SerializeField] Sprite lightON;
[SerializeField] Sprite lightOFF;
[SerializeField] Image image;

スイッチのON/OFFで画像を差し替える

   public void LightSwitch(bool isON)
    {
        if (isON)                 //isONがtrue
        {
            image.sprite = lightON;  //imageの画像をlightONにする
        }
        else                           //そうでなかったら
        {
            image.sprite = lightOFF;   //imageの画像をlightOFFにする
        }
    }

※テスト
ItemBoxの左から1つ目のボックスをクリックしたら点灯し
2つ目のボックスをクリックしたら消灯する

InspectorでItemBoxのButtonコンポーネントの On Click() にLightstandPanel(オブジェクト)をセットして、
LightstandManagerに表示される関数をからLightSwitch()を設定する。チェックをつけてtrueにする。

InspectorでIItemBox (1)のButtonコンポーネントの On Click() にLightstandPanel(オブジェクト)をセットして、
LightstandManagerに表示される関数をからLightSwitch()を設定する。チェックなしでfalseにする。

:interrobang:
※ImagesのLightDown
Images_q.png

教材

Unityゲームスタジオ スタジオしまづ
【Unity】初心者からの脱出!? 脱出ゲームの作り方 その5 電球のON/OFF
https://youtu.be/S8G9LE0LNq8

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

.NET CoreでNESを動かしてみる

はじめに

今回はWindows限定となりますがwinformsを試してみます。
https://github.com/namikitakeo/love-nes

実行環境

下記バージョンで動作確認しています。
- Windows 10
- .NET Core 3.0

$ dotnet --version
3.0.100

学習方針

コマンドプロンプトから実行します。

$ mkdir LoveNes
$ cd LoveNes
$ dotnet new winforms

まずは実行してみます。

$ dotnet run

以下を移植していきます。
https://github.com/dotnetGame/love-nes

不要なファイルを削除します。

NuGet.config
build/
src/LoveNes.Cli/
src/LoveNes.sln
src/LoveNes/IO/ReadWriteExtensions.cs
src/LoveNes/IO/Writer.cs

必要なファイルを追加します。

src/LoveNes/Host/BitVector16.cs
src/LoveNes/Host/BitVector8.cs
src/LoveNes/Host/Form1.cs
src/LoveNes/Host/Form1.Designer.cs
src/LoveNes/Host/Program.cs

プロジェクトを修正します。

LoveNes.csproj
<Project Sdk="Microsoft.NET.Sdk.WindowsDesktop">

  <PropertyGroup>
    <OutputType>WinExe</OutputType>
    <TargetFramework>netcoreapp3.0</TargetFramework>
    <UseWindowsForms>true</UseWindowsForms>
    <AllowUnsafeBlocks>true</AllowUnsafeBlocks>
  </PropertyGroup>

</Project>

プログラムを修正します。

IO/SpanReader.cs
using System;
using System.Collections.Generic;
using System.IO;
using System.Text;

namespace LoveNes.IO
{
    public ref struct SpanReader
    {
        private ReadOnlySpan<byte> _span;

        public bool IsCosumed => _span.IsEmpty;

        public SpanReader ReadAsSubReader(int length)
        {
            var reader = new SpanReader(_span.Slice(0, length));
            Advance(length);
            return reader;
        }

        public SpanReader(ReadOnlySpan<byte> span)
        {
            _span = span;
        }

        public uint ReadAsUnsignedInt()
        {
            // var value = _span.ReadBigEndian<uint>();
            uint value = (uint)_span[0]*256*256*256 + (uint)_span[1]*256*256 + (uint)_span[2]*256 + (uint)_span[3];
            Advance(sizeof(uint));
            return value;
        }

        public byte ReadAsByte()
        {
            // var value = _span.ReadBigEndian<byte>();
            byte value = _span[0];
            Advance(sizeof(byte));
            return value;
        }

        public byte[] ReadAsByteArray(int length)
        {
            var value = ReadBytes(length);
            return value.ToArray();
        }

        public ReadOnlySpan<byte> ReadAsSpan(int length)
        {
            return ReadBytes(length);
        }

        public byte[] ReadAsByteArray()
        {
            var bytes = _span.ToArray();
            _span = ReadOnlySpan<byte>.Empty;
            return bytes;
        }

        private ReadOnlySpan<byte> ReadBytes(int length)
        {
            var bytes = _span.Slice(0, length);
            Advance(length);
            return bytes;
        }

        public void Advance(int count)
        {
            _span = _span.Slice(count);
        }
    }
}
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

MRTK v2 で 診断バー(Diagnostics)を消す(Mixed Reality Toolkit、デバッグバー)

はじめに

こんにちは、のんびりエンジニアのたっつーです。
今回の記事に詳細があるのでぜひご参照ください。

先日、MRTK v2が正式にリリースされましたね。

実際にMRTK v2を触ってみた方も多いかと思います、Unity2018/2019を開発に使う場合には、必然的に MRTK v2 を使う事になるかと思います。

今回は MRTK v2を使ったときに表示されるこいつを消したいと思います。基本的には便利なツールなんですが、やっぱり邪魔になる時ありますよね。

WS000000-1.jpg

消し方の手順

MRTK v2 のダウンロード

MRTK v2 の本体をダウンロードしましょう。

Fundationが本体になるので、ダウンロードしてUnityに取り込みましょう。
※Exampleはサンプルコード
※Extentions/Toolsは便利コードだと思われます。

WS000000-2.jpg

Unityに取り込み

ダウンロードした、~.unitypackage をUnityに取り込みましょう。

実際に消す設定

まずは、メニューから今のシーンにMRTKをセットアップします。
WS000001-2.jpg

こんな感じで、「MixedRealityToolkit」「MixedRealityPlayspace」の2つが追加されました。
※メインカメラも削除されます。
WS000002-2.jpg

ヒエラルキーから「MixedRealityToolkit」を選択し、「Copy & Customize」選択でProfileを作成しましょう。
※従来であれば、UnityでPrefabを設置・除去してりしていましたが、 MRTKv2では各種設定をProfileを使って管理します。
WS000001.jpg

Profileをコピーするので、保存先フォルダ、保存ファイル名を指定して、「Clone」ボタンを選択してください。
WS000002.jpg

先ほどのヒエラルキーに戻り、「Diagnostics」を選択し、「Enable Diagnostics System」のチェックボックスをOFFにしましょう。

お疲れ様です。上記で設定完了になります。

実行して確認する

何も表示されていない画面なので面白くないですが、実際にDisgnostics画面が非表示になった事が確認できます。

WS000004.jpg

終わりに

よければ ブログ「Unity+AssetStoreおすすめ情報」の方にも色々記載しているのでぜひご参照いただければと思います。

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

MRTK v2 でオクルージョンする(Mixed Reality Toolkit、Occlusion)

はじめに

こんにちは、のんびりエンジニアのたっつーです。
今回の記事に詳細があるのでぜひご参照ください。

先日、MRTK v2が正式にリリースされましたね。

実際にMRTK v2を触ってみた方も多いかと思います、Unity2018/2019を開発に使う場合には、必然的に MRTK v2 を使う事になるかと思います。

今回は、実際に Hololens でオクルージョンしたいなと思い、MRTK v2 を使ってオクルージョンしてみたのでその手順をメモ程度に書いていきたいと思います。

オクルージョンとは?

以下の画像のように、CGの手前に建物などが被るような処理の事をオクルージョンと言います。WS000000.jpg
https://www.youtube.com/watch?v=7ZrmPTPgY3I

オクルージョン適用手順

MRTK v2 のダウンロード

MRTK v2 の本体をダウンロードしましょう。

Fundationが本体になるので、ダウンロードしてUnityに取り込みましょう。
※Exampleはサンプルコード
※Extentions/Toolsは便利コードだと思われます。

WS000000-2.jpg

Unityに取り込み

ダウンロードした、~.unitypackage をUnityに取り込みましょう。

実際にオクルージョンの設定

まずは、メニューから今のシーンにMRTKをセットアップします。

WS000001-2.jpg

こんな感じで、「MixedRealityToolkit」「MixedRealityPlayspace」の2つが追加されました。
※メインカメラも削除されます。

WS000002-2.jpg

次に、オクルージョンをするために環境情報を設定するためのProfileを作成します。
※従来であれば、UnityでPrefabを設置・除去してりしていましたが、 MRTKv2では各種設定をProfileを使って管理します。

「~SpatialAwarenessSystem」を選択し、該当Profileの「Clone」を選択しましょう。

image-27.png

Profileをコピーするので、保存先フォルダ、保存ファイル名を指定して、「Clone」ボタンを選択してください。

WS000004-1.jpg

上記で、作成したProfileに自動的に切り替わるので、次に「Display Option」を「Occlusion」に変更してください。

WS000005-1.jpg

お疲れ様です。上記で設定完了になります。

ちなみに各オプションで使いそうなやつは以下になります。

  • Startup Befavior:開始タイミング
  • Update Interval:メッシュ更新時間
  • Observation Extents:メッシュ範囲
  • Physics Layer:メッシュを展開するレイヤー

Hololensビルドして実行

以上でオクルージョンが設定できました。
シーン上にキューブなどを追加して確認してみてください。

終わりに

よければ ブログ「Unity+AssetStoreおすすめ情報」の方にも色々記載しているのでぜひご参照いただければと思います。

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

Unity:Unity Render Streaming(Unity公式のWebRTC)を使う【WebブラウザとUnityが連携!】

はじめに

こんにちは、のんびりエンジニアのたっつーです。
今回の記事に詳細があるのでぜひご参照ください。

今回は、先日 Unite Tokyo 2019 で発表された Unity Render Streaming (Unity公式のWebRTC)を試してみたいと思います。

Unity Render Streaming とは?

公式ブログの説明の説明を引用。

 Unity 向けの WebRTC ライブラリを開発するとともに、このライブラリを用いて Unity のアプリケーションをブラウザーから利用できるようにする

・・・

Unity Render Streaming をプロジェクトに追加することで、Google Chrome や Safari などの、皆さんが日頃利用しているブラウザーから Unity で開発したアプリケーションをリアルタイムに操作できます。また、私たちはデスクトップのブラウザーだけでなく、iPad や iPhone、Android の主要ブラウザーで動作を確認しています。
https://blogs.unity3d.com/jp/2019/09/17/stream-high-quality-real-time-graphics-through-your-browser-with-our-new-webrtc-framework/

ポイント

ポイントとしては以下のような感じです。

  • WebRTCを使って実現
  • ゲーム画面をWebブラウザに配信できる。
    • デスクトップ、主要なスマホのWebブラウザに対応
  • Webブラウザ側から操作が出来る。

必要な環境

このソリューションは、NVIDIAのグラボに最適化されているとの事。
ドキュメントによると、DirectX11 のみサポートしているそうなので、 NVIDIA VIDEO CODEC SDK で推奨されているグラフィックボードが公開されているのでチェックしましょう。

※将来的には、DirectX12をサポート予定になっています。

Unity Render Streaming を試す

NVIDIAドライバのバージョン

実行時に、以下のエラーが発生する時には、NVIDIAドライバーのバージョンが古いため出ているようなので、グラフィックドライバを最新バージョンに更新しましょう。

[WebRTC] The version of the hardware codec driver does not support API
UnityEngine.Debug:LogError(Object)
Unity.WebRTC.WebRTC:Initialize() (at Library/PackageCache/com.unity.webrtc@1.0.1-preview/Runtime/Srcipts/WebRTC.cs:236)
Unity.RenderStreaming.RenderStreaming:Awake() (at Assets/Scripts/RenderStreaming.cs:59)

NVIDIA VIDEO CODEC SDKのページでは、WindowsのNVIDIAドライバーは 436.15 以降 が必要との事でした。
WS000002-2.jpg

公式サンプルを試す

次に、公式のサンプルを試してみます。
このサンプルを用いる事により、Unity側でデバッグ実行して、Webブラウザ側で操作が出来るようになります。
※裏でサーバも起動させて、Unity<->ブラウザの通信を補助します

Unity Render Streamingのパッケージを追加

新しく Unityでプロジェクトを作成したら、パッケージマネージャーを起動してください。
メニュバーから「Window > PackageManager」を選択します。

WS000000-5.jpg

「Unity Renbder Streaming」はまだ Previewパッケージなので、「Show preview pakcages」を選択します。

WS000001-3.jpg

検索欄に「streaming」と入力すると、「Unity Render Streming」が表示されます。
※ Unityのバージョンが古いと、この一覧に出てこないので注意しましょう。
WS000002-3.jpg

対象パッケージを選択して、「Install」を選択しましょう。
WS000003-4.jpg

InputSystemの警告がでますが、問題無いので「Yes」を選択しましょう。
WS000004-2.jpg

サンプルプロジェクトの追加

次に、サンプルプロジェクトをインポートします。
※「Unity Render Streming」だけを使う場合は、ここは不要になります。

WS000005-1.jpg
WS000006-2.jpg

(HDRPプロジェクトなので結構時間がかかりますが気長に待ちましょう)
サンプルプロジェクトがインポートされると、こんな感じでファイルが展開されます。

WS000007-1.jpg

WebServerを実行

UnityとWebブラウザの通信を仲介するWebサーバを配置して、実行します。
メニュバーから、「Edit > Unity Render Streaming > Donwload web app」を選択して、適当なフォルダに webserver.exe を保存しましょう。
WS000008-1.jpg

存したら、webserver.exe を実行しましょう。
WS000009.jpg

実行すると、このような画面が立ち上がります。
WS000010.jpg

これで全ての準備が整いました、次に実際に事項して動作を確認してみましょう。

Unity・Webブラウザで実行して確認

Unity側で実行してください。
WS000011.jpg

次に、ブラウザで(localhost)を指定して表示してみてください。
WS000012.jpg

どうでしょうか、以下のようにWebブラウザでの操作がUnity側にフィードバックされているのがわかりますね。

終わりに

よければ ブログ「Unity+AssetStoreおすすめ情報」の方にも色々記載しているのでぜひご参照いただければと思います。

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

Unityを複数起動して、同一プロジェクトを共有する方法

はじめに

こんにちは、のんびりエンジニアのたっつーです。
今回の記事に詳細があるのでぜひご参照ください。

今回は、同一プロジェクトを複数のUnityで共有する方法をご紹介させていただきます。
この方法は1台のPC上で、複数のUnityを起動して実行するやり方になります。
※この方法は通常の使い方ではないので扱いには注意してください。

プロジェクト共有の使い道

同一プロジェクトを共有する事により、以下メリットがあるのかなと思います。

  • マルチユーザゲームなどの開発でつかる
    • 複数ユーザ扱える
  • Unityを複数起動するのでデバッグできる
  • 変更点が即時で反映される
    • Gitなどで共有するよりお手軽
  • プラットフォーム毎のビルド設定を保持して、切り替えの手間を無くす

また、デメリットとしては

  • Unityを複数起動するので重い
  • Unityの通常の使い方ではないので、不安定になる事も

プロジェクト共有のやり方

基本的には、特定のフォルダをシンボリックリンクする事で、片方のファイルを変更するともう一方のプロジェクトファイルも編集された事になるやり方になります。

対象のフォルダは、以下の3つになります。

  • Assets
  • ProjectSettings
  • Packages

実際に試してみる

まずは、こんな感じの適当なプロジェクトを用意しました。
「unity-proj1」に、コピー元プロジェクトが格納された状態です。

WS000000-6.jpg

次に、「unity-proj1」から「unity-proj2」をコピーして作成します。

WS000001-4.jpg

そして、「unity-proj2」から「Assets」「Pakcages」「ProjectSettings」を削除しましょう。
WS000002-4.jpg
WS000003-5.jpg

次にこの3つのフォルダを、「untiy-proj1」のフォルダと共有するために、コマンドラインを立ち上げて、シンボリックリンクを張りたいと思います。

コマンドラインを「管理者権限」で立ち上げてください。
WS000004-3.jpg

そして次のコマンドを入力して、ディレクトを共有してみましょう。

mklink /d <パス>\unity-proj2\Assets <パス>\unity-proj1\Assets
mklink /d <パス>\unity-proj2\Packages <パス>\unity-proj1\Packages
mklink /d <パス>\unity-proj2\ProjectSettings <パス>\unity-proj1\ProjectSettings

コマンドが成功すると、以下のようなメッセージが表示されるので確認しましょう。
~ <<===>> ~ のシンボリック リンクが作成されました

上記、コマンドがすべて成功すると以下のように、「unity-proj2」の「Assets」「Packages」「ProjectSettings」の3フォルダに、ショートカットのような矢印マークが表示されます。
WS000005-2.jpg

試した結果

それでは、今作成した「unity-proj2」をUnityで読み込んで立ち上げてみてください。
どうでしょうか、想定通りに同じプロジェクトがそれぞれのUnityで立ち上がった事が確認でいます。
WS000000-7.jpg

実際に、オブジェクトを追加した場合に、別Unityでそれが反映されるか確認してみました、、、見事に反映されましたね!
project-share2.gif

終わりに

よければ ブログ「Unity+AssetStoreおすすめ情報」の方にも色々記載しているのでぜひご参照いただければと思います。

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

Unityの新しいデバイス・シミュレータの使い方(Device Simulator )

はじめに

こんにちは、のんびりエンジニアのたっつーです。
今回の記事に詳細があるのでぜひご参照ください。

先日、Unityブログにてデバイスシミュレータが掲載されていたので実際に動かしてみました。

だいたいこんな感じで、Unityの環境にシミュレータウィンドウが表示されますので結構よさそうな感じです。
※画面の緑色の線は、「Safe Area」で最近流行りの上部のカメラ領域とかを除いた、ゲームだけに使える領域が表示できます。

WS000008.jpg

使い方

今回はここの手順に沿って導入してみました、本説明がわからない場合はこちらを参照してみてください!

Unity 2019.3以降が必要

Unity 2019.3以降 が必要なのでインストールしましょう。

WS000000-3.jpg

デバイスシミュレータの導入

デバイスシミュレータは、Unity の PackageManager から導入できます。
メニューから「Window > Package Manager」を選択します。

WS000001-1.jpg

PackageManagerを開いたら、検索から「device」を入力し、「Device Simulator」を選択し、「Install」ボタンでインポートしましょう。
※Unity 2019.3以降じゃないと表示されないので気を付けましょう。

WS000002-1.jpg

デバイスシミュレータの起動

起動方法は2種類あります、メニューから起動、GameウィンドウからSimulatorに変更の2つです。!

WS000003-2.jpg

WS000004-1.jpg

選択するとこのような画面が表示されます。
※画面に表示されているモデルは、アセットストアから無料のやつを使っています。

WS000008-2.jpg

デバイスシミュレータの詳細

画面構成は主に以下のように1~10に分かれています。

WS000005.png

1.ゲームビューモード
ゲームビューとシミュレータビューを切り替えます。

2.デバイスの選択
利用可能なデバイスから選択します。
こんな感じで、定義済みのデバイスが選択できます。

WS000005.jpg

3.再起動
ドメインのリロードを引き起こさずに、プレーヤー設定を使用してScreenクラスとSystemInfoクラスのシミュレーションを初期化します。

4.スケール
デバイスが内部に表示される大きさを制御します

5.画面に合わせる
ウィンドウ内にぴったりと収まるようにデバイスを自動的にスケーリングします。

6.回転
デバイスを物理的に回転させます。自動回転が有効か無効かに応じて、実際の画像はデバイスと一緒に回転する場合と回転しない場合があります。

7.セーフエリアを強調表示する
現在のScreen.safeAreaの周囲に境界線を描画します。

WS000006.jpg

8.デバイスの仕様
現在シミュレートされているデバイスの最も重要なデータを表示します。

  • デバイスOS
  • デバイスCPU / GPU
  • デバイス解像度

9.画面設定
Screenクラスの値の表示と変更を許可します。

10.プレーヤー設定
デバイスシミュレータPlayerの設定は、本当の上書きPlayerの設定を。AndroidまたはiOSサポートがインストールされていない場合に特に便利です。このメニューには、現在シミュレーションに影響しているすべてのプレーヤー設定が含まれています。

  • フルスクリーンで開始
  • 解像度スケーリングモード
  • デフォルトの向き
  • 許可されたオリエンテーション
  • Auto Graphics API
  • グラフィックスAPI

[プレーヤー設定を使用]は、実際のプレーヤー設定とシミュレートされたプレーヤー設定のどちらを使用するかを制御します。

終わりに

よければ ブログ「Unity+AssetStoreおすすめ情報」の方にも色々記載しているのでぜひご参照いただければと思います。

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