#必要性
今カードゲームっぽいものを作っているのですが、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を無理に使うよ!
- 指定したsortingLayerIDを持つSpriteRendererを
- sortingOrderの値でソートしてから
- 番号を振り直す
は普通に書くとそこそこ長いです!
それがLINQなら
- 指定したsortingLayerIDを持つSpriteRendererを → Where
- sortingOrderの値でソートしてから → OrderBy
- 番号を振り直す
となるので、
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によって用意されてたりします? さらっと探した感じは無かったんですが。
もしあるなら教えてください。 恥ずかしいので記事消しますから。