バージョン間でPrefabをコンフリクトさせないシステム

バージョン間でPrefabをコンフリクトさせないシステム

バージョン間でPrefabをコンフリクトさせないシステム

はじめに

株式会社QualiArtsでUnityエンジニアをしている中辻です。

私たちのプロジェクトでは大型施策により複数バージョン先の並行開発が必要になりました。Unityプロジェクトにおいてバージョンが増えるほど恐ろしいのがGitHub管理でのPrefabのコンフリクトです。PrefabはYAMLベースのシリアライズ形式で内部構造が複雑なため、Git上でコンフリクトすると手動での解消が極めて困難で、最悪の場合は作業のやり直しが発生します。

本記事では、この問題を GitHub Actions と Unity エディタ拡張 で解決したシステムを紹介します。

バージョン管理の課題

本記事での「バージョン」はGitHubレポジトリにおけるリリースラインごとのブランチ(例:version/1.0.0)を指し、「未来のバージョン」は対象の開発対応のバージョンよりも先行しているバージョンブランチ(例:version/1.1.0)のことです。

通常のフローでは、開発者の機能開発がバージョンにマージされたあと、未来のバージョンにマージするタイミングで初めてコンフリクトが判明し、Prefabの編集作業のやり直しが発生していました。理想は、機能開発のPRを作成した時点で未来のバージョンとのコンフリクトに気づき、対応バージョンの調整やPrefabがコンフリクトしないような対応で回避したりして効率的な作業ができる状態です。コンフリクトが回避できないとしても先にコンフリクトすると分かっていれば、コンフリクト解消時にどう解消すればいいかわかった状態で作業ができます。

従来フローと理想のフローの比較

システム全体像

プロジェクトのGitHubレポジトリ内に、編集されたPrefabのリストを保持するjsonデータを管理します。 そのバージョンで編集済みということは過去のバージョンとコンフリクトする可能性があるということなのでこのjsonデータをバージョンブランチごとに正しく更新し、それを元にCIでの警告や可視化を行います。

.github/data/prefab-paths.json

{
  "paths": [
    "Assets/Test/OutGame/Prefabs/Hoge.prefab",
    "Assets/Test/InGame/Prefabs/HogeHoge.prefab"
  ]
}

以降このファイルを「Prefabリストjson」と呼称します。

詳細

システムを細分化すると、5つのGitHub Actionsと1つのUnityエディタ拡張で構成されています。

GitHub Actions1~4でコンフリクトするPrefabのリストをそれぞれのバージョンで正しく更新されるように管理します。

GitHub Actions 5でPR時の警告を行います。

Prefabコンフリクト防止システム 全体図

GitHub Actionsタイミング役割
1. Trackerバージョンに対する開発対応のPR時変更Prefabを記録
2. CreateVersion新バージョンブランチ作成時記録を全消去
3. BranchMergeToolバージョンマージ時リストを新バージョン側に強制リセット
4. Cleanupバージョン反映マージPR時解決済みを整理
5. Checkerバージョンに対する開発対応のPR時コンフリクトを予知

Unityエディタ拡張でUnity作業時にコンフリクトの可能性を可視化します。

以下それぞれのシステムについて話します。

GitHub Actions 1: Tracker — 変更Prefabの記録

タイミング

バージョンブランチへの開発対応のPRが作成された時に発動します。

処理

PR内の変更差分からPrefabファイルのパスを抽出し、Prefabリストjsonにファイルパスをリストとして追加してコミットします。

GitHub Actions 2: CreateVersion — 新バージョンでのリスト初期化

タイミング

本プロジェクトでは、必ずGitHub Actions経由でバージョンブランチを作成するルールになっています。そのタイミングで発動します。

処理

バージョンブランチ作成のGitHub Actionsの最後に、Prefabリストjsonの内容を空にしてコミットします。

前のバージョンから切った新規バージョンのブランチはその時点で変更がないのでコンフリクトせず、前のバージョンからPrefabリストjsonを引き継ぐ必要はありません。

バージョンマージでのPrefabリストjsonの保護

今回のシステム実現のためには、バージョンブランチにおけるPrefabリストjsonは過去のバージョンでの作業でコンフリクトが発生しうるPrefabのリストを常に保持する必要があります。

そのためにバージョン間のマージ処理では、GitHub Actions 3とGitHub Actions 4でリストを管理しています。

GitHub Actions 3: BranchMergeTool — 古いバージョンのリストを流し込まない

バージョンブランチ間の反映は定期的にCIで自動でPRが出るようになっています。

ci/version/X.X.Xでブランチを作成され、よく発生するコンフリクトの解消などの調整が行われてPRになります。

バージョン間の自動マージを担う BranchMergeTool では、基本前のバージョンでのリストが次のバージョンに混入しないようにPrefabリストjsonが新しいバージョン側を常に優先するよう設定されています。 コンフリクトする場合は新しいバージョンを優先すればいいのですが、未来のバージョンでPrefabリストjsonに変更がなくコンフリクトしない場合もあります。その場合gitのマージ処理がそのまま通ることがあるので、Prefabリストjsonの変更に関してはリセットするコミットを行います。

GitHub Actions 4: Cleanup — 解決済みPrefabのリスト整理

Cleanup は、GitHub Actions 3で話したPRが出たあとにそのPR(ci/version/X.X.XのPR)に対して発動します。

マージ先の未来のバージョンのPrefabリストjsonに存在するパスのうち、PR内で変更があるものをリストから削除してコミットします。変更があるのにマージできるということは、コンフリクトの可能性がない(解決済み)と判断できるためです。

強制リセットでリストを保護し、Cleanupで解決済みのパスを除外。この2つが連携することで、各バージョンのPrefabリストjsonは常にそのバージョン固有の編集済みPrefabリストとして正しく機能します。

GitHub Actions 5: Checker — コンフリクトの予知

タイミング

バージョンブランチへの開発対応のPRが作成された時に発動します。

処理

PRで変更されたPrefabが未来のバージョンのPrefabリストjsonにも存在する場合、最も近いバージョンとJSONへのリンクをPRコメントとして警告します。

Checkerの警告PRコメント

Git の Blame 機能で変更 PR を追跡できるため、リンクは JSON の対象パス行の Blame へのリンクになっています。これにより、どの PR で変更が入ったか、誰が変更したかを確認できます。

なお、この仕組みは「同一Prefabが編集済み」であることを検知するもので、実際にコンフリクトするかどうかはケースバイケースです。あくまでコンフリクトの”可能性”の検知であり、最終判断はBlameリンクから変更内容を確認して行います。

Checkerはすべての未来のバージョンのPrefabリストjsonを参照しますが、各JSONはgit showで取得するだけなのでチェックアウト不要で軽量です。

Unityエディタ拡張 — 作業前に気づく

PR作成時の警告だけでは、Prefabの編集作業が終わった後に気づくことになり、作業コストを支払った後になってしまいます。

そこでUnity Editorで作業する時に開発者が気づく必要があったのでUnityEditor拡張でコンフリクトしそうなPrefabを可視化します。

可視化

EditorApplication.projectWindowItemOnGUI を使用し、コンフリクトの危険があるPrefabファイルの背景を赤く描画します。さらに、直近でコンフリクトするバージョンをタグの見た目で表示します。

Projectウィンドウでの赤い表示とバージョンタグ

Prefabアイコンにコンフリクト警告が表示されている様子

非同期データ取得

Gitから未来のバージョンのPrefabリストjsonを非同期で取得してキャッシュすることで、エディタの作業を阻害しません。 また、EditorApplication.FocusChangedでリストの更新が必要なら更新させることで、gitでブランチを切り替えていて見るべきバージョンの情報が変わったときや、gitでfetchが発生して、remoteのPrefabリストjsonが更新されている時に最新状態で見た目を更新できるよう考慮しています。

現在のバージョン判定

コミットを遡り、バージョンブランチにも含まれているコミットを探すことで、バージョンブランチ自体にチェックアウトしていなくても現在の作業ブランチがどのバージョンに属しているかを正しく自動判定しています。

今後の展開

対象ファイルの拡大

コンフリクトするのはPrefabだけではないので、他にもコンフリクトしやすい種類のアセットもプロジェクトによっては需要があれば監視対象にする対応を行う必要があると思います。

厳密なコンフリクト判定

今回のシステムはファイルを編集しているかどうかという判定なので、実際にコンフリクトするかどうかは判定できていません。 変更の有無だけでなく「実際にマージコンフリクトが発生するか」まで判定する展開も考えられますが、 現状でGitHub Actions内で単純にマージ処理を実際に行うなどをすると判定に時間が掛かってしまうので行っていません。 現状のシステムでPRに警告が出た上で、このレベルのPrefab変更なら大丈夫だろうという予想で判断している部分もあるので処理速度が抑えられるなら改善したいです。

横展開

まだプロジェクトにようやく不備無く動かせた状態なので、独立したライブラリとして他プロジェクトからも利用できる形に整備するのも時間がある時に行いたいです。

まとめ

バージョンの並行開発が増えるほど、Prefabコンフリクトのリスクは指数的に増大します。 本システムではGitHub Actionsのバージョン周りのCIとUnity拡張でこの問題に対処を行いました。

それにより、「コンフリクトしてから直す」のではなく「コンフリクトさせない」仕組みを作ることで、コンフリクトが発生して解消にかかる時間、発生しないようにPR全体をチェックしておくのにかかるコストを減らし、開発効率を向上させることができました。

過去は2,3個のバージョン開発になっただけでとてもバージョン間反映のコストが高かったのですが、GitHub Actionsでの自動化やコンフリクトを回避するシステムなどがあればそこまで苦には思わない環境が現実になってきています。 Unityプロジェクトにおいて同じような苦労を感じている方の参考になれば幸いです。

著者

中辻 智裕のプロフィール画像

(Nakatsuji Tomohiro)

2016年にサイバーエージェントに新卒入社。QualiArtsの前身であるAmebaゲームズに配属され、現在は運用プロジェクトでUnityチームのエンジニアリーダーを務めている。UI/UXや音関連にちょっとうるさい。

この記事をシェア

目次