2013年3月17日日曜日

Missing Scriptsエラーの解消(前編)

Unity使っててプロジェクトをインポートするときなどに稀に Missing (Mono Script) などと表示されていることがある。

慣れている方なら「仕方ないな」といって手動で正しいスクリプト見つけて修正していくのだが、これをエディタ上で自動的に修正してくれるパッケージがあったので紹介したい。

試す前にバックアップをとっておくことをオススメするが、再現ビデオにあるとおりうまく行った場合は複数のスクリプトを使用する変数から自動判断して修正してくれる。
(もし変数が同じスクリプトが複数ある場合は、選択するダイアログがある)

プログラムをダウンロード

メニューから Window > Fix Missing Scripts とすることで修正用のウィンドウが開く。

まずまず便利といったところだろうか。Unity Gemsというサイトでこのプログラムのロジックまで説明してあるので紹介したい! (ちょっと長いので前編・後編に分ける)

----------
http://unitygems.com/lateral1/
November 14, 2012

次のケースでこのチュートリアルが有効活用出来ます:

  • Missing Scripts エラー を Unity で修正する方法を理解したい
  • 複雑な問題を切り分けるのにデバッグを使うアプローチを理解したい
  • SerializedObjects および SerializedProperties の理解を深める
  • Bodging テクニックで出来ることを知りたい


はじめに


 "Missing MonoBehaviour" エラーは Unity でプログラミング始めて間もなく遭遇するエラーだ。インポートやプロジェクトの移動を行った後、あるいはウッカリ変更した何かによって、突然コンソールに表示されてスクリプトの参照が壊れてしまう現象だ。スクリプトそのものをリストアしても参照は復旧しない。
これが一つや二つのオブジェクトなら良いが、これが100だったら本当に悪夢そのものだ。

私もこの問題に遭遇したが幸いに自分なりの解決方法を見つけた。自分が辿った手順が何か別の複雑な問題を解決するヒントになるだろうと思ったので共有することにした。こういう問題では世界中を敵にしたような絶望を感じがちだが糸口はあるのだ。

問題の解決方法


この問題の解決は四つのステップに分かれている。

  1. Missing Scripts の発生したゲームオブジェクトおよびプレハブの特定
  2. 全ての利用可能なスクリプトを特定
  3. データ (インスペクタで表示されているフィールド) を利用可能なスクリプトのフィールドと比較
  4. 既存の壊れたオブジェクトを修正して正しいスクリプトを参照させる

Missing Scripts の発生したゲームオブジェクトおよびプレハブの特定


さて最初のステップは容易だろう。Unityで現在のシーンやプロジェクトビューにある全てのゲームオブジェクトは一覧に出来る。良くある方法で、FindMissingScripts スクリプトで全てのオブジェクトを選択する必要がある方法は不便なのでここでは別の方法を考える。

全てのオブジェクトを見つけるには Resources.FindAllObjectsOfType を使用すれば良いだけだ。これでリソースを見つけられるだけでなく、それに加えてUnity 独自のものも、全て見つけられるといって良い。まずは全てのゲームオブジェクトを見つけて、それからそれらの全てのコンポーネントを見つけて Null でないか見つけることになる。

Linq では魔法のように一行で記述することも出来るが、可読性のために分割してみる:
brokenList =
    // 全てのゲームオブジェクトを見つける
    Resources.FindObjectsOfTypeAll(typeof(GameObject))
        // FindObjectsOfTypeAll は Object[] を戻す
        // このため GameObject enumeration ひ変更
        .Cast()
        // 各々のオブジェクトをチェック
        .Where(
            // 全てのゲームオブジェクトを取得
            c=>c.GetComponents()
                // null を戻すものごあるか確認
                .Any(o=>o==null)
        )
        // これをリストに型変換
        .ToList();
これでプロジェクトやシーンでの有無にかかわらず、壊れてるスクリプトを見つけることが出来る。

全ての利用可能なスクリプトを特定


これも同じような方法で対処出来る容易なものだ。MonoScript 型のオブジェクトを全て見つけるだけだ。ここでは条件がもう一つだけある。次のステップではインスペクタで表示されているプロパティにもとづいて Missing Scripts とマッチングを行う。すなわちスキャンしている全てのスクリプトのプロパティを知る必要があるということだ。ここは時間をじっくりかけて SerializeField をセットしてある public および private フィールドを取得したうえで 参照を速やかに行えるように Dictionary に格納する。
// 候補となる全てのスクリプト
static List scripts;
// スクリプトをスキャンしたかどうか

static bool _initialized = false;

// スキャンされたスクリプトを保持
class ScannedScript
{
    //全ての serialized プロパティ
    public Dictionary properties;
    // インスタンス id
    public int id;
    // スクリプト自身
    public MonoScript script;
}

// これらスキャン完了したスクリプトを初期化
void Initialize()
{
    if(_initialized)
        return;
    _initialized = true;

    ScanAll();

}

void ScanAll()
{
    // 全てのスクリプトを取得
    scripts = Resources.FindObjectsOfTypeAll(typeof(MonoScript))
        // MonoScript のコレクションとする
        .Cast()
        // システム スクリプトでないことをチェック
        .Where(c=>c.hideFlags == 0)
        // コンパイル完了であり、
        // クラスが取得出来ることをチェック
        .Where(c=>c.GetClass() != null)
        // 各々についてスキャンされたスクリプトを作成
        .Select(c=>new ScannedScript { id = c.GetInstanceID(),
            script = c,
            // プロパティは全て [SerializeField] をセットし
            // public および private とする必要がある
            properties = c.GetClass().GetFields(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic)
                .Where(p=>p.IsPublic || (!p.IsPublic && p.IsDefined(typeof(SerializeField), false)))
                .ToDictionary(p=>p.Name)
        })
        .ToList();
}
実行することで結果として、候補となるスクリプトが得られ、各々のスクリプトには シリアライズされた dictionary が得られる。

テクニカルかつ高度だがわかり易い。
------------

後編に続くぜ!

関連記事

Missing Scriptsエラーの解消(後編)
http://gamesonytablet.blogspot.com/2013/03/missing-scripts_17.html

0 件のコメント:

コメントを投稿

ブックマークに追加

このエントリーをはてなブックマークに追加

自己紹介

自分の写真
Unity3D公式マニュアル翻訳やってる人がスマホ(iPhone, Android)のゲーム開発しています。気軽に面白く初心者が遊べる内容がモットー。Blogでは開発情報をひたすら、Twitterではゲーム作成の過程で参考にしている情報を中心につぶやきます

ページビューの合計

過去7日間の人気投稿