Skip to content

Generators

Roy Theunissen edited this page Sep 20, 2023 · 3 revisions

Generators

Why use generators?

Sometimes you might want a collection with items that represent something else in the project. For example: certain assets like tags and scenes you are expected to refer to by name or path, but this does not support code completion and is error prone / there is no good workflow for renaming. What if you made a Scriptable Object for every tag and scene instead? "Sounds like a hassle to manually make a new Scriptable Object every time" you might think.

That's where generators come in. In cases where you use a collection just to have a better representation of some other data in the project, it's better to have the items automatically generated. The Scriptable Object Collection package offers a simple syntax for implementing generators like that.

How to create a generator

Step 1: Create the collection

Firstly, create a new Scriptable Object Collection that will hold the generated items. We recommend using the wizard.

Think about whether the items should define any extra fields that you'd want to generate. For Tags we only need a name, but for Scenes you might want to define a path field, for example.

Step 2: Create a new generator

image

Go to your collection and use the Generator Wizard to create a new generator.

Step 3: Implement the generator logic

Your newly created generator should look something like this:

    /// <summary>
    /// Template for SOC items to generate. Any values assigned here will be copied over to the corresponding SOC item.
    /// This syntax lets you specify which items should exist and with which fields, without having to create instances
    /// of your SOC items, which would have a lot of overhead and would force you to have public setters for everything.
    /// </summary>
    public sealed class TagConfigTemplate : ItemTemplate
    {
        // TODO: Define any fields here for the generated items. The fields of the SOC items will be updated accordingly.
        // public string path;
    }
    
    /// <summary>
    /// Automatically generates SOC items.
    /// </summary>
    public sealed class TagConfigGenerator
        : IScriptableObjectCollectionGenerator<TagConfigCollection, TagConfigTemplate>
    {
        public bool ShouldRemoveNonGeneratedItems => true;

        public void GetItemTemplates(List<TagConfigTemplate> templates, TagConfigCollection collection)
        {
            // TODO: Create instances of the template for every item that should exist in the collection
            templates.Add(new TagConfigTemplate { name = "Hello world" });
        }
    }

The bool ShouldRemoveNonGeneratedItems { get; } property lets you specify whether it should remove any items in the collection that your generator doesn't specify any more. It makes sense to return true if you want a 1:1 mapping of the source data. If users need to manually add items to the collection, you should return false.

The void GetItemTemplates(List<TemplateType> templates, CollectionType collection); method is the meat of the generator logic and generates item templates for all the items that are supposed to exist. The system will then make sure corresponding items are created or updated in the collection.

A simple tag generator might then look something like this:

    /// <summary>
    /// Template for tag SOC items to generate.
    /// </summary>
    public sealed class TagConfigTemplate : ItemTemplate
    {
    }

    /// <summary>
    /// Automatically generates Tag Configs based on the Tags defined in the Unity project.
    /// </summary>
    public sealed class TagConfigGenerator
        : IScriptableObjectCollectionGenerator<TagConfigCollection, TagConfigTemplate>
    {
        public bool ShouldRemoveNonGeneratedItems => true;

        public void GetItemTemplates(List<TagConfigTemplate> templates, TagConfigCollection collection)
        {
            string[] tags = UnityEditorInternal.InternalEditorUtility.tags;

            for (int i = 0; i < tags.Length; i++)
            {
                templates.Add(new TagConfigTemplate { name = tags[i] });
            }
        }
    }

Just make new item templates and add them to the list. That's it!

Step 5: Run your generator

image

Once you have defined your generator a Generate Items button will now appear on the Scriptable Object Collection you made the generator for.

You can press that to manually run the generator.

If you want to call the generator from code, for example from a Menu Item, the syntax for that is as follows:

    [MenuItem("Generate/Generate Tag Configs")]
    private static void GenerateTagConfigs()
    {
        CollectionGenerators.RunGenerator<TagConfigGenerator>(true);
    }

You can optionally have it generate static access code immediately as well.

Conclusion

Mirroring weakly-typed data structures via an automatically generated Scriptable Object Collection lets you refer to them via the inspector or through code with code completion, and lets you easily define additional metadata as well, like whether a Tag is marked as obsolete or not. Take a moment to think about which data sources and collection in your project could leverage generators, you might gain a lot from it.