Unity2Dのスプライトの描画順をLINQで正規化する


必要性

今カードゲームっぽいものを作っているのですが、Unity2DのSpriteは描画順をレイヤー(Sorting Layer)で管理しつつ、同じレイヤー内ではOrder in Layerの小さい順に描画するのは御存知の通り。

で、カードゲームや、Windowsのウインドウのように、選択したものを一番上に持ってくる操作(レイズ操作)をするにはどうするか。

それは簡単で、Order in Layerの値に1万とか2万とか足しちゃえばとりあえず、一番上に来るはず(よっぽど多かったり、変な数値入れてない限り)

ちなみにInspectorだとOrder in Layerですが、スクリプト上ではRendererのメンバのsortingOrderです。

コードにするとこんな感じ(もちろん、SpriteRendererは最初に取っておいた方がいいけれど)

    public void Raise(int num) {
        var spr = GetComponent<SpriteRenderer>();
        spr.sortingOrder += num;
    }

これでいいのか

でもこの方法にはもちろん問題があって、元もとsortingOrderが10のオブジェクトをRaise(1000); で1010にしたらとりあえず一番上に来たとして、別のsortingOrderが5とかのオブジェクトを同じくRaise(1000);しても、1005なので一番上には来ない!どうしよう!

やっと本題

というわけで、sortingOrderの正規化が必要なのです。
と、正規化などと難しく言いましたが、順番はそのままに数字を振り直すだけです。
BASICでいうとRENUM命令って事です(どれくらいの人がピンとくるのか)

LINQを無理に使うよ!

  1. 指定したsortingLayerIDを持つSpriteRendererを
  2. sortingOrderの値でソートしてから
  3. 番号を振り直す

は普通に書くとそこそこ長いです!
それがLINQなら

  1. 指定したsortingLayerIDを持つSpriteRendererを → Where
  2. sortingOrderの値でソートしてから → OrderBy
  3. 番号を振り直す

となるので、

    public void SortOrderNomalize(int targetLayer)
    {
        int cnt = 0;
        foreach (var card in FindObjectsOfType<SpriteRenderer>().Where(sr => sr.sortingLayerID == targetLayer).OrderBy(c => c.sortingOrder))
        {
            card.sortingOrder = cnt++;
        }
    }

で! 短い!すごいね!

一応コレだけで指定したレイヤーのsortingOrderを0から振り直してくれます。

ちなみに、ただ行数を減らすという方針ならforeachをArray.ForEach使うって手もありますが、Array.ForEachは配列限定なのでToArray()を走らせる必要があるのでメモリ的に不利っぽい気がします。

一応書くと

    public void SortOrderNomalize2(int targetLayer)
    {
        int cnt = 0;
        Array.ForEach(FindObjectsOfType<SpriteRenderer>().Where(sr => sr.sortingLayerID == targetLayer).OrderBy(c => c.sortingOrder).ToArray(),spr=>spr.sortingOrder = cnt++);
    }

です。 まぁ、短ければ良いってもんじゃないよね。

閑話休題

これを、先ほどのRaiseメソッドと併せて

    public void Raise(int num) {
        var spr = GetComponent<SpriteRenderer>();
        spr.sortingOrder += num;
        SortOrderNormalize(spr.sortingLayerID);//sortingOrder振り直し
    }

とやってあげればいいんではないでしょうかー。

もちろん

FindObjectsOfTypeとか決して軽い処理では無いので途中で増減しない限りはStartとかで確保しておくことをおすすめします。

これだけやっておいて

実はsortingOrderの振り直しがすでにUnityによって用意されてたりします? さらっと探した感じは無かったんですが。

もしあるなら教えてください。 恥ずかしいので記事消しますから。