suguru.dev

バンクーバーで働くエンジニアの備忘録

Unity Editor上でのVersion Controlの使い方 〜Perforce編〜

概要

UnityにはBuilt-inのVersionControlという関数があり、これを利用することでシェルコマンドを叩くこと無くGitやPerforceなどにCommit・Submitすることができます。

Unity Editor上からファイルを自動生成・削除、及びPerfoceに反映させるツールを作ったのでその一部を紹介します。

Version Controlの設定

[Project Setitngs] > [Editor]上にVersion Controlという項目を設定します。今回はPerfoceを使用するためPerfoceの設定をしました。

f:id:suguru03:20190623052840p:plain

Connectのボタンを押してConnectedになっていることを確認してください。

VersionControlクラスの使い方

まずUnityEditor.VersionControlをインポートします、C#ではusingを使用します。

using UnityEditor.VersionControl;

また各関数でassetsのパスが必要となるため、assetsを用意しておきます。

var assetPath = Application.dataPath;
var assets = new AssetList
{
    new Asset(assetPath)
};

Task

今回使用する関数はどれもUnityEditor.VersionControl.Taskを返します。必要に応じでWaitを呼んでください。 このTaskはawaitableではないため同期処理になりプロセスを専有します。他の作業がブロックされるのでなるべく呼ぶ回数を減らすことをおすすめします。

Checkout

Perfoceではすべてのファイルがreadonlyなので、ファイルを変更する前に必ずcheckoutする必要があります。 そのため必ずWaitを呼ぶ必要があります。

Provider.Checkout(assets, CheckoutMode.Asset).Wait();

PerfoceにはChaneglistという概念があります。 Gitのbranchに似てますが、同時に複数のChangelistを使用することができます。

例えば作業AとBを同時にする必要があり、それぞれを別のChangelistで管理することでSubmitするタイミングを調整することができます。

ここでCheckoutされたファイルは自動的にdefaultのChangelistに入ります。

Add

新しいファイルを追加します。ディレクトリパスを指定することでディレクトリ配下の新規ファイルを追加することができます。

Provider.Add(assets, true).Wait();

これらのファイルはAddコマンドを呼ばない限りPerfoce上で確認することができません。

これがとても厄介で、追加漏れしたファイルは存在しているにも関わらず、Perfoce上で確認ができないため困ることが多々あります。 この追加漏れをチェックするreconcileというコマンドがありますが、かなり不便です。

Delete

既存のファイルを削除します。

Provider.Delete(assets).Wait();

Revert

変更を破棄します。 Perforceの場合、チェックアウトされたすべてのファイルがSubmitの対象になります。そのため変更されてないファイルはUnchangedで破棄する必要があります。

Provider.Revert(assets, RevertMode.Normal).Wait();
Provider.Revert(assets, RevertMode.Unchanged).Wait(); // 未変更のファイルのみを破棄

Submit

変更を反映します。 changeSetのdescriptionはChangelistのdescriptionにリンクします。第四引数はSaveOnlyです。

var changeSet = GetChangeSet();
Provider.Submit(changeSet, assets, changeSet.description, true).Wait();

Submitを2回呼ぶとコネクションが切れることがあるので、2回呼ぶことは避けたほうが良さそうです。 コネクションが切れてしまった場合はProject Settingsからまた有効にする必要があります。

Example

実際のコードではasset配下は範囲が広すぎてパフォーマンス上の懸念があるため、作業範囲を限定します。 この例は、Zipファイルを展開し、Zip内に存在するファイルの追加・変更と存在しないファイルの削除をResources配下に反映します。

// Zipファイルの展開
using (var archive = new ZipArchive(stream, ZipArchiveMode.Read, true))
{
  var assetPath = Application.dataPath;
  var resourcePath = Path.Combine(assetPath, "Resources");

  var assets = new AssetList
  {
      new Asset(resourcePath)
  };

  // Resource配下をCheckout
  Provider.Checkout(assets, CheckoutMode.Asset).Wait();

  // Zipファイル内に存在するパスのキャッシュ
  var assetPathSet = new HashSet<string>();
  foreach (var entry in archive.Entries)
  {
      var destinationPath = Path.Combine(resourcePath, entry.FullName);
      var dirName = Path.GetDirectoryName(destinationPath);
      if (dirName != null)
      {
          // ディレクトリが存在しない場合エラーになるので作成する
          Directory.CreateDirectory(dirName);
      }
      entry.ExtractToFile(destinationPath, true);
      // フルパスではなくasset配下のパスが必要なためassetのパスを取得する。これはmetaファイルにも使用される。
      assetPathSet.Add(new Asset(destinationPath).assetPath);
  }

  // ディレクトリのmeta情報のために追加する
  assetPathSet.Add(new Asset(resourcePath).assetPath);

  // ディレクトリ配下の新規ファイルを追加する
  var addTask = Provider.Add(assets, true);
  addTask.Wait();

  // Zipファイルに存在しないファイルの削除
  var deleteAssets = new AssetList();
  // addTask.assetListはチェックアウトされているすべてのファイル名を取得できる
  deleteAssets.AddRange(addTask.assetList.Where(asset => !assetPathSet.Contains(asset.assetPath)));
  Provider.Delete(deleteAssets).Wait();

  // これらのファイルをデータ配下のChangelistに移動させる
  var changeSet = GetChangeSet("データ");
  Provider.Submit(changeSet, addTask.assetList, changeSet.description, true).Wait();

  // 未変更のファイルを取り消す
  Provider.Revert(assets, RevertMode.Unchanged).Wait();
}

まとめ

Unity EditorのVersionControlの使い方について紹介しました。 この順番で呼ばないとうまく動かなかったりハマりどころが多かったりしますが、自動化すると便利なことは多いのでぜひトライしてみてください。

Gitを使いたい。

リンク