Unity を検索

Preset でワークフローの改良、意思決定の検証、エラー回避を実現する

2019年10月11日 カテゴリ: テクノロジー | 10 分 で読めます
取り上げているトピック
シェア

Is this article helpful for you?

Thank you for your feedback!

Preset を使用すると、Unity のなかにあるほぼすべて(コンポーネント、インポーター、マネージャー)のデフォルト状態をコーディングなしでカスタマイズできます。Preset は、反復作業の合理化や設計上の決定事項の検証から、チーム内の標準やプロジェクトテンプレートの強化まで、あらゆる規模の開発チームにメリットをもたらすことができます。この記事では、基本機能、ヒントとコツ、高度なユースケースなど、Preset によって提供される多くの機能について掘り下げていきます。

まず第一に:Preset って何?

基本的に、Preset(プリセット)とは指定のコンポーネント、インポーター、またはマネージャー(実際には、Unity.Object を拡張したものならすべて)のデフォルトをオーバーライドすることができるアセットです。あなたのプロジェクトでは、リジッドボディのデフォルトの質量を 1 から 10 に変えて、重力はオフに設定する方が効率的ですか?では、プリセットでそのように設定しましょう。

ヒエラルキーでインポートまたはインスタンス化されるもの(テクスチャ、FBX ファイル、ライト、カメラ、またはリジッドボディのような MonoBehaviour コンポーネント)にプリセットを使用できます。

Preset は、ランタイム機能ではなく、オーサリングワークフローを改良するために作られたエディター機能です。プリセットはバイナリに同梱して配布されないため、GameObject.AddComponent() ではプリセットに設定したものと同じデフォルト値を適用できません。ただし、編集モードで実行される ObjectFactory API は Preset を サポートするため、ObjectFactory.AddComponent() はプロジェクトのプリセットが適用されているという想定の下に動作します。

プリセットを作成するには、同じタイプの既存のインポーターまたはコンポーネントに移動し、インスペクターで調整してから、右上の Preset アイコンをクリックするだけです。ウィンドウが開き、「Save Current To…」を選択するとアセットにおける設定値が保持されます。

プリセットを使ってすばやく作業するためのヒントとコツ

以下にプロジェクトでプリセットを適用する簡単なやり方をいくつかご紹介します。

Preset アイコンをクリックする

これは(うまくいけば)明らかに分かりやすい方法です。インスペクターで Preset アイコンをクリックすると、同じコンポーネントまたはファイルの Type のプリセットを選択できます。

インスペクターにドラッグアンドドロップする

あるコンポーネントの値を変更したい場合は、Project ウィンドウからインスペクターのコンポーネントにプリセットをドラッグするだけです。

プリセットをインスペクターにドラッグして、新しいコンポーネントをゲームオブジェクトに追加することもできます。

ヒエラルキーにドラッグアンドドロップする

プリセットを用いて、ヒエラルキー内に新しいオブジェクトを作成することもできます。Project ウィンドウからヒエラルキーに直接ドラッグして、プリセットがひもづけられたコンポーネントを含む新しいゲームオブジェクトをすばやく作成できます。

このワークフローはプレハブを使用した作業に少し似ていますが、プリセットはプレハブとは異なることにご留意ください!プリセットは、意図的にそのプリセットから作成されたオブジェクトとのリンクを保持しないように作られています。

複数のプリセットをドラッグアンドドロップして、関連するすべてのコンポーネントを含むゲームオブジェクトを生成することもできます!

プリセットを管理する

プリセットは Preset Manager と連携して機能します。Preset Manager の UI は Edit → Project Settings… で表示されるウィンドウの中にあります。

デフォルトを追加するには、Preset Manager を開き、「Add Default Preset」をクリックしてから、デフォルトにする Type を選択します。最後に、「None (Preset)」の右側にある丸型アイコンをクリックし「Select Preset」ポップアップウィンドウから Type に一致するプリセットアセットを選択します。

以下に示すのは、このブログ記事の冒頭で作成した RigidBody 10 のプリセットです。Preset Manager のウィンドウにいくらかデータが入力されていることがわかります。この設定がこのプロジェクトの RigidBody のデフォルトとして用いられます。

この設定では作成されたすべてのリジッドボディはデフォルトで質量が 10 になり、重力が無効になります。

ここで Preset Manager がどのようにして Type ごとに複数のデフォルトを許可するのかをチェックしてみましょう。プリセットのアイデアを強力に拡張したこの機能(Unity 2019.3 の新機能)では、名前に基づいて識別できる複数の「デフォルト」を持つことが可能になりました。上記の画像に含まれるカメラを見ると、空白のフィルタが Camera Gameplay プリセットを使用していることが分かります。これはデフォルトですべてのカメラが、このプリセットを使用することを意味します。ただし、UI カメラに紐づけて使うために、フィルタ「UI」を Camera UI プリセットに連携しました。 この設定によって、「UICamera」、「Camera UI」、「HUDUICamera」のように、ゲームオブジェクト名のどこかに「UI」を使ってカメラを作成した場合、Camera UI プリセットは、デフォルトのものより優先して使用されます。

Preset Manager に関する注記:

  • 文字列照合では、大文字小文字を区別しないため、上記「UI」の例の「GuideCamera」のような名前にご注意ください。一致する「UI」文字列は含まれますが、意図したものとは異なる場合があります!
  • オブジェクトが単一タイプの複数のデフォルトにマッチする場合、リスト内で最後にマッチしたプリセットが優先して適用されます。
  • コンポーネントの「Reset」を選択すると、コンポーネントをデフォルトにリセットすることができます。
  • インポーターについて、ゲームオブジェクト名ではなくファイル名で、どのフィルターが使われるかを動作させます。

マネージャーにプリセットを適用する

プリセットの大きな利点は、主要なアートとデザインの決定に関してチーム内で足並みを揃えられることです。ライトの色、リジッドボディの重さ、法線マップをインポートするための基準など、揃えるべきことはたくさんあります。各種設定のマネージャーにプリセットを適用すると、こうした問いに対する答えをプロジェクトレベルに展開することができます。ここでは Physics Manager と Tags and Layers Manager の例を見てみましょう。

この例では、レイヤー間(敵は他の敵との接触がなく、味方および味方の弾は他の味方の弾と接触しない)の特定の物理インタラクションの設定を確実に行うためにかなり手間がかかっています。これらの決定は、後続プロジェクトや関連性の高いプロジェクトにも影響する場合がありますが、プリセットを使用することで、決定事項を記憶し、さらに他のプロジェクトへと決定事項を共有することが簡単になります。

以下のアニメーションでは、2 つのマネージャーを保存して別々のプロジェクトにそれらを適用する方法を確認できます。これは、組織内の新しいプロジェクトをブート処理する際に大いに役立つでしょう。

API も公開されています

ほとんどの場合、ここまでで紹介したビジュアルインターフェイスで十分ですが、たとえばインポートパイプラインを作成している場合やツールを構築している場合は、プリセットをプログラムで制御できるようにすると便利です。これを可能にして生産性を高めていただけるように、Presets API を公開しました。

次の例はすべて Github 上で公開されています。

簡単な例から始めましょう。 単一のライトから 1 つ以上のプロパティをシーン内のすべてのライトに適用するツールを追加したいとします。

[MenuItem("CONTEXT/Light/Replicate Color in Scene")]
public static void ApplyAllLights(MenuCommand command)
{
    // 選択中のライトを取得する
    var referenceLight = command.context as Light;

    if (referenceLight != null)
    {
        // 取得したライトからプリセットを作成する
        var lightPreset = new Preset(referenceLight);

        // 参照しているライトのあるシーン内にあるすべてのライトを取得する
        var allLights = referenceLight.gameObject.scene.GetRootGameObjects()
            .SelectMany(r => r.GetComponentsInChildren<Light>(true));

        // 取得したすべてのライトに適用するシリアライズされたプロパティを選択する
        var propertyToApply = new[] { "m_Color" };

        // すべてのライトの選択されたプロパティについてのみプリセットを適用する
        foreach (var light in allLights)
        {
            lightPreset.ApplyTo(light, propertyToApply);
        }
    }
}

このコードは、参照オブジェクト、ライトを受け取ってから、オンザフライでプリセットを作成します (var lightPreset = new Preset(referenceLight))。次に、referenceLightからm_Color を使用して、その値をシーン内の他のすべてのライトに適用します。

以下に Unity 内での新しいツールの動作の例を示します。

これは、部分プリセットのプログラムの例を示しています。つまり、コードはプリセットの完全なプロパティのサブセットのみを取得して適用します。この機能は、近いうちに UI で公開したいと思っています。

次の例では、プロジェクトフォルダー内の同じ Type のすべてのアセットに単一のプリセットを適用する方法を見ていきましょう。

[MenuItem("CONTEXT/Material/Replicate Material Color in Folder")]
public static void ApplyAllMaterialsInFolder(MenuCommand command)
{
    // 選択中のマテリアルを取得する
    var referenceMaterial = command.context as Material;

    if (referenceMaterial != null)
    {
        var assetPath = AssetDatabase.GetAssetPath(referenceMaterial);
        if (!string.IsNullOrEmpty(assetPath))
        {
            // 取得したライトからプリセットを作成する
            var materialPreset = new Preset(referenceMaterial);

            // 同じフォルダーにあるマテリアルアセットをすべて取得する
            var assetFolder = Path.GetDirectoryName(assetPath);
            var allMaterials = AssetDatabase.FindAssets("t:Material", new[] { assetFolder })
                .Select(AssetDatabase.GUIDToAssetPath)
                .Select(AssetDatabase.LoadAssetAtPath<Material>);

            // 最初のカラーのエントリを選択する。標準シェーダーの場合、このエントリは _Color になる
            var propertyToApply = new[] { "m_SavedProperties.m_Colors.Array.data[0]" };

            // すべてのマテリアルの選択されたプロパティについてのみプリセットを適用する
            foreach (var material in allMaterials)
            {
                materialPreset.ApplyTo(material, propertyToApply);
            }
        }
    }
}

以前の通り、今回は選択された Material である参照オブジェクト (var referenceMaterial = command.context as Material) を取得し、それを使用してオンザフライで (var materialPreset = new Preset(referenceMaterial)) を作成します。次に、このマテリアルが存在するフォルダーを見つけ、同じフォルダー内のすべてのマテリアルに Color プロパティを適用します。

繰り返しますが、これがエディターでどのように再生されるかを見てみましょう。

最後に、もう少し複雑な例を見てみましょう。このスニペットでは、選択されたライトを取得しています。ライトのデフォルトがすでに存在するかどうかに応じて、そのプリセットを作成するか、デフォルトのプリセットを選択に合わせて更新します。以下のコード内のコメントを読むと、どのように動作するのかについて理解が深まります。


public static void UpdateOrAddLightDefaults(MenuCommand command)
{
    // 選択中のライトを取得する
    var referenceLight = command.context as Light;

    // 対象のライトに適用するデフォルトのプリセットのリストを取得する
    var defaults = Preset.GetDefaultPresetsForObject(referenceLight);

    if (defaults.Length == 0)
    {
        // ここの段階ではデフォルトがないので、新たに作る
        var defaultLight = new Preset(referenceLight);

        // 作成したデフォルトをセーブする場所をたずねる
        var path = EditorUtility.SaveFilePanelInProject("Light のデフォルトプリセットの作成",
                "Light", "preset", "新しいデフォルトを保存するフォルダーを選択");

        // 選択したパスにプリセットがある場合は、そのプリセットの instanceID が単純な置換によって変更される
        // この機能を利用して値だけを置き換え、既存のアセットを参照しているオブジェクトが壊れないようにする
        var existingAsset = AssetDatabase.LoadAssetAtPath<Preset>(path);

        if (existingAsset != null)
        {
            EditorUtility.CopySerialized(defaultLight, existingAsset);
            defaultLight = existingAsset;
        }
        else
        {
            AssetDatabase.CreateAsset(defaultLight, path);
        }
        // 既存のデフォルトリストを読み込む
        // フィルターを利用したために特定のゲームオブジェクトを指している設定を壊さないようにするため
        var existingDefault = Preset.GetDefaultPresetsForType(defaultLight.GetPresetType()).ToList();

        // フィルターを使わずに新しいプリセットをリストの先頭に入れる
        // こうするとデフォルトのないライトに新しいプリセットが適用される
        existingDefault.Insert(0, new DefaultPreset("", defaultLight));

        // 新しいリストをライトのデフォルトに設定する
        Preset.SetDefaultPresetsForType(defaultLight.GetPresetType(), existingDefault.ToArray());
    }
    else
    {
        // 最後にあるデフォルトに対してのみ値の更新を行う
        // 他のプリセットはすでに他のオブジェクトに適用されている可能性があるため
        // またその場合には他のプリセットの値を変えないようにするため
        var lastPreset = defaults.Last();
        lastPreset.UpdateProperties(referenceLight);
    }
}

Unity を使うと、これがどのように動作するかについて以下のアニメーションでご覧いただけます。最初のパススルーでは、新しいプリセットの作成を通して手順を説明していますが、2 番目のパスでは既存のプリセットを更新するだけです。

次のステップ:Preset は皆さんが望んだとおりの機能になります!

(前述のように)部分プリセット、改良されたフィルタリング機能、およびフォルダーへのプリセットの適用など、私たちは Preset の今後のバージョンに対して進めているアイデアがいくつかあります。ただし、最初に述べたように、Preset は Unity を皆さまの望むように動かすための機能です。そのため、皆さまからのご意見をぜひ伺いたいと思っています。この機能をワークフローの改良につなげるアイデアについて、皆さまからのコメントをお待ちしております!

2019年10月11日 カテゴリ: テクノロジー | 10 分 で読めます

Is this article helpful for you?

Thank you for your feedback!

取り上げているトピック