Unityのプロジェクトはなぜ壊れるのか。原因と対策


(この記事は、Unity 2 Advent Calendar 2015の22日目の記事です)

ある程度Unityを使っている人は、こんな画面を一度は見たことがあると思います。
これはスクリプトやテクスチャが「行方不明」になっているときに起きるエラーで、Unityプロジェクトが壊れる原因はほぼこの「アセットが行方不明」ということが原因です。


うまく直せれば問題ないのですが、直せないとテクスチャやスクリプトをオブジェクトごとに手作業で設定しなおす羽目になります。下手すると何百というオブジェクトに設定しなおす必要がある上に、どこで行方不明がおきているかもわかりづらいので、なかなか厄介な現象です。手作業で修正するのは大変ですし、スクリプトが行方不明になったからと新しく設定しようとしても、インスペクターの値の設定なども全部やり直しになってしまいます。深刻な場合は復旧は絶望的で、実質的には「プロジェクトが壊れた」という状態になります。


とはいえ、世の中に何十万人といるUnityユーザーが、そんないつ壊れるともわからない物騒な状態で開発をしているかというとそうではありません。
この現象には原因と対策がちゃんとあります。少し長くなりますが、なるべく丁寧に解説していこうと思います。

注釈:宴では

この話は、宴で言うと主に「UIのテクスチャ更新・差し替え」と「宴のアップデート」のときのみ関係のある話です。
シナリオ内で使うキャラクターなどのファイル管理には関係ない話なので、その点は気にしなくて良いです。

なぜアセットは行方不明になるのか

「ファイルがなくなったなら置き直せばいい」と考えて、「同じ場所に同じ名前の同じファイルを置いたのにエラーがなおらない!」というケースもあると思います。
そもそも、ファイルをおき直すもなにも、同じ場所にちゃんとファイルがあることも多いでしょう。
そのケースこそまさにワナで、「アセットの行方不明とファイルパスとは関係ない」というのが重要なポイントです。

そもそも「アセット」ってなんだろう

いまさらですがUnityでいう「アセット」とは、「テクスチャファイルとかスクリプトファイルとか含めて、プロジェクトウィンドウ内に表示されてるものの総称」です。
「ファイル」とほぼ同じイメージですが、「フォルダ」も「アセット」の1つであったり、アセットには「metaファイル」というアセット情報ファイルが付属していたりと違いがあります。

ごく単純なプロジェクトを例にとります。
「Dir」というフォルダ、「icon」というスプライト、「Scirpt」というスクリプトファイル、そして「test」シーンという4種類のアセットがあります。

注釈:プロジェクトビューのレイアウト変更 

いったん話は逸れますが、プロジェクトビューは右上の設定アイコンから「One Columen Layout」を選択すると、このように一覧表示のように見せ方を変えることができます。
このほうが全体の見通しが良いので、以下のスクショではこちらのレイアウトを採用します

metaファイル

このプロジェクトを、Unity上ではなくエクスプローラーなどで確認するとこんな風になっています。

このmetaファイルがポイントです。

Unityのアセットは「ファイル(またはデレィクトリ)」と「同名のmetaファイル」のセットなのです
メタファイルには、アセットとして必要な情報が書き込まれています。

.metaファイルをテキストエディタなどで開いてみるとこうなっています。

バージョンの情報などが書いてありますが、重要なのは「GUID」です。

GUID

GUIDは「アセットに個別に割り振られたID」です。
基本的にはUnityは「ファイルパスではなく、このGUIDでアセットを管理しています。」
ここが重要ポイントです。

例えば、このようなテクスチャへの参照をもつコンポーネントがあったとします。

シーンファイルではこれはどう記録されているかというと、
該当するオブジェクトの部分

シーンファイルの先頭

つまり、「icon.pngのスプライトを参照している」ではなく
「GUID:f90e72619a94f374fa82cee3909170d6のスプライトを参照している」という風に記録されています。

これが「UnityはファイルパスではなくGUIDでアセットを管理している」ということの意味です。

注釈:GUIDとは 

そもそものGUIDの意味は、グローバル 一意識別子です。
つまり、プロジェクト内でのみ通じるIDではなく、「全世界のUnityプロジェクト内で唯一のID」が各アセットに割り振られているはずです。この仕組みがあるからこそ、Unityが配布するサンプルパッケージやアセットストアのアセットの互換性を保つことができるようになっているのだと思われます。

よくあるミス〜テクスチャを入れ替える〜

よくある「テクスチャを入れ替えたらMissingになった」というケースを例にとって説明します。

上書きするつもりで、別のアセットを作ってしまう 


このやり方では、名前が同じにしても、全く別のアセットになってしまいます。

同じ名前でも、GUIDが違うので「アセットが行方不明」になります。

対策

Unityの標準機能では、新しいファイルで古いアセットを上書きすることはできません。
(これができれば数多くのエラーがなくなると思うのですが・・・)

ですので、Unityの外でアセットのファイルを上書きする必要があります。

編集ソフトから上書き保存


Unityとしてはこちらが推奨と思われます。というのも「編集用のファイルをそのままUnityプロジェクト内に置く」のがおそらくUnityの想定しているワークフローと思われるからです。
Unityはpsdファイルもスプライト画像などとしてUnityのアセットと認識できますので、編集用のファイルと分けなくても済むようになっています。
・・・とはいえ、プロジェクト内のファイルサイズが肥大化するなどの欠点もあるので、実際の管理的にはこのアプローチが難しい場合も多いでしょう。

エクスプローラー上で移動して置換


少々手間ですが、もっともわかり易いやり方です。基本的にはこの方法を使うのが良いでしょう。

エディタ拡張をする

Unity上でのファイルの上書きは「Unityの標準機能では」できませんが、拡張すれば可能です。
テラシュールウェアさんにソースコードも公開されています。こちらを実装してみるのも良いでしょう。
個人的にはこの機能をUnityの標準機能として追加してほしいと思っています。

よくあるミス〜スクリプトの名前を変える〜

Unityではスクリプトもアセットの1つです。
「どのオブジェクトにどのコンポーネントがAddComponetされているか?」という情報も、スクリプトのパスではなくGUIDで管理されています。
なので、スクリプト(特にコンポーネント系)も「パスが同じなら良い」というわけにはいかなくなります。

これは、MonodevelopなどのIDE上でクラス名をリファクタリングするときにミスが起きやすいかと思います。


IDE上でクラス名を変えてしまうと一見問題ないようですが、スクリプトがMissingになってしまいます。


IDE上でクラス名を変えると、古い名前のアセットが消されて新しい名前の別アセットが生成されてしまうのです。

対策

対策としては、先にUnity上でアセット名を変えておくことです。



ただし、[System.serializable]などを使って、シリアライズ可能にしている非コンポーネントクラスの名前を変更した場合、Missingにこそなりませんが、データは全て初期状態にリセットされてしまいますので注意してください。
これは、アセット管理とは別のシリアライズの話になります。

よくあるミス〜アセットストアのアセットのアップデート〜

アセットストアのアセットをアップデートする場合は、DLしたアセットのフォルダを移動しているかどうかでけっこう話が変わります。
個人的には「DLしたアセットは移動しないでそのまま使うもの」だと思っていたのですが、「アセットストアのアセットは整理用のフォルダ以下に移動する」という人も多いらしく、気になってTwitter上でアンケートをとってみました。

結果は、ちょうど5:5くらいの割合でした。
実は「フォルダを移動する」という場合は少し面倒なことが起きます。以下で、それぞれの場合を説明していきます。

フォルダを移動しない場合

アセットストアのアセットをDLしたまま移動していない場合は、さほど問題がおきることはありません。
ただし、それでも「前のバージョン内あるアセットの一部が消えたりするアップデート」の場合は注意が必要です。アセットストアのアセットのバージョンアップ・インポートでは、「アセットを消す」ということは自動でやってくれません。その場合、アセットストアには注意を促す仕組みがありませんので、アセット製作者のWebサイトなどの注意書きを見て、手動でアセットを消す必要がでてきます。
「宴」の場合はリリースノートで告知をしています。

アセットによってその告知方法はまちまちでしょうし、エラーが出るたびに製作者の告知を探すのも手間です。
また「一部のアセットの名前が変わったり移動した」というケースでもエラーがおきやすいのですが、これはさらに複雑なケースで、手作業で修正はちょっと難しいかもしれません。
こういった場合は以下の対策が必要になります。

対策

アップデートでエラーが出た場合は、「一度アセットをフォルダごと削除してからインポートする」ことで大抵は解決できます。
ただし、アセットを消して新しくインポートするまでの間は、Unityエディタはエラーが出た状態になっていると思われます。
エラーが出た不安定な状態のままシーンやプレハブを上書き保存したりしないように注意してください。
迂闊にこの状態で保存してしまうと、シーンが壊れたまま保存されてしまう恐れがあります。

フォルダを移動してある場合

この場合は、上記の「一部のアセットが消えた場合」のほかに、「新しいアセット追加された場合」も面倒なことになります。
追加のない更新のみのアップデートであれば、アセットが移動していてもGUIDが同じアセットのファイルがちゃんと更新されるので問題はありません。
しかし、新しいアセットが追加されている場合は、移動前のフォルダを作り直して、そのフォルダ以下に新しいアセットが追加されてしまいます。
追加されたアセットを手作業で移動すれば良いといえば良いのですが、フォルダ構成が複雑なアセットの場合は面倒なことになってしまいます。

対策

・アセットをフォルダごとを削除
・アップデートをインポート
・再びフォルダを移動
とするのが良いかと思います。

ですので、アセット製作者としてはそもそもフォルダ移動はお勧めしません。
「アップデートされる可能性のあるアセットは移動しない」ことを推奨したいと思います。
ただ、これはあくまで個人的な意見です。Unityのアセットストアのガイドラインには特に記述が見当たらないため、公式としては推奨でも非推奨でもないと思います。
また、アップデートを予定していないものや、構造が単純なもの(テクスチャやサウンドのみの素材ファイルのみなど)であれば自由に移動しても大丈夫だと思います。

バックアップは非常に重要

これまで述べてきたように、アセットが行方不明になるミスはなかなか避けられません。
しかも、一度消失してしまったアセットのGUIDをバックアップなしに復旧させるのはほぼ不可能です。
そのため、バックアップは非常に重要になります。

初歩的なバックアップ

Unityのプロジェクトを構成するファイルは一見沢山ありますが、基本的にはAssetsフォルダとProjectSettingsフォルダあれば大丈夫です。

また、Assets以下の全てのファイルを1つのファイルにパッケージ化することもできます。

ただし、Unityエディタのパッケージ機能ではProjectSettingsはバックアップできませんので注意してください。
宴ではエディタ拡張したツールで可能です

Unity公式のバージョン管理ツール UnityCollaborate

一番オススメなのは、Unity公式のバージョン管理ツールである「Unity Collaborate」を使うことです。

使い方はUnity公式ドキュメントにまとまっていますが、基本的には3クリックくらいでセットアップできます。

現在(Unity5.6.1)はまだβ版なのですが、もうだいぶ使いやすくなっています。
公式ツールなので、metaファイルのコミット漏れはおきないのでその点は安心して使えます。
基本的には、ウィンドウ右上のCollaborateボタンから使うのですが、この場合は「今のプロジェクトの変更をすべて丸ごとサーバーに送る」しかできません。

Unity5.6からは「プロジェクトビュー―内で変更のあったアセットアイコンは強調される」
「右クリック>Collaborate>publish」で「選択したアセットだけをサーバーにアップ」
という公式ツールならではの直感的なインターフェースが追加されてかなり使いやすくなっています。

自分でサーバーを用意しなくても使え、ボタン一つでプロジェクトのバックアップが取れます。
自分のように1人開発で使っている場合でも便利に使えるので、最近はずっとこれを使っています。
履歴はこんな感じで見れるので、「特定のバージョンに丸ごと巻き戻す」のであればボタン一つで可能です。

ほかにも、Unity CloudBuildにもボタン一つで同期が可能なので、寝る前にプロジェクトをUnityCollaborate でバックアップしておけば、
朝には「全プラットフォームのビルドチェック」と「ビルドしたアプリのDLリンクの通知の受信」が可能になります。
通知はSlackでも受け取れるので、スマホアプリなどでDLリンクを開いて直接開発アプリをDLしてチェック…みたいなことが可能です。
後述するGitなどでも同じことは可能ですが、Git自体かなり習熟が必要になります。
その点Unity Collaborate&Unity CloudBuildは手軽に設定できるので、「Gitとかよくわからない」という人こそオススメかと思います。
ただしβ版ということもあり、たまに微妙な動作をすることもあるので、その点シビアな使い方をしたい人はちょっとまだ様子見したほうがいいかもしれません。
(特に、削除したファイルと同名のアセットを新たに作る・・・というのを一度にやる場合に弱そうな気が)

一般的なバージョン管理ツール

ある程度の規模のチームだと、バージョン管理ツールを使っていることが多いと思います。
慣れないと初期設定は大変ですが、公式ドキュメントのほかにもやり方をまとめたブログもたくさんあります。参考にしてください。
Unity で外部のバージョン管理システムを使用
Unity で GitHub しよう!
UNITYをSUBVERSIONで管理し、複数人で編集する

一般的なバージョン管理ツールを使う場合の注意

バージョン管理ツールを使うときに起きがちなのが、「.metaファイルのコミット漏れ」というケースです。
チームでバージョン管理ツールを使っている場合は「間違ったファイルをアップロードしたら何が起きるかわからない」という恐れから、「自分が触った覚えのないファイルは怖いのでコミットしない」「metaファイルはコミットしないほうが良い」と思ってしまう人が出てくることを想定すべきでしょう。
これまで説明してきたように.metaファイルにはGUIDをはじめアセットに必要な情報が保存されています。

必ずアセットのファイルと一緒に同名の.metaファイルもコミットするようにチーム内に周知しましょう。

まとめ

長くなりましたので、最後に要点だけをまとめます。

アセット操作の対応まとめ

目的 やり方
ファイルの上書き更新 Unityエディタの外で行う
ファイルの新規追加 Unityエディタ内でも、外でもどちらでも良い
アセットの移動、名前変更、削除 Unityエディタ内で行う
スクリプトのクラス名変更 Unityエディタ内でアセット名を変えた後に、ソースコード内の記述を変更

アセットストアのアセットのアップデート

・基本的には、アップデートの予定があるものはフォルダを移動しないことを推奨
・アップデートでエラーが出た場合は一度アセットをフォルダごと削除してからインポートする

バックアップは重要

・「ProjectSettingsフォルダ」と「Assetsフォルダ」をバックアップする
・Assetsフォルダ以下はパッケージファイルを作ることもできる
・なるべくならUnityCollaborateなどのバージョン管理ツールを使う
・外部のバージョン管理ツールを使う場合には、.metaファイルをコミットするのを忘れないこと

アセットはパスではなくIDで管理されている

さて、アセットはパスではなくID(GUID)で管理されているのがわかったと思います。
ですが、そもそもなぜそのような仕組みなのでしょうか。パスで管理すれば、事故が少なくなるようにも思えますがどうでしょうか。
仮にIDではなくパスで管理されていたら、アセットの名前を変えたり場所を移動するたびに、そのアセットを使っている全ての箇所に変更を反映しないといけません。こちらのほうが遥かに手間が多くなりますし、アセットストアのアセットやサンプルパッケージの互換性を保つのも困難になるでしょう。
そして、Unityは「アセットをGUI上で設定できる」というのが長所でもあります。ユーザーとしては、「この場所にあるアセット」ではなく「このアセット」という漠然としたイメージで設定してるため、「このIDのアセット」というID管理のアプローチが自然だったのではないかと思っています。
アセットがID管理されていること自体は優れたアプローチです。そして、もしエラーが起きたときにはこのまとめを思い出していただければと思います。