20200914のC#に関する記事は9件です。

ASP.NET Core 3.1 MVC における構成ファイル appsettings.json からの値取得

はじめに

以前の以下の投稿で、.NET Core 1.1 での ASP.NET Core において appsettings.json からの値取得の方法を説明しました。

今回は、.NET Core 3.1 での ASP.NET Core において、appsettings.json からの値取得の方法を説明します。
基本的な方法はほとんど同じです。

appsettings.json の定義

appsettings.json を以下のように定義したとします。

appsettings.json
{
  "UserSettings": {
    "IsDemoMode": false,
    "DefaultUser": {
      "Name": "足利 義教",
      "Age": 48
    }
  },
  "Logging": {
    "IncludeScopes": false,
    "LogLevel": {
      "Default": "Warning"
    }
  }
}

IConfiguration での構成情報の取得

何もしなくても構成情報は、依存性の注入がされているので、コントローラーから、構成情報を IConfiguration で取得できます。

HomeController.cs
public class HomeController : Controller
{
    private readonly IConfiguration _configuration;
    private readonly ILogger<HomeController> _logger;


    public HomeController(IConfiguration configuration, ILogger<HomeController> logger)
    {
            this._configuration = configuration;            
            this._logger = logger;
    }

    public IActionResult Index()
    {   
        //ブール値のロード
        bool isDemoMode = this._configuration.GetValue<bool>("UserSettings:IsDemoMode");
        //文字列値のロードは、インデクサで指定可能
        string defaultUserName = this._configuration["UserSettings:DefaultUser:Name"];
        //int 値のロード
        int defaultUserAge = this._configuration.GetValue<int>("UserSettings:DefaultUser:Age");

        return View((isDemoMode, defaultUserName, defaultUserAge));
    }

    ...
}

オプション パターンでの構成情報の取得

前回の記事でも触れましたが、構成情報をクラスにバインドして、そこから取得した方が可読性があがりますね。

JSON の定義に対応するクラスを定義します。
前回も説明しましたが、Visual Studio からクラス ファイルを追加し、JSON ファイルの文字列をコピーして、[編集] - [形式を選択して貼り付け] - [JSON をクラスとして貼り付ける] を選択すると、クラスの定義をペースト出来るので便利です。

UserSettings.cs
    public class UserSettings
    {
        public bool IsDemoMode { get; set; }
        public DefaultUser DefaultUser { get; set; }
    }

    public class DefaultUser
    {
        public string Name { get; set; }
        public int Age { get; set; }
    }

次に、StartUp.ConfigureServices からサービス コレクション経由で構成情報を注入します。

Startup.cs
public class Startup
{
    public Startup(IConfiguration configuration)
    {
        Configuration = configuration;
    }

    public IConfiguration Configuration { get; }

    // This method gets called by the runtime. Use this method to add services to the container.
    public void ConfigureServices(IServiceCollection services)
    {
        //構成情報の注入
        services.Configure<UserSettings>(this.Configuration.GetSection("UserSettings"));
        services.AddControllersWithViews();            
    }

    ...
}

これで、コントローラーから、注入した構成情報を取得できます。

HomeController.cs
public class HomeController : Controller
{
    private readonly UserSettings _userSettings;

    private readonly ILogger<HomeController> _logger;        

    public HomeController(IOptions<UserSettings> userSettings, ILogger<HomeController> logger)
    {
        //構成情報の取得
        this._userSettings = userSettings.Value;
        this._logger = logger;
    }

    public IActionResult Index()
    {
        //構成情報の参照
        return View(this._userSettings);
    }

    ...
}

固有のクラス経由で構成情報を取得した方が、やはり可読性があがりますね。

参考サイト

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

【C#】IReadOnlyList<T>とReadOnlyCollection<T>とImmutableList<T>の違い

はじめに

先日、下記記事を公開しました。

【C#】インターフェイスの利点が理解できない人は「インターフェイスには3つのタイプがある」ことを理解しよう
https://qiita.com/yutorisan/items/d28386f168f2f3ab166d

この記事でIReadOnly系インターフェイスの役割と重要性について簡単に説明し、

Listをそのまま渡すのではなく、IReadOnlyListとして渡すだけで、渡した先で勝手に書き換えられる危険性が皆無になります

と記述しましたが、これに対して「IReadOnlyListではIListにキャストされてしまうので、変更される危険性が皆無ではない。本当に皆無にしたければImuutableListで公開すべき」とのご意見をいただきました。

ImmutableListのことは正直あまり知らなかったので、調べてみると「不変なコレクション」とのこと。
ReadOnlyCollectionとどう違うんだ…?と思ったので、それぞれ違いを整理しました。

IReadOnlyList<T>インターフェイス

IReadOnlyList<T>は、ListのReadOnlyなインターフェイスです。

定義は次のようになっています。

public interface IReadOnlyList<out T> : IEnumerable<T>, IEnumerable, IReadOnlyCollection<T>
{
    T this [int index] {
        get;
    }
}

IReadOnlyList<T>が継承1しているIReadOnlyCollection<T>の定義はこうです。

public interface IReadOnlyCollection<out T> : IEnumerable<T>, IEnumerable
{
    int Count {
        get;
    }
}

つまり、インデクサによる値の取得要素数の取得、それからIEnumerator<T>の取得(foreachで回す)だけができるコレクションというわけですね。
List<T>IReadOnlyList<T>で渡すと、コレクション変更系メソッドが呼べなくなるので安全ですよ、というインターフェイスです。

問題点:キャストされるとReadOnlyじゃなくなる

さて、ここからが本題で、冒頭で記述した「キャストされたら普通に変更されちゃう問題」についてです。

IReadOnlyList<T>List<T>のインターフェイス2なので、List<T>にキャストされてしまえば普通に変更できちゃうという問題です。

以下の例をご覧ください。

class Program
{
    static void Main(string[] args)
    {
        IReadOnlyList<int> ireadonlylist = new List<int>() { 1, 2, 3 };

        ReadOnlyBreaker.Break(ireadonlylist);

        foreach (var item in ireadonlylist)
        {
            Console.WriteLine(item);
        }
    }
}

class ReadOnlyBreaker
{
    //IReadOnlyListで受け取っても…
    public static void Break(IReadOnlyList<int> rolist)
    {
        //キャストしてしまえばAddできる
        (rolist as IList<int>).Add(100);
    }
}

1,2,3でList<int>を初期化して、IReadOnlyList<int>にして渡しても、受け取った側が勝手にIList<int>にキャストすればAddできてしまうという例です。

実行結果がこちら。

1
2
3
100

IReadOnlyList<int>で渡したのに、100がAddされてしまっています。

これは、IReadOnlyList<T>がインターフェイスであるがゆえ、必ず具象クラスが存在するので、もともとの具象クラス側で変更を許可しているならば無理やり変更できるという、悲しい問題です。

しかし、普通のプログラマーであれば「IReadOnlyList」と書いてあれば、「あ、このコレクションは変更してはいけないんだな」と理解できるため、わざわざキャストしてまで無理やり変更されることは少ないと思います。
したがって、決してIReadOnlyList<T>では危険だ!と主張しているわけではありませんのでご留意ください。

しかしながら、汎用的なライブラリとして公開するなど、どんな使い方をされるかわからない場合や、キャストによる変更される危険もなくしたいと言った場合は、次に紹介するReadOnlyCollection<T>クラスまたはImmutableList<T>クラスを使用すれば、この問題を解決することができます。

ReadOnlyCollection<T>クラス

ReadOnlyCollection<T>クラスは、List<T>をラップして読み取り専用なメンバのみ外部に公開するクラスです。

このクラスを生成するにはAsReadOnly()メソッドを利用するか、IList<T>を引数にとったコンストラクタを利用します。

List<int> list = new List<int>() { 1, 2, 3 };

//AsReadOnly()による生成
ReadOnlyCollection<int> readonlyCollection = list.AsReadOnly();

//コンストラクタによる生成
ReadOnlyCollection<int> readonlyCollection = new ReadOnlyCollection<int>(list);

IReadOnlyList<T>との違い

IReadOnlyList<T>はインターフェイスで、実体としてはあくまでも具象クラスを参照しているため、具象クラスにキャストされればList<T>が変更される危険があります。
ところが、このReadOnlyCollecion<T>はインターフェイスではなく、元のList<T>とはまた別のクラスなので、キャストされる心配がありません。

具体的には、元となるList<T>の参照を内部で持ち、読み取り専用のメンバのみを外部に公開しています。
そのため、内部のList<T>は完全にプロテクトされており、変更される危険がないのです。

図による解説

ちょっと分かりづらいかと思いますので図を用意しました。
IReadOnlyList<T>の場合は次のようになります。

IReadOnlyList.png

IReadOnlyList<T>List<T>のうち読み取り専用のメンバのみを提供するインターフェイスです。
しかし、あくまでもList<T>に実装されたインターフェイスなので、キャストされればList<T>に直接アクセスされます。

対して、ReadOnlyCollection<T>は次のようになります。
ReadOnlyCollection.png

ReadOnlyCollection<T>は、インターフェイスではなく実体があるクラスで、内部に元となるList<T>の参照を持っています。
ReadOnlyCollection<T>List<T>のメンバのうち読み取り専用メンバのみを外部に公開するため、List<T>自体は安全が保たれます。
さらに、外部からアクセスされるReadOnlyCollection<T>は内部のList<T>とは独立した存在なので、キャストによって内部のList<T>が変更される危険はありません。

ImmutableList<T>クラス

ImmutableList<T>クラスも、ReadOnlyCollection<T>と同様に、元となるList<T>を内部にラップします。

このクラスを生成するには、ToImmutable()メソッドを利用するか、ImmutableList.CreateRange(IEnumerable<T>)ファクトリメソッドを利用します。コンストラクタによる生成はできません。

List<int> list = new List<int>() { 1, 2, 3 };

//ToImmutable()による生成
ImmutableList<int> immutableList = list.ToImmutable();

//ImmutableList.CreateRangeによる生成
ImmutableList <int> immutableList = ImmutableList.CreateRange(list);

参照ではなくコピーをラップする

ReadOnlyCollection<T>との違いは、参照ではなくコピーをラップすることです。
参照ではなくコピーをラップするため、ImmutableList<T>を生成したあとに元となるコレクションに変更を加えても、ImmutableList<T>の読み出し値は変わりません。

以下のコードで、ReadOnlyCollection<T>ImmutableList<T>を両方生成したあとに、元のコレクションに変更を加えて、動作の違いを確認してみます。

static void Main(string[] args)
{
    List<int> list = new List<int>() { 1, 2, 3 };

    ReadOnlyCollection<int> readonlyCollection = list.AsReadOnly();
    ImmutableList<int> immutableList = list.ToImmutableList();

    //ReadOnlyCollection, ImmutableListを生成したあとに元となるコレクションに変更を加える
    list.Add(100);

    Console.WriteLine("ReadOnlyCollection:");
    foreach (var item in readonlyCollection)
    {
        Console.WriteLine(item);
    }
    Console.WriteLine("ImmutableList:");
    foreach (var item in immutableList)
    {
        Console.WriteLine(item);
    }
}
ReadOnlyCollection:
1
2
3
100
ImmutableList:
1
2
3

ReadOnlyCollection<T>のほうは、インスタンス生成後に元となるコレクションに変更が加えられた場合でも、その変更内容が反映されています。元となるコレクションの参照を内部にラップしているからですね。
対して、ImmutableList<T>のほうは、インスタンス生成後の変更内容が反映されていません。これは、元となるコレクションのコピーを内部にラップしているためです。

言うならば、コレクションのスナップショットを撮影しているような感覚だと思います。

変更系メソッドが利用可能

ImmutableList<T>の場合は変更系メソッドが呼び出し可能です。
変更系メソッドを呼び出した場合、内部にラップされた元となるList<T>のコピーがまた生成され、そのコピーに対して変更内容が適用されて、メソッドの戻り値として返されます。

したがって、変更系メソッドを呼び出した場合でも、元となるコレクションはもちろん、内部にラップしたコレクションにも変更が加えられることはありません。

図による解説

ImmutableList<T>の挙動を図で表すと次のようになります。
ImmutableList.png

ReadOnlyCollection<T>と同様、キャストによる変更の危険性はなくなっています。

まとめ

IReadOnlyList<T>, ReadOnlyCollection<T>, ImmutableList<T>の違いをまとめると次のようになります。

IReadOnlyList<T> ReadOnlyCollection<T> ImmutableList<T>
種類 インターフェイス クラス クラス
利用方法 そのまま渡せばOK AsReadOnly()
または
new ReadOnlyCollection(IList<T>)
ToImmutable()
または
ImmutableList.CreateRange(IEnumerable<T>)
元コレクション 直接参照している 参照がラップされる コピーがラップされる
変更系メソッド 利用不能
(ただしキャストで利用可能)
利用不能 利用可能
ただしコピーに適用され、ソースには影響しない
利用場面 「このListは変更してほしくない」という意思を伝えたい Listを絶対に変更できない形で渡したい コレクションのスナップショットを渡したい

メソッド名から伝わるニュアンス

AsReadOnly()ToImmutable()のメソッド名をこうやって比べてみると、確かにAsの方は「オブジェクト参照はそのままで、型を変化させる」といったニュアンスが伝わりますし、Toの方は「オブジェクトを元にして別のインスンタンスを生成する」といったニュアンスが伝わります。細かなメソッド名の違いも、その挙動を正確に表現しているようです。メソッド名はきちんと考えて付けなければならないと感じました。

おまけ:IList<T>インターフェイスの実装について

実は、ReadOnlyCollection<T>クラスとImmutableList<T>クラスは、共にIList<T>インターフェイスを実装しています。
IList<T>インターフェイスを実装しているということは、AddRemoveができるはずですよね?

すごく疑問に思い調べてみると、インターフェイスの明示的な実装というものを使っているらしいです。

インターフェイスの明示的な実装

詳しくはこのサイトを参考にしてほしいのですが、インターフェイスのメンバにあるけども、publicにしたくないメンバを隠すために使われることがあるようです。

インターフェイスの明示的な実装を行うには下記のよう、インターフェイス名.メンバ名でインターフェイスメンバを実装します。

public interface ITestInterface 
{
    void MethodA();
    void MethodB();
}

public class TestClass : ITestInterface 
{
    //普通のインターフェイス実装
    public void MethodA()
    {

    }

    //明示的なインターフェイスの実装
    void ITestInterface.MethodB()
    {

    }    
}

このようにすると、MethodBTestClassクラスのインスタンスから呼ぶことはできなくなり、メンバを隠すことができます。

しかし、インターフェイスを実装している以上、メンバは呼べなければなりません。
ではどうすれば呼べるかと言うと、インターフェイス名.メンバ名で呼ぶことができます。
実装しているクラスのインスタンスから直接呼び出すことはできないけども、インターフェイス経由ならば呼び出せるようです。

「インターフェイスの明示的な実装」、使う場面にはあまり出くわしたことがないですが、まだ知らないことがたくさんあると勉強になりました。

呼び出すと必ずNotSupportedExceptionがスローされる

では、ReadOnlyCollection<T>ImmutableList<T>でも、IList<T>インターフェイス経由であればAddRemoveができるのかといえば、そうではないようです。

両者のクラスは、IList<T>インターフェイス経由で変更系メソッドを呼び出すと、必ずNotSupportedExceptionがスローされるようです。

この実装って正しいんですかね…?そこまでしてIList<T>インターフェイスを実装する理由は…?
まぁ、マイクロソフトのやることなので正しいのでしょう(適当)

誰かReadOnlyCollection<T>ImmutableList<T>IList<T>を実装してる理由知っていたら教えて下さい。

以上、長くなってしまいましたがIReadOnlyList<T>ReadOnlyCollection<T>ImmutableList<T>の違いでした。


  1. ちなみにインターフェイスがインターフェイスを「継承」しているっていうんですかね?それとも「実装」?意味合い的には「継承」が正しい気もしますが。 

  2. もちろんList<T>だけじゃなくて色々なクラスに実装されていますが代表として挙げました 

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

【Unity】動画で見るLerp

Vector3のLerpを動画にしました。
Lerpをなんとなく使っている人に見ていただけると幸いです。

動画


オブジェクトの色も線形補間(Color.Lerp)によって変化させています。

Lerp(Vector3 a, Vector3 b, float t)

引数

  • Vector3 a :開始点
  • Vector3 b :終了点
  • float t :二点の補間値

二点間を線形補間するメソッド。
$y=f(x)$上の二点$(x_1,y_1),(x_2,y_2)$に対して、

y = y_1 + \frac{y_1-y_2}{x_2-x_1}(x-x_1) 

で近似を行う。

二点の補間値tは以下の式となる。

 t = \frac{x-x_1}{x_2-x_1}

すなわち、$y=f(x)$上の点$(x,y)$が、どのくらい終点に近づいているかを表している。
この辺は以下の記事に大変わかりやすい図が載っています。

[Unity] Vector3.Lerpの使い方

LerpExample

LerpExample.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class LerpExample : MonoBehaviour
{
    //始点
    public Transform startMarker;
    //終点
    public Transform endMarker;
    //速度
    public float speed = 1.0f;

    //ColorLerpクラスのために非表示public
    //二点の補間値
    [System.NonSerialized]
    public float fractionOfJourney;

    private float startTime;

    private float distance;

    private void Start()
    {
        //動き出した時間
        startTime = Time.time;
        //二点間の距離
        distance = Vector3.Distance(startMarker.position, endMarker.position);
    }

    private void Update()
    {
        //移動距離 = 経過時間 * 速度
        float distCovered = (Time.time - startTime) * speed;

        //移動距離の割合 = 移動距離 / 二点間の距離
        fractionOfJourney = distCovered / distance;

        //線形補間による移動
        transform.position = Vector3.Lerp(startMarker.position, endMarker.position, fractionOfJourney);
    }
}

ColorLerp

ColorLerp.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class ColorLerp : MonoBehaviour
{
    private LerpExample lerpExample;

    private Material mat;

    private void Start()
    {
        //LerpExampleから補間値を取得
        mat = this.GetComponent<Renderer>().material;
        lerpExample = GetComponent<LerpExample>();
    }

    void Update()
    {
        //青色→赤色へLerpExampleと同じ補間値で推移
        mat.color = Color.Lerp(Color.blue, Color.red, lerpExample.fractionOfJourney);
    }
}

まとめ

  • Lerpは線形補間をするメソッド。
  • 補間を行ってくれるので、これを使用すると動きが滑らかになる。
  • 次回はSlerpの記事を書くつもり
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Unity(C#)】テクスチャを画像データに変換して端末に保存、読み込み

はじめに

画像の保存処理で手間取ったのでメモを残します。

今回は下記の処理を行います。
・ImageコンポーネントにアタッチされたSprite(Texture)をPngに変換して保存
・Pngを読み込んでImageコンポーネントにSprite(Texture)としてアタッチ

デモ

実際にAndroid端末にて検証したデモがこちらになります。
Qiita画像保存.gif

書き込んだ画像を消去したのち復元に成功しました。
端末内に画像データは保存されているのでアプリを終了しても復元が可能です。


下記画像はPC内部の保存先ディレクトリを参照した際のスクショです。
ちゃんとPng形式で保存されていました。
PaintQiitaSaveSS.PNG

お絵描きの部分のコード

お絵描きの部分の実装は完全にそのまんま使わせていただいてます。

【参考リンク】:UI.Image にペイントする。線を手書きする。

スマホ対応部分とテクスチャのリセットだけ下記のように追記してます。

Painterクラスに追記
    /// <summary>
    /// テクスチャーをリセット
    /// </summary>
    public void ResetTexture()
    {
        var img = GetComponent<Image>();
        var rt = GetComponent<RectTransform>();
        var width = (int)rt.rect.width;
        var height = (int)rt.rect.height;
        texture = new Texture2D(width, height, TextureFormat.ARGB32, false);
        img.sprite = Sprite.Create(texture, new Rect(0, 0, texture.width, texture.height), Vector2.zero);

        Color32[] texColors = Enumerable.Repeat<Color32>(bgColor, width * height).ToArray();
        texture.SetPixels32(texColors);
        texture.Apply();
    }

    void Update()
    {
#if UNITY_EDITOR
        if (Input.GetMouseButtonDown(0))
        {
            beforeMousePos = GetPosition();
        }
        else if (Input.GetMouseButton(0))
        {
            Vector3 v = GetPosition();
            LineTo(beforeMousePos, v, lineColor);
            beforeMousePos = v;
            texture.Apply();
        }
#elif UNITY_ANDROID && !UNITY_EDITOR
        if (0 < Input.touchCount)
        {
            Touch touch = Input.GetTouch(0);

            if (touch.phase == TouchPhase.Began)
            {
                beforeMousePos = GetPosition();
            }
            else if (touch.phase == TouchPhase.Moved || touch.phase == TouchPhase.Stationary)
            {
                Vector3 v = GetPosition();
                LineTo(beforeMousePos, v, lineColor);
                beforeMousePos = v;
                texture.Apply();
            }
        }
#endif
    }

画像の保存と読み込み

ボタンの押下処理はUniRxで実装してます。特に意味はないです。

using System.IO;
using UniRx;
using UniRx.Triggers;
using UnityEngine;
using UnityEngine.UI;

/// <summary>
/// テクスチャー ⇔ Png画像 の変換と保存と読み込み
/// </summary>
public class TexturePngConverter : MonoBehaviour
{
    [SerializeField] private Button _saveButton;
    [SerializeField] private Button _loadButton;
    [SerializeField] private Button _resetButton;
    [SerializeField] private Image _paintImage;
    [SerializeField] private Painter _painter;

    private const string IMAGE_SAVE_FOLDER = "Image";

    private void Start()
    {
        //セーブボタン
        _saveButton.OnPointerClickAsObservable().Subscribe(_ => ConvertToPngAndSave(GetSavePath(IMAGE_SAVE_FOLDER))).AddTo(this);
        //ロードボタン
        _loadButton.OnPointerClickAsObservable().Subscribe(_ => ConvertToTextureAndLoad(GetSavePath(IMAGE_SAVE_FOLDER))).AddTo(this);
        //リセットボタン
        _resetButton.OnPointerClickAsObservable().Subscribe(_ => _painter.ResetTexture());
    }

    ///  /// <summary>
    /// 保存先のパス取得
    /// </summary>
    /// <param name="folderName">区切りのフォルダ名</param>
    /// <returns>保存先のパス</returns>
    private string GetSavePath(string folderName)
    {
        string directoryPath = Application.persistentDataPath + "/" + folderName + "/";

        if (!Directory.Exists(directoryPath))
        {
            //まだ存在してなかったら作成
            Directory.CreateDirectory(directoryPath);
            return directoryPath + "paint.png";
        }

        return directoryPath + "paint.png";
    }

    /// <summary>
    /// 画像に変換&保存
    /// </summary>
    private void ConvertToPngAndSave(string path)
    {
        //Pngに変換
        byte[] bytes = _paintImage.sprite.texture.EncodeToPNG();
        //保存
        File.WriteAllBytes(path, bytes);
    }

    /// <summary>
    /// テクスチャに変換&読み込み
    /// </summary>
    private void ConvertToTextureAndLoad(string path)
    {
        //読み込み
        byte[] bytes = File.ReadAllBytes(path);
        //画像をテクスチャに変換
        Texture2D loadTexture = new Texture2D(2, 2); 
        loadTexture.LoadImage(bytes);
        //テクスチャをスプライトに変換
        _paintImage.sprite = Sprite.Create(loadTexture, new Rect(0, 0, loadTexture.width, loadTexture.height), Vector2.zero);
    }
}

Application.persistentDataPath

Application.persistentDataPathを呼ぶと
文字通りアプリ内に存在する永続的なパスへアクセスできます。
ここにフォルダを作って画像を保存しています。

EncodeToPNG

ImageConversionクラスからEncodeToPNGを呼び出すとbyte配列としてpngデータに変換してくれます。

【参考リンク】:ImageConversion.EncodeToPNG

WriteAllBytes,ReadAllBytes

WriteAllBytes,ReadAllBytesそれぞれデータの書き込みと読み込みを担います。
これ書くだけでやってくれるのでありがとうございますって感じです。

おわりに

書き込み、読み込み時のリソースの開放とかがよくわかりませんでした。
書かなくても動いたので"まあいっか"ってなってます。
素直で良い子なので危険なコード書いてたら教えて下さい。

参考リンク

【参考リンク】:EncodeToPNG hangs the script
【参考リンク】:はなちるのマイノート

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

VSCodeをIDE化する方法

はじめに

.NET 関連での開発をする場合は、基本的に Visual Studio を利用すると思います。
しかし Visual Studio はポータブルな環境ではないため、Windows と Mac / Linux とを行き来している人からするとツライものがあります。
これは裏を返せば Visual Studio が非常に優秀な IDE だということなのですが、いかんせん Windows 以外で利用できないのが残念なところです。

// Visual Studio for Mac はまだまだ発展途上なので Visual Studio に追いつくのは遠そうですし...。

そこで今回は Visual Studio Code でなるべく .NET開発 が快適にできるように Extensions でがんばれるところまでがんばるためのものを紹介したいと思います。

拡張機能

C#

C# で開発する場合はもちろん、F# で開発する場合にも必須の拡張機能です。

スクリーンショット 2020-09-14 19.03.04.png

Ionide-fsharp

F# で開発する場合にはほぼ必須の拡張機能です。
使い方
スクリーンショット 2020-09-14 19.03.47.png

Bookmarks

Visual Studio でお馴染みの Bookmark 機能を実現するための拡張機能です。

スクリーンショット 2020-09-14 19.08.48.png

Todo Tree

Visual Studio でお馴染みの TODO 機能を実現するための拡張機能です。

スクリーンショット 2020-09-14 19.10.13.png

.NET Core Test Explorer

テストプロジェクトを実行・管理するための拡張機能です。
使い方

スクリーンショット 2020-09-14 19.11.03.png

GitLens

Git の履歴なんかをいい感じに見せてくれる拡張機能です。

スクリーンショット 2020-09-14 19.12.45.png

Git Graph

Git のブランチをいい感じに見せてくれる拡張機能です。

スクリーンショット 2020-09-14 19.14.25.png

Rainbow Brackets

対応するカッコに色を付けてくれる拡張機能です。
Bracket Pair Colorizer の方が有名ですが、' がついている変数や関数を用いた場合に正しく色がついてくれません。
Rainbow Brackets はいい感じにちゃんと色がついてくれるのでおすすめです。

スクリーンショット 2020-09-14 19.16.23.png

おまけ

REST Client

VSCode を REST クライアント化してくれる拡張機能です。

スクリーンショット 2020-09-14 19.20.05.png

SQLTools

VSCode を SQL クライアント化してくれる拡張機能です。
使い方

スクリーンショット 2020-09-14 19.21.24.png

Swagger Viewer

Swagger の Yaml 記法を使っていい感じに API 仕様書を書けるようになる拡張機能です。

スクリーンショット 2020-09-14 19.22.19.png

Draw.io Integration

UMLDFD などの各種ダイアグラムを GUI で書けるようになる拡張機能です。
スクリーンショット 2020-09-14 19.25.04.png

Azure Tools

Azure系拡張機能の欲張りセット。

スクリーンショット 2020-09-14 19.26.12.png

おわりに

TODO として今後時間があるときにここで紹介した拡張機能の使い方を簡単に紹介する章を追加しようと思います。

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

ASP.NET Web APIの自動デシリアライズでInternal Server Error(500)

現象

クライアントからASP.NetのWebApiを呼び出した際に、自動でパラメータがデシリアライズされるが、
あるデータクラスで受け取ろうとした場合に、HttpStatusCode:500を吹いてエラーになる。

***Controller.cs
        [HttpPost()]
        public async Task<ActionResult> Post(Photo photo)
        {
            return Ok();
        }
Photo.cs
    public class Photo{
        public string name{get;set;}
        public string src{get;set;}

        public Photo(string name, string src){
            this.name = name;
            this.src = src;
        }
    }
送りつけてるデータ
    { name: "aaa", src: "ccc"}
エラー内容
System.NotSupportedException: Deserialization of reference types without parameterless constructor is not supported. Type '*****.Models.Photo'
   at System.Text.Json.ThrowHelper.ThrowNotSupportedException_DeserializeCreateObjectDelegateIsNull(Type invalidType)

原因

デシリアライズ時に引数無しのコンストラクタが無いため、エラーとなっていた。
そりゃそうか。。。

Photo.cs
    public class Photo{
        public string name{get;set;}
        public string src{get;set;}

        public Photo(){}

        public Photo(string name, string src){
            this.name = name;
            this.src = src;
        }
    }
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

ASP.NET Core の IHttpClientFactory を使うときの注意点

IHttpClientFactory を使うと、使い方に癖のある HttpClient をいい感じに使えるようにしてくれます。

ASP.NET Core で IHttpClientFactory を使用して HTTP 要求を行う

IHttpClientFactory から CreateClient することで、いい感じに使いまわしたり適切なタイミングで破棄したり色々やってくれるので便利です。名前付きクライアントを使うと、IHttpClientFactory には依存せずに設定した通りに構成された HttpClient が渡されて、もっといい感じになります。

IHttpClientFactory を使う上で重要なのは HttpClient のインスタンスを必要な時に生成して不要になったら手放す(Dispose はしなくていい)という感じで作ることです。

Singleton? 

なので、Singleton クラスに HttpClient を DI したり型指定されたクライアントを DI したりすると台無しになってしまいます。型指定されたクライアントは Transient で DI コンテナに登録されているのに Singleton のクラスに DI すると、ずっと同じインスタンスが使いまわされてしまいます。

例えば以下のように…

// DI コンテナへの登録処理
services.AddHttpClient();
services.AddSingleton<SomeSingleton>();

// SomeSingleton の定義
class SomeSingleton
{
    private readonly HttpClient _httpClient; // 私はいつ開放されるの!?

    public SomeSingleton(HttpClient httpClient)
    {
        _httpClient = httpClient;
    }

    // 省略
}

ということで

なので、IHttpClientFactory を DI するようにするか Singleton じゃなくて Scoped か Transient で管理するようにしましょう。

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

Unity 開発に関する 50 の Tips 〜ベストプラクティス〜(2016 Edition)

20200910152454.png

この記事は「Unity開発に関する50のTips 〜ベストプラクティス〜(翻訳)」を読んでとても参考になったので、その続編になる「50 Tips and Best Practices for Unity (2016 Edition)」の内容を超訳(勉強のため個人的に意訳・誤訳)したものです。

ブログにも掲載していますが、5日作業の結構な労力だったので Qiita にも掲載しておきます。(辺境のブログtwitter をよろしくしてくれると幸いであります?)

変なことを書いていたり、誤訳等あったらごめんなさい。仕事から帰ってきてから趣味でやっていたので、疲れたところなんかは、翻訳ツールが増えてます。まわりくどく感じた文の省略や、省略による文脈の欠落もあるかもしれません。(そのあたりは、2012 年版の翻訳と同じで、諸々あらかじめご了承ください)
コメントに書いているのは、個人的にメモとして加筆しています。
(括弧)の内容は、訳した元々の文章から括弧をされていたり、個人的に加筆したものがあります。



前書き

以下の記事の内容は、Gamasutra のコミュニティメンバーによって書かれたものです。考えや意見は執筆者のものであって、Gamasutra や Unity(公式)のものではありません。

約4年前にオリジナルの「50 Tips for working with Unity」の記事を公開しました。その内容の多くはいまでも関連性がありますが、多くのことが変わっています:

  • Unity がよくなったこと
    たとえば、今では FPS カウンターを信用しています。(Tips 42 のこと)Property Drawer が使えるようになったことで、カスタムエディターを書く必要がなくなりました。Prefab の働きによって、明示的にネストされた Prefab や代替案を用意する必要が減りました。Scriptable object はより便利になりました。
  • Visual Studio との統合が改善された
    デバッグが各段に楽になり、引っ掻き回すようなデバッグが減りました。
  • サードパーティ製のツールやライブラリーが改善された
    Assets Store には、デバッグやロギングを改善するよいものがあります。
  • バージョン管理がよくなった
    Prefab の複製やバックアップコピーを用意する必要がなくなった。(というか、バージョン管理の効果的な使い方がわかってきた)
  • 私がより多くの経験を得た
    過去4年間、私は多くの Unity で仕事をしました。その中には、たくさんのゲームのプロトタイプ(30日で30個のゲーム)を作った経験、Father.IO のような(執筆者が作った)プロダクションゲームの経験、そして、私たちの代表的な Unity Assets である Grids(現在は非推奨になって使えない)が含まれます。

ここでの Tips は、上述のすべてを考慮してオリジナルの Tips を修正したものです。Tips に入るまえに、ここで免責事項を述べておきます。(基本的にオリジナルと同じ)

これらの Tips はすべての Unity プロジェクトに当てはまるわけではないとした上で:

  • Tips は、3~20人ほどの小さなプロジェクトでの私の経験に基づいている。
  • 構造、再利用性、明確さなどには(時間のように)対価がある。チームの規模、プロジェクトの規模・目標によって、その対価を支払うべきかどうかが決まる。たとえば、GameJam(クリエイターが集まって短時間でゲームを作るイベント)ではこれらの多くは使うことはない(リファクタリングの条件を説明しているのだと思います)
  • 多くの Tips は好みの問題になる。(ここで挙げた Tips は衝突するものがあるけど、どの Tips にも優れたメリットがある)
  • Unity のサイトにもいくつかのベストプラクティスが掲載されている。(ただ、多くはパフォーマンスの観点によるもの)



ワークフロー (Workflow)

1. 最初にスケールを決定して、すべて同じスケールになるようにビルドする

そうしないと、あとでアセットを作り直す必要がでるかもしれません。(たとえば、アニメーションのスケールはいつも正しいとは限りません)3Dゲームの場合、通常は1unity unit = 1m がベストです。

ライティングや物理を使用しない2Dゲームでは、通常1 unity unit = 1 pixel がよいでしょう。UI(と2Dゲーム)では、解像度 (HD or 2xHD) を選択して、その解像度でスケールするようにすべてのアセットをデザインします。


2. すべてのシーンを実行できる状態にしておくこと

ゲームを実行するためにシーンを切り替える必要がないようにします。ただし、すべてのシーンで必要とされる永続的なオブジェクトを持っている場合は厄介です。方法をひとつ挙げると、オブジェクト自身を singleton にすることです。singleton については、別の tips で説明します。

旧 tips.10 と同じ内容だと思います。


3. バージョン管理システム(Git など)の効率的な使い方を学んで利用すること

  • アセットをテキストとしてシリアライズする。
    実際は、シーンや Prefab がマージしやすくなるわけではないけど、何が変更されたか確認しやすくなります。
  • シーンと Prefab をまとめる手法を採用する
    一般的に、複数の人が同じシーンや Prefab を作業してはいけません。小さなチームの場合、あらかじめ(作業をはじめる前に)、他に作業していないことを聞いておくだけで十分かもしれません。シーンの所有権を示すための物理的なトークンを用意しておくと便利かもしれません。(机の上にシーンの所有権を示すトークンがあるときだけ、そのシーンの作業することができる)
  • タグをブックマークとして利用する
  • ブランチの手法を決めて採用する
    シーンと Prefab はスムーズにマージできないので、ブランチ化はすこし複雑です。どのようにブランチを使うのか決めるにしても、シーンと Prefab は一緒にするようにします。
  • サブモジュールは注意して利用する
    サブモジュールは、再利用可能なコードを維持するための素晴らしい方法です。しかし、いくつか注意点があります。
    • メタファイルは一般的に複数のプロジェクトで一貫性がありません。一般的には、non-Monobehaviour or non-Scriptable object なコードでは問題ありませんが、MonoBehaviours and Scriptable objects では問題になる恐れがあります。
    • 多くのプロジェクト(サブモジュールを1つ以上含む)で作業をしていると、ときには雪崩を起こすことがあり、すべてのプロジェクトのコード管理を安定させるために、様々のプロジェクトで pull-merge-commit-push をしなければいけないことがあります。(そして、このメンテナンス中に他の誰かが変更を加えてしまうと、さらに雪崩を起こす)この影響を最小化するための方法のひとつは、常にサブモジュール専用のプロジェクトからサブモジュールに変更を加えることです。サブモジュールを使うプロジェクトは、常に pull するだけでよくなります。


4. テストシーンとコードは分離すること

一時的なアセットやスクリプトはリポジトリーにコミットして、完了したらプロジェクトから削除します。


5. ツール(もっぱら Unity 本体)をアップデートするときは、他の人も同時にする

Unity は異なるバージョンでプロジェクトを開いたときの対応がとてもうまくなりましたが、複数の作業者が異なるバージョンで作業するとリンクが失われることがあります。

旧 tips.2 とは少し違う内容です。


6. 綺麗な状態のプロジェクトにサードパーティー製アセットをインポートして、そこで新しいパッケージをエクスポートして利用する

アセットは、プロジェクトに直接インポートすると問題を発生することがあります。

  • 特に、Plugins フォルダーの直下にファイルを追加するアセット、または、Standard Assets を利用しているアセットは、衝突(ファイル名など)する可能性があります。
  • 展開されたファイルは、プロジェクト上に整理されていないかもしれません。これは使用しないと決めて削除した場合に問題(面倒)になります。

安全にインポートをする手順は以下に従ってみてください。

  1. 新しいプロジェクトを作成し、アセットをインポート
  2. サンプルを実行して、動作することを確認
  3. アセットをより適切なフォルダー構造に整理
    (通常だと、アセットに自分の好むフォルダー構造を強制しません。しかし、すべてのファイルがひとつのフォルダーに入っていること、そして、プロジェクトを上書きするような重要な場所にファイルが追加されないことを確認します)
  4. サンプルを実行して、動作することを再確認
  5. 不要なものをすべて削除する(サンプルなど)
  6. アセットがコンパイルされ、Prefab がすべてリンクを持っていることを確認し、なにかあれば、テスト
  7. すべてのアセットを選択し、パッケージをエクスポート
  8. プロジェクトにインポート


7. ビルドプロセスを自動化する

小さなプロジェクトでも有効ですが、とくに有効なのは次のとおり:

  • ゲームの異なるバージョンをたくさんつくる場合
  • 技術力の劣るメンバーがビルドをする場合
  • ビルドをする前に、プロジェクトに微調整を加える場合

詳細は「Unity Builds Scripting: Basic and advanced possibilities」(著者の過去記事)を参照してください。


8. コーディング資料 (your setup) をドキュメント化すること

ほとんどのドキュメントはコードの中にあるべきですが、しかし、特定のものはコードの外でドキュメント化されるべきです。

コーディング資料を得るために、デザイナーにコードを調べさせるのは、時間の無駄です。ドキュメント化されたコーディング資料は、開発効率を向上させます。(ドキュメントが最新の状態を保持できれば)

ドキュメントがフォローするもの:

  • タグの使用
  • レイヤーの用途(collision, culling, raycasting など、どのレイヤーに入れるか)
  • レイヤーの GUI の深さ(なにをどの上に表示していくか)
  • 複雑な Prefab の構造
  • イディオムの設定
  • ビルドの方法

わりと Unity の設定(セットアップ)に関する技術資料を指している気がします。

旧 Tips 49 と同じ内容だと思います。



一般的なコーディングについて (General Coding)

9. すべてのコードを名前空間に入れること

これにより、独自のライブラリーとサードパーティのコートとの衝突を避けることができます。しかし、重要なクラスとの衝突を避ける目的で名前空間を頼らないこと。異なる名前空間を使用する場合であっても、クラスの名前を「Object」、「Action」、「Event」のようにしないでください。

後ろはリーダブルコードのような内容の指摘だと思います。


10. Assert を使うこと

アサーションは、コードの実行結果が変わらないことをテストしたり、ロジックのバグを洗い出したりするのに便利な機能です。

Unity では「Unity.Assertions.Assert」を利用できます。これらはなんらかの条件のテストをして、条件を満たさなかった場合、コンソールにエラーメッセージを表示します。

アサーション(というよりも、テスト)がどのように役に立つのかをよく知らない場合は、「The Benefits of programming with assertions(リンク切れ)」を参照してください。


11. 表示されるテキスト以外の文字列は使用しない

特に、オブジェクトや Prefab の識別子としてテキストをそのまま使わないこと。例外もあります。(Unity では名前でしかアクセスできないものが、まだいくつかあります)そのような場合は、 AnimationNames や AudioModuleNames のようにファイルで文字列を定数として定義してください。これらのクラスが管理しきれなくなった場合は、入れ子になったクラスを使用して、AnimationNames.Player.Run のように定義します。

旧 Tips 34 と同じ内容だと思います。


12. Invoke と SendMessage を使わないこと

MonoBehaviour のこれらのメソッドは、名前をつけて他のメソッドを呼び出します。テキストの名前で呼び出されるメソッドはコードで追跡するのが難しいです。(Usages を見つけることができないし、SendMessage は範囲が広いので、さらに追跡できない)

コルーチンや C# のアクションを使って、独自の Invoke を簡単に用意できます:

public static Coroutine Invoke(this MonoBehaviour monoBehaviour, Action action, float time)
{
   return monoBehaviour.StartCoroutine(InvokeImpl(action, time));
}

private static IEnumerator InvokeImpl(Action action, float time)
{
   yield return new WaitForSeconds(time);

   action();
}

MonoBehavior から、次のように使うことができます:

this.Invoke(ShootEnemy); //ShootEnemy メソッドの引数はなし (void)

独自に MonoBehaviour を継承する基底クラスを実装する場合は、そこに独自の Invoke メソッドを追加することができます。

旧 Tips 21 と同じ内容です。

より安全な SendMessage の代替を実装するのは、難しいです。その代わりとして、通常は GetComponent を使って、対象のコンポーネントを取得し、直接呼び出すようにします。

補足として、Unity の「ExecuteEvent」についての提案が挙がっています。今のところ、よく調べられていませんが調査する価値がありそうです。


13. ゲーム実行中に生成したオブジェクトがヒエラルキーをごちゃごちゃにしないこと

親オブジェクトをシーンオブジェクトに設定することで、ゲームの実行中にオブジェクトを見つけやすくします。

コードからのアクセスをしやすくするために、空の Game Object や、Behaviour を持たない Singleton クラスを使用することもできます。

こうしたオブジェクトのことを DynamicObjects と呼びます。

旧 Tips 28 と同じような内容です。


14. 正しい値として null を使用するときは具体的にして、可能な限りそれを避けること

null は不正なコードを検出するための役にたちます。しかし、もし、null を暗黙的に渡すことを習慣にしてしまうと、不正なコードは楽々実行されるようになり、また、そのバグに気づくのはかなり後になります。

さらに、それぞれのレイヤーが null の値を渡すことで、コードの深い部分でそれがはっきりすることもあります。私は、null を正しい値として使用することを完全に避けるようにしています。

私の好ましいイディオムは null の値のチェックを一切行わず、問題のあるところではコードを失敗させることです。より深いレベルのインターフェースとして機能するメソッドでは、値が null であるかどうかをチェックし、それが失敗する可能性のある他のメソッドに渡す代わりに例外を投げます。

場合によっては、正しい値が null になることがあり、別のやり方で対処する必要があります。このような場合は、コメントを追加して、いつ、なぜ、値が null になっているのかを説明するコメントを追加してください。

よくあるシナリオ(ケース)では、インスペクターが設定した値を使用することです。ユーザーは値を指定することができますが、ユーザーが指定しない場合はデフォルト値が使用されます。

これより好ましい方法は、T の値をラップする Optional<T> クラスを使用することです。(Nullable<T> とすこし似てる)

特別なプロパティのレンダラーを使って、チェックボックスをレンダリングして、チェックが入っている場合にのみ、値を編集するボックスを表示するようにします。(残念ながら、ジェネリッククラスを直接使うことはできないので、T を特定する型にクラスを拡張しないとダメです)

[Serializable]
public class Optional<T>
{
   public bool useCustomValue;
   public T value;
}

コードでは次のように使用することができます:

health = healthMax.useCustomValue ? healthMax.Value : DefaultHealthMax;

補足として、これは多くのコメントで struct を使ったほうがよいという指摘がありました。ただし、これだと、非ジェネリッククラスのベースクラスとしては使えないということなので、実際にインスペクターで使えるフィールドに適します。


15. コルーチンを使うなら効率的な使い方を学ぶ

コルーチンは多くの問題を解決する強力な手段になりえます。しかし、デバッグが難しく、(自分を含めた)誰もが理解できないよくわからないコードを簡単につくることができます。

知っておくべきことは:

  • コルーチンを並列に実行する方法
  • コルーチンを連続して実行する方法
  • 既存のコルーチンから新しいコルーチンを作成する方法
  • CustomYieldInstruction を使ったカスタムコルーチンの作成方法
Enumerator RunInSequence()
{
   yield return StartCoroutine(Coroutine1());
   yield return StartCoroutine(Coroutine2());
}

public void RunInParallel()
{
   StartCoroutine(Coroutine1());
   StartCoroutine(Coroutine1());
}

Coroutine WaitASecond()
{
   return new WaitForSeconds(1);
} 


16. インターフェースを共有するコンポーネントを操作するときは拡張メソッドを利用する

今は、GetComponent はインターフェースも動作するようになったので、この Tips は冗長になっています。

特定のインターフェースを実装したコンポーネントを取得したり、そんなコンポーネントを持つオブジェクトを見つけるときに便利です。

以下の実装では、これの汎用的な実装に typeof を使用しています。ジェネリック版だと、インターフェースは動作しないけど、typeof は動作します。以下のメソッドは、これを通常のメソッドでラップしています。

public static TInterface GetInterfaceComponent<TInterface>(this Component thisComponent)
   where TInterface : class
{
   return thisComponent.GetComponent(typeof(TInterface)) as TInterface;
}


17. 拡張メソッドを使用して構文をより簡便にする

たとえば、つぎのようなもの:

public static class TransformExtensions 
{
   public static void SetX(this Transform transform, float x)
   {
      Vector3 newPosition = 
         new Vector3(x, transform.position.y, transform.position.z);

      transform.position = newPosition;
   }
   ...
}

旧 Tips 24 と同じような内容です。


18. 防御的な GetComponent の代替メソッドを使うこと

RequireComponent を使用してコンポーネントの依存関係を強制しても、他のクラスで GetComponent を呼び出す場合は、常に取得可能であるとは限らないし、望ましいことでもないです。RequireComponent を使用する場合であっても、コンポーネントを取得するコードの中で、コンポーネントが存在することを期待しているので、存在しない場合はエラーであることを示すとよいです。

エラーメッセージを表示するか、見つからなかった場合に役立つ例外をスローする拡張メソッドを用意します。

public static T GetRequiredComponent(this GameObject obj) where T : MonoBehaviour
{
   T component = obj.GetComponent();

   if(component == null)
   {
      Debug.LogError("Expected to find component of type " 
         + typeof(T) + " but found none", obj);
   }

   return component;
}

旧 Tips 25 と同じような内容です。


19. 同じことをするのに異なるイディオムを使うのは避ける

多くの場合、ひとつのものに複数のイディオム(慣用句)があります。そんな場合、プロジェクト全体で使う慣用句をひとつ選びましょう。その理由は:

  • イディオム同時はうまく働かない。ひとつのイディオムを使うと別のイディオムには適していない方向にデザインを強制されます。
  • 全体をとおして同じイディオムを使うことで、チームメンバーは、そこでなにをしているのか理解しやすくなります。構造・コードが理解しやすくなって、ミスをしづらくなります。

同じ意味のイディオムになるケースを挙げると:

  • Coroutine と State Machine
  • Nested Prefab と Lined Prefab と God Prefab
  • データを分離するための手法
  • 2D ゲームのスプライトの使用方法
  • Prefab の構造
  • スポーンするときのやり方
  • オブジェクト見つけるための方法:type, name, tag, layer, reference などいろんなやり方がある
  • オブジェクトをグループ化する方法:type, name, tag, layer, reference などいろんなやり方がある
  • 他のコンポーネントからメソッドを呼び出す方法(Tips 12 みたいなこと)
  • オブジェクトのグループを見つける方法とグループに登録する方法(Tips 13 みたいなこと)
  • 実行順序の制御
  • ゲーム内でのマウスによるオブジェクト・位置・ターゲットの選択
  • シーン変更の間にデータを保持する方法:PlayerPrefab を介して、または、新しいシーンがロードされたときに Destroy されないオブジェクト
  • アニメーションを組み合わせる方法
  • 入力のやり方

これだけ聞くと、半端に UniRx を使うのって微妙な気持ちになる気も。学習不足でコードをあまり追えない状態で利用すると、いくつかの Tips に反してしまうけど、前書きのリファクタリングに関する説明がそれを肯定する関係に思った。


20. ポーズを簡単にするために独自の時間クラスを準備する

ポーズとタイムスケール(遅くしたりする)をするために Time.DeltaTimeTime.TimeSinceLevelLoad をラップしておきます。これは厳格さを必要とするけど、特に異なるタイマーを管理している場合は物事を簡単にします。(インターフェースのアニメーションとゲームのアニメーション速度が別々になるときなど)

補足として、Unity は unscaledTimeunscaledDeltaTime をサポートしており、多くの状況で独自の時間クラスを持つことは冗長になります。Tips 21 のようなケースの場合はまだ便利です。

旧 Tips 20 と同じ内容だと思います。


21. 更新を必要とするカスタムクラスはグローバルな静的時間にアクセスしないこと

更新を必要とするカスタムクラスはグローバルな静的時間にアクセスするべきではないです。

その代わりに、Update メソッドのパラメーターに delta time をとる必要があります。上の Tips 20 で説明したように、ポーズの機能を実装する場合や、カスタムクラスの動作を高速化したり遅くしたりする場合に、このクラスを使用することができる。


22. WWW を使うときは、共通のやり方にする

サーバー通信の多いゲームでは、数十もの WWW のコールがあるものです。

Unity の用意した WWW クラスを使用する場合でも、プラグインを使用する場合でも、その上にひな形 (boiler plate) になるレイヤーを入れることで便利になります。

通常だと、Call メソッド(Get と Post をひとつずつ)、CallImpl コルーチン、MakeHandler を定義します。基本的に、Call メソッドは Make Handler メソッドを使用して、パーサー・成功時と失敗時のハンドラを構築します。また、CallImpl コルーチンを呼び出し、URL を入れて、呼び出し、完了するまで待機してからスーパーハンドラを呼び出します。

大まかにはこんな感じ:

public void Call<T>(string call, Func<string, T> parser, Action<T> onSuccess, Action<string> onFailure)
{
    var handler = MakeHandler(parser, onSuccess, onFailure);
    StartCoroutine(CallImpl(call, handler));
} 

public IEnumerator CallImpl<T>(string call, Action<T> handler)
{
    var www = new WWW(call);
    yield return www;
    handler(www);
}

public Action<WWW> MakeHandler<T>(Func<string, T> parser, Action<T> onSuccess, Action<string> onFailure)
{
   return (WWW www) =>
   {
      if(NoError(www)) 
      {
         var parsedResult = parser(www.text);
         onSuccess(parsedResult);
      }
      else
      {
         onFailure("error text");
      }
   }
}

これには、いくつかのメリットがあります。

  • 定型的なコードを書く必要がなくなります。
  • 特定のこと(読み込み中の UI コンポーネントの表示や、特定の一般的なエラー処理など)をまとめて処理することができます。

23. 文字数の多いテキストは、ファイルにする

インスペクターで編集するフィールドに入れないでください。

Unity エディターを開かなくても、シーンを保存しなくても、簡単に変更できるようにしておきましょう。

旧 Tips 38 と同じ内容だと思います。


24. ローカライズを計画しているなら、すべての文字列をひとつのところに配置します

このやりかたはたくさんあります。ひとつの方法は、各文字列のpublic string のフィールドを持つ Text クラスを定義して、デフォルトを英語に設定しておくことです。他の言語は、これを基底としたサブクラスにして、対応する言語でフィールドを再設定します。

より洗練されたテクニック(テキストの本文が長い or 多くの言語に対応する)は、スプレッドシートを読み込んで、選択した言語に基づいて正しい文字列を選択するロジックを提供することです。

旧 Tips 39 と同じ内容だと思います。


クラスのデザイン (Class Design)

25. インスペクターで操作できるフィールドの実装方法を決めて、標準化する

フィールドは、public にするか private にして「SerializeField」属性を与えるか2つの方法があります。

後者は「より正しい」方法ですが、便利ではありません。(Unity が普及させた方法でないことは確かです)どちらの方法を選ぶにしても、あなたのチームの開発者が public フィールドをどのように扱うのか知っているように、それを標準としてください。

  • インスペクターで操作できるフィールドが public のとき、そのフィールドは「実行時にデザイナーが変更しても安全であり、コードで値を設定しない」ことを意味します。
  • インスペクターで操作できるフィールドが private だけど SerializeFiled 属性を持つとき、public なフィールドは「コード中でこの変数を変更しても安全だ」という意味です。(なので、あまり多くは表示されないはずで、MonoBehaviourScriptable Object には public フィールドは存在しないはずです)


26. コンポーネントはインスペクターで調整するべきではない変数を決して公開しないこと

そうしないと、そのパラメーターがなにをするのか明確でない場合、デザイナーによって調整されてしまいます。レアケースとして、それが避けることができない場合があります。(たとえば、エディタースクリプトが取得する必要がある場合など)その場合は、HideInInspector 属性を使ってインスペクターの中から隠すことができます。

旧 Tips 30 と同じ内容だと思います。


27. インスペクターの独自 (Propery drawers) を利用して、フィールドをより使いやすくすること

インスペクターの独自 (Propery drawers) は、インスペクターのコントロールをカスタマイズするために使用することができます。

これによって、データの性質にあわせたコントロールを作成したり、特定のセーフガードを設置したりできます。(Range のような範囲を設定するなど)Header 属性を利用してフィールドを整理したり、ToolTips 属性を利用して追加のドキュメント(あんちょこ)を提供します。

例に挙がっているとおり、属性でインスペクターに追加するフィールドをわかりやすくすることだと思います。ただ、単純に属性を追加するカスタムエディターと違う点だけフォローする。


28. カスタムエディターよりも独自の (Propery drawers) を優先すること

独自の (Propery drawers) はフィールドタイプごとに実装されているため、実装の手間が大幅に軽減できます。また、再利用性も高いので、ある型のために一度実装すれば、どのクラスでも再適用できます。

カスタムエディターは MonoBehaviour ごとに実装されるため、再利用性が低く、実装の手間がかかります。


29. デフォルトでは MonoBehaviour を seal すること

一般的には、Unity の MonoBehaviour は継承にフレンドリーではありません:

  • Unity が Start() や Update() などのメソッドを呼び出す方法は、サブクラスからこれらのメソッドを扱うのは面倒です。注意しないと、間違ったものが呼ばれたり、ベースになるメソッドの呼び出しを忘れてしまったりします。
  • カスタムエディターを使う場合、通常はエディターの継承構造を複製している必要があります。クラスのひとつを拡張したいなら、独自のエディターを提供するか、提供されているものを利用します。

継承が必要な場合は、避けられる場合は Unity のメソッド(Start や Update)を提供しないようにしてください。もしも、それらを提供するなら仮想化してはいけません。必要であれば、メソッドから呼び出される空の virtual 関数を定義して、子クラスが override して追加の作業をすること。

public class MyBaseClass
{
   public sealed void Update()
   {
      CustomUpdate();
   }

   virtual public void CustomUpdate(){};
}

public class Child : MyBaseClass
{
   override public void CustomUpdate()
   {

   }
}

これによって、誤って override することを防ぎますが、それでも Unity のメッセージにフックされることがある。このパターンがよくと思わない理由のひとつは、物事の順序が問題になることです。上の例では、クラスが自身の更新をした後に、子クラスで更新があるかもしれません。

最後は、整合性がとれないという指摘だと思います。


30. インターフェースをゲームロジックから分離すること

インターフェースのコンポーネントは、通常だと使用されるゲームについて何も知らないはずです。
認識するために必要なデータを与え、イベントを購読して、ユーザーがそれらと相互に作用したときにわかるようにしておきます。

インターフェースのコンポーネントは、ゲームロジックを行うべきではないです。入力をフィルタリングして有効であることを確認することはできるけど、主なルールチェックは別のところですべきです。

多くのパズルゲームでは、コマはインターフェースの延長線上にあって、ルールを含むべきではありません。(例えば、チェスの駒は駒自体が次の手を計算してはいけない)

同様に、入力はその入力に基づいて動作するロジックから切り離されるべきです。入力コントローラーを使用して、手を動かす意図をだけをアクターに通知します。(コントローラーの操作自体ではない)

ユーザーがリストの中から武器を選択する UI コンポーネントを例にします。これらのクラスがゲームについて知っていることは Weapon クラスだけです。(Weapon クラスは、このコンテナに表示するデータ自身です)逆にゲームはコンテナーについて、何も知りません。

public WeaponSelector : MonoBehaviour
{
   public event Action OnWeaponSelect {add; remove; } 

   public void OnInit(List  weapons)
   {
      foreach(var weapon in weapons)
      {

          var button = ... //Instantiates a child button and add it to the hierarchy

          buttonOnInit(weapon, () => OnSelect(weapon)); 
          // child button displays the option, 
          // and sends a click-back to this component
      }
   }
   public void OnSelect(Weapon weapon)
  {
      if(OnWepaonSelect != null) OnWeponSelect(weapon);
   }
}

public class WeaponButton : MonoBehaviour
{
    private Action<> onClick;

    public void OnInit(Weapon weapon, Action onClick)
    {
        ... //set the sprite and text from weapon

        this.onClick = onClick;
    }

    public void OnClick() //Link this method in as the OnClick of the UI Button component
    {
       Assert.IsTrue(onClick != null);  //Should not happen

       onClick();
    }    
}

旧 Tips 31 と同じ内容だと思います。個人的には、GUI クラスをすべてクリアしたとしても、そのゲームはコンパイルできるべきだ、という文言が好きでした。疎結合感が伝わりやすい。


31. コンフィグ・ステート・記録を分離すること

  • コンフィグに関する変数
    これは、インスペクターで微調整される変数で、プロパティを通してオブジェクトを定義します。たとえば、maxHealth のような値です。
  • ステート(状態)に関する変数
    これは、オブジェクトの現在の状態を決定する変数で、ゲームがセーブをサポートしているならセーブする必要がある変数です。たとえば、currentHealth のような値です。
  • 記録 (Bookkeeping) に関数変数
    これは、スピード・利便性・移り変わる状態のために使用する変数です。それらはステートに関する変数から決定することができます。たとえば、previousHealth のような変数です。

これらのタイプの変数を分離することで、何を変更できるのか、何を保存する必要があるのか、何をネットワーク経由で送受信する必要があるのかを容易に知ることができ、(変数の管理に対して)ある程度の強制力を持つことができるようになります。単純な例を示します。

public class Player
{
   [Serializable]
   public class PlayerConfigurationData
   {
      public float maxHealth;
   }

   [Serializable]
   public class PlayerStateData
   {
      public float health;
   }

   public PlayerConfigurationData configuration;
   private PlayerState stateData;

   //book keeping
   private float previousHealth;

   public float Health
   {
      public get { return stateData.health; }
      private set { stateData.health = value; }
   }
}

旧 Tips 32 と同じ内容かもしれません。サンプルを見る限りだと Bookkeeping は Memento に近いようにも思いました。


32. インデックスで関連づけた public 配列の使用を避けること

たとえば、武器の配列、弾丸の配列、パーティクルの配列を定義してはいけません。

public void SelectWeapon(int index)
{ 
   currentWeaponIndex = index;
   Player.SwitchWeapon(weapons[currentWeapon]);
}

public void Shoot()
{
   Fire(bullets[currentWeapon]);
   FireParticles(particles[currentWeapon]);
}

この場合、コード中でやるというより、インスペクターで間違わないように設定するべきです。いっそのこと、3つの変数をカプセル化したクラスを定義して、それを配列にします。

[Serializable]
public class Weapon
{
   public GameObject prefab;
   public ParticleSystem particles;
   public Bullet bullet;
}

コードがすっきりしているように見えるけど、(大切なのは)インスペクターでデータを設定する際にミスをしづらくなります。

旧 Tips 35 と同じ内容だと思います。


33. シーケンス以外のデータ(連続性のないデータ)に配列を使用することを避けるべき

たとえば、プレイヤーは、3つの攻撃タイプを持っているとします。それぞれ、今に装備している武器を使用するけど、異なる弾を生成して、異なる動作をします。

3つの弾丸を配列に入れて、このようなロジックを使いたくなるかもしれませんが、避けるべきです。

public void FireAttack()
{
   /// behaviour
   Fire(bullets[0]);
}

public void IceAttack()
{
   /// behaviour
   Fire(bullets[1]);
}

public void WindAttack()
{
   /// behaviour
   Fire(bullets[2]);
}

列挙型はコードの見栄えをよくしますが、インスペクターではできません。

public void WindAttack()
{
   /// behaviour
   Fire(bullets[WeaponType.Wind]);
}

別々の変数を使ったほうが、どの内容を入れるのかを示すのに役立つように、名前は別の変数を使ったほうがよいでしょう。クラスを使ってすっきりさせましょう。

[Serializable]
public class Bullets
{
   public Bullet fireBullet;
   public Bullet iceBullet;
   public Bullet windBullet;
}

補足すると、これは3つの属性(火・氷・風)以外のデータがないことを前提にしています。(拡張性に留意)

旧 Tips 36 の内容と同じだと思います。


34. インスペクターの中をすっきりさせるために、Serializable なクラスにしてデータをグループ化すること

いくつかのエンティティに、微調節可能な変数がたくさんあるとき、インスペクターで適切な変数を調節するのはひどい悪夢みたいな作業になるかもしれません。これを簡単にするためには、次の手順に従ってみてください:

  • 変数のグループに対して個別のクラスを定義する
  • それらを public なクラスにして、Serializable 属性を付与する
  • プライマリークラス(項1,2のクラスを含むクラス)では、定義されたそれぞれのクラスを public 変数で定義する
  • これらの変数はシリアライズ可能なので Awake() や Start() で初期化しない
  • (コード中の)定義に値を代入することで、これまでと同じようにデフォルト値を指定することができる

これによってインスペクター内で折りたたみができるようになるので、変数のグループを扱いやすくなる。

[Serializable]
public class MovementProperties //Not a MonoBehaviour!
{
   public float movementSpeed;
   public float turnSpeed = 1; //default provided
}

public class HealthProperties //Not a MonoBehaviour!
{
   public float maxHealth;
   public float regenerationRate;
}

public class Player : MonoBehaviour
{
   public MovementProperties movementProeprties;
   public HealthPorperties healthProeprties;
}

旧 Tips 37 と同じ内容だと思います。


35. public フィールドを使用しておらず MonoBehaviour ではないクラスはシリアライズ可能なクラスにすること

インスペクターがデバッグモードになっているときに、インスペクターでクラスのフィールドを確認することができます。これは入れ子になっているクラス (private or public) でも動作します。


36. インスペクターの微調整できる変数は、コード内で変更を加えないようにすること

インスペクターで調整可能な変数は設定変数であるため、実行時定数として扱われ、状態に関する変数として二重に扱うべきではないです。

このルールに従うことで、コンポーネントの状態を初期状態にリセットするメソッドが書きやすくなり、変数はなにをするのか明確になります。

public class Actor : MonoBehaviour
{
   public float initialHealth = 100;

   private float currentHealth;

   public void Start()
   {
      ResetState();
   }   

   private void Respawn()
   {
      ResetState();
   } 

   private void ResetState()
   {
      currentHealth = initialHealth;
   }
}



パターン (Patterns)

パターンとは、頻繁に発生する問題を標準的な方法で解決する方法のことです。

Bob Nystrom氏の著書「Game Programming Patterns」(オンライン上で無料で読める)は、ゲームプログラミングで発生する問題にパータンがどのように適用されるのかを知るために便利な資料です。

Unity もこれらのパターンの多くを使っています。Instantiate() は prototype パターンの一例だし、MonoBehaviour は template パターンに従っているし、UI とアニメーションは observer パターンを使っているし、新しいアニメーションのエンジンは state machine パターンを使用しています。

これら(ここで)の Tips は、特に Unity で使用するパターンを紹介します。


37. 便利に singleton を使うこと

つぎのクラスは、継承しているクラスを自動的に singleton にします。

public class Singleton<T> : MonoBehaviour where T : MonoBehaviour
{
   protected static T instance;

   //Returns the instance of this singleton.
   public static T Instance
   {
      get
      {
         if(instance == null)
         {
            instance = (T) FindObjectOfType(typeof(T));

            if (instance == null)
            {
               Debug.LogError("An instance of " + typeof(T) + 
                  " is needed in the scene, but there is none.");
            }
         }

         return instance;
      }
   }
}

singleton は ParticleManager、AudioManager、GUIManager などのようなマネージャー(クラス)のために役にたちます。

多くのプログラマーは、クラスの名前を XManager のように曖昧なものにすることに対して警告していますが、それは名前のつけ方が悪かったか、関連性のないタスクが多すぎるクラスになっていることを示しています。一般的には、私もこれに同意します。しかし、どんなゲームにもちょっとはマネージャークラスが存在していて、どんなゲームでも同じことをしていて、これらのクラスは実際のところイディオムです。

  • マネージャーではない Prefab のユニークなインスタンスに singleton を利用することは避けてください。(Player など)
    この原則に従わないと、継承が複雑になって、変更が難しくなります。マネージャーで管理したいなら、GameManager のようなもの(適切なマネージャークラス)にこれらの参照を追加してください。
  • クラスの外から頻繁に使用されるように(しやすいように)、static プロパティ、メソッドを定義します。こうすることで、GameMAnager.Instance.Player とする代わりに GameManager.Player と書くことができる。

他の Tips で説明したように、singleton は、オブジェクトのデフォルト生成位置を作成したり、シーンやロードの間を超えて永続しているオブジェクトを持つのにも便利です。

旧 Tips 29 と同じ内容だと思います。


38. state machine(パターン)を使用して、複数の振る舞い (state) をつくり、状態遷移をしてコードを実行すること

簡易な state machine はいくつかの state を持ち、それぞれの state に対して、その state に入る・存在しているときに実行・更新するアクションが決まります。

これによって、コードを綺麗な状態にして、エラーを発生しづらくすることができます。state machine の恩恵を受けることができる兆候は、Update() メソッドのコードに if 文や switch 文があってそれらがなにかを変更する場合や、hasShownGameOverMessage のような変数がある場合です。

public void Update()
{
   if(health <= 0)
   {
      if(!hasShownGameOverMessage) 
      {
         ShowGameOverMessage();
         hasShownGameOverMessage = true; //Respawning resets this to false
      }
   }
   else
   {
      HandleInput();
   }
}

state の数が増えてくると、この問題は非常にやっかいになります。state machine パターンを適用することで、よりすっきりしたコードにすることができます。


39. UnityEvent 型のフィールドを使用して、インスペクターに observer パターンを使えるようにしておくこと

UnityEvent クラスを使用すると、インスペクターで、ボタンのイベントと同じ UI インターフェースを使用して、(最大4つの引数を使える)メソッドをリンクすることができます。この機能は、特に入力を扱う場合に便利です。


40. observer パターンを使って、フィールドの値が変化したタイミングを検出する

変数の値が変化したときだけ、コードを実行したいというケースは、ゲームの中で頻繁に発生します。この問題の一般的な解決策として、値が変わるたびにイベント登録できる汎用クラスを準備します。

ここでは health を例に示します。以下のようになります:

/*ObservedValue*/ health = new ObservedValue(100);
health.OnValueChanged += () => { if(health.Value <= 0) Die(); };

これで、例えばこんな感じで、チェックする場所ごとにやらなくても、どこでもチェックできるように(通知してくれるように)なりました。

if(hit) health.Value -= 10;

health の値が0以下になれば Die() メソッドが呼び出されます。これについて、さらなる議論・実装等については、こちらの「記事」を参照してください。

これも、個人的に気になったので訳をした「記事」をあげました。


41. Prefab に actor パターンを使用する

このパターンは標準的なパターンではありません。基本的な考え方は、Kieran Load の「プレゼンテーション」からのものになります。

actor とは、Prefab のメインコンポーネントであり、通常は Prefab の「アイデンティティ」を決めるコンポーネントであり、上位レベルのコードが最も頻繁に相互作用するものになります。actor は、同じオブジェクト(子オブジェクト上)から他のコンポーネントに「ヘルパー」を使って作用します。

Unity でメニューからボタンオブジェクトを作成すると、Sprite と Button コンポーネントを持つ Game Object が生成されます。(Text コンポーネントを持つ子要素も生成されます)この場合、ボタンは actor コンポーネントです。

同様に、メインカメラも通常は Camera コンポーネントが付属しているだけではなくて、いくつかのコンポーネント(GUI and Flare layer、Audio Listener)が付属しています。Camera も actor です。

actor が正しく動作するためには、他のコンポーネントが必要になる場合があります。actor コンポーネントに以下の属性を与えることで、Prefab をより堅牢で有用なものにすることができます。

  • RequiredComponent を使用して、actor が同じゲームオブジェクト上で必要とするすべてのコンポーネントを指定します。(これによって、actor は常に安全に GetComponent を呼び出すことができ、返された値が null かどうかをチェックしなくてもよくなります)
  • 複数の同じコンポーネントがアタッチされるのを防ぐためには、DisallowMultipleComponent を使用します。これによって、同じコンポーネントが複数存在しないことになるので、actor は常に GetComponent を呼び出すことができます。
  • actor オブジェクトに子要素がある場合は、SelectionBase を使用します。これによって、シーンビューでの選択がやりやすくなります。
[RequiredComponent(typeof(HelperComponent))]
[DisallowMultipleComponent]
[SelectionBase]
public class Actor : MonoBehaviour
{
   ...//
}


42. ランダムなものとパターンのあるデータストリームには generator を使用すること

これは標準的なパターンではないけれど、非常に有用であることがわかりました。

generator は random generator に似ています。これは、Next() メソッドを持つオブジェクトで、特定の方の新しいアイテムを取得するために呼び出すことができます。

generator はそのアイテムを構築する間に操作して、多様なパターン、異なるタイプのアイテムとして生成することができます。

generator は、新しいアイテムを生成するロジックをアイテムが必要な場所と別のところに(generator 自身のロジックとして)配置することができるので、コードをよりすっきりさせることができるので、便利です。

いくつかの例を挙げます:

var generator = Generator
   .RamdomUniformInt(500)
   .Select(x => 2*x); //Generates random even numbers between 0 and 998

var generator = Generator
   .RandomUniformInt(1000)
   .Where(n => n % 2 == 0); //Same as above

var generator = Generator
    .Iterate(0, 0, (m, n) => m + n); //Fibonacci numbers

var generator = Generator
   .RandomUniformInt(2)
   .Select(n => 2*n - 1)
   .Aggregate((m, n) => m + n); //Random walk using steps of 1 or -1 one randomly

var generator = Generator
   .Iterate(0, Generator.RandomUniformInt(4), (m, n) => m + n - 1)
   .Where(n >= 0); //A random sequence that increases on average

私たちは、障害物の生成、背景色の変更、手続き的な音楽、単語ゲームの単語の文字列生成などに generator を使用してきました。また、generator の機能は、一定ではない間隔で繰り返すコルーチンを制御することもうまく機能します。

while (true)
{
   //Do stuff

   yield return new WaitForSeconds(timeIntervalGenerator.Next());
}

generator について詳しく知りたいなら、これちらの「記事」を確認ください。



Prefab と Scriptable object

43. すべてのものに Prefab を使うこと

シーンの中で Prefab(または Prefab の一部)であってはならないゲームオブジェクトはフォルダーだけです。

一度しか使用されないユニークなオブジェクトであっても、Prefab にするべきです。これによって、シーンを変更せずに(オブジェクトの)変更を簡単にすることができます。

旧 Tips 16 と同じ内容だと思います。


44. Prefab を Prefab にリンクし、インスタンスをインスタンスにリンクしないこと

Prefab へのリンクは、Prefab をシーンにドロップしたときに維持されます。インスタンスへのリンクは維持されません。シーンへの設定を減らして、Prefab にリンクさせることで、シーンを変更する必要を減らすことができます。

可能な限り、インスタンス間のリンクは自動的に確立します。もし、インスタンス間にリンクの必要がないなら、プログラム(コーディング)からリンクを確立します。たとえば、Player の Prefab は GameManager が起動したときに、自身を登録したり、インスタンスを見つけて登録すること。

旧 Tips 18 と同じ内容だと思います。


45. なにかスクリプトを追加したいとき、Prefab のルートにメッシュを配置しないこと

メッシュから Prefab を作るときは、最初にメッシュを空の game object の親として、それをルートにします。

スクリプトはメッシュのノードではなく、ルートに配置します。そうすれば、インスペクターで設定を見失うことなく、メッシュを別のメッシュに置換することもやりやすくなります。

旧 Tips 19 と同じ内容だと思います。


46. 共有するコンフィグ(設定)データには、Prefab の代わりに Scriptable object を使用すること

そうするとこうなります:

  • シーンが小さくなる
  • (Prefab のインスタンス上の)ひとつのシーンに対して誤って変更を加えることができない


47. レベルを表すデータには、 ScriptableObject を使用すること

レベルを表すデータには、XML や JSON で保存されることが多いですが、代わりに ScriptableObject を利用すると、いくつかの利点があります:

  • エディターで編集できる。これによって、データの検証が楽になって、技術的な知識のないデザイナーにもフレンドリーになる。さらに、カスタムエディターを使っておくことで、編集がもっと楽になる。
  • データの read/write や解析を気にする必要がなくなる。
  • 分割したり、ネストしたり、結果として得られる Assets の管理が楽になるので、大きなコンフィグからレベルを構成するのではなく、積み木 (building block) からレベルを構成します。


48. ScriptableObject を利用して、インスペクターの中で振る舞いを設定すること

Scriptable Object は通常、データを設定することに使いますが、「メソッド」をデータのようにして設定することもできます。

Enemy という型があって、それぞれの Enemy がいくつも SuperPower というデータを持っているシナリオを検討してみます。

これらの基礎になるクラスを作成して、Enemy クラスの中にそれらのデータをリストに持たせることができます……が、カスタムエディターでなければ、異なる SuperPower(それぞれ独自のプロパティを持つ)リストをインスペクターの中で設定することができません。

しかし、SuperPower をアセット(ScriptableObject として実装すれば)、(設定を)できるようになります。

こんな感じになります:

public class Enemy : MonoBehaviour
{
   public SuperPower superPowers;

   public UseRandomPower()
   {
       superPowers.RandomItem().UsePower(this);
   }
}

public class BasePower : ScriptableObject
{
   virtual void UsePower(Enemy self)
   {
   }
}

[CreateAssetMenu("BlowFire", "Blow Fire")
public class BlowFire : SuperPower
{
   public strength;
   override public void UsePower(Enemy self)
   {
      ///program blowing fire here
   }
}

このパターンに従う際は、いくつか注意するべきことがあります:

  • ScriptableObject を抽象化することはできません。代わりに、具体的な base クラスを使って、抽象化するべきメソッドは NotImplementedException の例外を投げるようにします。また、abstract を定義して、抽象化するべきクラスやメソッドにマークをつけることもできます。
  • ScriptableObject はシリアライズできません。しかし、ジェネリックな base クラスを使用して、ジェネリックを指定したサブクラスのみをすべてシリアライズすることができます。


49. 特殊化した Prefab に ScriptableObject を使用すること

もしも、2つのオブジェクトの構成が一部のプロパティにだけ違いがあるなら、シーンの中に2つのインスタンスを配置して、インスタンスの上でそれらのプロパティを調整するのが普通です。

大抵は異なる2つのプロパティを持つオブジェクト同士を、別々の ScriptableObject として分けたほうがよいです。

(そうすると)、柔軟さを得ます:

  • 特殊化したクラスの継承を使用して、型の異なるオブジェクトに特定のプロパティを与えることができます。
  • シーンの設計はより安全になります。(オブジェクトを目的の型にするために、すべてのプロパティを調整しないし、適切な ScriptableObject を選択するだけになります)
  • コードを通じて実行時にこれらのオブジェクトを操作するのが、もっと楽になります。
  • 2つの型のインスタンスを複数持っているなら、変更を加えたときにそれらのプロパティが常に等価(一貫してるもの)であることがわかります。
  • コンフィグの変数のセットを、混ぜたり・合わせたりできるセットに分けることができます。

設定の簡単な例を紹介します:

[CreateAssetMenu("HealthProperties.asset", "Health Properties")]
public class HealthProperties : ScriptableObject
{
   public float maxHealth;
   public float resotrationRate;
}

public class Actor : MonoBehaviour
{
   public HealthProperties healthProperties;
}

特殊化したものの数が多いときは、特殊化したものを通常のクラスとして定義して、そのリストを ScriptableObject の中で用いて、それを取得できる適切な場所(GameManager クラスなど)にリンクをしておくとよいです。

安全に、速く、便利にするためにはもうすこし貼り付ける(馴染ませる)必要があります。以下に小さな例を示します:

public enum ActorType
{
   Vampire, Wherewolf
}

[Serializable]
public class HealthProperties
{
   public ActorType type;
   public float maxHealth;
   public float resotrationRate;
}

[CreateAssetMenu("ActorSpecialization.asset", "Actor Specialization")]
public class ActorSpecialization : ScriptableObject
{
   public List healthProperties;

   public this[ActorType]
   {
       get { return healthProperties.First(p => p.type == type); } //Unsafe version!
   }
}

public class GameManager : Singleton
{
   public ActorSpecialization actorSpecialization;

   ...
}

public class Actor : MonoBehaviour
{
   public ActorType type;
   public float health;

   //Example usage
   public Regenerate()
   {
      health 
         += GameManager.Instance.actorSpecialization[type].resotrationRate;
   }
}

この内容は最近 Unity Blog に載っていたものと近い気がしました。まとめたのが「これ」。インスペクターでの設定機能を強化することを好む Tips が多い印象です。


50. CreateAssetMenu の属性を利用して、自動的に ScriptableObject の作成をするメニューを追加すること

本文はありません。タイトルだけで全部です。Project ビューを右クリックして ScriptableObject を追加できるようにすることだと思います。



デバッグ (Debugging)

51. Unity のデバッグ機能を有効に使う方法を学ぶこと

  • Debug.Log() ステートメントに context object を追加しておけば、どこから(ログが)生成されたのかを確認することができます。
  • Debug.Break() を使用すればエディターからゲームを一時停止することができます。(たとえば、エラーになる状態が発生したとき、エラーが発生したフレームにおけるコンポーネントのプロパティを調べたりする際に便利です)
  • ビジュアルのよいデバッグには、Debug.DrawRay()Debug.DrawLine() 関数を使用します。(たとえば、Debug.DrawRay() は、レイキャストがヒットしない原因をデバッグする際にとても便利です)
  • ビジュアルのよいデバッグのために、Gizmos を利用します。DrawGizmo の属性を付与すると、MonoBerhaviour の他に Gizmo の機能による(デバッグ画面への)描画を提供することもできます。
  • インスペクターの画面にあるデバッグ機能を使用します(インスペクターを使用すると、Unity 実行時に private なフィールドの値を確認することができます)


52. IDE にあるデバッガー機能を有効に使う方法を学ぶこと

Debugging Unity games in Visual Studio」のサンプルを確認してください。


53. 時間の経過で変わる値のグラフを描画する知覚的なデバッガーを使用すること

これは、物理・アニメーション・その他の動的なプロセス・特に散発的な不具合のデバッグにとても役立ちます。グラフの中に不具合を見て取ることができるし、他の変数がどのように変化しているのか確認することができます。

また、目視によるチェックでは、よく変化する値や、原因がわからないけど値が狂う値など、いくつかのタイプの奇妙な挙動をするバグも明らかになります。

私たちは、Monitor Component を利用していますが、他にもいろいろあります。


54. 改善されたコンソールロギングを使用すること

カテゴリーに応じて出力するログを色分けしておけば、このカテゴリーに応じて出力するログをフィルタリングできるエディター拡張機能を使用します。

私たちは、Editor Console Pro を使用していますが、これも他にもいろいろあります。


55. Unity のテストツールを使用して、特にアルゴリズムや数学を使うコードをテストすること

これの詳細は、「Unity Test Tools(リンク切れ)」のチュートリアルや「Unit testing at the speed of light with Unity Test Tools」を参照してください。


56. Unity のテストツールを使用して、「スクラッチパッド」のテストをすること

Unity のテストツールは、フォーマルなテストに適当なだけではありません。

シーンを実行していなくても、エディターで走らせることができる便利なスクラッチパッドのテストにも利用することができます。

正直なところ、私はこれが何を指摘しているのかよくわかりませんでした。「scratchpad」という名詞が強調されていますが、よくわかりません。unrealengine にも同じ名前のパネルがあるみたいです。意味的にはメモ用紙のつづり紙のような、ちょっとしたものくらいことかとも思いました。


57. スクリーンショットを撮影するためのショートカットを実装すること

多くのバグは目に見えるものなので、画像を撮影できるようにすれば報告しやすくなります。理想的なシステムは、連続したスクリーンショットが上書きされないように、Prefab の中でカウンターを維持しておくことです。

また、誤ってリポジトリーに(画像を)コミットしてしまわないように、画像はプロジェクトフォルダーの外に保存しておくこと。

旧 TIps 43 と同じ内容だと思います。


58. 重要な変数のスナップショットを記録するためのショートカットを実装すること

ゲーム中になにか予期しないことが起こったとき、チェックできる(に使える)情報を簡単に記録することができる。もちろんだけど、どの変数もゲームに依存しています。(なので)あなたは、あなたのゲームで発生するバグに引き回されます。たとえば、プレイヤーと敵の位置、AI のロジックなどが挙げられます。

これは、画像を記録するという意味ではないと思います。


59. テストを簡単にするためのデバッグオプションを実装すること

いくつか例を挙げると:

  • すべてのアイテムをアンロックする
  • 敵の(出現など)を無効化する
  • GUI を無効化する
  • プレイヤーを無敵にする
  • すべてのゲーム動作を無効化する

これらのデバッグオプションを誤ってコミットしないように注意してください。デバッグオプションの変更によって、他チームの開発者を混乱させる恐れがあります。

旧 Tips 45, 46 と同じ内容だと思います。RPG のデバッグルームのような話だと思います。


60. デバッグ用のショートカットキーの定数を定義して、ひとつのところにまとめておくこと

デバッグに使うキーは、ゲーム入力のあまったところのように、普通だと一か所にありません。

ショートカットはキーとキーの衝突を避けるために、中央的な(一元的な)ところに定数を定義します。別の方法としては、デバッグの関数であるかどうかは関係なく、すべてのキーを一か所で処理することになります。(欠点は、このクラスのためにオブジェクトへの余分な参照が必要になるかもしれません)


61. Procredural Mesh を生成するときに、小さな sphere を頂点して描画・スポーンすること

これは triangle や UV をいじってメッシュを表示するまえに、メッシュが正しいサイズであること、正しいと想定したところにあることを確認するための役に立ちます。

これもよくわかりませんでした。なので、変な訳になっている恐れがあります。



パフォーマンス (Performance)

62. パフォーマンス上の理由からの設計・デザインに関する一般的なアドバイスには注意すること

  • このようなアドバイスには、神話であったりテストに裏打ちされていません。
  • 時にはテストに裏打ちされているかもしれないけれど、そのテストには欠陥があると思っておきましょう。
  • 時にはアドバイスは正しいテストに裏打ちされているかもしれないけれど、それは非現実的なものであったり、異なるコンテキストかもしれないです。(たとえば、list より array を使うほうが高速であることを示すのは簡単です。しかし、実際のゲームの中では、ほとんどの場合この違いはごく僅かなことです。同じように、テストをするときターゲットのデバイスと異なるものでやっているなら、その結果はあなたにとって意味のあるものではないかもしれないです)
  • アドバイスは正しい(かった)が、時代遅れかもしれないです。
  • 時にはそのアドバイスが適用することもあります。しかし、トレードオフがあります。完成した低速な(パフォーマンスが十分ではない)ゲームは、完成しなかったパフォーマンスのよいゲームよりもよいものでしょう。また、いきすぎた最適化は、完成を遅らせる可能性が高いトリッキーなコードを含んでいるかもしれません。

パフォーマンスのアドバイスは、以下にアウトラインしたプロセスを使って、実際の問題の発生源をより速く見つけるための手助けになります。


63. 開発の初期段階からターゲットとなるデバイスで定期的にテストすること

デバイスによってパフォーマンスの特徴は大きく異なるので、驚かないように。問題は早くに知れば知るほど、より効率的に問題に対処することができます。


64. プロファイラーを効果的に使用することで、パフォーマンスの問題の原因を調べる方法を学ぶこと

  • もしプロファイリングに初めてさわるなら、「Introduction to the Profiler」を参照してください。
  • 独自にフレームを定義する方法(Profiler.BeginFrameProfiler.EndFrame を使う)を習得して、詳細な分析をする方法を説明します。
  • iOS 用のビルドイン プロファイラーなど、プラットフォーム固有のプロファイリングの使用方法を学びます。
  • ビルトインプレーヤーの「profile to file(リンク切れ)」とプロファイラーの「display the data(リンク切れ)」について学びます。


65. より正確なプロファイリングをするために、必要に応じてカスタムプロファイラを利用すること

時には Unity のプロファイラはなにが起こっているのかをわかりやすく表示することができないことがあります。プロファイルのフレームが不足していたり、詳細プロファイル (Deep Profile) によってゲームの速度が低下してテストが意味をなくすことがあります。

私たちはこのことに対して、独自のプロファイラーを使用しますが、Asset Store で他のプロファイラを見つけることができるはずです。


66. パフォーマンスを強化する影響を測定すること

パフォーマンスを上げるための変更をおこなったときは、本当に改善されたかどうかを確認するために測定します。もしも、測定できなかったり、測定に漏れがあった場合は元に戻します。


67. パフォーマンスのために読みにくいコードを書かないこと

もしそうしたコードを書くときは:

  • プログラムに問題があるなら、プロファイラでソースを特定し、変更のあとに改善点を測定して、その改善のメリットが保守性の損失と比較して十分だと判断した場合
  • 自分で自分がなにをしているのか分かっている場合



命名規則とフォルダーの構造

68. 文書化した命名規則とフォルダー構造に従うこと

一貫性のある命名規則とフォルダー構造は、データを見つけやすく、わかりやすくします。

独自の命名規則とフォルダー構造を作りたいと思っているだろうけど、一例は以下のようになります。


一般的な規則

  1. (命名する)モノが「なに」であるかを呼んでみる。単に鳥であったなら「bird」とするべきです。
  2. 発音できて、覚えることができる名前を選ぶ。もし、あなたがマヤ族のゲームを作るなら、あなたは「Quetzalcoatl(ケツァルコアトル) is Return」みたいな名前はやめよう。(発音できない、覚えれない)
  3. 一貫性があること。(なんらかの)名前を選択したとき、その名前を一貫してつかう。どこかで ButtonHolder としたら、別のところで ButtonContainer とするのはやめましょう。
  4. Pascal ケースの命名規則を適用すること。たとえば、ComplicatedVerySpecificObject のようにします。 (space), _, - は使用しないこと。(例外:最後に書いてある「オブジェクトに様子(アスペクト)の名前を付け加える」を参照しておく)
  5. バージョン番号や進行状況を表すような単語を使わないこと。(WIP, Final とか)
  6. 略語にしないこと。(DVamp@W は、DarkVampire@Walk とするべきです)
  7. デザインのドキュメントにある用語を使用すること。もしも、ドキュメントに死ぬアニメーションを「Die」としているなら、DarkVampire@Death ではなくて DarkVampire@Die とすること。
  8. 名前の語句は(左並びを)ふつうに保つこと。VampireDark ではなく DarkVampire がいい。ButtonPaused ではなく PauseButton がいい。たとえば、すべてのボタンが Button で始まらないほうが、インスペクターで一時停止を見つけやすくなります。逆の並びを好むのは視覚的にわかりやすいからだけど、名前はグループ化するためのものではないです。(フォルダーはそのためのものだけど)名前は、同じ種類のオブジェクトを区別して、それらを確実かつ迅速に見つけることができるようにするためのものです。
  9. 名前の中には、連続性を構成するものがあります。これらの名前には、PathNode0, PathNode1 のように数字を使用すること。必ずこれは1からではなく、0から開始すること。
  10. 連続性のないものには数字を使用しないこと。たとえば、Bird0, Bird1, Bird2 は、 Flamingo, Eagle, Swallow のようにします。
  11. 一時的なオブジェクトの名前の前に二重のアンダースコア __ をつけること。(例:__Player_Backup)


オブジェクトに様子(アスペクト)の名前を付け加える (Naming Different Aspects of the Same Thing)

オブジェクトの主要な「なに」を表す(コアな名前)と様子(アスペクト)を記述もるものの間には、アンダースコア _ を使うこと。たとえば、このような感じです:

  • GUI ボタンの状態:EnterButton_Active, EnterButton_InActive
  • テクスチャー:DarkVampire_Diffuse, DarkVampire_Normalmap
  • スカイボックス: JungleSky_Top, JungleSky_North
  • LOD グループ: DarkVampire_LOD0, DarkVampire_LOD1

この規則はアイテムの種類が異なるものを区別するために使用しないこと。たとえば、Rock_Small と Rock_Large は SmallRock と LargeRock にすべきです。


構造 (Structure)

あなたのシーン・プロジェクトフォルダー・スクリプトフォルダーの構成は、似たような構成にするべきです。ここでは、あなたが(こうした構成を)はじめるためのいくつかの簡単な例を挙げます。


フォルダーの構成

MyGame
   Helper
      Design 
      Scratchpad
   Materials
   Meshes
      Actors
         DarkVampire
         LightVampire
         ...
      Structures
         Buildings
         ...
      Props
         Plants
         ...
      ...
   Resources
      Actors
      Items
      ...
   Prefabs
      Actors
      Items
      ...
   Scenes
      Menus
      Levels
   Scripts
   Tests
   Textures
      UI
      Effects
      ...
   UI 
MyLibray
   ...
Plugins
SomeOtherAsset1
SomeOtherAsset2
...


シーンの構成

Main
Debug
Managers 
Cameras
Lights
UI
   Canvas
      HUD
      PauseMenu
      ...
World
   Ground
   Props
   Structures
   ...
Gameplay
   Actors
   Items
   ...
Dynamic Objects
Scripts Folder Structure
Debug
Gameplay
   Actors
   Items
   ...
Framework
Graphics
UI
...

旧 Tips 50 と同じ内容だと思います。

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

【WPF】Button内テキストをButtonサイズに合わせて変更する

はじめに

WPFでリサイズ可能なウィンドウ内にボタンを置いた時に
ボタンサイズに応じてテキストサイズも変更する方法。
テキストだけでなく、ボタン内ボタンとかでも大丈夫。(めったに無いと思うけど・・)

ViewBoxの存在を知らなかったので
もし同じような人がいたら、ということで。

方法

MainWindow.xaml
<Window ~~~ 省略 ~~~ >
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition/>
            <RowDefinition/>
        </Grid.RowDefinitions>
        <Button Grid.Row="0">
            <Viewbox>
                <TextBlock>可変サイズ</TextBlock>
            </Viewbox>
        </Button>
        <Button Grid.Row="1">
            サイズ固定
        </Button>
    </Grid>
</Window>

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