Editor Script to Order Components?

I was looking to put together an editor script to order a particular type of component on a selected gameobject. In the below scenario, an object will have multiple of these IPredicate interface type components - I’d ideally like to fetch them all, sort them, and add them back to the GameObject.

Unfortunately, it doesn’t seem possible to delete a component that’s stored as an interface - so this approach seems a dud, is there a way around this / a built in way to sort components?

[MenuItem("Tools/Mob | Order Selected Mob Predicates")]
public static void OrderSelectedMobPredicates()
{
    var selectedObjects = Selection.gameObjects;

    foreach (var selectedObject in selectedObjects)
        OrderMobPredicates(selectedObject);
}

private static void OrderMobPredicates(GameObject gameObject)
{
    var predicates = gameObject.GetComponents<IStatePredicate<Mob, MobStateId>>();
    var orderedPredicates = predicates.OrderBy(x => x.StateId.ToString()).ToArray();
    for (int i = 0; i < predicates.Length; i++)
    {
        Debug.Log(orderedPredicates[i].StateId);
    }
}

You can do this, you just have to test-cast it (perhaps with as) to a Component first.

        GameObject go = new GameObject("Asteroid");

        // Blaster.BlasterAsteroid implements the IBumpable interface
        IBumpable bump = go.AddComponent<Blaster.BlasterAsteroid>();

        Component com = bump as Component;

        // TODO: test if it is null!

        // use it
        Debug.Log( com);

Since you’re in editor mode, be sure to use DestroyImmediate()!

Fantastic! Thank you very much! :slight_smile:

Unfortunately that does add another small issue - adding the now ordered components back onto the gameobject, after destroying all the unordered objects, with AddComponent it doesn’t seem possible to add an already existing component reference to a gameObject, only fresh ones?

Yes, you can not “detach” components from the host gameobject. They are welded together and can never be separated. You can destroy a component which will remove it or add a new one, but you can not have it detached.

However Unity has an internal utility that is actually used by the inspector as well and it’s called UnityEditorInternal.ComponentUtility. It has several methods to perform those operations you can do from the context menu in the inspector.Namely:

  • MoveComponentDown
  • MoveComponentUp
  • CopyComponent
  • PasteComponentValues
  • PasteComponentAsNew

The class is undocumented and also in the UnityEditorInternal namespace, so it may be subject to changes without notice. However Unity is using it itself in the inspector code, so for an editor tool it should be fine to use them.

Since you just want to reorder your components, using MoveComponent Up / Down is probably the best solution. You just need to figure out how you want / need to move them. The “copy” and “paste” options actually use the clipboard and can only “copy” a single component at a time. I would not recommend to actually removing / destroying the old one and somehow rebuild them.

Oh crikey, so you would need to basically implement a bespoke sorting algorithm to do what I’m trying to do?

In addition to those methods there are internal methods (link to the line they start below) that move components relative to other components.

Helper class for accessing them via reflection - GPT-4o

using System;
using System.Reflection;
using UnityEngine;

public static class ComponentUtilityHelper
{
    private static MethodInfo _moveComponentRelativeToComponentMethod;
    private static MethodInfo _moveComponentsRelativeToComponentMethod;

    static ComponentUtilityHelper()
    {
        Type componentUtilityType = Type.GetType("UnityEditor.ComponentUtility, UnityEditor");

        if (componentUtilityType != null)
        {
            _moveComponentRelativeToComponentMethod = componentUtilityType.GetMethod(
                "MoveComponentRelativeToComponent",
                BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic);

            _moveComponentsRelativeToComponentMethod = componentUtilityType.GetMethod(
                "MoveComponentsRelativeToComponent",
                BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic);
        }
        else
        {
            Debug.LogError("Unable to find UnityEditor.ComponentUtility type.");
        }
    }

    public static void MoveComponentRelativeToComponent(Component componentToMove, Component targetComponent, bool aboveTarget)
    {
        if (_moveComponentRelativeToComponentMethod != null)
        {
            _moveComponentRelativeToComponentMethod.Invoke(null, new object[] { componentToMove, targetComponent, aboveTarget });
        }
        else
        {
            Debug.LogError("Method MoveComponentRelativeToComponent not found.");
        }
    }

    public static void MoveComponentsRelativeToComponent(Component[] componentsToMove, Component targetComponent, bool aboveTarget)
    {
        if (_moveComponentsRelativeToComponentMethod != null)
        {
            _moveComponentsRelativeToComponentMethod.Invoke(null, new object[] { componentsToMove, targetComponent, aboveTarget });
        }
        else
        {
            Debug.LogError("Method MoveComponentsRelativeToComponent not found.");
        }
    }
}

I gotta say this has a bit of a code smell…

Do they have to be MonoBehaviours?? Otherwise you could make ScriptableObjects and slot them all into a list of that interface type, then iterate that list. It does require something like Odin inspector, and I’ve actually never done it.

Come to think of it you could do the same with a list of MonoBehaviours, dragging each individual one into an ordered list on some master MonoBehaviour on that same object…

Alternately I have sometimes used priority numbers within things to control their order.

A master component would have higher performance too if you were planning on large numbers of objects as there is a very tiny bit of overhead with every magic method. For that matter a master object for the master components would reduce the overhead even further.

Right. Unfortunately those are internal and require reflection to use them. Your GPT4 generated example wouldn’t even work, as the class is in the wrong namespace and the method name has overloads, so you have to specify the actual parameter types. Though besides those issues I’m almost disappointed about GPT4 here. You could actually create a delegate for this method and get rid of all the reflection overhead (besides the one time setup cost). As a delegate it also wouldn’t have any boxing overhead, just the delegate overhead which is much more performant than calling through reflection.

ComponentUtilityHelper

public static class ComponentUtilityHelper
{
    private delegate bool MoveComponentDelegate(Component aTarget, Component aRelative, bool aMoveAbove);
    private static MoveComponentDelegate m_MoveComponent = null;
    static ComponentUtilityHelper()
    {
        var componentUtilityType = typeof(UnityEditorInternal.ComponentUtility);
        var moveComponentMI = componentUtilityType.GetMethod(
            "MoveComponentRelativeToComponent",
            BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic, null, new System.Type[] {
                typeof(Component),
                typeof(Component),
                typeof(bool)
            }, null);
        if (moveComponentMI == null)
            throw new System.Exception("Internal method MoveComponentRelativeToComponent was not found");
        m_MoveComponent = (MoveComponentDelegate)System.Delegate.CreateDelegate(typeof(MoveComponentDelegate), moveComponentMI);
    }
    public static bool MoveComponent(this Component aTarget, Component aRelative, bool aMoveAbove)
    {
        if (m_MoveComponent == null)
            throw new System.Exception("Internal method MoveComponentRelativeToComponent was not found");
        return m_MoveComponent(aTarget, aRelative, aMoveAbove);
    }
}

This class actually works and the MoveComponent method is directly declared as extension method ^^. (but can still be used explicitly, of course).

With that method I made a simple example which is somewhat based on the OP question:

public class SortComponents
{
    [MenuItem("CONTEXT/Component/Sort")]
    public static void SortComps(MenuCommand aCommand)
    {
        var comp = (Component)aCommand.context;
        var go = comp.gameObject;

        var comps = new List<IPredicate>(go.GetComponents<IPredicate>());
        if (comps.Count == 0)
            return;
        var first = (Component)comps[0]; // first unordered component
        comps.Sort((a,b)=> a.StateId.CompareTo(b.StateId));
        // Move first ordered component to the very top (abobe the first unordered)
        ((Component)comps[0]).MoveComponent(first, true);
        // iterate through the remaining ordered components and put them after the previous one
        for(int i = 1; i < comps.Count;i++)
        {
            var c = (Component)comps[i];
            var cTarget = (Component)comps[i-1];
            c.MoveComponent(cTarget, false);
        }
    }
}

This adds a context menu to all components and allows you to invoke this sorting method. Since “IPredicate” was not provided, I just implemented it like this:

public interface IPredicate
{
    string StateId { get; }
}

In the code I use the context to simply get the gameobject, call GetComponents with our IPredicate type and put the result in a list. I remember the first unordered element as a reference. I sort the list based on the predicate value and then simply put them in order with our new extension method.

Keep in mind that our example here only sorts components which implement the given interface. There may be other components in between them but after the sorting they will be all clumped together after the first component that implements the interface.

Whoops. I didn’t look closely enough at the code. I just saw reflection in use and assumed the bot wasn’t throwing me under the bus. That’s the catch with it. It has a tendency to get it right enough that it lulls me into thinking the next time will be fine too. :stuck_out_tongue:

It certainly is a code smell yes!
I ended up in this situation, with many components all implementing the same interface, as it was not possible to serialize a list of implementations of an interface as a list directly, which would have been ideal (The important part, being able to drag and drop say, any scriptable object implementing a certain interface, into this list without custom editor overhead). They really shouldn’t be monobehaviours, and have no use being so, outside of it being the only way (that I know of) of serialising a list of interfaces (in this case an object being the container of the “list”).

Is that only possible with Odin inspector? I’m not averse to shelling out for it, but it looks like the only feature I’d actually need is that.

Ideal setup would be:

interface IPredicate {}

class ConcreteOne : ScriptableObject, IPredicate {}
class ConcreteTwo : ScriptableObject, IPredicate {}

And then be able to drag and drop either Concrete one or two into this array in the inspector.

I did find THIS which sounds perfect, but doesn’t seem to work, was this feature never added in the end?
https://discussions.unity.com/t/743433

For example:

public class MobPredicateContainer : MonoBehaviour
{
    [SerializeReference] public List<IMobStatePredicate> list;
}

public interface IMobStatePredicate : IStatePredicate<Mob, MobStateId>
{

}

[Serializable]
public class ExampleMobStatePredicate : IMobStatePredicate
{
    public MobStateId StateId => throw new System.NotImplementedException();

    public MobStateId[] NextStates => throw new System.NotImplementedException();

    public Dictionary<string, object> NextStateArgs => throw new System.NotImplementedException();

    public void BeginStatePredicate(Mob controller, Dictionary<string, object> args = null)
    {
        throw new System.NotImplementedException();
    }

    public bool IsPredicateMet(Mob controller)
    {
        throw new System.NotImplementedException();
    }
}

Then shows in the editor as:

With no way to manipulate Element 0

Wow, that’s really tidy - thank you! A good place for me to start if I continue down this route. Really appreciate it :slight_smile:

There’s very little inspector support for SerializeReference, though the functionality of the serialization itself is quite mature. Need to remember serialization and inspector drawing are two separate things.

I did post a simple reusable example that will allow use of it: https://discussions.unity.com/t/948612/15

Otherwise addons like Odin Inspector support this out of the box.

That’s really cool thanks - could be worthwhile.

In theory then, I could do something like this, with absolutely no thrills - not exactly as clean as interfaces, but might get me over the line.

Edit: No, I’m being stupid, scriptable objects won’t work for this use case, as each needs to contain its own unique data at runtime , would need to be a serializable class inheritance. But then back to the issue on being unable to serialize those out of the box.

public class MobPredicateContainer : MonoBehaviour
{
    public MobStatePredicate[] predicates;
}

public abstract class MobStatePredicate : ScriptableObject, IStatePredicate<Mob,MobStateId>
{
    public virtual MobStateId StateId => throw new System.NotImplementedException();

    public virtual MobStateId[] NextStates => throw new System.NotImplementedException();

    public virtual Dictionary<string, object> NextStateArgs => throw new System.NotImplementedException();

    public virtual void BeginStatePredicate(Mob controller, Dictionary<string, object> args = null)
    {
        throw new System.NotImplementedException();
    }

    public virtual bool IsPredicateMet(Mob controller)
    {
        throw new System.NotImplementedException();
    }
}

[CreateAssetMenu(fileName = "data", menuName = "ScriptableObjects/OneMobStatePredicate")]
public class OneMobStatePredicate : MobStatePredicate
{
    public override MobStateId StateId => MobStateId.Loiter;
    public string someTestData;
}

[CreateAssetMenu(fileName = "data", menuName = "ScriptableObjects/TwoMobStatePredicate")]
public class TwoMobStatePredicate : MobStatePredicate
{
    public override MobStateId StateId => MobStateId.Wander;
}

It does seem like the only way to easily serialize a list of classes inheriting an interface out of the box is to treat the gameobject component list as a List, and add the implementations as monobehaviours.

Uh, hello, [SerializeReference] supports this out of the box. The Serialization works very well. You just need some custom inspector support, and my example should work as-is.

I’ve been using SerializeReference to serialize inheritance hierarchies alongside other uses for by-reference serialization for over a year now.

OH,

Hot damn - pardon me, I completely misunderstood your previous answer. That’s genius, and seems to actually work really well for my needs. Will experiment a lot, but huge thanks!


public class MobPredicateContainer : MonoBehaviour
{
    [SerializeReference, SubclassSelector]
    public List<IDiggingTool> predicates;
}

public interface IDiggingTool
{
   
}

[Serializable]
public class Shovel : IDiggingTool
{
    public string spadeSize;
}

public class Spade : IDiggingTool
{
    public string spadeHandleType;
    public int cracks;
}

Just want to give an update to this, for anyone who stumbles across the thread.
While this is fantastic, just a heads up, it’s quite brittle to refactoring / renaming the classes involved.

As far as I’m aware, there’s no equivalent of [FormerlySerializedAs] for class names?

No, there is not as Unity will actually store the full assembly qualified class name as a string in the serialized data and just stores the serializable fields. You should use FormerlySerializedAs also just sparingly when really necessary. You can actually “fix” serialized data manually. Just open the actual asset in a text editor and have a look at the references. It’s just YAML so it can be edited manually. Especially when you “just” renamed a field or class. Though as always when it comes to serialized data, you want to avoid this. So plan your structure ahead of time.

There is, but it is annoyingly: undocumented.

But it is under UnityEngine.Scripting.APIUpdating.MovedFromAttribute.

Maybe one day Unity will document this attribute.

Interesting. Though the comment in the source code is a bit vague, especially the “Notes” at the end.

Yeah it realy needs to be on the documentation explaining how to use it.

As the name implies - “MovedFrom” - you need to input into the constructor the assembly name, namespace and class name that it was changed from. It’s only ever worked for me when using all three parameters, even if only one of the three has changed.

In older versions of Unity (2021 and prior, I believe), collections would fail to deserialise until all the elements could have their managed references resolved correctly. Newer versions it can at least deserialise some of the members.

Using SerializeReference does mean you need to be conscious of renaming or moving your types, namespaces or assemblies. Generally when I do, I use the MovedFromAttribute where needed, reserialise all assets, then remove the attribute afterwards.