Chapter 06

Prefab と Scriptable object

sh1ch
sh1ch
2020.09.23に更新

Prefab と Scriptable object

43. すべてのものに Prefab を使うこと

シーンの中で Prefab(または Prefab の一部)であってはならないゲームオブジェクトはフォルダーだけです。

一度しか使用されないユニークなオブジェクトであっても、Prefab にするべきです。これによって、シーンを変更せずに(オブジェクトの)変更を簡単にすることができます。

旧 Tips 16 と同じ内容だと思います。

44. Prefab を Prefab にリンクし、インスタンスをインスタンスにリンクしないこと

Prefab へのリンクは、Prefab をシーンにドロップしたときに維持されます。インスタンスへのリンクは維持されません。シーンへの設定を減らして、Prefab にリンクさせることで、シーンを変更する必要を減らすことができます。

可能な限り、インスタンス間のリンクは自動的に確立します。もし、インスタンス間にリンクの必要がないなら、プログラム(コーディング)からリンクを確立します。たとえば、Player の Prefab は GameManager が起動したときに、自身を登録したり、インスタンスを見つけて登録すること。

旧 Tips 18 と同じ内容だと思います。

45. なにかスクリプトを追加したいとき、Prefab のルートにメッシュを配置しないこと

メッシュから Prefab を作るときは、最初にメッシュを空の game object の親として、それをルートにします。

スクリプトはメッシュのノードではなく、ルートに配置します。そうすれば、インスペクターで設定を見失うことなく、メッシュを別のメッシュに置換することもやりやすくなります。

旧 Tips 19 と同じ内容だと思います。

46. 共有するコンフィグ(設定)データには、Prefab の代わりに Scriptable object を使用すること

そうするとこうなります:

  • シーンが小さくなる
  • (Prefab のインスタンス上の)ひとつのシーンに対して誤って変更を加えることができない

47. レベルを表すデータには、 ScriptableObject を使用すること

レベルを表すデータには、XML や JSON で保存されることが多いですが、代わりに ScriptableObject を利用すると、いくつかの利点があります:

  • エディターで編集できる。これによって、データの検証が楽になって、技術的な知識のないデザイナーにもフレンドリーになる。さらに、カスタムエディターを使っておくことで、編集がもっと楽になる。
  • データの read/write や解析を気にする必要がなくなる。
  • 分割したり、ネストしたり、結果として得られる Assets の管理が楽になるので、大きなコンフィグからレベルを構成するのではなく、積み木 (building block) からレベルを構成します。

48. ScriptableObject を利用して、インスペクターの中で振る舞いを設定すること

Scriptable Object は通常、データを設定することに使いますが、「メソッド」をデータのようにして設定することもできます。

Enemy という型があって、それぞれの Enemy がいくつも SuperPower というデータを持っているシナリオを検討してみます。

これらの基礎になるクラスを作成して、Enemy クラスの中にそれらのデータをリストに持たせることができます……が、カスタムエディターでなければ、異なる SuperPower(それぞれ独自のプロパティを持つ)リストをインスペクターの中で設定することができません。

しかし、SuperPower をアセット(ScriptableObject として実装すれば)、(設定を)できるようになります。

こんな感じになります:

public class Enemy : MonoBehaviour
{
   public SuperPower superPowers;

   public UseRandomPower()
   {
       superPowers.RandomItem().UsePower(this);
   }
}

public class BasePower : ScriptableObject
{
   virtual void UsePower(Enemy self)
   {
   }
}

[CreateAssetMenu("BlowFire", "Blow Fire")
public class BlowFire : SuperPower
{
   public strength;
   override public void UsePower(Enemy self)
   {
      ///program blowing fire here
   }
}

このパターンに従う際は、いくつか注意するべきことがあります:

  • ScriptableObject を抽象化することはできません。代わりに、具体的な base クラスを使って、抽象化するべきメソッドは NotImplementedException の例外を投げるようにします。また、abstract を定義して、抽象化するべきクラスやメソッドにマークをつけることもできます。
  • ScriptableObject はシリアライズできません。しかし、ジェネリックな base クラスを使用して、ジェネリックを指定したサブクラスのみをすべてシリアライズすることができます。

49. 特殊化した Prefab に ScriptableObject を使用すること

もしも、2つのオブジェクトの構成が一部のプロパティにだけ違いがあるなら、シーンの中に2つのインスタンスを配置して、インスタンスの上でそれらのプロパティを調整するのが普通です。

大抵は異なる2つのプロパティを持つオブジェクト同士を、別々の ScriptableObject として分けたほうがよいです。

(そうすると)、柔軟さを得ます:

  • 特殊化したクラスの継承を使用して、型の異なるオブジェクトに特定のプロパティを与えることができます。
  • シーンの設計はより安全になります。(オブジェクトを目的の型にするために、すべてのプロパティを調整しないし、適切な ScriptableObject を選択するだけになります)
  • コードを通じて実行時にこれらのオブジェクトを操作するのが、もっと楽になります。
  • 2つの型のインスタンスを複数持っているなら、変更を加えたときにそれらのプロパティが常に等価(一貫してるもの)であることがわかります。
  • コンフィグの変数のセットを、混ぜたり・合わせたりできるセットに分けることができます。

設定の簡単な例を紹介します:

[CreateAssetMenu("HealthProperties.asset", "Health Properties")]
public class HealthProperties : ScriptableObject
{
   public float maxHealth;
   public float resotrationRate;
}

public class Actor : MonoBehaviour
{
   public HealthProperties healthProperties;
}

特殊化したものの数が多いときは、特殊化したものを通常のクラスとして定義して、そのリストを ScriptableObject の中で用いて、それを取得できる適切な場所(GameManager クラスなど)にリンクをしておくとよいです。

安全に、速く、便利にするためにはもうすこし貼り付ける(馴染ませる)必要があります。以下に小さな例を示します:

public enum ActorType
{
   Vampire, Wherewolf
}

[Serializable]
public class HealthProperties
{
   public ActorType type;
   public float maxHealth;
   public float resotrationRate;
}

[CreateAssetMenu("ActorSpecialization.asset", "Actor Specialization")]
public class ActorSpecialization : ScriptableObject
{
   public List healthProperties;

   public this[ActorType]
   {
       get { return healthProperties.First(p => p.type == type); } //Unsafe version!
   }
}

public class GameManager : Singleton
{
   public ActorSpecialization actorSpecialization;

   ...
}

public class Actor : MonoBehaviour
{
   public ActorType type;
   public float health;

   //Example usage
   public Regenerate()
   {
      health 
         += GameManager.Instance.actorSpecialization[type].resotrationRate;
   }
}

この内容は最近 Unity Blog に載っていたものと近い気がしました。まとめたのが「これ」。インスペクターでの設定機能を強化することを好む Tips が多い印象です。

50. CreateAssetMenu の属性を利用して、自動的に ScriptableObject の作成をするメニューを追加すること

本文はありません。タイトルだけで全部です。Project ビューを右クリックして ScriptableObject を追加できるようにすることだと思います。