同一のTextureから複数のSpriteを生成した時のSetPassCallは1という話+動的にそれを行う方法

この記事の内容はタイトルそのままです。
複数画像を1つのテクスチャにパッキングして、そこから元の画像の枚数分Spriteを生成した時、SetPassCallは1で済むということです。
SetPassCallをどれだけ減らせるかが大事なモバイル環境では特に有用なのではないかと思います。

今回は**TexturePackingもSprite生成も動的に行う**ことによって柔軟にこれを活用できる方法を書いていきます。

以下にわかりやすい結果の画像を貼っておきます。
![test1.png](https://qiita-image-store.s3.amazonaws.com/0/88657/4b675fec-8e96-951e-0f2a-efda156ed42d.png)

(※わかりづらいですが、何もない場合のSetPassCallは2なので増えたのは1だけです。)

なお、Unity5.6.0f3で検証しています。


#はじめに
<blockquote class="twitter-tweet" data-lang="ja"><p lang="ja" dir="ltr">まさかと思うけど大量のTextureをPackingして1枚画像にして、そこからSprite.Createした場合のSetPassCallって1なんですか</p>&mdash; Negipoyoc (@CST_negi) <a href="https://twitter.com/CST_negi/status/853166359575842816">2017年4月15日</a></blockquote>
<script async src="//platform.twitter.com/widgets.js" charset="utf-8"></script>

<blockquote class="twitter-tweet" data-lang="ja"><p lang="ja" dir="ltr"><a href="https://twitter.com/CST_negi">@CST_negi</a> そういうことですよ。</p>&mdash; 伊藤周 (@warapuri) <a href="https://twitter.com/warapuri/status/853168041114218496">2017年4月15日</a></blockquote>
<script async src="//platform.twitter.com/widgets.js" charset="utf-8"></script>

#検証する

Scriptを1つ書きます。
内容は
1. WWWを使って、画像を10枚ダウンロードする。
2. すべての画像がダウンロードできたらそれらを用いて1枚の画像にパッキングする。
3. パッキングされたテクスチャからSprite.Createを用いて元の画像をSprite化する。
4. 10個のSpriteRendererに対して3.で作ったSpriteを割り当てる。

```TexPacker.cs
using System.Collections;
using System.Collections.Generic;
using UniRx;
using UnityEngine;
using System.Linq;

public class TexPacker : MonoBehaviour
{
    void Start()
    {
        //10個SpriteRendererを取得する
        var spriteRenderers = GetComponentsInChildren<SpriteRenderer>();
        //1.WWWを使って、画像を10枚ダウンロードする
        var streams = Enumerable.Range(0,spriteRenderers.Count()).Select(i => Observable.FromCoroutine<Texture2D>(obs => GetTextures(i.ToString(), obs)));
        //2. すべての画像がダウンロードできたら
        Observable.WhenAll(streams)
            .Subscribe(textures =>
            {
                //2.それらを用いて1枚の画像にパッキングする。
                var packedTexture = new Texture2D(4096, 4096);
                
                //このuvsにはPackした画像のどこに元画像があるかを格納するものです。
                //(x:0.39, y:0.88, width:0.25, height:0.06)のような値がパックした枚数分入ってます。
                var uvs = packedTexture.PackTextures(textures, 0, 4096, false);

                //元画像を切り出すためにPackした画像のWとHをとっておく
                var packedTextureWidth = packedTexture.width;
                var packedTextureHeight = packedTexture.height;

                for (var i = 0; i < spriteRenderers.Length; i++)
                {
                    //3.元の画像をSprite化する。Packした画像のwとh、そしてuvsを使うことで元の画像を切り抜ける。
                    //またメッシュは矩形でいいので、SpriteMeshType.FullRectを指定してSprite化するとき余計な負荷がかからないようにする。
                    var s = Sprite.Create(packedTexture, new Rect(packedTextureWidth * uvs[i].x, packedTextureHeight * uvs[i].y, packedTextureWidth * uvs[i].width, packedTextureHeight * uvs[i].height), Vector2.one * 0.5f, 100, 10, SpriteMeshType.FullRect);
                    
                    //4.10個のSpriteRendererに対して3.で作ったSpriteを割り当てる。
                    spriteRenderers[i].sprite = s;
                }
            });
    }

    //WWWを使ってダウンロードするやつ。今回はStreamingAssetsからロードしている。
    IEnumerator GetTextures(string i, IObserver<Texture2D> observer)
    {
        var filePath = "file:///" + Application.streamingAssetsPath + "/image" + i + ".png";
        var www = new WWW(filePath);
        {
            while (!www.isDone) { yield return null; }
            var tex = www.texture;
            observer.OnNext(tex);
        }
        observer.OnCompleted();
    }
}
```

この時のHierarchyはこうなっています。
![CropperCapture[324].png](https://qiita-image-store.s3.amazonaws.com/0/88657/2ae6ffc0-a83a-4d29-f234-7e9ae9f40bbc.png)

#終わり
結果は記事の最初に示した通りです。

以上で複数のSpriteがあってもSetPassCallを1に抑えることができました。
動的にできるのでネット上に画像があってもWWW使ってやればいいと思います。
こういうときにUniRxのWhenAllのありがたみを感じますね。