のにっき

UnityにおけるSingleton管理基板の提案

概要

Unityでシーン遷移などに依存せず、アプリ全体で共有したいオブジェクトがある場合、
Singletonパターンを利用する方は多いのではないかと思います。

今回は、よくあるSingletonパターンの実装例に対する個人的な問題点と、
それらを改善するために実装した Singleton管理基盤 の提案を行います。

よくあるパターン

実装例

以下のように、Singleton用の抽象クラスを継承させることで
Singletonを生成するパターンはよく見かける実装だと思います。

using UnityEngine;
public class SingletonMonoBehaviour <T> : MonoBehaviour where T :MonoBehaviour
{
    public static T instance;
    protected virtual void Awake()
    {
        if (instance == null)
            instance = (T)FindObjectOfType(typeof(T));
        else
            Destroy(gameObject);
    }
}

問題点(※個人の感想)

実際に上記の形で実装してみると、以下の問題が発生しました

  • Singletonクラスがいくつ存在しているのか把握しづらい
  • ライフサイクルがクラスごとにバラバラになる
    → Singletonを「初期化、リセット、解放」すべきタイミングで全て出来てるのか分からない

そのためこれらの問題は、致命的なバグにつながりやすい上に
ライフサイクルがシーン遷移などの明確なイベントに依存しない分
原因調査や修正が非常に面倒になりがちです。

Singleton管理基板の提案!

上記のデメリットを改善するため、Singletonの管理を一元化する基盤を実装してみました。

作成物

GitHubリポジトリの内容をそのままプロジェクトに追加することで、すぐに利用できる形になっています。 github.com

使い方

使い方の詳細については、GitHubリンク先のREADMEをご参照ください。
※ サンプルもリポジトリ内に含めているため、イメージしやすいと思います。

この基盤の利点

  • SingletonManagerクラス以外はSingletonにしない設計にすることで、意図せずSingletonが増えることを防止しました
  • SingletonClassBase<T>SingletonMonoBehaviourBase<T>を継承するだけでSingletonのように扱えるクラスを作成できます
  • 利用側は今まで通り {クラス名}.Instanceインスタンスを取得できます
  • Singletonクラスの登録は、細かいことを意識せず初期化するだけで自動的に行われます
  • ライフサイクルを一元管理しました。SingletonManagerクラスのメソッドを呼ぶだけで制御できます

このように、
よくあるSingleton管理のデメリットを解消することを目的とした管理基盤を実装してみました。
よろしければぜひ使ってみてください。 感想やフィードバックなどもいただけますと幸いです。

VisualStudio豆機能紹介

概要

VisualStudioの、普段自分が使ってる機能をまとめて紹介します。
使いどころも含めて書きましたので、ご一読いただくと作業効率が上がるかも!?

機能まとめ

ページ分割

別クラスの関数、変数名をコピペしたいときに使います

ページ内分割

同じクラスの変数名を参照したい時に使います

ページ複製

同じクラスの別関数と変数名を参照したい時に使います (あんまり使わないかも・・・)

クラス内に定義された関数、変数にジャンプ

クラス内の関数が多いと結構便利

ブックマーク

何回も確認する場所をブックマークにしとけば迷わないから便利!
※ブックマークについてコメントも書けるので分かりやすい

再起動を素早く行う

最近VisualStudioの調子が悪いのか、定義に移動が動かなくなったりカーソル合わせてもSummaryが出なかったりします。
Unity使ってると、新規クラスファイルを作成した時にVisualStudio再起動しないとUsing参照が上手く動かなかったりします。
そんなときは、VisualStudioを再起動するんじゃなくてソリューションを再起動するとめっちゃ速いのでおすすめです!

ブレークをまとめて削除

ブレーク貼りまくってると処理が重くなったり、変なところでとまって鬱陶しい!
という時に、一括で消せるので便利です

namespaceのリネーム

当該クラスの参照先含めてNamespaceをリネームしてくれます

クラス・変数・関数名のリネームのリネーム

「Ctrl +R」を2回入力で参照先含めてリネームしてくれます
Unityであれば、クラス名変更時に自動でファイル名もリネームしてくれるので
リネームによるヒューマンエラーが大幅に減ると思います!

ソースファイルを素早く開く

UnityのPJが大きくなると、ダブルクリックしてコードを開くと異常に時間がかかるのですが
ソースファイルをUnityからドラッグ&ドロップでVisualstudioに直接貼り付けると、一瞬でコードが開けます!

コード領域の一括展開

「Ctrl + M」→「Ctrl + L」で一括で展開/折り畳みができます。
定数まとめたクラスなど、1ファイル内にクラスがまとめられたコードを読むときに使います

【Unity】TextMeshProで、単位付きの数値を表示する拡張関数(RitchTextTag)

何か数値を出すときに、単位と一緒に表示することがよくあると思います。
この時、数値と単位でフォントサイズを変えたい!
けど数値のTextと別Objectで持つと桁の変動を考慮した配置とか
いろいろ面倒臭いことになる!!という方にお勧め。
TextMeshPro のリッチテキストタグを用いて簡単に表示を作る関数をご紹介します

リッチテキストタグとは?

タグ文字を使用して、表示するテキストの「文字サイズ、色」など
いろいろな表現を行うことができます。
利点としては、1つのTextオブジェクト内でも文字ごとに表現を変えれることが利点です。
今回は、「文字サイズの拡縮」タグを使用します
■実装サンプル(単位だけ全体の60%に縮小させる)

123456<size=60%></size>


どんなことができるのかは、以下のサイトが見やすかったので紹介させていただきます
■リッチテキストタグ一覧
www.midnightunity.net

事前準備

TextMeshProでRitchTextを使用する際の設定を、以下の画像を用いて説明します
①:
作成したTextMeshProです。リッチテキストタグを使うことで、単位だけ縮小されてます
②:
実際に記入しているテキストです。上記の
|■実装サンプル(単位だけ全体の60%に縮小させる)
を記入しています。
③:
リッチテキストタグを有効にするために赤枠の設定を有効にする必要があります。
※デフォで有効なので基本問題ないですが、リッチテキストタグが使えなかったときはここを確認してみてください。

TextMeshProサンプル

単位付きの数値を表示する拡張関数

拡張関数
using TMPro;

public static class TextMeshProUguiExtensions
{
    /// <summary>
    /// 単位付きテキスト設定
    /// ※単位を拡縮する
    /// </summary>
    /// <param name="setText"></param>
    /// <param name="setNum">設定する数値</param>
    /// <param name="setUnit">単位</param>
    /// <param name="setUnitPer">単位の拡縮% ※デフォで60%</param>
    public static void SetTextWithUnit(this TextMeshProUGUI setTmp, string setNum, string setUnit, float setUnitPer = 60)
    {
        if (setTmp.richText)
        {
            setTmp.text = $"{setNum}<size={setUnitPer}%>{setUnit}</size>";
        }
        else
        {
            UnityEngine.Debug.Log("設定するTextMeshProUGUIの「RichText」が有効になってないので単位が拡縮されません。");
            setTmp.text = $"{setNum}{setUnit}";
        }
    }
}
呼び出しサンプル
//設定するTextMeshPro
[SerializeField] TextMeshProUGUI textSample;

//デフォで、単位を60%に縮小させる
textSample.SetTextWithUnit("123", "連");
//拡縮率を直接指定も可能
textSample.SetTextWithUnit("123", "連", 25);

いちいちタグをつけるのは面倒なので関数でまとめました。
今回は単位に絞った変換なので、拡張性の低い実装ですが使いやすくはなってるかなと思います。

拡張性を上げてみた

拡張関数

タグ文字でいろいろしたい!というときに使いやすくなるように改修してみました

public static class StringExtensions2
{
    public class TagIf
    {
        public float? Size = null;
        public int? Alpha = null;
        public Color? Color = null;
        public bool IsBold = false;
        public bool IsItalic = false;
    }

    /// <summary>
    /// テキストにRitchTextTag設定
    /// </summary>
    /// <param name="setText"></param>
    /// <param name="tagIf">設定するタグ情報</param>
    public static string GetTextTag(this string setText, TagIf tagIf)
    {
        string tagFront = "";
        string tagBack = "";
        if (tagIf?.Size != null)
        {
            tagFront += $"<size={tagIf.Size}%>";
            tagBack += $"</size>";
        }
        if (tagIf?.Color != null)
        {
            Color setColorInt = (Color)tagIf.Color;
            string setColor = String.Format("#{0:X2}{1:X2}{2:X2}",
                (int)setColorInt.r, (int)setColorInt.g, (int)setColorInt.b);
            tagFront += $"<color={setColor}>";
            tagBack += $"</color>";
        }
        if (tagIf?.Alpha != null)
        {
            int setAlpha = (int)tagIf.Alpha;
            tagFront += $"<alpha={String.Format("#{0:X2}", setAlpha)}>";
            tagBack += $"<alpha=#FF>";
        }
        if (tagIf?.IsBold == true)
        {
            tagFront += $"<b>";
            tagBack += $"</b>";
        }
        if (tagIf?.IsItalic == true)
        {
            tagFront += $"<i>";
            tagBack += $"</i>";
        }
        return $"{tagFront}{setText}{tagBack}";
    }
}
呼び出しサンプル
public class TestTmp : MonoBehaviour
{
    [SerializeField] TextMeshProUGUI setTmp;
    void Start()
    {
        string setTextPart1 = "てすと123";
        string setTextPart2 = "テスト123";
        string setTextPart3 = "Test123";
        setTextPart1 = setTextPart1.GetTextTag(new StringExtensions2.TagIf
        {
            Size = 160,
            Alpha = 100,
            Color = new Color(255, 0, 0),
            IsBold = true,
        });
        setTextPart2 = setTextPart2.GetTextTag(new StringExtensions2.TagIf
        {
            Size = 100,
            Alpha = 255,
            Color = new Color(0, 255, 0),
            IsItalic = true,
        });
        setTextPart3 = setTextPart3.GetTextTag(new StringExtensions2.TagIf
        {
            Size = 40,
            Alpha = 255,
            Color = new Color(0, 0, 255),
        });
        setTmp.text = $"Part1:{setTextPart1}\nPart2:{setTextPart2}\nPart3:{setTextPart3}";
    }
}

■実際の表示

実際の表示

TagIfを追加することで、好きにタグを追加できます。
変数をNullAbleにすることで、好きなタグだけ設定すればOKという形にしています。
TextMeshProではなくString に対して拡張関数を作ることで、文字列ごとにタグをつけれるようにしました。

【C#】VisualStudioでusingの整理を行う機能紹介

今までVisualStudioを何年も使用していて、

結構最近知った機能の紹介です。

 

### 概要

コード上で右クリックしたときのメニューで、

不要なUsingを削除してもらえます。

Using整理機能の挙動

不要なUsingをほっとくと、参照先が消えた時に

意図しないコンパイルエラーになったりよくないので

こまめに消すことを習慣化できるこの機能は便利だなと思いました

【Unity】LayoutGroupの自動レイアウトがうまくいかないときの対処法

ScrollViewなどでLayoutGroupを使った際に、
レイアウト調整がうまくいかなかった時の対処法です

うまくいかない現象

以下の動画のように各アイテムのスペースの調整が本来自動で行ってほしいところ、
うまくいかない場合があります。
スクリプトからアイテムをInstanceした際によく起こる現象かなと思います

動的にInstanceした時の挙動

対処法

 //LayoutGroupの中にInstantiateでアイテム生成
 Instantiate(GameObjectPrefab, LayoutGroupContent.transform);
 //LayoutGroupのRectTransformに対してレイアウトの再構築を実行する
 LayoutRebuilder.ForceRebuildLayoutImmediate(LayoutGroupContent.GetComponent<RectTransform>());

上記のように、アイテム生成後に
"LayoutRebuilder.ForceRebuildLayoutImmediate()"の関数で
レイアウトの再構築を実行することで、以下のようにうまく描画することができます。

レイアウト再構築後の挙動

LayoutRebuilder.ForceRebuildLayoutImmediate()関数は
自動レイアウト調整後にGameObjectの高さを取得したいけど正常な値が取れないときなど、
自動レイアウト周りの不具合の解決に最適な場合が多いです。
覚えておいて損はないかなと思います。

【Unity】InputFieldの行数をonValueChangedで取得する

InputFieldで表示を変えたりするときに、
onValueChangedのイベントはよく使われると思います。
今回は、onValueChangedでテキストの行数を取得するときに
ちょっと詰まったので注意点と対策を共有します。

行数を取得する方法

int lineCount = TMP_InputField(変数名).textComponent.textInfo.lineCount;

上記の実装でテキストの行数が取得できます。
この「textInfo」は行数以外にもいろいろ情報が入ってるので、覚えておくと便利です
Class TMP_TextInfo

実際の挙動

上の手順がわかれば、あとはonValueChangedで処理を描けばいいだけと思ったのですが、
実際の挙動は以下のようになりました。

※onValueChangedでlineCountを取得してコンソールに出力した

実際は改行が起きた次の文字でlineCountが1→2に切り替わる挙動となってしまいました。
見た感じ、「lineCount」の情報が1文字遅れてるのでしょうか?
※削除したときはちゃんと表示に合わせて2→1になってるので
単純な遅れってわけじゃなさそう・・・

対応策

onValueChangedの中で、

TMP_InputField(変数名).textComponent.text = value;

を行えばちゃんと表示に合わせてlineCountの数値も変わりました。
この実装を行うことで、onValueChangedが二重で呼ばれたりしないのか不安だったんですが、
特にそんなことはなく正常に動きました。

二度手間感はありますが一旦はこの対応でしのげます。

【C#】VisualStudioでoverrideを楽に実装する機能紹介

概要

VisualStudioでoverrideを実装するときに、

関数名やプロパティを記入するの面倒くさかったりしないでしょうか?

基底クラスを開きなおして関数名を調べたり、、、

VisualStudioの機能でそんな問題が解決します

※VisualStudio以外でも有効かもですが未調査です

手順

  1. 派生クラス内で override と記入する。
  2. 入力補完で基底クラスのvirtualなプロパティ、メソッドが候補にでる
  3. 選択するだけでベースの記述を行ってくれる

たったこれだけです。

実装サンプル

入力保管動作サンプル

サンプルだと基底クラスと派生クラスを同じソースファイルに書いてるので

パット見恩恵がわかりづらいですが、

Unityなどでソース書くときは基本1クラス1ファイルになるともうので

この入力補完の機能知ってると知ってないとじゃ大違いです!

ぜひお試しください