← Works
Tool

Avatar VCS

VRC用アバター改変のUnityパッケージ

Period
1か月
Year
2026
Role
個人開発
Stack
Unity / C#

背景

Unityプロジェクトにおいてバージョン管理はGitで行われることが多い。しかし、UnityのアセットはGitで管理するには不向きなものが多く、特にVRC用アバターの改変はGitで管理することが難しい。そのため、視覚的にアバターの改変を管理できるツールを作成することにした。

工夫したこと

できるだけ関数を単純化することでテストしやすくした。また、アバターの改変を視覚的に管理できるようにUIを工夫した。利用者からのフィードバックをもとに、UIの改善を行った。

振り返り

Unityにおけるテストの難しさを痛感した。Unityのテストは、Unityのエディタ上でしか動作しないため、CI/CDの導入が難しい。また、WEB開発と違い、クライアント側で持つ情報が多いために互換性を持ったバージョンアップを行うことが難しいと感じた。設計の段階から、どのようにバージョンアップを行うかを考える必要があると感じた。

技術詳細開く閉じる

アセンブリの分け方

Unity のテストが書きにくい原因の多くは、ロジックが Editor API とシーンに貼り付いていることにある。そこで UnityEditor に依存するコードと依存しないコードを、アセンブリの単位で分けた。

flowchart TB
  E["AvatarVcs.Editor / UI・記録・適用・履歴操作"]
  C["AvatarVcs.Core / モデル・履歴・差分・表示ロジック"]
  R["AvatarVcs.Runtime / マーカーコンポーネント"]
  E --> C
  C --> R
  E --> R

Core は UnityEditor API を一切呼ばないので、シーンも AssetDatabase も立てずにテストできる。差分計算やコミットIDの検証、ブランチ HEAD の更新といった、壊れると痛いところはすべてこの層に置いた。Editor 層に残るのは Unity API を実際に叩く部分だけになる。

UI と状態の分離

EditorWindow に状態を持たせるとテストできる範囲が一気に狭まるため、状態と遷移は AvatarVcsPresenter に寄せ、ウィンドウ側は描画と操作の受け渡しだけにした。Presenter が依存するのは3つのポートだけで、Editor 実装はそれぞれの裏に置いている。

ポート 役割 Editor 実装
IHistoryStore コミット・インデックス・設定の読み書き EditorHistoryStore
IAvatarGateway シーン上のアバターへの操作 EditorAvatarGateway
IUserPrompt 確認ダイアログの表示 EditorUserPrompt

ブランチ切り替えや比較モードの遷移は Presenter のテストで確認できる。ダイアログを差し替えられるので、「削除の確認で No を押したとき」のような分岐もウィンドウを開かずに書ける。

保存する場所と形式

コミットは Unity のアセットではなく ProjectSettings の下に JSON で置いている。

ProjectSettings/AvatarVcs/
├── guid-remapping.json
└── avatars/{avatarGuid}/
    ├── config.json     ブランチ名 → HEAD コミットID
    ├── index.json      コミット一覧
    └── commits/{commitId}.json

アバターは32文字の hex GUID で識別する。この GUID はアバターに付けたコンポーネントが持っていて、アバターを複製すると GUID が重複するため、OnValidate で検知して振り直している。GUID とコミットIDはそのままファイルパスになるので、書き込み前に形式を検証してパストラバーサルを防いでいる。

記録するのは Prefab そのものではなく、Prefab の GUID 列と、その上に乗せた差分(Transform、tag/active/layer、コンポーネントのフィールド値、BlendShape、マテリアルのスロット、シェーダープロパティ)である。アバターの改変は「どの衣装を入れたか」より「入れたあとに何をどういじったか」が本体で、そこだけが Git で扱いにくかったため、そこを構造化して持つことにした。

コミット時にはアセットの内容ハッシュも一緒に記録しておき、checkout のときに現在の内容と突き合わせる。衣装側が更新されていた場合に、黙って適用せず警告を出せるようにするため。

配布とCI

配布は VPM リポジトリの形を取っていて、リポジトリ直下の index.json がパッケージの索引になっている。リリースはタグを押すと GitHub Actions が走る形にした。

タグの v を除いた文字列と、そのタグ地点の package.jsonversion が一致しない場合はそこで失敗させている。main の先頭を指していないタグからリリースすると、名前と中身の食い違った zip が出てしまうため。検証を通ると zip を作り、index.json に版を追記して main へ書き戻し、GitHub Release を作る。

テストは push と PR のたびに game-ci/unity-test-runner で EditMode を実行する。Core と Editor の2アセンブリに分けてあるので、シーンを必要としない方は同じ仕組みの中で軽く回る。