Alchemy for Unity

repository·main·Indexed 22 days ago

https://github.com/annulusgames/alchemy

A library providing attribute-based Inspector extensions and serialization for Unity. It enables advanced editor customization via over 30 attributes, supports non-native types like dictionaries, tuples, and hashsets through a source generator and Unity.Serialization, and provides AlchemyEditorWindow for building attribute-driven custom tools. It also includes Hierarchy extensions for toggles, icons, and visual decorators.

Tokens
17.4K
Snippets
62
Records
88
Agent score
77%

What's inside Alchemy

  1. Overview of Alchemy for Unity

    main

    Alchemy is a library designed to provide rich editor extensions for the Unity Editor. It primarily focuses on extending the Inspector through a collection of over 30 attributes and providing advanced serialization capabilities.

    Key capabilities include:

    • Inspector Extensions: Use specialized attributes to customize how fields and classes are displayed in the Unity Inspector.
    • Advanced Serialization: By leveraging the Unity.Serialization package and a dedicated source generator, Alchemy enables the serialization and editing of types that Unity does not natively support, such as Dictionary, HashSet, Nullable, and ValueTuple.
    • Editor Tooling: Includes extensions for editor-window and the Unity Hierarchy to help developers build custom tools and streamline development workflows.
  2. Overview of Alchemy editor extensions

    main
    Alchemy is a library designed to extend the Unity Editor with a variety of tools and features. It provides developers with enhanced Inspector attributes, Hierarchy extensions, editor-window extensions, and source-generated serialization extensions to improve the development workflow within Unity.
  3. Compare Alchemy with other Inspector and editor-extension libraries

    main

    Alchemy is an open-source library for Unity that provides advanced inspector and editor-extension capabilities. Compared to other popular libraries, Alchemy distinguishes itself with the following features:

    • Open Source: Unlike Odin Inspector, Alchemy is open source.
    • Editing NonSerialized Members: Alchemy allows editing of members that are not marked with [SerializeField] or are otherwise non-serialized.
    • SerializeReference Support: Full support for [SerializeReference.
    • Serialization Extension: Alchemy can extend serialization using partial types (whereas Odin requires inheritance from dedicated base types).
    • UI Toolkit Support: Native support for Unity's UI Toolkit.
    • EditorWindow & Hierarchy Extensions: Provides extensions for both EditorWindow and the Unity Hierarchy, which are not available in NaughtyAttributes, Tri Inspector, or Odin Inspector.
    • Button Attributes: Supports button attributes with arguments, unlike NaughtyAttributes or Unity Editor Toolbox which only support them without arguments.
  4. Hierarchy Extensions: Toggles, Icons, and Decoration

    main

    Alchemy provides several ways to improve the usability of the Unity Hierarchy:

    • Toggles and Icons: You can display active-state toggles and component icons for each object in the Hierarchy. This is configurable via Project Settings.
    • Decoration: You can create objects from the Create menu that act as visual decorators in the Hierarchy. These objects are automatically excluded from builds. If a decorative object has children, those children are unparented before the decorative object is deleted.
  5. How the Alchemy serialization process works

    main

    Alchemy uses a source generator to implement Unity's ISerializationCallbackReceiver on any type marked with the [AlchemySerialize] attribute.

    The Process:

    1. Collection: The source generator identifies all fields marked with [AlchemySerializeField].
    2. JSON Conversion: It uses the Unity.Serialization package to convert these fields into JSON data.
    3. Object Reference Handling: Since UnityEngine.Object instances (like GameObject, Texture, etc.) cannot be represented directly in JSON, Alchemy extracts them into a separate list of UnityEngine.Object references and writes their indices into the JSON string.
    4. Lifecycle Hooks:
      • OnBeforeSerialize: Clears the reference list, converts the marked fields to JSON, and populates the reference list.
      • OnAfterDeserialize: Parses the JSON and reconstructs the fields using the stored object references.

    Performance Note: Using [AlchemySerializeField] introduces serialization and deserialization overhead. You should only use it for fields that Unity's native serialization cannot handle (e.g., certain complex collections or types).

    using UnityEngine;
    using Alchemy.Serialization;
    
    [AlchemySerialize]
    public partial class MySerializedClass : MonoBehaviour
    {
        // This field will be handled by Alchemy's custom serialization
        [AlchemySerializeField, NonSerialized]
        public Dictionary<string, GameObject> myDictionary = new();
    }
  6. Extend the Inspector using Attributes

    main

    You can customize how class fields are displayed in the Alchemy Inspector by applying specific attributes directly to your fields. This allows you to modify labels, hide elements, restrict asset types, or add informational UI elements like help boxes and titles without writing custom editor code.

    To use these attributes, ensure you include the Alchemy.Inspector namespace in your script.

    using UnityEngine;
    using UnityEngine.UIElements;
    using Alchemy.Inspector;  // Import the Alchemy.Inspector namespace
    
    public class AttributesExample : MonoBehaviour
    {
        [LabelText("Custom Label")]
        public float foo;
    
        [HideLabel]
        public Vector3 bar;
        
        [AssetsOnly]
        public GameObject baz;
    
        [Title("Title")]
        [HelpBox("HelpBox", HelpBoxMessageType.Info)]
        [ReadOnly]
        public string message = "Read Only";
    }
  7. Create custom EditorWindows with AlchemyEditorWindow

    main

    Instead of inheriting from EditorWindow, inherit from AlchemyEditorWindow to use Alchemy attributes (like [Button], [HorizontalGroup], or [ListViewSettings]) to build your tool's UI.

    Data for windows inheriting from AlchemyEditorWindow is automatically saved as JSON in the project's ProjectSettings folder.

    using UnityEditor;
    using UnityEngine;
    using Alchemy.Editor;
    using Alchemy.Inspector;
    using System.Collections.Generic;
    
    public class EditorWindowExample : AlchemyEditorWindow
    {
        [MenuItem("Window/Example")]
        static void Open()
        {
            var window = GetWindow<EditorWindowExample>("Example");
            window.Show();
        }
        
        [Serializable]
        [HorizontalGroup]
        public class DatabaseItem
        {
            [LabelWidth(30f)]
            public float foo;
        }
    
        [ListViewSettings(ShowAlternatingRowBackgrounds = AlternatingRowBackground.All, ShowFoldoutHeader = false)]
        public List<DatabaseItem> items;
    
        [Button, HorizontalGroup]
        public void Button1() { }
    }
  8. Combine custom editors with Alchemy attributes

    main

    By default, Alchemy attributes do not work if a MonoBehaviour or ScriptableObject uses a standard Unity Editor class. To enable Alchemy attribute support within a custom editor, you must inherit from AlchemyEditor instead of the standard UnityEditor.Editor class.

    When overriding CreateInspectorGUI(), you must call base.CreateInspectorGUI() to ensure the Alchemy-driven inspector elements are generated and included in your custom UI.

    using UnityEditor;
    using UnityEngine.UIElements;
    using Alchemy.Editor;
    
    [CustomEditor(typeof(Example))]
    public class EditorExample : AlchemyEditor
    {
        public override VisualElement CreateInspectorGUI()
        {
            // Always call the base CreateInspectorGUI to enable Alchemy attributes
            var root = base.CreateInspectorGUI();
    
            // Add your custom logic here
    
            return root;
        }
    }
  9. Use the FoldoutGroup attribute to create collapsible groups

    main

    The [FoldoutGroup] attribute allows you to organize multiple members into collapsible sections within the Unity Inspector. To group members together, apply the attribute with the same group name to each field.

    Groups can be nested by using a forward slash / in the group path (e.g., [FoldoutGroup("Parent/Child")]).

    [FoldoutGroup("Group1")]
    public float foo;
    
    [FoldoutGroup("Group1")]
    public Vector3 bar;
    
    [FoldoutGroup("Group1")]
    public GameObject baz;
    
    [FoldoutGroup("Group2")] 
    public float alpha;
    
    [FoldoutGroup("Group2")]
    public Vector3 beta;
    
    [FoldoutGroup("Group2")]
    public GameObject gamma;
  10. Customize EditorWindow data saving and loading

    main

    By default, any editor window derived from AlchemyEditorWindow automatically saves its data as JSON in the project's ProjectSettings folder.

    To change where the data is stored or how it is serialized, you must override the following three methods in your class:

    1. GetWindowDataPath(): Return a string representing the custom destination path for the data.
    2. LoadWindowData(string dataPath): Implement the logic to read and apply data from the specified path.
    3. SaveWindowData(string dataPath): Implement the logic to serialize and write data to the specified path.
    using UnityEditor;
    using UnityEngine;
    using Alchemy.Editor;
    
    public class EditorWindowExample : AlchemyEditorWindow
    {
        [MenuItem("Window/Example")]
        static void Open()
        {
            var window = GetWindow<EditorWindowExample>("Example");
            window.Show();
        }
    
        protected override string GetWindowDataPath()
        {
            // Return the path where the data will be saved
            return "Assets/MyCustomPath/window_data.json";
        }
    
        protected override void LoadWindowData(string dataPath)
        {
            // Implement the loading process here
            ...
        }
    
        protected override void SaveWindowData(string dataPath)
        {
            // Implement the saving process here
            ...
        }
    }
  11. Use the HideInEditMode attribute to hide fields in the editor

    main

    Apply the [HideInEditMode] attribute to a field to prevent it from being visible while the editor is in Edit Mode. This is useful for cleaning up the inspector interface by hiding internal or non-essential data that should only be visible during runtime or in specific modes.

    Note that the field remains part of the object's data but is simply hidden from the UI during editing.

    [HideInEditMode]
    public float foo;