20201012のC#に関する記事は5件です。

C#で例外処理実装時に意識するべき3つのポイント

C#での例外処理について学んだのでその中でも特に意識しておくべきことを挙げていきます。

1.リソースの使用時にはusingを使う

プログラム中でファイルやデータベースなど、他のアプリケーションと共有するようなリソースを使用する際にはそのリソースを開放する必要があります。
例えば、ファイルに書かれている内容を読み込むStreamReaderを使用した場合はClose()メソッドを呼び出す必要があります。
しかし、下記のような書き方をした場合ある問題が発生します。

Sample.cs
            string filePath = @"c:\sample.txt";

            StreamReader reader = null;
            try {
                reader = new StreamReader(filePath);

                // 読み込んだファイルに対して何かしらの処理を行う

                reader.Close();

            } catch(FileNotFoundException e) {
                Console.WriteLine(e.StackTrace);
            }

上記プログラムの問題点は読み込んだファイルに対して何かしらの処理を行うの部分で例外が発生した場合にreader.Close();が呼ばれないところです。
これを防ぐための1つの手段としてfinallyを使用することがあげられます。

Sample.cs
            string filePath = @"c:\sample.txt";

            StreamReader reader = null;
            try {
                reader = new StreamReader(filePath);

                // 読み込んだファイルに対して何かしらの処理を行う

            } catch(FileNotFoundException e) {
                Console.WriteLine(e.StackTrace);

            } finally {
                reader.Close();
            }

finallyブロックでClose()メソッドを呼び出すことで例外が発生しても確実に呼び出すことができます。
しかし、例外が発生した際にファイルをクローズする前に何かしら処理を行いたいということがなければこの書き方は冗長です。
C#ではusingメソッドを使用して下記のように省略した書き方が可能です。

Sample.cs
            string filePath = @"c:\sample.txt";

            StreamReader reader = null;
            try {
                using (reader = new StreamReader(filePath)) {

                    // 読み込んだファイルに対して何かしらの処理を行う
                }
            } catch(FileNotFoundException e) {
                Console.WriteLine(e.StackTrace);
            }

usingの内部で生成されたオブジェクトはusingブロックを抜けるタイミングで自動的に破棄されます。これによって処理の途中で例外が発生しても確実にリソースを開放することができます。

ちなみに、Closeメソッドとusingで自動的にリソースを開放する場合でもどちらもDispose()メソッドを呼び出します。なのでDispose()メソッドを実装していないクラスのリソースをusingで取得しようとするとコンパイルエラーが発生します。
StreamReaderクラスはIDiposesableインターフェイスを実装しているのでusing内で使用することができるわけです。

2.例外フィルターを使ってマルチキャッチを行う

例えば指定されたパスに存在するcsvファイルを読み込み、カンマ区切りで2つ目に記述された文字を出力する処理を書く場合、少なくとも下記の3つの例外を考慮する必要があります。

  • 指定されたパスがnullか空文字("")
  • ディレクトリが存在しない
  • ディレクトリは存在するがファイルが存在しない
  • ファイルを読み込むことはできるが、カンマ区切りの2つ目が存在しない

これらをの例外を全て考慮したプログラムは下記のようになります。
(実際にここまで厳密にcatchするかは別問題です。)

sample.cs
        public void sample(string filePath) {

            StreamReader reader = null;

            try {
                using (reader = new StreamReader(filePath)) {
                    string str = reader.ReadLine().Split(',')[1];
                    Console.WriteLine(str);
                }
            } catch (ArgumentException ex) {
                // 引数がnullか空文字("")の場合に発生
                Console.WriteLine("ファイルを開けませんでした。");
                Console.WriteLine(ex.StackTrace);
            } catch (DirectoryNotFoundException ex) {
                // ディレクトリが見つからなかった場合に発生
                Console.WriteLine("ファイルを開けませんでした。");
                Console.WriteLine(ex.StackTrace);
            } catch (FileNotFoundException ex) {
                // ファイルが見つからなかった場合に発生
                Console.WriteLine("ファイルを開けませんでした。");
                Console.WriteLine(ex.StackTrace);
            } catch (IndexOutOfRangeException ex) {
                // CSVのフォーマットが不正な場合に発生
                Console.WriteLine("指定されたcsvファイルのフォーマットが不正です。");
                Console.WriteLine(ex.StackTrace);
            }
        }

ここで、ArgumentExceptionとFileNotFoundExceptionとDirectoryNotFoundExceptionは同じ処理をしています。
つまり本来は同じ処理を何度も書きたくないのに例外をキャッチするために仕方なく同じ処理を複数回書いている状態です。
これを回避する1つの方法としてExceptionをcatchで指定する方法があります。

sample.cs
        public void sample(string filePath) {

            StreamReader reader = null;

            try {
                using (reader = new StreamReader(filePath)) {
                    string str = reader.ReadLine().Split(',')[1];
                    Console.WriteLine(str);
                }
            } catch(IndexOutOfRangeException ex) {
                Console.WriteLine("指定されたcsvファイルのフォーマットが不正です。");
                Console.WriteLine(ex.StackTrace);
            } catch(Exception ex) {
                Console.WriteLine("ファイルを開けませんでした。");
                Console.WriteLine(ex.StackTrace);
            }
        }

Exceptionをキャッチすることで同じ処理を2度書く必要はなくなりました。
しかし、こう書いてしまうと新たな問題が発生します。
それは例外処理の対処があいまいになってしまったり不適切な例外処理をしてしまうからです。
原則として例外をキャッチする際にはExceptionクラスではなくてExceptionクラスから派生した詳細な例外クラスを指定すべきです。

では結局のところ詳細に例外クラスを指定するために最初の例のように同じ処理でも複数回記述する必要があるのでしょうか?
これに対応するためにC#6以降では例外フィルターというものが使用できるようになり、catch文に例外の種類に加えて条件を指定できるようになっています。
これを使用して下記のようにマルチキャッチを行うことで上記の問題は解決できます。

sample.cs
        public void sample(string filePath) {

            StreamReader reader = null;

            try {
                using (reader = new StreamReader(filePath)) {
                    string str = reader.ReadLine().Split(',')[1];
                    Console.WriteLine(str);
                }
            } catch (IndexOutOfRangeException ex) {
                Console.WriteLine("指定されたcsvファイルのフォーマットが不正です。");
                Console.WriteLine(ex.StackTrace);
            } catch (Exception ex) when (ex is ArgumentException || ex is DirectoryNotFoundException || ex is FileNotFoundException) {
                Console.WriteLine("ファイルを開けませんでした。");
                Console.WriteLine(ex.StackTrace);
            }
        }

catch(Exception ex)の後ろにwhenで条件を指定することでその条件に一致した時のみ例外処理を行うことができます。
このように1つのcatch文で複数の例外を処理することをマルチキャッチと言います。
これによって例外処理の対象を明確にした上で同じ処理を複数回記述する必要がなくなります。

3.例外処理のオーバーヘッドに注意する

基本的には意識する程ではないのですが、例外処理はオーバーヘッドが大きく、レスポンスに影響を及ぼす可能性がある処理です。
レスポンスに問題がある場合、まず見直すのはデータベースやネットワーク関係ですが、それでも解決できない場合はプログラムの処理を見直す必要が出てきます。
その際には例外処理が頻発していないかをチェックすべき1つのポイントになります。
ここでは、例外処理を頻発させないための実装パターンを2つ紹介します。

1.Tester-doerパターン

これは実行できるかどうかテストをしてから実行するパターンです。
例えば、Dictionaryクラスでは存在しないキーを指定するとKeyNotFoundExceptionが発生します。

sample.cs
        public void sample() {

            var myDictionary = new Dictionary<string, string>() {
                ["リンゴ"] = "Apple",
                ["バナナ"] = "Banana",
                ["ミカン"] = "Orange"
            };
            try {
                Console.WriteLine(myDictionary["パイナップル"]);
            } catch(KeyNotFoundException ex) {
                Console.WriteLine("指定されたキーに対応する値は存在しません。");
                Console.WriteLine(ex.StackTrace);
            }
        }

上記の例だとConsole.WriteLine(myDictionary["パイナップル"]);で例外が発生してcatchブロックへ飛びます。
これが頻発すると処理の遅延が発生する可能性があるので事前に「パイナップル」というキーが存在するかチェックしてから処理を行うのがTester-doerパターンです。
今回例で使用しているDictionaryクラスの場合は下記のように書き換えることができます。

sample.cs
        public void sample() {

            var myDictionary = new Dictionary<string, string>() {
                ["リンゴ"] = "Apple",
                ["バナナ"] = "Banana",
                ["ミカン"] = "Orange"
            };
            string value;
            if (myDictionary.TryGetValue("パイナップル", out value)) {
                Console.WriteLine(value);
            } else {
                Console.WriteLine("指定されたキーに対応する値は存在しません。");
            }
        }

TryGetValue()メソッドは第一引数で指定したキーが存在するかをtrue/falseで判定するメソッドです。キーが存在する場合は第2引数で指定した変数に対応する値を代入してくれます。
ContainsKey()メソッドを使用してチェックもできますが、TryGetValueを使うほうが効率がいいそうです。こちらのサイト様で詳しく検証されていました。

このように処理を実行しても例外が発生しないか事前に確認し、例外が発生する場合はelseで処理
することでtry-catch構文にする必要がなくなり、処理効率が上がります。

Try-parseパターン

このパターンはParseメソッドを呼び出して変換を行う際に使われるパターンです。
例えば、下記の例は文字列クラス⇒日付クラスに変換する処理です。
時間に0~24以外の数値を指定しているので例外が発生します。

        public void sample() {

            var date = "2019/1/5 25:06:30";
            try {
                Console.WriteLine(DateTime.Parse(date));
            } catch (FormatException ex) {
                Console.WriteLine("フォーマットが不正です");
                Console.WriteLine(ex.StackTrace);
            }
        }

これを実行前に事前に判定しようとしても判定しようとした時点で例外が発生するので事前のチェックができません。
よって、処理の失敗は避けられないのですが、処理に失敗しても例外を返さないメソッドを使用することで例外による遅延を防ぐことができます。
今回の例だとTryParse()メソッドを使用すれば変換できない際に例外ではなくfalseを返すのでif-elseで処理することができます。

sample.cs
        public void sample() {

            var date = "2019/1/5 25:06:30";
                DateTime dt;
            if (DateTime.TryParse(date, out dt)) {
                Console.WriteLine(dt);
            }else{
                Console.WriteLine("フォーマットが不正です");
            }
        }

このように、安全のためにとにかくtry-catchで囲むのではなく、そもそも例外が発生しないように設計することでレスポンスを向上させることができるかもしれません。

まとめ

  • ファイルなどの共有リソースを開放する際の注意点
    • 開放前にやりたい処理があるならfainallyでClose()を呼び出して開放する
    • そういうものがなければusingを使用して自動で開放する
  • catchによる例外処理の注意点
    • Exceptionをcatchすると例外処理の対象があいまいになるので原則使用しない
    • 複数の例外で同じ処理を行いたい場合は例外フィルターを使用してマルチキャッチを行う
  • そもそも例外はなるべく発生させないようにする
    • 事前にチェック可能な場合はTester-doerパターンを使用する
    • 事前のチェックが不可能な場合はTry-parseパターンを使用する
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

新しい関数ポインタの機能を試しましょう

はじめに

.NET 5には新しい機能「関数ポインタ」を追加されました。これは、ネイティブ相互運用を改善し、パフォーマンスを上げるために作られました。

関数ポインタは下位レベルのデリゲートで、関数呼び出しのより効率的な方法を提供します。既知の方法にSystem.Action<>, System.Func<>といい、System.Delegateといい、どちらもオーバーヘッドがたくさんあります。でも関数ポインタはそういうオーバーエンドがありません。

さらに関数ポインタはマネージ関数のみをサポートすることではなく、アンマネージ関数もサポートされています。だからこそ、ネイティブ相互運用を行う場合は、関数ポインタを使用すると非常に便利になります。

じゃ、早速始めましょう。

使い方

関数ポインタの型の形式は delegate* + managed/unmanaged + [呼び出し規則(ネイティブのみ)]+<パラメータリスト(void を使えます)>です。
例えば:

言語 宣言 関数ポインタの型
C++ void __cdecl f() delegate* unmanaged[Cdecl]<void>
C++ void __cdecl f(int) delegate* unmanaged[Cdecl]<int, void>
C++ int __cdecl f(int) delegate* unmanaged[Cdecl]<int, int>
C# static string f(int) delegate* managed<int, string>
C# static void f(string) delegate* managed<string, void>

[Cdecl] のみではなく、[Stdcall][Fastcall][Thiscall]も使えますよ。

アンマネージ関数の使い方

まずはネイティブ相互運用を紹介しましょう。

C++コードを書けます:

#define WIN32
#define UNICODE
#include <cstring>
#include <cstdio>

extern "C" __declspec(dllexport)
// C# 関数はパラメーター gen を介して渡され、呼び出されます
char* __cdecl foo(char* (*gen)(int), int count) {
    return gen(count);
}

関数ポインタを使用する場合は、不安全なコードを許可するが必要です。そのために、プロパティファイル*.csprojに次のコードを追加してください:

<PropertyGroup>
    <AllowUnsafeBlocks>true</AllowUnsafeBlocks>
</PropertyGroup>

C#コード:

using System;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;

// unsafe が必要です
unsafe class Program
{
    // ネイティブ関数の宣言
    // nint はネイティブ整数の意味である、IntPtr にコンパイルされます
    [DllImport("./foo.dll", EntryPoint = "foo")]
    static extern string Foo(delegate* unmanaged[Cdecl]<int, nint> gen, int count);

    // UnmanagedCallersOnly でマークされたメソッドには次の制限があります:
    // * "static" に設定する
    // * マネージコードから呼び出すことはダメ
    // * Blittable パラメータのみ
    [UnmanagedCallersOnly(CallConvs = new[] { typeof(CallConvCdecl) })]
    static nint Generate(int count)
    {
        // count 個 'w' を含む文字列の生成
        var str = Enumerable.Repeat("w", count).Aggregate((a, b) => $"{a}{b}");
        return Marshal.StringToHGlobalAnsi(str);
    }

    static void Main(string[] args)
    {
        // 関数ポインタの作成
        var f = (delegate* unmanaged[Cdecl]<int, nint>)(delegate*<int, nint>)&Generate;
        // 呼び出す
        var result = Foo(f, 5);
        Console.WriteLine(result);
    }
}

上記のコードは、C# の関数を C++ に渡して、C++ は C# から渡された関数を呼び出します。

最後はコードをコンパイルして実行します:

$ clang++ foo.cpp -shared -o foo.dll # C++ コードのコンパイル
$ dotnet run # C# コードのコンパイルと実行

出力:

wwwww

ここまで関数ポインタでネイティブ相互運用を使う方法は紹介しました!

マネージ関数の使い方

これはアンマネージ関数と大体同じで、より簡単ですよ。

C# コード:

using System;
using System.Linq;

unsafe class Program
{
    static string Generate(int count)
    {
        // count 個 'w' を含む文字列の生成
        var str = Enumerable.Repeat("w", count).Aggregate((a, b) => $"{a}{b}");
        return str;
    }

    static void Main(string[] args)
    {
        // 関数ポインタの作成
        var f = (delegate*<int, string>)&Generate;
        // 呼び出す
        var result = f(5);
        Console.WriteLine(result);
    }
}

コンパイルと実行:

$ dotnet run

出力:

wwwww

Bravo!

パフォーマンスの比較

デリゲートと比較して、なぜ関数ポインタがより効率的ですか?コンパイルされた IL コードを見てみましょう。

C# コード:

static void FuntionPointerApproach()
{
    // 関数ポインタの作成
    var f = (delegate*<int, string>)&Generate;
    // 呼び出す
    f(5);
}

static void DelegateApproach()
{
    // デリゲートの作成
    var f = (Func<int, string>)Generate;
    // 呼び出す
    f(5);
}

IL コード:

.method private hidebysig static 
    void FuntionPointerApproach () cil managed 
{
    // Method begins at RVA 0x2084
    // Code size 16 (0x10)
    .maxstack 2
    .locals init (
        [0] method string *(int32)
    )

    IL_0000: ldftn string Program::Generate(int32)
    IL_0006: stloc.0
    IL_0007: ldc.i4.5
    IL_0008: ldloc.0
    IL_0009: calli string(int32)
    IL_000e: pop
    IL_000f: ret
} // end of method Program::FuntionPointerApproach

.method private hidebysig static 
    void DelegateApproach () cil managed 
{
    // Method begins at RVA 0x20a0
    // Code size 20 (0x14)
    .maxstack 8

    IL_0000: ldnull
    IL_0001: ldftn string Program::Generate(int32)
    IL_0007: newobj instance void class [System.Private.CoreLib]System.Func`2<int32, string>::.ctor(object, native int)
    IL_000c: ldc.i4.5
    IL_000d: callvirt instance !1 class [System.Private.CoreLib]System.Func`2<int32, string>::Invoke(!0)
    IL_0012: pop
    IL_0013: ret
} // end of method Program::DelegateApproach

IL を見れば理解できるはずです。関数ポインタは冗長オブジェクトSystem.Func<>を作成する必要はなく、関数はcalli 命令を介して直接呼び出すことができます。

シンプルなテストコード:

static void Main(string[] args)
{
    var count = 100000000;
    var st = new Stopwatch();
    st.Start();
    for (var i = 0; i < count; i++)
    {
        FuntionPointerApproach();
    }
    st.Stop();
    Console.WriteLine($"関数ポインタ: {st.ElapsedMilliseconds} ms");

    st.Restart();
    for (var i = 0; i < count; i++)
    {
        DelegateApproach();
    }
    st.Stop();
    Console.WriteLine($"デリゲート: {st.ElapsedMilliseconds} ms");
}

結果:

$ dotnet run -c Release
関数ポインタ: 11608 ms
デリゲート: 12462 ms

予想通り!

まとめ

関数ポインタを使用するとネイティブインターロップはすぐに簡単になります。
さらに、これはデリゲートよりもっと効率的です。

こんなに素晴らしい機能があるし、早速.NET 5をダウンロードして新しい関数ポインタの機能を試しようぜ!

参考文献

Improvements in native code interop in .NET 5.0

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

windowsformのカスタムコントロールの作り方

モチベーション

  • TextBoxクラスを継承してイベントの処理を追加する。
  • TextBoxクラスのプロパティを追加する。

より良い手順

  • @userisgodさんに教えてもらった手法
  • テキストボックスのクラスを継承する
継承したテキストボックス.cs
public class 継承したテキストボックス : TextBox
{
    private 選択肢クラス 選択肢 = 選択肢クラス.選択肢1;

    protected override void OnKeyPress(KeyPressEventArgs e)
    {
        base.OnKeyPress(e);
        // Do something(イベント処理)
    }

    [Browsable(true)]
    [Description("プロパティの説明")]
    [Category("プロパティの中のカテゴリ")]
    public 選択肢クラス プロパティ名
    {
        get { return 選択肢; }
        set { 選択肢 = value; }
    }
}

public enum 選択肢クラス
{
    選択肢1 = 1,
    選択肢2 = 2
}

(良くない)手順

これを参考にする。

  1. プロジェクトの追加・新しい項目・ユーザーコントロール(Windows フォーム)を選択する。
  2. 継承したテキストボックス.csのクラスの継承元をTextBoxクラスにする。
    継承したテキストボックス.cs
    namespace MyProject
    {
        public partial class 継承したテキストボックス : TextBox
        {
            // もとからある実装
        }
    }
    

  3. 継承したテキストボックス.Designer.csの中の
    this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
    でエラーが発生するので、この行を削除する。
  4. private void InitializeComponent()内にイベントを追加していく。(ここではイベント処理というメソッドを追加した)
    継承したテキストボックス.Designer.cs
    private void InitializeComponent()
    {
        components = new System.ComponentModel.Container();
        this.KeyPress += new System.Windows.Forms.KeyPressEventHandler(this.イベント処理);
    }
    

  5. 継承したテキストボックス.csの中に追加したイベント処理の実装を書く
    プロパティも継承したテキストボックスクラスに実装する。
    プロパティの選択肢はenumのクラスを作成する。
    すると、デザイナーのツールボックス内でenumクラスのメンバーが選択できるようになる。
    継承したテキストボックス.cs
    public partial class 継承したテキストボックス : TextBox
    {
        private void イベント処理(object sender, KeyPressEventArgs e)
        {
            // Do something
        }
    
        private 選択肢クラス 選択肢 = 選択肢クラス.選択肢1;
    
        [Browsable(true)]
        [Description("プロパティの説明")]
        [Category("プロパティの中のカテゴリ")]
        public 選択肢クラス プロパティ名
        {
            get { return 選択肢; }
            set { 選択肢 = value; }
        }
    }
    
    public enum 選択肢クラス
    {
        選択肢1 = 1,
        選択肢2 = 2
    }
    

  6. ビルドする。
  7. フォームのツールボックスから追加して使用できるようになる。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Unity LineRendererでシェーダーを用いて点線を描画する

概要

以下のように丸を並べた点線をLineRendererで描画します。ついでにアニメーションもつけました。
dotted-line.gif

プロジェクトのソースはこちら
https://github.com/Arihide/unity-dotted-line

解説

LineRendererをアタッチする

まず、適当なGameObjectにLineRendererをアタッチします。
その後、下のように[Texture Mode]を[Tile]に設定します。
スクリーンショット_2020-10-12_0_03_03.png

シェーダーを書く

次に、LineRendererに設定する点線のマテリアルのためのシェーダーを書いていきます。

1.丸を並べる

まずは下図のように、丸を隙間なく敷き詰めるLineRenderer用のシェーダーを書きます。
スクリーンショット 2020-10-12 0.24.09.png

どのようなロジックでこれを実現するかというと、
先程のTextureMode:Tileの設定によって、以下のようなUV座標になるので、
dotted-line.png
以下のように0...1になるように座標を変更したあと、
dotted-line2.png
中心:(0.5, 0.5)、半径:0.5 の円を考えて、内側は不透明、外側は透明になるように描画してあげればよいです。
それを踏まえたシェーダーのコードは以下になります。

Shader "Custom/DottedLine"
{
    SubShader
    {
        Tags { "RenderType" = "Transparent" }
        Blend SrcAlpha OneMinusSrcAlpha

        Pass
        {
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag

            #include "UnityCG.cginc"

            struct appdata
            {
                half2 texcoord : TEXCOORD0;
                half4 vertex : POSITION;
                fixed4 color : COLOR0;
            };

            struct v2f
            {
                half2 uv : TEXCOORD0;
                half4 pos : SV_POSITION;
                fixed4 color : COLOR0;
            };

            v2f vert (appdata v)
            {
                v2f o;
                o.pos = UnityObjectToClipPos(v.vertex);
                o.uv = v.texcoord;
                o.color = v.color;
                return o;
            }

            fixed4 frag (v2f i) : SV_TARGET
            {
                // 1の剰余をとることで、0と1の間を繰り返すようにする
                i.uv.x = i.uv.x % 1;

                fixed4 col = i.color;

                // 円の内側を不透明、外側を透明にする
                half s = length(i.uv - half2(1, 1) * 0.5);
                col.a = saturate((0.5 - s) * 100);

                return col;
            }
            ENDCG
        }
    }
}

上記のシェーダーを記述後、マテリアルを作成しLineRendererのマテリアルにアタッチすると、はじめの図のように線に沿って丸が描画されると思います。

ここで、以下のコード部分が若干複雑なので補足します。

// 円の内側を不透明、外側を透明にする
half s = length(i.uv - half2(1, 1) * 0.5);
col.a = saturate((0.5 - s) * 100);

この関数をプロットすると以下のようになります。(xy平面がそれぞれUV、z軸はアルファ値)
geogebra-export.png

図を見てもらうとわかるように、円柱ではなく円錐台になっています。
このようにすることによって、円の境界線をぼかしてきれいに描画できるようになります。

円の隙間を開ける

隙間が詰まりすぎていて気になるので、次は間隔を少し開けてみます。
スクリーンショット 2020-10-12 1.54.10.png
これは以下のように、_Spaceという変数を用意して、剰余と円の中心をずらせば実現できます。

Shader "Custom/DottedLine"
{
    Properties
    {
        _Space ("Space Between Dots", Range(1, 5)) = 1.2
    }
    SubShader
    {
        //...(中略)

            half _Space;

            fixed4 frag (v2f i) : SV_TARGET
            {
                i.uv.x = i.uv.x % _Space;

                fixed4 col = i.color;

                // 円の内側を不透明、外側を透明にする
                half s = length(i.uv - half2(_Space, 1) * 0.5);
                col.a = saturate((0.5 - s) * 100);

                return col;
            }
            ENDCG
        }
    }
}

変更後、マテリアルから[Space Between Dots]の値を調節することで隙間があくのを確認できます。

アニメーションさせる

これで完成でもよいのですが、せっかくなので冒頭のGifのように時間経過によって丸が動くようなアニメーションもつけたいと思います。これはとても簡単で、frag関数冒頭を

i.uv.x = (i.uv.x + _Time * 50) % _Space;

と置き換えることで実現できます。見ていただけばわかるように時間ごとに座標をずらしています。

まとめ

以上、LineRendererのシェーダーで点線を描画する方法について説明しました。
シェーダーを工夫すれば、円ではなく楕円などの点線にすることもできると思うので、色々試してみるのも面白いかもしれません。

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

Win10+VSCodeでC#開発環境構築

今回はWindows10+VSCodeでC#開発環境の構築をしていきます。

環境

項目 バージョン
OS Windows 10 Home Edition x64 [10.0.18363.1082]
Visual Studio Code 1.50.0(https://visualstudio.microsoft.com/ja/downloads/
.NET Core SDK 3.1.402(https://dotnet.microsoft.com/download
C#(VSCodeの拡張機能) 1.23.2

※取り敢えず構築当時の最新。

1. Visual Studio Codeをインストールする

下記ページを参照してVSCodeをインストールしましょう。
Windows10にVSCodeインストール

2. .NET Core SDKをインストールする

次に.NET Core SDKをインストールします。
「.NET Core」って何?という話については資料が溢れるほどあるので詳しくはそちらを参照してもらうとして
簡単に言えば、「どのOSでも同じようにソフトウェア開発できる便利な環境」という認識でいいと思います。

そしてSDKは"Software Development Kit"の略で、ソフトウェア開発の為の道具一式と言えます。
合わせて「.NET Core SDK」=「.NET Coreでソフトウェア開発する為の環境と道具一式」ということになりますね。

さて、説明は終わりにしてダウンロードページからインストーラをダウンロードしましょう。
ダウンロードページの左側の3つある内の真ん中「Download .NET Core SDK」を選びます。

6.PNG

ダウンロードしたインストーラ「dotnet-sdk-*-win-x64.exe」を実行してインストールします。(*はバージョン部分)
7.PNG
インストール後、コマンドコンソールから"dotnet --version"と入力してバージョンが表示されることを確認しましょう
8.PNG
※バージョンは時期によって変わるので同じでなくても問題ありません。

もしコマンドコンソールからdotnetがコマンドとして認識されない場合、
PATH環境変数にdotnetのインストールフォルダ(デフォルトなら"C:\Program Fiels\dotnet")が追加されているかどうか確認し、
再インストールしたり、コマンドコンソールの再起動を試してみてください。

3. ワークスペースを作成する

インストールが完了したら、自分のPCの好きな場所に「新しいフォルダー」を作り、ワークスペースを用意します。
名前は自由ですが、分かりやすい名前がいいです。(※私の場合はD:\WorkSpace\Csにしました)
5.png

フォルダを右クリックして「Codeで開く」を選択してVSCodeを起動しましょう。

4. VSCode拡張機能(C#)をインストールする

VSCodeを起動すると、左側メニューの下に"Extensions"があるのでそれを選択し、検索欄に"C#"と入力します。
恐らくトップにMicrosoft製のC#[ms-dotnettools.csharp]が出てくるのでそれをインストールします。

9.PNG

さて、これで準備が整いました。
折角なのでHello Worldくらいやりましょう。

5. Hello World !

VSCodeのツールバーの[Terminal]→[New Terminal]を選択し、ターミナルを起動します。
起動したら"dotnet new console -o HelloWorld"というコマンドを入力します。

10.PNG

入力するとワークスペース下に"HelloWorld"というフォルダが出来上がり、中に*.csprojファイルや*.csファイルがあるのが確認できます。
では続けて"dotnet run --project .\HelloWorld\HelloWorld.csproj"とコマンドを入力します。

11.PNG

無事、「Hello World!」と出力されるのが確認できたでしょうか。
出てこない場合は手順を遡って環境が正しい状態か、確認してみてください。

  • ".NET Core SDK"はちゃんとインストールされているか
  • 実行ファイルへのパスが環境変数に入っているか、
  • ワークスペースをVSCodeで開いているか

では今回はここまで。

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