UnityのMaterialにMultipleSpriteをセットしたい！

　MaterialにTextureはセットできますが。
　描画負荷を軽くしようと、TexturePacker等でSpriteをまとめてしまうと、その中身を直接セットすることができなくなってしまいます。uGUIの場合はImageにセットすれば解決なのですが、ParticleのMaterialとして使いたい場合、コレでは困ることがあります。
　Unityの標準のPackingTagもありますけど、Packing後の画像に圧縮や最適化などの加工をしたいときに困りますよね？
　ということでなんとかしてみました。

#まずMultipleSpriteをMaterialにセットしてみる
UnityのSpriteのリファレンスを見てみる
https://docs.unity3d.com/ja/current/ScriptReference/Sprite.html
rectとtextureへの参照が出来ることがわかります。

MaterialにSpriteをセットすると、textureとして扱われるのは、このtextureをセットしているのでしょう。
rectの中に、画像の中のXの位置、画像の中のYの位置、Xサイズ、Yサイズのパラメータが格納されています。

Textureにはwidthとheightで画像のサイズが所得できます。
https://docs.unity3d.com/ja/current/ScriptReference/Texture.html
こんな感じでTextureから、Spriteをクリッピングできそうです。

```
OffsetX = Sprite.rect.x/Sprite.texture.width
OffsetY = Sprite.rect.y/Sprite.texture.height
TilingX = Sprite.rect.width/Sprite.texture.width
TilingY = Sprite.rect.height/Sprite.texture.height
```
#では、実際に作ってみましょう
・Shaderの実装　ParticleAddtiveを使いました
MultipleParticleAddtive.shader

``` 
Shader "Unlit/MultipleParticleAddtive" {
	Properties {
		_TintColor ("Tint Color", Color) = (0.5,0.5,0.5,0.5)
		_MainTex ("Particle Texture", 2D) = "white" {}
		_InvFade ("Soft Particles Factor", Range(0.01,3.0)) = 1.0
	}
	FallBack "Particles/Additive"
	CustomEditor "MultipleParticleAddtiveView"
}
```

・MaterialのEditor拡張
MultipleParticleAddtiveView.cs

```
using UnityEngine;
using System.Collections;
using UnityEditor;
using UnityEngine.UI;
using System.Linq;

public class MultipleParticleAddtiveView : MaterialEditor
{
    [SerializeField]
    Material material
    {
        get { return (Material)target; }
    }

    private Sprite _sprite;

    public override void OnEnable()
    {
        //テクスチャ情報を確認
        RestoreSprite();        
        base.OnEnable();
    }

    public override void OnInspectorGUI()
    {
        if (isVisible == false) { return; }

        // 入力内容が変更されたかチェック
        EditorGUI.BeginChangeCheck();
        EditorGUILayout.BeginVertical(GUI.skin.box);
        EditorGUILayout.LabelField("MultipleSpriteをMaterialにセットします");
        _sprite =  EditorGUILayout.ObjectField(_sprite,typeof(Sprite),true) as Sprite;
        if (GUILayout.Button("SetTexture"))
        {
            var _rectSprite = SpriteToTextureRect(_sprite);
            material.SetTexture("_MainTex", _sprite.texture);
            material.SetTextureOffset("_MainTex", new Vector2(_rectSprite.x, _rectSprite.y));
            material.SetTextureScale("_MainTex", new Vector2(_rectSprite.width, _rectSprite.height));
        }

        if (GUILayout.Button("ResetParam"))
        {
            material.SetTextureOffset("_MainTex", new Vector2(0f,0f));
            material.SetTextureScale("_MainTex", new Vector2(1f,1f));
        }

        EditorGUILayout.EndVertical();
        GUILayout.Space(20);
        EditorGUILayout.BeginVertical(GUI.skin.box);
        base.OnInspectorGUI();
        EditorGUILayout.EndVertical();
    }

    /// <summary>
    /// MainTexとOffset、ScaleからセットされているSpriteを見つけます
    /// </summary>
    private void RestoreSprite()
    {
        //今　Materialにセットされている画像を所得
        var _setTexObject = material.mainTexture;
        Rect _setTexRect = TexRect(material, _setTexObject);

        //画像のInstanceIDを所得
        var _texObjectID = AssetDatabase.GetAssetPath(_setTexObject.GetInstanceID());
        //InstanceIDからMultipleSpriteかどうかを判定　nullなら単体のSpriteか普通の画像
        Sprite[] _setSprites = AssetDatabase.LoadAllAssetsAtPath(_texObjectID).OfType<Sprite>().ToArray();

        //Spriteではないということで何も処理しない　通常Textureです
        if (_setSprites == null) return;

        if (_setSprites.Length > 1)
        {
            for (var i = 0; i < _setSprites.Length; i++)
            {
                if (_setTexRect == _setSprites[i].rect)
                {
                    _sprite = _setSprites[i];
                }
            }
        }
        else
        {
            //MultipleSpriteではないただのSpriteです
            _sprite = _setSprites[0];
        }
    }

    /// <summary>
    /// OffsetとScaleからRectを生成します
    /// </summary>
    /// <param name="_mat">material</param>
    /// <param name="_tex">texture</param>
    /// <returns></returns>
    private Rect TexRect(Material _mat,Texture _tex)
    {
        Vector2 _offset = _mat.GetTextureOffset("_MainTex");
        Vector2 _Scale = _mat.GetTextureScale("_MainTex");
        return new Rect(
            _offset.x * _tex.width,
            _offset.y * _tex.height,
            _Scale.x * _tex.width,
            _Scale.y * _tex.height);
    }

    /// <summary>
    /// SpriteのRectからTextureのOffsetとScaleを生成します
    /// </summary>
    /// <param name="_sp">Sprite</param>
    /// <returns></returns>
    private Rect SpriteToTextureRect(Sprite _sp)
    {
        var offset_x = _sprite.rect.x / _sprite.texture.width;
        var offset_y = _sprite.rect.y / _sprite.texture.height;
        var tiling_x = _sprite.rect.width / _sprite.texture.width;
        var tiling_y = _sprite.rect.height / _sprite.texture.height;
        return new Rect(offset_x,offset_y,tiling_x,tiling_y);
    }

}
```

#説明
・SetTextureボタンでSpriteをセットします。
・ResetParamボタンで、セットされているTilingやOffsetをResetします
MultipleでないSpriteやテクスチャはParticleTextureに直接セットしてもらって大丈夫です。

<table>
<tr><td>・Material作成</td><td>・Spriteをセット</td></tr>
<tr><td><img src="https://qiita-image-store.s3.amazonaws.com/0/212169/f05c2df4-6d2c-15c9-f18d-de839308afd7.png" width="200"></td><td><img src="https://qiita-image-store.s3.amazonaws.com/0/212169/c0b89242-7854-d119-c955-dec1c461b694.png" width="200"></td></tr>
<tr><td>・MultipleSpriteをセット</td><td>・通常のTextureをセット</td></tr>
<tr><td><img src="https://qiita-image-store.s3.amazonaws.com/0/212169/d47afb8f-7a64-c56f-9949-cab8258e431e.png" width="200"></td><td><img src="https://qiita-image-store.s3.amazonaws.com/0/212169/e8f68cda-2bcd-580b-46ba-b3ea33042a8f.png" width="200"></td></tr></table>


#苦労したところ
RestoreSprite()の中のここ(↓)に苦労しました。

```
        //画像のInstanceIDを所得
        var _texObjectID = AssetDatabase.GetAssetPath(_setTexObject.GetInstanceID());
        //InstanceIDからMultipleSpriteかどうかを判定　nullなら単体のSpriteか普通の画像
        Sprite[] _setSprites = AssetDatabase.LoadAllAssetsAtPath(_texObjectID).OfType<Sprite>().ToArray();
```

　Editor拡張でセットした値を残しておかないと、MaterialのInspectorを閉じた時点で情報（この場合MultipleSprite）が失われてしまいます。
　今回は、MaterialですのでSerializeのtargetはMaterialが保持するものになります。
　”MultipleSpriteには何をセットしているのか”　といったSprite型のObjectはMaterialでは保持していません。　
　又、新しくScriptableObjectを作った場合、こっちはユニークなObjectにならなかった為、この拡張を使う全てのMaterialで情報が共有されてしまうことになりました。

　結局、MultipleSpriteの情報を保持することをあきらめて、MaterialにセットされているTextureからInstanceIDを所得し、AssetDatabase経由でそのInstanceIDを保持するObjectがSprite型のarrayを持っているかを見つけてMultipleSpriteかどうかの判断と、MultipleSpriteの情報の復元をすることにしました。

　これもっと上手いやりかた知っている人がいたら教えてほしいなぁ･･･

#参考サイト
http://kan-kikuchi.hatenablog.com/entry/MultipleSprite_LoadAllAssetsAtPath