Unity SerializeReferenceExtensions

repository·main·Indexed 22 days ago

https://github.com/mackysoft/unity-serializereferenceextensions

A Unity package (com.mackysoft.serializereference-extensions) that provides the [SubclassSelector] attribute to add an Inspector dropdown for fields serialized with [SerializeReference]. It enables the selection of concrete subtypes, supports collections, nested types, and generic type handling for Unity 2023.2+. It also includes the [AddTypeMenu] attribute for customizing type display names in the popup menu.

Tokens
1.8K
Snippets
3
Records
5
Agent score
30%

What's inside Unity SerializeReferenceExtensions

  1. Generic support for SerializeReference (Unity 2023.2+)

    main

    If you are using Unity 2023.2 or later, the library provides enhanced support for generic field types. This includes:

    • Generic interfaces as base types: [SerializeReference, SubclassSelector] ICommand<int> cmd; will show candidates implementing ICommand<int>, respecting variance rules (in/out).
    • Abstract generic base types: [SerializeReference, SubclassSelector] BaseCommand<int> cmd; will show candidates derived from BaseCommand<int>.

    For versions older than 2023.2, generic instances may suffer from Unity engine serialization limitations.

  2. How type discovery and eligibility works for SubclassSelector

    main

    The SubclassSelector dropdown is populated by enumerating candidate types and filtering them based on two sets of rules: intrinsic eligibility and compatibility with the field's base type.

    1. Candidate Type Eligibility (Intrinsic Rules)

    Types must meet these criteria to be considered:

    • Top-level public types: Included.
    • Nested private types: Included (useful for encapsulation).
    • Nested public types: Included.
    • [Serializable]: Must have this attribute applied.
    • [HideInTypeMenu]: If applied, the type is explicitly excluded.

    Excluded types:

    • abstract classes (cannot be instantiated).
    • generic types (open or constructed) as candidates (though generic base types are supported).
    • Types deriving from UnityEngine.Object (due to Unity [SerializeReference] limitations).

    2. Compatibility Rules

    Once eligible, the candidate must be compatible with the field's base type:

    • Non-generic interface/abstract class: Checked via baseType.IsAssignableFrom(candidateType).
    • Generic base type (Unity 2023.2+): Candidate must implement/derive from the same generic definition and match type arguments (supports variance for in/out).
    • Generic base type (Unity < 2023.2): Compatibility is limited by Unity engine constraints.
  3. Install Unity SerializeReferenceExtensions

    main

    You can install this package using one of the following three methods:

    Via .unitypackage

    Download a version from the GitHub releases page.

    Via Git URL

    In the Unity Package Manager, select "Add package from git URL..." and use:

    https://github.com/mackysoft/Unity-SerializeReferenceExtensions.git?path=Assets/MackySoft/MackySoft.SerializeReferenceExtensions

    To pin a specific version (e.g., 1.7.0), append #{VERSION}:

    https://github.com/mackysoft/Unity-SerializeReferenceExtensions.git?path=Assets/MackySoft/MackySoft.SerializeReferenceExtensions#1.7.0

    Via Open UPM

    Use the Open UPM registry:

    openupm add com.mackysoft.serializereference-extensions
  4. Use the SubclassSelector attribute for [SerializeReference] fields

    main

    To enable a dropdown menu in the Unity Inspector for fields using [SerializeReference], apply the [SubclassSelector] attribute to the field. This allows you to select concrete types, search via a fuzzy finder, and supports collections (like T[] or List<T>) and nested types.

    Basic Usage

    using System;
    using UnityEngine;
    
    public class Example : MonoBehaviour {
        // Enables the dropdown for this interface implementation
        [SerializeReference, SubclassSelector]
        ICommand m_Command;
    
        // Supports collections
        [SerializeReference, SubclassSelector]
        ICommand[] m_Commands = Array.Empty<ICommand>();
    }
    
    public interface ICommand {
        void Execute();
    }
    
    [Serializable]
    public class DebugCommand : ICommand {
        public void Execute() => Debug.Log("Debug");
    }
    using System;
    using UnityEngine;
    
    public class Example : MonoBehaviour {
    
    	// The type that implements ICommand will be displayed in the popup.
    	[SerializeReference, SubclassSelector]
    	ICommand m_Command;
    
    	// Collection support
    	[SerializeReference, SubclassSelector]
    	ICommand[] m_Commands = Array.Empty<ICommand>();
    
    	void Start () {
    		m_Command?.Execute();
    
    		foreach (ICommand command in m_Commands) {
    			command?.Execute();
    		}
    
    	}
    
    	// Nested type support
    	[Serializable]
    	public class NestedCommand : ICommand {
    		public void Execute () {
    			Debug.Log("Execute NestedCommand");
    		}
    
    	}
    
    }
    
    public interface ICommand {
    	void Execute ();
    }
    
    [Serializable]
    public class DebugCommand : ICommand {
    
    	[SerializeField]
    	string m_Message;
    
    	public void Execute () {
    		Debug.Log(m_Message);
    	}
    }
    
    [Serializable]
    public class InstantiateCommand : ICommand {
    
    	[SerializeField]
    	GameObject m_Prefab;
    
    	public void Execute () {
    		UnityEngine.Object.Instantiate(m_Prefab,Vector3.zero,Quaternion.identity);
    	}
    }
    
    // Menu override support
    [AddTypeMenu("Example/Add Type Menu Command")]
    [Serializable]
    public class AddTypeMenuCommand : ICommand {
    	public void Execute () {
    		Debug.Log("Execute AddTypeMenuCommand");
    	}
    }
    
    [Serializable]
    public struct StructCommand : ICommand {
    	public void Execute () {
    		Debug.Log("Execute StructCommand");
    	}
    }
    "}
  5. Customize type display names with [AddTypeMenu]

    main

    You can override how a type appears in the SubclassSelector dropdown menu by applying the [AddTypeMenu] attribute to the class. This is useful for organizing types into custom menu paths.

    [AddTypeMenu("Example/Add Type Menu Command")]
    [Serializable]
    public class AddTypeMenuCommand : ICommand {
        public void Execute () => Debug.Log("Executed");
    }
    [AddTypeMenu("Example/Add Type Menu Command")]
    [Serializable]
    public class AddTypeMenuCommand : ICommand {
    	public void Execute () {
    		Debug.Log("Execute AddTypeMenuCommand");
    	}
    }