Unlocking Efficiency: How to Manage Runtime GameObjects with Scriptable Objects in Unity

Unlocking Efficiency: How to Manage Runtime GameObjects with Scriptable Objects in Unity

At runtime we might often need to track a list of GameObjects or components in our scene. For example, you may need to maintain a list of enemies or NPCs.

Reading data directly from a ScriptableObject is also more optimal than searching the Scene Hierarchy with a find operation such as Object.FindObjectOfType or GameObject.FindWithTag. Depending on your use case and the size of your hierarchy these are relatively expensive methods that can be inefficient for per-frame updates.

Basic Runtime Set

Instead consider storing data on a ScriptableObject as a “Runtime Set”, This is a specialized data container that maintains a public collection of elements but also provides basic methods to add and remove to the collection.

Here’s a basic Runtime Set that tracks a list of GameObjects:

using System.Collections.Generic;
using UnityEngine;

[CreateAssetMenu(menuName = "GameObject Runtime Set", fileName = "GORuntimeSet")]
public class GameObjectRuntimeSetSO : ScriptableObject
{
    private List<GameObject> items = new List<GameObject>();
    
    public List<GameObject> Items => items;

    public void Add(GameObject thingToAdd)
    {
        if (!items.Contains(thingToAdd))
        {
            items.Add(thingToAdd);
        }
    }

    public void Remove(GameObject thingToRemove)
    {
        if (items.Contains(thingToRemove))
        {
            items.Remove(thingToRemove);
        }
    }
}        

At runtime any MonoBehaviour can reference the public Items property to obtain the full list. Another script or component must be responsible for managing how the GameObjects are added or removed from this list.

In your MonoBehaviour script, reference the RuntimeSet. Then, within the OnEnable and OnDisable event functions, add or remove the object from the RuntimeSet's Items list. Alternatively, you could use an event channel to send a GameObject as its payload (e.g., via a GameObjectEventChannel).

Generic Version

You may want to use a RuntimeSet that handles a specific type of MonoBehaviour. For example, this would allow you to maintain a list of enemies or pickups that are accessible across your scene. In such cases, you could create specific RuntimeSets for each type, such as EnemyRuntimeSet, PickupRuntimeSet, etc.

To streamline the process of creating multiple RuntimeSets, you can use a generic abstract class, which would simplify the creation of additional sets for different types.

using System.Collections.Generic;
using UnityEngine;

public abstract class RuntimeSetSO<T> : ScriptableObject
{
    [HideInInspector]
    public List<T> Items = new List<T>();

    public void Add(T item)
    {
        if (!Items.Contains(item))
        {
            Items.Add(item);
        }
    }

    public void Remove(T item)
    {
        if (Items.Contains(item))
        {
            Items.Remove(item);
        }
    }
}        

This approach works similarly to the original GameObjectRuntimeSet, but with added flexibility through the use of generics. If you want to create a RuntimeSet for a custom component, such as a Foo component, you can easily do so by creating a concrete FooRuntimeSetSO class, like this:

using UnityEngine;

[CreateAssetMenu(menuName = "Runtime Sets/Foo Runtime Set")]
public class FooRuntimeSetSO : RuntimeSetSO<NPC>
{
    // Additional logic specific to Foo can be added here if needed.
}        

You can build as many concrete classes as needed for gameplay (e.g., enemies, NPCs, inventory items, quests, etc.), each having its own RuntimeSet. To do this, simply declare a new, empty class with the correct type.

using UnityEngine;

public class NPC : MonoBehaviour
{
    [SerializeField] private NPCRuntimeSetSO npcRuntimeSet;

    private void OnEnable()
    {
        npcRuntimeSet.Add(this);
    }

    private void OnDisable()
    {
        npcRuntimeSet.Remove(this);
    }
}        

One limitation of this technique is that if you inspect the ScriptableObject at runtime, you won’t be able to see the contents of the RuntimeSet list in the Inspector. If you attempt to publicly expose the list, you’ll encounter a "type mismatch" error due to the generic nature of the RuntimeSetSO<T> class.

To overcome this limitation, you can use Editor Scripting to display the contents of the RuntimeSet in the Inspector during runtime. Here’s how you can achieve this:

using UnityEngine;
using UnityEditor;

[CustomEditor(typeof(NPCRuntimeSetSO))]
public class NPCRuntimeSetSOEditor : Editor
{
    public override void OnInspectorGUI()
    {
        // Draw the default inspector for other fields
        base.OnInspectorGUI();

        // Get reference to the NPCRuntimeSetSO
        NPCRuntimeSetSO npcRuntimeSet = (NPCRuntimeSetSO)target;

        // Display the contents of the runtime set
        EditorGUILayout.LabelField("Runtime Set Items", EditorStyles.boldLabel);
        
        // Loop through and display each item in the runtime set
        if (npcRuntimeSet.Items != null && npcRuntimeSet.Items.Count > 0)
        {
            for (int i = 0; i < npcRuntimeSet.Items.Count; i++)
            {
                EditorGUILayout.ObjectField($"NPC {i + 1}", npcRuntimeSet.Items[i], typeof(NPC), true);
            }
        }
        else
        {
            EditorGUILayout.LabelField("No NPCs currently in the Runtime Set.");
        }
    }
}        

so, in this way we can keep all runtime object which needed to access often in a same place to reuse them without overhead the searching with game tag and full hierarchy.


#unity #scriptableobject #runtimeset

要查看或添加评论,请登录

Golam Mostofa的更多文章

社区洞察

其他会员也浏览了