Override Drag&Drop MonoScript to Inspector

Hi Unity.

How do I override the drag and drop MonoScript to Inspector Window action? I want to replicate the functionality of [GenerateAuthoringComponent] from Unity.Entities — where drag and drop MonoScript with IComponentData+[GenerateAuthoringComponent] on Inspector adds another script to the object, which is a wrapper over the original struct.

Also, it’s interesting how the new type generated and uploaded to AppDomain is linked to the MonoScript/struct.

2 Likes

So, I tried overriding DrawAndDrop through the Unity Editor, at least manually. And the first time I drag and drop a file named HealthComponent, it actually hinges HealthBehaviour on the GameObject, but any subsequent action will crash the Unity Editor. What am I doing wrong?

Update is function of my test EditorWindow

We’re doing this by reflecting into Unity’s DragAndDropService, which you can see the code for here.

Note that this works in 2020.3 LTS, I believe that they refactored that code somewhat for 2021, and that’s not on GitHub so you’ll have to look at a decompiler to see what happens.

After making a wrapper for the internal code so we can call it, the code is pretty simple. Here’s an example from our code base.

public static class SpriteAnimationDragAndDrop {

    [InitializeOnLoadMethod]
    public static void HookUpDragAndDropSpriteAnimationToInspector() {
        var dropHandler = (DragAndDropService.InspectorDropHandler) OnInspectorDrop;

        if (!DragAndDropService.HasHandler(DragAndDropService.kHierarchyDropDstId, dropHandler))
            DragAndDropService.AddDropHandler(dropHandler);
    }

    private static DragAndDropVisualMode OnInspectorDrop(object[] targets, bool perform) {
        // stuff here
    }

perform will be true when the user has dropped the thing, false otherwise. Targets are all the GameObjects that the inspector is showing for, so if you have a bunch selected, then there’s more in there. The dragged stuff is in
DragAndDrop.objectReferences, as usual. The return value just indicates how to style the mouse cursor during the drag, though if you return None, then other registered drag and drop handlers gets to run (so you’d return none if DragAndDrop.objectReferences doesn’t have any HealthComponents)

1 Like

For folks coming here like I just did… Unity 2021 adds this to make things a lot easier:

2 Likes

Yup! The entire reflection thing isn’t necessary now, which is great.

As a bonus, here’s a simple one we have in our project, the SpriteDragAndDrop. It allows you to drag-and-drop sprites (and textures with sprites) onto a GameObject’s inspector, and have that automatically add a SpriteRenderer with that Sprite assigned to that GameObject. It’s probably a better example than the very barebones ones in the documentation:

8512943--1134515--Unity_N4Q6arB6UV.gif

using System.Collections.Generic;
using UnityEditor;
using UnityEngine;

public static class SpriteDragAndDrop {
    [InitializeOnLoadMethod]
    public static void HookUpDragAndDropToInspector() {
        DragAndDrop.AddDropHandler(OnInspectorDrop);
    }

    private static List<GameObject> targetGameObjects = new List<GameObject>();
    private static DragAndDropVisualMode OnInspectorDrop(Object[] targets, bool perform) {
        targetGameObjects.Clear();
        foreach (var target in targets)
            if (target is GameObject gameObject && gameObject.GetComponent<SpriteRenderer>() == null)
                targetGameObjects.Add(gameObject);

        if (targetGameObjects.Count == 0)
            return DragAndDropVisualMode.None; // this allows other drag and drop handlers to handle the drag

        Sprite sprite = null;
        foreach (var obj in DragAndDrop.objectReferences) {
            if (obj is Sprite s) {
                sprite = s;
                break;
            }
        }

        if (sprite == null) {
            foreach (var obj in DragAndDrop.objectReferences) {
                if (obj is Texture t) {
                    sprite = AssetDatabase.LoadAssetAtPath<Sprite>(AssetDatabase.GetAssetPath(t));
                    if (sprite != null)
                        break;
                }
            }
        }

        if (sprite == null)
            return DragAndDropVisualMode.None;

        if (perform)
            foreach (var gameObject in targetGameObjects)
                Undo.AddComponent<SpriteRenderer>(gameObject).sprite = sprite;

        return DragAndDropVisualMode.Link;
    }
}
5 Likes

Thanks for your example. However, how do I tell which editor and GUI element the targets were dropped unto ? The callback only specifies the targets, not the drop “target” (kind of a reverse naming isnt it)

The thing(s) you are dragging are in DragAndDrop.objectReferences
The thing(s) you are dragging the objects onto is the Object[] targets array.

If you look at my code sample, I’m finding GameObjects without SpriteRenderers in targets, and I’m creating SpriteRenderers and assigning a sprite I found in DragAndDrop.objectReferences to those GameObjects.

2 Likes

Thanks,
I found out that what I actually was looking for was to check whether a gui rect contains the input.mousePosition and then handle the EventType.DragPerform and EventType.DragUpdated events rather than adding a handler.