Skip to content

Generators

Roy Theunissen edited this page Aug 28, 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

Create a new script file that will hold the generator code. We recommend naming it after the type of items you will generate + the "Generator" suffix, so for example TagConfigGenerator. Note that this file needs to be in an Editor folder. You will need to define two classes in here.

Step 3: Define an item template

Firstly we need a basic "item template" class that will be the thing that the generator generates. This is a simple way to let you express to the system what items should exist and with which properties, without having to worry about getters and setters on the real collection items. It simplifies the syntax quite a bit.

Here's what an item template would look like for scene configs that have a name and a path. Note that it should inherit from ItemTemplate, which defines the name field.

    /// <summary>
    /// Template that informs the generator what items to generate and with which fields.
    /// </summary>
    public sealed class SceneConfigTemplate : ItemTemplate
    {
        public string path;
    }

NOTE: If your collection items won't have any special fields and will have only the name, the class can be empty. You could choose not to define a custom template type at all and use ItemTemplate directly, but it's not a bad idea to define a template type regardless because it allows you to easily add fields later.

Step 4: Define a generator

Now that you have an item template, we can define the generator itself. It's a pure C# class that should implement the IScriptableObjectCollectionGenerator interface. The interface is generic and requires you to specify two types:

  • CollectionType, the type of collection for which to generate items
  • TemplateType, the type of template to use to specify which items should exist

Once you've created a class and implemented the interface, let your IDE generate missing properties and method.

You will be asked to implement a property and a method:

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.

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.

A simple tag generator might then look something like this:

    /// <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.

Clone this wiki locally