ScriptableObject

Unity Engine

Scriptable Objectという名前はUnity Editor上でオブジェクトを作れるC#Scriptという意味でしょうか。ScriptableObjectは色々な目的で役に立つ基底クラスです。

  • 共有できるデータをScriptableObjectの派生クラスのインスタンスに持たせることでメモリを節約できる。
  • Sceneをまたがった情報をScriptableOjbectの派生クラスのインスタンスに持たせることができる。
  • ScriptableObjectの派生クラスのインスタンスはSceneをまたぐManagerとして使うことができる。

似たようなものに静的クラス、静的メンバがありますが、これらはInspectorに表示されません。また、静的クラスは派生できません。

ScriptableObjectはシングルトンより考え方がシンプルです。

ScriptableObjectはComponentではないのでGameObjectにアタッチできません。

共有データとしてのScriptableObject

Prefabから多くの複製を行うと大量のメモリを使います。

Prefabから共有できるデータを切り出してScriptableObjectの派生のインスタンスで共有すればメモリを節約できます。

SceneをまたがるScriptableObject

SceneをまたがりGameを管理するGameManagerや複数のSceneで使用したいデータとしてScriptableObjectが使えます。

Sample

小さなサンプルを作成してみます。

  • SceneAでは赤いCubeの敵、SceneBでは緑のSphereの敵、SceneCでは青いCapsuleの敵が出現するとします。
  • 大量の敵の数の代わりに3つ、敵の膨大なデータの代わりのサンプルとしてColorデータを持つものとします。
  • 次にどのSceneに遷移するかはGameManagerが管理しているとします。

プロジェクトを作成する

Sample用のUnityプロジェクトを作成します。

Sceneを作成する

Project windowでAssetsの下にScenesフォルダを作成し、その下で[+]の[Scene]またはコンテキストメニューの[Create]-[Scene]でSceneA、SceneB、SceneCを作成します。

* [Scene(s) Have Been Modified] windowが表示された場合はその都度[Save]を選択します。

[File]-[Build Settings]で[Build Settings] windowの[Scenes in Build]の中にSceneA、SceneB、SceneCをドラッグ&ドロップします。

SceneA、SceneB、SceneCの順にします。

Scene内のManager GameObjectを作成する

HierarchyでGameObjectを作成します。

  • SceneAでは[Create Empty]で空のGameObjectを作成し、名前をManagerAとします。
  • SceneBでは[Create Empty]で空のGameObjectを作成し、名前をManagerBとします。
  • SceneCでは[Create Empty]で空のGameObjectを作成し、名前をManagerCとします。

Prefabを作成する

Project windowでAssetsの下にPrefabsフォルダを作成します。

  • HierarchyでCube GameObjectを作成しPrefabsフォルダにドラッグ&ドロップして名前をEnemyAとします。HierarchyのCube GameObjectは削除します。
  • HierarchyでSphere GameObjectを作成しPrefabsフォルダにドラッグ&ドロップして名前をEnemyBとします。HierarchyのSphere GameObjectは削除します。
  • HierarchyでCapsule GameObjectを作成しPrefabsフォルダにドラッグ&ドロップして名前をEnemyCとします。HierarchyのCapsule GameObjectは削除します。
  • Hierarchyで[Create Empty]により空のGameObjectを作成します。作成したGameObjectにマウスを置いた状態でCylinderとSphereを作成します。SphereをCylinderの上に乗る位置に移動します。空のGameObjectをPrefabsフォルダにドラッグ&ドロップして名前をPlayerとします。Hierarchyの空のGameObjectは削除します。一緒に子のCylinderとSphereは削除されます。

C#Projectを作成する

Project windowでAssetsの下にScriptsフォルダを作成します。その下でC#Scriptを作成していきます。

  • C#Scriptを作成し名前をMultiSceneGameManagerとします。
  • C#Scriptを作成し名前をSingleSceneManagerとします。
  • C#Scriptを作成し名前をEnemyとします。
  • C#Scriptを作成し名前をEnemyDataとします。

* C#Script名(ファイル名)はclass名と一致する必要があるので作成時に名前を入力するのが効率的です。C#Script名(ファイル名)を変更した場合はVisual Studio等でclass名も変更しましょう。

* GameManagerという名前でC#Scriptを作成すると歯車アイコンとなります。どうやらGameManagerにはUnityで特別な意味があるようなのでMultiSceneGameManagerとしました。

[Open C# Project]あるいはC#ScriptのダブルクリックでVisual Studioを開きます。

MultiSceneGameManager

using UnityEngine;
using UnityEngine.SceneManagement;

[CreateAssetMenu(menuName = "Sample/MultiSceneGameManager")]
public class MultiSceneGameManager : ScriptableObject
{
    // Inspectorで設定してSingleSceneManagerから参照される
    public GameObject playerPrefab;

    // SingleSceneManagerからScene終了時に呼ばれるメソッド
    public void Next()
    {
        string nextSceneName = null;

        // 現在のシーンの状態により次のシーンを決める
        // * 今ここではシーン名だけで決めている。
        switch (SceneManager.GetActiveScene().name)
        {
            case "SceneA": nextSceneName = "SceneB"; break;
            case "SceneB": nextSceneName = "SceneC"; break;
            case "SceneC": nextSceneName = "SceneA"; break;
        }

        if (nextSceneName != null)
        {
            // 次のシーンへ
            SceneManager.LoadScene(nextSceneName);
        }
    }
    public void Quit()
    {
#if UNITY_EDITOR
            UnityEditor.EditorApplication.isPlaying = false;
#else
            Application.Quit();
#endif
    }
}

SingleSceneManager

using System;
using UnityEngine;

public class SingleSceneManager : MonoBehaviour
{
    [SerializeField]
    public MultiSceneGameManager multiSceneGameManager; // Inspector上からアタッチ

    [SerializeField]
    GameObject enemyPrefab; // Inspector上からアタッチ

    // 初期化
    void Start()
    {
        // Playerの配置
        if (multiSceneGameManager != null && multiSceneGameManager.playerPrefab != null)
        {
            var player = Instantiate(multiSceneGameManager.playerPrefab);
            player.transform.position = new Vector3(-5, 0, 0);
        }

        // 敵の配置
        if (enemyPrefab != null)
        {
            for (int i = 0; i < 3; ++i)
            {
                var obj = Instantiate(enemyPrefab);
                obj.transform.position = new Vector3(5, 0, 2 * (i - 1));
            }
        }

        // 初期化ではないがScene上での処理の代わり
        DoSomething();
    }

    // Scene上での処理
    void DoSomething()
    {
        // シーンの終了時にSceneEndを呼ぶ
        // * ここでは3秒後に呼んでいるだけ
        Invoke(((Action)SceneEnd).Method.Name, 3);
    }

    // [ESC]か[Space]で終了する
    bool quit = false;
    private void Update()
    {
        if (Input.GetKey(KeyCode.Escape) || Input.GetKey(KeyCode.Space))
        {
            quit = true;
            multiSceneGameManager.Quit();
        }
    }

    // Scene終了
    void SceneEnd()
    {
        if (!quit &&  multiSceneGameManager != null)
        {
            multiSceneGameManager.Next();
        }
    }
}

EnemyData

using UnityEngine;

[CreateAssetMenu(menuName = "Sample/Enemy Data")]
public class EnemyData : ScriptableObject
{
    // 敵の膨大なデータの代わりのサンプルとしてColorデータを持つ
    public Color color;
}

Enemy

using UnityEngine;

public class Enemy : MonoBehaviour
{
    [SerializeField]
    EnemyData data; // Inspector上でアタッチ

    // 初期化
    void Start()
    {
        if (data != null)
        {
            GetComponent<MeshRenderer>().material.color = data.color;
        }
    }
}

AssetsメニューからScriptableObjectを生成する

Project windowのAssetsの下にObjectsフォルダを作成します。その下にScriptableObjectsの派生クラスのオブジェクトを作成してきます。

  • [Assets]-[Create]-[Sample]-[MultiSceneGameManager]により作成し、名前をMutiSceneGameManagerとします。
  • [Assets]-[Create]-[Sample]-[Enemy Data]により作成し、名前をEnemyADataとします。
  • [Assets]-[Create]-[Sample]-[Enemy Data]により作成し、名前をEnemyBDataとします。
  • [Assets]-[Create]-[Sample]-[Enemy Data]により作成し、名前をEnemyCDataとします。

色を設定する

  • EnemyADataを選択し、InspectorのColorのRを255にして赤にします。
  • EnemyBDataを選択し、InspectorのColorのGを255にして緑にします。
  • EnemyCDataを選択し、InspectorのColorのBを255にして青にします。

アタッチする

Unity Editor上でアタッチによりインスタンスのフィールドの設定をします。

MultiSceneGameManager

  • Project windowのObjects/MultiSceneGameManagerを選択し、Inspectorで表示されるようにします。右端の丸いアイコンをクリックし、Playerを選択します。

SingleSceneManager

Project windowのScenes/SceneAをダブルクリックしてSceneAに切り替えます。

  • Hierarchy windowのManagerAにProject windowのScripts/SingleSceneManagerをドラッグ&ドロップしてManagerAのComponentにSingleSceneManager C#Scriptを登録します。
  • ManagerAを選択し、Inspector windowのSingle Scene Manager (Script)の設定をします。
    • Multi Scene Game Managerの右端の丸いアイコンをクリックしMultiSceneGameManagerを選択します。
    • Enemy Prefabの右端の丸いアイコンをクリックしEnemyAを選択します。

Project windowのScenes/SceneBをダブルクリックしてSceneBに切り替えます。

  • Hierarchy windowのManagerBにProject windowのScripts/SingleSceneManagerをドラッグ&ドロップしてManagerBのComponentにSingleSceneManager C#Scriptを登録します。
  • ManagerBを選択し、Inspector windowのSingle Scene Manager (Script)の設定をします。
    • Multi Scene Game Managerの右端の丸いアイコンをクリックしMultiSceneGameManagerを選択します。
    • Enemy Prefabの右端の丸いアイコンをクリックしEnemyBを選択します。

Project windowのScenes/SceneCをダブルクリックしてSceneCに切り替えます。

  • Hierarchy windowのManagerCにProject windowのScripts/SingleSceneManagerをドラッグ&ドロップしてManagerCのComponentにSingleSceneManager C#Scriptを登録します。
  • ManagerCを選択し、Inspector windowのSingle Scene Manager (Script)の設定をします。
    • Multi Scene Game Managerの右端の丸いアイコンをクリックしMultiSceneGameManagerを選択します。
    • Enemy Prefabの右端の丸いアイコンをクリックしEnemyCを選択します。

Enemy

EnemyA

  • Project windowのPrefabs/EnemyAを選択し、Inspector windowにEnemy A (Prefab Asset)が表示されるようにします。Inspectorの一番下にある[Add Componet]から”Enemy”を選んでEnemy C#ScriptをComponent登録します。
  • InspectorのEnemy (Script)のDataの右端の丸いアイコンからEnemyADataを選択します。

EnemyB

  • Project windowのPrefabs/EnemyBを選択し、Inspector windowにEnemy B (Prefab Asset)が表示されるようにします。Inspectorの一番下にある[Add Componet]から”Enemy”を選んでEnemy C#ScriptをComponent登録します。
  • InspectorのEnemy (Script)のDataの右端の丸いアイコンからEnemyBDataを選択します。

EnemyC

  • Project windowのPrefabs/EnemyCを選択し、Inspector windowにEnemy C (Prefab Asset)が表示されるようにします。Inspectorの一番下にある[Add Componet]から”Enemy”を選んでEnemy C#ScriptをComponent登録します。
  • InspectorのEnemy (Script)のDataの右端の丸いアイコンからEnemyCDataを選択します。

Playする

ちょっと色がおかしいですが、シーンが変わるようになりました。

Generate Lightingする

Sceneが変わると色が暗くなっていたのでそれぞれのScene(SceneA、SceneB、SceneC)で次の設定をします。

[Window]-[Rendering]-[Lighting]でLighting windowを開きます。一番下にある[Generate Lighting]をクリックします。

コメント