PackageManagerから必要なPackageをインポートする

#はじめに
~~はやく民主化して~~
こんにちは、アドベントカレンダー19日目担当の避雷です。unitypackageを配布したことのある人の中には、相手方と自分のプロジェクトに入っているリソースの乖離に苦しんだことがある人もいると思います。
例えばこっちがPPSv2でイイ感じの画を提供していても、インポート先のProjectにPackageManagerのPostProcessingが入っていなければその魅力を伝えることが出来ません。
今回はEditor拡張を用いてPackageManagerを操作する方法を調べてみましょう。
#PackageManagerを操作する
PackageManagerに対する基本的な操作は`UnityEditor.PackageManager`で行います。
https://docs.unity3d.com/ja/2017.4/ScriptReference/PackageManager.Client.html
##インポートされているパッケージの一覧を取得
インポートされているパッケージの一覧を取得するには、`Client.List`を使います。Listの返り値はListRequestです。
https://docs.unity3d.com/ja/2017.4/ScriptReference/PackageManager.Client.List.html
返り値の名称からもわかるように、この関数は非同期で実行されます。ListRequestはStatus,Resultなどの変数を持つので、これらを参照して非同期的に書きましょう。
↓サンプルコード（インポートされたすべてのpackageを出力）

```C#
using System.Collections;
using System.Collections.Generic;
using System.Threading.Tasks;
using UnityEditor;
using UnityEditor.PackageManager;
using UnityEngine;

public class ListPackage
{
    [MenuItem("PackageManager/List")]
    static async void List()
    {
        var listRequest = Client.List();
        while (listRequest.Status == StatusCode.InProgress)
        {
            await Task.Delay(100);
        }

        if (listRequest.Status == StatusCode.Success)
        {
            var result = "";
            foreach (var package in listRequest.Result)
            {
                result += package.packageId + "\n";
            }
            Debug.Log(result);
        }

        if (listRequest.Status == StatusCode.Failure)
        {
            Debug.Log("Error!");
        }
        
    }

}
```

MenuItemアトリビュートによってUnityEditorのメニューバーにPackageManagerの欄が追加されていると思うので、Listをクリックしてみましょう。一瞬のディレイの後、以下のようなログが出力されます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/256231/1a48d929-839c-5114-f015-0c01b70f8086.png)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/256231/2e8e154f-f831-7fbb-1dc1-6f690697a463.png)

`Hoge@huga`と言うのがそれぞれのpackageのpackageIDです。
@より前がpackageの名前、@より後がpackageのversionです。
現在は`com.unity.hoge`となっている（つまりUnity公式の）packageしかありませんが、将来的にPackageManagerが使えるようになれば、他の人がPackageをインポートすることが出来るようになるはずです。


##パッケージを指定してインポートする
新しくpackageをインポートするときは`Client.Add`を使います。Addの返り値はAddRequestです。
これを実行するとプログレスバーが表示され、通常の方法でのpackageのinstallと同じようにインポートされます。
https://docs.unity3d.com/ja/2017.4/ScriptReference/PackageManager.Client.Add.html
Listと同様非同期で処理されます。引数のpackageIdOrNameは、nam（@の前）だけを指定すると最新版をインポートしてくれるようになっています。packageIDによってバージョン（@の後ろ）まで指定すれば、古いバージョンのpackageをインポートしてくれます。
↓サンプルコード（適当なpackageをインポートする）

```C#

```

同様にメニューバーにAddがあるはずなので押してみます。今回はPostProcessingを直打ちして最新版をインストールするようにしています。しばらく経過すると下記のようなプログレスバーが出てきて、インポートの進捗を伝えてくれます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/256231/141294ab-0309-933b-6f5c-f8b2ba0e51c6.png)

更に暫くすれば、AddRequest.StatusがSuccessとなり、以下のようなログが出力されます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/256231/d7c25a57-07d4-db6d-ece1-fa7e5520d779.png)

Packageを見るとちゃんとPostProcessingがインストールされていることが分かります。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/256231/65878659-cb16-6a87-ac4d-21c03e708868.png)

既にインストールされているpackageについては何も起こりません。(特に処理が行われず、Succeededとなる)
#unity packageをインポートした直後にPackageManagerを呼びたい
`[InitializeOnLoad]`アトリビュートを付けることによって、UnityEditor起動時とそのクラス自身のコンパイル時にクラスのコンストラクタが走るようになります。
ここに先ほどの処理を書けばunitypackageインポート直後にpackageをインストールすることが出来ます(UnityPackageとPackageManagerのpackageで紛らわしいですね…)
ただし、コンストラクタ自身はasyncを付けることが出来ないため、コンストラクタ内で別の非同期メソッドを走らせるようにしましょう。

#おわりに
他のIDEにおけるNugetのような役割を果たしているPackageManagerですが、現在はpublicなrepositoryをjson直書きで登録できるみたいですが、そのうち民主化されて楽にPackageが公開されるようになるといいですね。
個人的にはPackageで追加されるアセット類はgitで共有せずに済むところが気に入っています。