【Unity】Textの文字を動く虹色にする

UnityのTextオブジェクトは、タグによってある程度自由な表現ができます。
今回はタグのテストみたいなもので文字を動く虹色に出来るスクリプトを作りました。

実際の挙動

f:id:apuridasuo:20200131134323g:plain
図:スクリプトの実挙動
こんな感じです。
Textに記入した文字に自動でタグ付けをして一文字づつ色を変えます。
追加機能で、Inspectorの設定で
方向転換・変色スピードの設定変更もできる様にしています。

ソースコード

スクリプトファイルをGitHubにアップしてますのでご自由にどうぞ
github.com

落とすのがメンドイ方は以下にソースを記入しますのでコピペして下さい

public class TextRainbow : MonoBehaviour
{
    // Inspectorで設定する変数
    public bool IsTokeiMawari = true;
    public int ChangeSpeed = 5;
    // 変換管理変数
    public static int _ChangeCnt = 0;
    public static int _NijiStartId = 0;
    // 変換管理定数
    public const string Df_Tag_Hedder = "<color=#Value>";
    public const string Df_Tag_Footer = "</color>";
    // 虹色の中身 ※ 2桁づつで区切って[00 00 00]=[ R, G, B ]の値になっている
    //       ※ この配列の要素を追加・編集したらオリジナルの動く色が作れる
    public static string[] Df_ColorTag = new string[] 
    {
        "ff0000",
        "ffff00",
        "00ff00",
        "00ffff",
        "0000ff",
        "ff00ff",
    };

    /// <summary>
    /// 毎割り込みイベント
    /// </summary>
    void Update()
    {
        // 設定したスピードに合わせて色変更処理を呼ぶ
        _ChangeCnt--;
        if (_ChangeCnt <= 0)
        {
            _ChangeCnt = ChangeSpeed;
            SetTextColorChange(IsTokeiMawari, this.GetComponent<Text>());
        }
    }

    /// <summary>
    /// テキスト色を変える処理(毎割り込み)
    /// </summary>
    /// <param name="TxtSet">変更するテキストオブジェクト</param>
    public static void SetTextColorChange(bool IsVecLR,Text TxtSet)
    {
        // テキストの文字を取得※Tag文字を取り除く
        string textNoTag = TxtSet.text;
        textNoTag = textNoTag.Replace(Df_Tag_Footer, "");
        for (int i_ColorId = 0; i_ColorId < Df_ColorTag.Length; i_ColorId++)
        {
            textNoTag = textNoTag.Replace(Df_Tag_Hedder.Replace("Value", Df_ColorTag[i_ColorId]), "");
        }
        // 一文字ずつ色を設定
        int setColorId = _NijiStartId;
        StringBuilder textSet = new StringBuilder();
        for (int i_Word = 0; i_Word < textNoTag.Length; i_Word++)
        {
            textSet.Append(Df_Tag_Hedder.Replace("Value", Df_ColorTag[setColorId]));
            textSet.Append(textNoTag.Substring(i_Word, 1));
            textSet.Append(Df_Tag_Footer);
            if(IsVecLR)
            {
                setColorId--;
                if (setColorId <0)
                {
                    setColorId = Df_ColorTag.Length-1;
                }
            }
            else
            {
                setColorId++;
                if (setColorId >= Df_ColorTag.Length)
                {
                    setColorId = 0;
                }
            }
        }
        // 次回の開始色を更新
        _NijiStartId++;
        if (_NijiStartId >= Df_ColorTag.Length)
        {
            _NijiStartId = 0;
        }
        // テキスト文字を変更
        TxtSet.text = textSet.ToString();
    }
}

ソースの中で、「虹色の中身」の部分に注目してください。
この配列が設定する文字の色です。
現在は虹に見える様に6色に設定しているのですが、
自由に要素を増やしたり、色データの値を変更すれば
オリジナルの配色にできる様になってます。

使い方

1)虹にしたいTextオブジェクトにスクリプトファイルを追加
2)オブジェクトのTextコンポーネント
 「Rich Text」項目にチェックが入ってなければチェックを入れる
3)ビルドしたら虹になるので、スピード・方向を設定すればOK!

Textのタグのついて

Textに記入している文字を「<○○>表示文字<○○>」のように囲むと
囲んだ文字(表示文字)が変化して表示されるという機能です。
【例】文字「AAA」を赤色でサイズを50にして表示する

<size=50><color=#ff0000>AAA</color></size>

※上記をTextにコピペ!
こんな感じで、AAAをタグでくくることで表示が変更されます
タグの詳細は以下のリファレンスを参照
docs.unity3d.com

以上です。
正直、文字を綺麗に動かすなら「TextMeshPro」一択です!
しかし、単調な動きならスクリプトで汎用化させておくと
疲れた時に楽できるのでとても重宝します。。。


P.S.初めてGIF貼り付けしてみたけど面倒くさすぎて泣けた・・・

【python】tryの中で呼び出した関数がエラーになった場合の挙動

pythonの組み込みで必ず使用するtryを使った例外処理の話です。

tryの中で呼び出した関数でエラー

tryの中で外部関数を呼び出した場合、
・外部関数でエラーになった時は呼び出し元のtryが動いてくれるのか?
・ちゃんとexcept以下の処理が動くのか?
・外部関数で外部関数を呼び出した時も同じ挙動になるのか?
・外部関数の中でtryが組み込まれていた場合はどうなるのか?

これらの挙動を簡単なソースで確認してみました。

【外部にtryなし】外部でエラーが起きた場合の挙動

A )外部の外部の外部でエラーが起きた場合

import sys
#******************************************************************************
# 呼び出す外部関数_1st
def SetDisp_01():
    print("01")
    SetDisp_02()
#******************************************************************************
# 呼び出す外部関数_2nd
def SetDisp_02():
    print('02')
    SetDisp_03()
#******************************************************************************
# 呼び出す外部関数_3rd
def SetDisp_03():
    print('03')
    print(1+"")# こいつがエラーになる行( int+str でエラー)   
#******************************************************************************
# メインルーチン
try:
    SetDisp_01()
    print("正常")
except:
    print("エラー")
    print(sys.exc_info())
    pass

このソースでは、メインルーチンのtryの中で
SetDisp_01を呼ぶ→SetDisp_02を呼ぶ→SetDisp_03を呼ぶ→エラー
という挙動になるように組み込みました。
実行結果

01
02
03
エラー
(<class 'TypeError'>, TypeError("unsupported operand type(s) for +: 'int' and 'str'",), <traceback object at 0x000001DEFBB12308>)

結果を見る限り、
外部でエラーになってもちゃんとtryが機能していて処理が中止されることはない。
ちゃんとエラー発生後にexcept以下の処理が実行されている
という事がわかりました。
B )外部でエラーが起きた場合、その後の処理は中断されるのか?

import sys
#******************************************************************************
# 呼び出す外部関数_1st
def SetDisp_01():
    print("01")
    print(1+"")# こいつがエラーになる行( int+str でエラー)
    SetDisp_02()
#******************************************************************************
# 呼び出す外部関数_2nd
def SetDisp_02():
    print('02')
#******************************************************************************
# メインルーチン
try:
    SetDisp_01()
    print("正常")
except:
    print("エラー")
    print(sys.exc_info())
    pass

このソースでは、メインルーチンのtryの中で
SetDisp_01を呼ぶ→エラー(の後、SetDisp_02を呼ぶ)
という挙動になるように組み込みました。
実行結果

01
エラー
(<class 'TypeError'>, TypeError("unsupported operand type(s) for +: 'int' and 'str'",), <traceback object at 0x000001DEFBB126C8>)

結果を見る限り、
外部でエラーになってもエラー発生後にすぐ
except以下の処理が実行されているから、エラー後の処理は行われない。
という事がわかりました。

まとめ
・外部でエラーになってもちゃんとtryが機能していて処理が中止されることはない。
・ちゃんとエラー発生後にexcept以下の処理が実行されている
・外部でエラーになってもエラー発生後にすぐ
 except以下の処理が実行されているから、エラー後の処理は行われない。

【外部にtryあり】外部でエラーが起きた場合の挙動

A )外部のtryの中でエラーが起きた場合

import sys
#******************************************************************************
# 呼び出す外部関数_1st
def SetDisp_01():
    try:
        print("01")
        print(1+"")# こいつがエラーになる行( int+str でエラー)
    except:
        print("子01のエラー")
        print(sys.exc_info())
        pass
    SetDisp_02()
#******************************************************************************
# 呼び出す外部関数_2nd
def SetDisp_02():
    try:
        print("02")
        print(1+"")# こいつがエラーになる行( int+str でエラー)
    except:
        print("子02のエラー")
        print(sys.exc_info())
        pass    
#******************************************************************************
# メインルーチン
try:
    SetDisp_01()
    print("正常")
except:
    print("親のエラー")
    print(sys.exc_info())
    pass

このソースでは、メインルーチンのtryの中で
SetDisp_01(tryの中でエラー発生)→SetDisp_02(tryの中でエラー発生)
という挙動になるように組み込みました。
実行結果

0101のエラー
(<class 'TypeError'>, TypeError("unsupported operand type(s) for +: 'int' and 'str'",), <traceback object at 0x000001DEFBB12788>)
0202のエラー
(<class 'TypeError'>, TypeError("unsupported operand type(s) for +: 'int' and 'str'",), <traceback object at 0x000001DEFBB12B08>)
正常

B )外部のtryの外でエラーが起きた場合

import sys
#******************************************************************************
# 呼び出す外部関数_1st
def SetDisp_01():
    try:
        print("01")
        print(1+"")# こいつがエラーになる行( int+str でエラー)
    except:
        print("子のエラー")
        print(sys.exc_info())
        pass
    SetDisp_02()
#******************************************************************************
# 呼び出す外部関数_2nd
def SetDisp_02():
    print('02')
    print(1+"")# こいつがエラーになる行( int+str でエラー)
#******************************************************************************
# メインルーチン
try:
    SetDisp_01()
    print("正常")
except:
    print("親のエラー")
    print(sys.exc_info())
    pass

このソースでは、メインルーチンのtryの中で
SetDisp_01(tryの中でエラー発生)→SetDisp_02(tryの外でエラー発生)
という挙動になるように組み込みました。
実行結果

01
子のエラー
(<class 'TypeError'>, TypeError("unsupported operand type(s) for +: 'int' and 'str'",), <traceback object at 0x000001DEFBB12B48>)
02
親のエラー
(<class 'TypeError'>, TypeError("unsupported operand type(s) for +: 'int' and 'str'",), <traceback object at 0x000001DEFBB12B88>)

まとめ
A)とB)の結果を比較して、
・親tryの中で呼んだ外部関数のtry(以下、子try)の中でエラーが起きたら、
そのエラーは子tryの中で完結するので親tryから見たら正常扱いとなる。
・子tryはその中で完結しているので、子try外でのエラーは親tryのエラーとなる。
そのため、エラー発生時に処理は中断されて親tryのexcept以下の処理が実行される。

以上です。
組み込んでて色々気になることを検証しているのですが、
説明がめんどくさくて記事に出来ない事ばっかりなので難しいですね。
今回もこんなに記事が長くなるとは思いませんでした。。。まとめ力が成長しない。。。

【Unity】バージョンアップ時にプロジェクトが開けなくなった

昨日、unityを起動したら急に発生した現象です。

アホみたいなことを繰り返してようやく復旧できたので

対処法をまとめておきます。

(ネットに全く参考記事が無くて孤独死しそうでした・・・)

 

 

 バグった現象

Unityプロジェクト起動後に、
今まで問題なかったプロジェクトのコンポーネントがほぼエラー状態になっていた。
※自作スクリプトではなくUnityに元からあるものもエラーになっていた

f:id:apuridasuo:20191129153335p:plain

図:壊れたプロジェクトのエディター表示

f:id:apuridasuo:20191129153416p:plain

図:VSでスプリクトを開いた時のエラー内容(一部抜粋)

色々確認していったのですが、

何かしらが原因でUnityEngineのdll参照が壊れているのかなー?

ぐらいしか分かりませんでした。

原因(予想)

原因は分からないのですが、心当たりがいくつかあるのでリスト化

・UnityHubの方のバージョンアップ通知があったので更新して

 更新後にプロジェクトを開いた。

 ※これが原因だと思ってます。

 でも、エディターでもないHubの更新でプロジェクトが壊れるとは思えない・・・

・当該PJは、当初2017バージョンのUnityエディタで制作していて

 1ヶ月前ぐらいに2019バージョン(2019.2.11)に切り替えていた

 ※切り替え後も上手く動いていて、2019バージョンで

 アプリリリースも行ってますので関係ないとは思う

上記の2つの要因を掛け合わせて予想した原因は、

UnityHubの更新を行ったことで

2017verエディタで作成したプロジェクトの2019verへの互換性がなくなった!

という事だと思ってます。

対応したこと(失敗編)

結局直りませんでしたが、行ったことをまとめておきます

・PCを再起動

 →変わらず・

・UnityHub、Unityエディターを再インストール

 →変わらず・・

・最新のエディター(2019.2.14)をインストールしてPJ(プロジェクト)を開く

 →変わらず・・・(*_*;

・壊れたPJのAssetsフォルダ全てをエクスポートして新規作成PJにインポート

 →新規作成PJは問題なく動くが、インポートしたとたん

 「'csc'は、内部コマンドまたは外部コマンド、
 操作可能なプログラムまたはバッチ ファイルとして認識されていません」

 というエラーが出てビルドできなかった・・・(; ・`д・´)

・PJのバックアップ(2017のエディターに対応)を引っ張り出してきて

 2019バージョンのエディタで起動

 →起動したPJが壊れていた・・・(*´з`)

 この対応で、

 「もしかしてHubの更新が原因で

  2019verのエディタと2017verエディタの互換性がなくなったんじゃ・・・?」

 と思いつきました。

対応したこと(解決編)

・PJのバックアップ(2017のエディターに対応)を2018.4.13のエディタで起動

2019バージョンのエディタで起動したら壊れたPJですが、

2018バージョンなら壊れずに正常に起動できました。

※2017バージョンでも起動できましたが、

 APIレベル28以上の基準が満たせないので2018バージョンで起動

 

あとは、エディター周りの設定を一度削除してしまっていたので

AndroidSDK、NDKの環境を整えて上手くビルドできる様になりました。

 

 分かったこと(大事!)

・UnityHubの更新でもバージョンの互換性は変化する

・バージョンごとに互換性があるため、安易に対応エディタのver更新をしない

・バックアップは偉大・・・( ..)φメモメモ

・2017verのプロジェクトと2018verのプロジェクトは互換性がある

 

いじょうです。

同じ境遇の方がいましたらご参考にしてみてください!

usingクラス取得ツール作った

namespaseを利用してクラス名を書かずに

別クラスのメソッド等を呼び出す方法がとても便利なのですが、

「using static ○○○」をスクリプトファイルごとに書くのが面倒くさい!

ということで、簡易的にツール化したのでご紹介します。

 

  

クラス名を書かずに別クラスのメソッド等を呼び出す方法

下記URLで説明してますので、ぜひご一読ください・・・

【C#6】別スクリプトの関数・定数を簡潔に呼び出す方法 - のにっき

 

ツール置き場 

仕様書も置いているので一緒に落として確認してみてください。

github.com

 

使い方 ※仕様書抜粋

1)まずは、Assetsフォルダのパスを記入(ファイルのD&DでOK!)

f:id:apuridasuo:20191108194043p:plain

図:初期設定

2)「検索開始」ボタン押下で「using static ○○○」リストを出力!

  ※そのまま貼り付けれる形式でリスト化されてます

f:id:apuridasuo:20191108194108p:plain

図:検索開始時の挙動

3)namespaseごとに、表示の切り替えができる様になってます

f:id:apuridasuo:20191108194128p:plain

図:チェックボックスの仕様

4)パスの記入内容は「保存する」にチェックしておくと、次回起動で

  パスが記入された状態で開かれるのでとても便利です。

f:id:apuridasuo:20191108194159p:plain

図:パスの保存チェック仕様

 

 以上です。

ぜひ使ってみてご意見・ご感想などいただけたら嬉しいです!

 

○進数を○進数に変換する関数

私の会社は16進数のデータをいじくりまわすことがある。
いちいち「Convert」を使って変換するのもめんどくさいので
各進数を別の進数に変換する関数を作成しました。

ソースコード

        /// <summary>
        /// 進数変換モード
        /// </summary>
        public enum Md_Conv
        { c10to2 = 0, c10to16, c16to2, c16to10, c2to10, c2to16 };

        /// <summary>
        /// ○進数を○進数に変換
        /// </summary>
        /// <param name="Mode">進数変換モード</param>
        /// <param name="SetWord">変換文字</param>
        /// <returns>変換後の文字※桁合わせしてる</returns>
        public static string Get_ConvertNum(Md_Conv Mode, string SetWord)
        {
            string setNumOff = SetWord.Trim();
            string cnv = "";
            switch (Mode)
            {
                case Md_Conv.c10to2:
                    cnv = Convert.ToString(int.Parse(setNumOff), 2);
                    cnv = cnv.PadLeft(8, '0');
                    break;
                case Md_Conv.c10to16:
                    cnv = Convert.ToString(int.Parse(setNumOff), 16);
                    cnv = "0x" + cnv.PadLeft(2, '0').ToUpper();
                    break;
                case Md_Conv.c16to10:
                    cnv = Convert.ToInt32(setNumOff, 16).ToString();
                    break;
                case Md_Conv.c2to10:
                    cnv = Convert.ToInt32(setNumOff, 2).ToString();
                    break;
                case Md_Conv.c16to2:
                    cnv = Convert.ToInt32(setNumOff, 16).ToString();
                    cnv = Convert.ToString(int.Parse(cnv), 2);
                    cnv = cnv.PadLeft(8, '0');
                    break;
                case Md_Conv.c2to16:
                    cnv = Convert.ToInt32(setNumOff, 2).ToString();
                    cnv = Convert.ToString(int.Parse(cnv), 16);
                    cnv = "0x" + cnv.PadLeft(2, '0').ToUpper();
                    break;
            }
            return cnv;
        }

使用例

▼ テストソース

            // テスト用変数作成
            int Num_10sin = 4095;
            string Num_2sin = "0111";
            string Num_16sin = "FFFF";
            string Num_16sin2 = "0x00FF";
            // 各進数表記に変換
            string Result = "";
            Result = "◆ 10進数「Num_10sin ("+ Num_10sin + ")」を変換\r\n";
            Result += "16進数:" + Get_ConvertNum(Md_Conv.c10to16, Num_10sin.ToString()) + "\r\n";
            Result += " 2進数:" + Get_ConvertNum(Md_Conv.c10to2, Num_10sin.ToString()) + "\r\n";
            Result += "◆  2進数「Num_2sin (" + Num_2sin + ")」を変換\r\n";
            Result += "16進数:" + Get_ConvertNum(Md_Conv.c2to16, Num_2sin) + "\r\n";
            Result += "10進数:" + Get_ConvertNum(Md_Conv.c2to10, Num_2sin) + "\r\n";
            Result += "◆ 16進数「Num_16sin (" + Num_16sin + ")」を変換\r\n";
            Result += "10進数:" + Get_ConvertNum(Md_Conv.c16to10, Num_16sin) + "\r\n";
            Result += " 2進数:" + Get_ConvertNum(Md_Conv.c16to2, Num_16sin) + "\r\n";
            Result += "◆ 16進数「Num_16sin2 (" + Num_16sin2 + ")」を変換\r\n";
            Result += "10進数:" + Get_ConvertNum(Md_Conv.c16to10, Num_16sin2) + "\r\n";
            Result += " 2進数:" + Get_ConvertNum(Md_Conv.c16to2, Num_16sin2) + "\r\n";
            MessageBox.Show(Result,    "出力結果");

▼ 出力結果

f:id:apuridasuo:20191108153238j:plain
図:出力結果

関数概要

上記の感じで「16・10・2」進数の相互変換を行うことができます。
注意点としては、
10進数だろうが何だろうが文字列型でやり取りしている点です。
なので、数値型を引数として渡す場合はテストデータのように「ToString()」で文字列に直してください。
また、16進数の戻り値の表記は「0x」が頭について大文字になってます。
また、関数内の「cnv.PadLeft」の引数で0埋めの桁数を調節できます。

Scroll View内に高さが可変のノードを作る

ScrollViewの要素の高さを可変にしたい!

ScrollViewの要素にScrollViewを入れたい!という方にオススメ

設定を変えるだけで意外と簡単にできたので方法をまとめておきます

 

 

ヒエラルキーの内容

下図の感じでオブジェクトを設定します

「Scrl_Parent -> Content_P」の中にノード(P)を追加していくスクロールがあり、

そのノード(P)の中に、ノード(C)を追加していく。という仕様になります。

 

f:id:apuridasuo:20191029175150p:plain

図:オブジェクト構成

高さが可変のノードとは

ノード(C)が追加されるたびにノード(P)の高さが調節されるという仕様です。

スクロールのノードの高さがノードごとに可変であるという事がポイントです。

f:id:apuridasuo:20191029175711p:plain

図:実際の挙動

各オブジェクトの 設定

まずは、いつも通りScrollViewを作ってください。

その上で変更のある点は下記の2点です。

・親スクロールのContentオブジェクトの「○○ Layout Group」

「Control Child Size」にチェックを入れる

・親スクロールに追加するノードに「○○ Layout Group」を追加する

 

▼ 下図の設定は「ヒエラルキーの内容」項目のオブジェクト名と

照らし合わせて参考にしてください

f:id:apuridasuo:20191030093525p:plain

図:各オブジェクトのInspector内容

 

以上で、ScrollView内のノードに高さが可変のノードを作成することができます。

 

NCMBとGoogleAdMobの競合によるビルドエラー解決法【2019年10月】

何番煎じか分からない項目なのですが、

シャキッとした答えが見つからなかった。

バージョンの違いで対応が違っていたので

現バージョンのパッケージで解決した方法をまとめときます。

 

 

パッケージのバージョン

GoogleMobileAds・・・v4.0.0

NCMB・・・v4.0.3

ビルドエラー時の状況

・NCMBパッケージを先にインポートする

 →たぶんこれがビルドエラーの原因

・GoogleMobileAdsパッケージをインポートしてビルド

▼下図のエラー発生

UnityEditor.BuildPlayerWindow+BuildMethodException: 528 errors

CommandInvokationFailure: Gradle build failed.

f:id:apuridasuo:20191029113628p:plain

図:エラーメッセージ

今回行った対処法

NCMBパッケージでインポートした「Plugins -> Android」内のファイルを削除

ビルドエラーの原因は、ライブラリの競合にあるので

NCMBパッケージでインポートした

Pluginフォルダ内のファイルを削除して競合しないようにします。

◆削除するファイル名

・firebase-analytics-impl-16.0.0

・firebase-common-16.0.0

・firebase-iid-16.2.0

・firebase-iid-interop-16.0.0

・firebase-messaging-17.1.0

・play-services-base-15.0.1

・play-services-basement-15.0.1

・play-services-measurement-base-15.0.0

・play-services-tasks-15.0.1

・support-compat-26.0.2

・support-core-utils-26.0.2

・support-v4-26.0.2

f:id:apuridasuo:20191029115108p:plain

図:削除するファイル一覧

f:id:apuridasuo:20191029115712p:plain

図:削除前後の「Plugins -> Android」フォルダ

 

ビルドしてみる

これで上手くビルドが出来ているはずです。

ビルドエラーが発生したとき、

パッケージを複数インポートした場合は

ライブラリの競合が発生している可能性が多々あると思います。

そういった場合は、

1.検証用に新規プロジェクト(AAA)を作る

2.エラーの出るプロジェクトに入れたパッケージを(AAA)に入れる

3.インポートした「Plugins」フォルダのファイルを

 パッケージごとに消していきエラーが回復するか確認する

このやり方で、競合するファイルが何か判別できると思います。