UdonSharpのツールをVPM対応する話

# （前置き１）VCCがもたらした大きな変化
2022/8/30 VRChatでCreator Companion(VCC)がリリースされました。

　[公式ブログ](https://hello.vrchat.com/blog/creator-companion-live)　[非公式和訳](https://bdunderscore.notion.site/VRChat-492c3fd156a74f0690a81ef304298f36)

最初はピンときませんでしたが、
いざ向き合ってみると、なかなか大きな変化がVRChatで起こっていたことがわかりました。
特に、私が現在発表している「Trigger2to3」というツールにおいては、
何から何まで改修が必要な要件だったのです。

まずは、UdonSharpの劇的な変化。
[UdonSharpのGitHubリポジトリ](https://github.com/vrchat-community/UdonSharp)を見ても、ver0.20.4からまったく更新されないな？　と思っていたら、
実はもうメジャーバージョンが上がってver1.xとなり、VCCに組み込まれていたのです。
ver0から1への変化は、これまでの互換性をある程度保ちながらも、劇的な変化が発生していました。
大抵のことは、ver0で作ったUdonSharpスクリプトはそのまま機能するでしょう。
しかし、Trigger2to3のように、CustomEditorでコンポーネントの着脱や変数の操作などを行っている場合には、改修を余儀なくされます。

次に、VRChat専用アセットのパッケージング方法の変化。
今後、VRChatではVRChat Package Manager(VPM)形式でのパッケージングが推奨されることになり、
VPMに対応することで、ユーザーがVCCに組み込んで楽に扱えるようになります。
従来どおりのunitypackage配布でもいいんですが、プロジェクトを変えても組み込みたいような定番ツールやアセットは、VPMに対応しておくとユーザーには親切であることは間違いないので、Trigger2to3は是非対応しておくべきだと思いました。

大きく分けてこの２つの変化をTrigger2to3で対応し、無事にVPM版を公開することができましたが、
あらゆる場面でつまづいて苦労することが多かったので、これはもう書き記しておかないとマズいだろうと思い、長文になりますが、じっくりと書き記すことにしました。

# （前置き２）Trigger2to3とは
Trigger2to3とは、かつてVRChat SDK2に存在したVRC_TriggerをSDK3でも使えるように、VRC_Triggerの機能の再現を目指して作ったツールです。
![title.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/275693/757940a0-e198-d816-9527-f79828bdbc84.png)
Udonを組むことが難しい人がVRChatのワールドギミックを組み立てるための手段の一つとして、多くの人に利用していただいています。

導入方法などは、[こちらのサイト](https://www.wicurio.com/trigger2to3/)から。
もちろん、VPM対応済みです！

VPM版を落としてみて、VPMパッケージングの参考にしてもいいかもしれません。
ツール自体は、以降の話にはあまり関係ありません。

# UdonSharp ver1.x に向けての対応
さて、UdonSharp ver1.xはver0と比べて何が変わったのでしょうか。
一番わかりやすいのが、こちらの[黒鳥さんのまとめ](https://kurotorimkdocs.gitlab.io/kurotorimemo/010-VR/010_VRChat/UDON/UdonSharp1.0/)です。

「継承サポート」

やったーーー！！！！！
これは、Trigger2to3にとっては非常に大きなニュースです。
Trigger2to3には、例えばActionというカテゴリーの中で共通の機能を持ったソースファイルが50個以上あるのですが、
今までは継承が使えなかったので、その50個以上に同じ内容のコードを書き込む必要がありました。
そして、もしその共通部分に修正が必要になった場合、その50個以上に同じ修正を入れなければなりませんでした。非常に面倒です。
そのたびに、「ああ継承が欲しいなぁ……」と呟いたものです。
継承があれば、その共通部分のコードが１つあればよく、それを修正すれば、それを継承している50個以上のプログラムが修正できるというわけです。
この共通化作業はなかなか骨が折れることでしたが、これは是非ともやっておきたい修正でした。
綺麗にまとめて、不要になったコードを消していく作業がなかなか爽快で、
最終的に、ソースファイルのサイズを半分以下に減らすことができました。
大変でしたが、純粋に気持ちいい作業でした。

なお、enumの対応なども有難い話でしたが、Trigger2to3は敢えてそちらに切り替えず、既存のフィールドを維持しました。
ここでもし切り替えてしまったら、既にユーザーのプロジェクトで設定されているフィールドが壊れてしまうので、それは極力避けたかったのです。

あと、大きく影響したのが、コンポーネント構造の変化です。
CustomEditorを扱わない人にとっては、あまりピンとこないかもしれません。これは、アプリケーション実行時の話ではなく、Editor操作の話です。
CustomEditorを扱わない人は、読み飛ばしても大丈夫です。

ver0のUdonSharpの場合、GameObjectにつけるコンポーネントとしてはUdonBehaviourが表面にあって、表示されない隠れた部品としてUdonSharpのクラスが存在しているようなイメージでした。
そのため、直接Add ComponentでUdonSharpクラスのコンポーネントをつけようとすると、UdonBehaviourに変換するように促されます。これに従って変換しないと、機能させることができません。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/275693/89a8d28a-512c-0ca1-244c-83b903bba2b1.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/275693/b6577129-ef67-9cac-aba7-181e17d6e193.png)
しかし、ver1の場合は、UdonSharpクラスをAdd Componentで直接つけることができるようになっています。完全にUdonSharpクラスのコンポーネントが表面に出ています。今度は逆にUdonBehaviourが裏に隠れているようです。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/275693/190698b2-56c2-3f09-1127-fdf2aa6f39b8.png)
これによって、UdonSharpクラスのコンポーネントを追加したり、検索して値を変更したり、などの場合のEditor操作が変わります。
これまでは、UdonSharpクラスのコンポーネントを追加したい場合、後でUdonBehaviourに変換する操作が必要でしたが、もう普通のコンポーネントと同じくAddComponentだけで済むようになりました。
UdonSharpクラスのコンポーネントを検索するときも、UdonBehaviourを検索して、そのProxyを参照して……などの流れが必要なく、ただのGetComponentで済むようになりました。public値もそのまま参照できるようです。
（更新には UdonSharpEditorUtility.CopyProxyToUdon が必要です。）

# VPMパッケージングのための変更対応
[VPMのパッケージ変更方法のドキュメント](https://vcc.docs.vrchat.com/guides/convert-unitypackage/#changes-needed)に、満たすべき条件があります。
項目が４つありますが、事前準備としてソース修正すべきことは２つです。
>Editor フォルダーと Runtime フォルダーに完全に分離します。UnityEditor クラスを使用するものはすべて、Editor フォルダーにある必要があります。

つまり、UdonSharpの実行クラスにEditorのクラスを含めるような構造は認められず、Editorの部分を別ファイルにし、Editorフォルダに分離しなければなりません。
Trigger2to3もそういう構造で、100個近くのソースファイルにEditorが組み込まれていたので、それらの分離作業が必要でした。
継承対応と同じく、ソースを１つ１つ整理していく地道な作業でした。

>パス "Assets/YourPackageName" にハードコーディングされたパスを置き換えて、代わりにパッケージパスを使用します。

これも１箇所だけ該当していましたが、もともと継承対応の過程で変えようと思っていた部分だったので、別の形で解決しました。
もし、アセット参照を持続したい場合は、本文で解説されている変換方法を頼るといいんだと思います。

# GitHubパッケージテンプレート
パッケージを運用管理するための[GitHubテンプレート](https://github.com/vrchat-community/template-package)があります。
これを使えば、GitHubに更新をPushするだけで、パッケージを作成・公開できるというものですが、必須ではありません。
これがなくても、パッケージは作成できます。
Trigger2to3の運用では普通に有用なので、利用することにしました。
利用する場合、
- 「Use this template」で自分のリポジトリとしてコピーを作成
- ローカルにクローンする
- Unityプロジェクトとして開いて、パッケージ化したいアセットを持ってくる。

というステップで、新しいプロジェクトとして管理することになります。

# パッケージメーカー
いよいよアセットをパッケージ化するステップです。
公式ドキュメントでは[こちらの手順のNo.5から](https://vcc.docs.vrchat.com/guides/convert-unitypackage/#package-maker-tool)です。（No.1～4はパッケージテンプレートの話）

VRChat SDK > Utilities > Package Maker で、パッケージメーカーのWindowを呼び出します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/275693/74f098dc-a55c-cf52-a79f-a40c62f34011.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/275693/e8e9ac6c-2c1d-0dec-a168-2454ccd61e40.png)
TargetFolderの欄に、パッケージ化したいアセットのフォルダをドラッグ＆ドロップすると、入力欄に自動セットされます。
Package IDには、逆ドメイン表記でパッケージ名を入れるのですが、ドメインを持っていないと何を入れれば……となりますね。
とにかく、VRChat内で一意となることが条件なので、VRChatユーザー名を試そう、と公式では解説されています。

私も現在はドメインを持っていないのですが、かつてt-hoke.comというドメインを持っていたという経緯で、
com.t-hoke.trigger2to3
というパッケージ名にしました。

UdonSharpを使っているので、Related VRChat PackageはUdon Sharpが最適でしょう。

「Convert Assets to Package」ボタンで、アセットをパッケージ化します。

さて……

# パッケージング後のエラー応酬
パッケージ化を実行すると、UdonSharpのスクリプトで急にコンパイルエラーがズラーっと発生して、絶望します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/275693/541bbcc1-604c-7182-b831-70a463c6420b.png)
> error CS0246: The type or namespace name 'UdonSharp' could not be found (are you missing a using directive or an assembly reference?)

名前空間「UdonSharp」が見つからない？　どういうこと？？
　
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/275693/ed0577a2-0e2d-e4ad-a725-e38746cac7c0.png)
実は、パッケージの「Runtime」「Editor」フォルダそれぞれに付けられた、謎のジグソーパズルアイコンのアセット、「Assembly Definition」というものが影響しています。
これは、そのフォルダ以下のスクリプトは独立した領域でビルドするよ、ということを示しているアセットで、その領域では何もしなければ共通部分以外の他の領域のスクリプトを参照することができません。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/275693/d2cbe601-a16e-ae44-5d3a-b8f788d5bca4.png)
そこで、Assembly Definition References に参照が必要な領域の Assembly Definition を指定してあげると解決します。
UdonSharpを活かすために、UdonSharp.Runtime を加えます。
また、UdonBehaviour などを使用していれば、VRC.Udon も加える必要があります。
編集後、「Apply」をすれば反映されます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/275693/a5598502-9235-7491-ede0-2ea56421a87e.png)
次に、必要に応じてEditor側の Assembly Definition も編集します。
これでひととおりのコンパイルエラーが消えるでしょう。

さあ、これで問題解決！
めでたしめでたし

……と思いきや、実はまだこのパッケージは問題を抱えています。

今の時点では、UdonSharpのリコンパイルができません。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/275693/2a598288-055c-a221-5bcf-ff150fd4a883.png)
試しにUdonSharpのProgramAssetをどれか選択し、Compile All UdonSharp Programsを押してみましょう。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/275693/f89f1701-b6b1-fb8f-4c54-dfa870a6b4f3.png)
また、エラーがどっさり……
> Script 'Packages/xxx/Runtime/yyy.cs' does not belong to a U# assembly, have you made a U# assembly definition for the assembly the script is a part of?

何を言っているのか、最初はまったくわかりませんでした。Assembly Definition は設定したし……。

エラー発生個所のソースを追って、ようやく理解しました。
UdonSharp を使用している領域の Assembly Definition に対して、U# Assembly Definition という別のアセットが必要になるそうです。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/275693/c9c5ab35-8962-f22d-9cd1-8f45ef3edf24.png)
Createの項目に「U# Assembly Definition」というものがあるので、Runtimeのフォルダに作りましょう。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/275693/c3ab1309-3124-c2c4-ba9c-690982ec1e8f.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/275693/29f63487-76dd-9752-36e3-a21887c2f80f.png)
そして、Source Assemblyの項目に、RuntimeのAssembly Definitionをセットしてください。

それをアセットに反映させるため、Save Project をしておきましょう。
設定前のキャッシュが残っているので、一度Unityを開きなおしてみます。

再び、Compile All UdonSharp Programs を実行してみると、無事にコンパイルが成功しました。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/275693/5e55191d-ec06-fca0-6274-96443c4edc3d.png)
これでようやく本当にめでたしめでたし、ということになります。

……しかし、この手順は本当に正規のものでしょうか？
これらの問題を発生させないための導線を見逃していたのでしょうか？
Assembly Definitionについて触れられているのはこの一文だけ。
> Provide Assembly Definition files for all the scripts in your package.

ただ、この設定はPackage Makerでサポートしてもいいと思うし、
そうでなくても、設定方法をもっと詳しく解説してもいいと思うし……

ということで、拙い英語で[Canny投稿してみました](https://vrchat.canny.io/creator-companion-beta/p/package-maker-should-guide-setting-assembly-definition-for-udonsharp)。
まだリリースされたばかりですし、今後、改善されて楽になるといいなと思います。

# パッケージマニフェストの設定
問題がひととおり解消したところで、パッケージマニフェストの設定を行います。
パッケージのルートフォルダにあるpackage(.json)を開いてみましょう。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/275693/f4706431-a6ea-90a2-b8b8-ba3b739de96e.png)
最低でも設定しておくべきものは、この３つです。
- Display name
- Version
- Brief Description

VCCの表示やバージョン管理に関わってきます。
Descriptionは別に日本語でもいいと思います。
更新版を公開するときは、バージョンを上げましょう。

Inspectorでは全てが出てこないので、一度ファイルを直接テキストで開いて手直しした方がよさそうです。
```package.json
{
  "name": "com.t-hoke.trigger2to3",
  "displayName": "Trigger2to3",
  "version": "2.0.0",
  "unity": "2019.4",
  "description": "An alternative to VRC_Trigger that can be used with VRChat SDK3",
  "author": {
    "name": "Hoke"
  },
  "gitDependencies": {},
  "vpmDependencies": {
    "com.vrchat.udonsharp": "1.1.1"
  },
  "legacyFolders": {
    "Assets\\Trigger2to3": ""
  }
}
```
だいたいの項目は任意でいいと思います。
ただ、初期状態でlocalPathというものがありますが、自分のPC上のパスが丸見えなので、配布の前に消しておいた方がいい気がしますね。


以上で、パッケージは完成です！

このパッケージのフォルダを（圧縮して）配布すれば、誰もがVCCに組み込んでパッケージをインポートできるようになります。

# GitHub運用
さて、GitHubテンプレートを使用して、Github運用をする場合、そのリポジトリの編集が必要になります。
修正箇所は、およそ[template-packageのReadme](https://github.com/vrchat-community/template-package)に書かれている通りですが、
私はそれに加え、Assetsフォルダ全体をgitignoreで除外させました。
- release.yml のパッケージ名を修正
- プロジェクトルートのgitignoreに "/[Aa]ssets/" を追加
- Packagesのgitignore のパッケージ名を修正
- Packages/com.vrchat.demo-template を削除
- README.mdを変更

これだけやって、いざCommit & Push！
すると、ちゃんとJobが流れ、zipとunitypackageが生成されたのですが、エラーの通知が……。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/275693/5b5ea765-b665-ac03-cf7d-5bb1853d7373.png)
これは、まだテスト中だと思い、リポジトリをprivateにしていたことが原因でした。
publicに変更し、再実行すると、正常終了して無事にwebページも公開されていました。