6

Say you have a trivial prefab, "Box", which we'll say is nothing more than a standard meter cube.

  • 1 - The prefab Box is in your Project panel

  • 2 - Drag it to the Scene

  • 3 - Obviously it now appears in the Hierarchy panel also, and probably selected and shown in Inspector

To be clear, game is NOT Play when you do this, you're only in ordinary Editor mode.

Is it possible to make a script (an "Editor script"?) so that,

  • when you do "1" and "2" above, (again this is in Editor mode, not during a game)

  • when 3 happens, we can affect the new Box item in the scene

  • So, simple example: we will set the Z position to "2" always, no matter where you drop it.

In short: Editor code so that every time you drag a prefab P to the scene, it sets the position z to 2.0.

Is this possible in Unity? I know nothing of "editor scripts".

It seems very obvious this should be possible.

Damien_The_Unbeliever
  • 234,701
  • 27
  • 340
  • 448
Fattie
  • 27,874
  • 70
  • 431
  • 719
  • that's not really correct, Jin ... or else, you'd have to show me what you mean in the whole code?? – Fattie Aug 29 '18 at 07:22
  • 1
    I don't think there is a Unity API for this but I think you should add code of what you've tried to the question. I am saying this because of your last 3 previous questions including this one. – PassetCronUs Aug 29 '18 at 22:19
  • I've looked into this and it's possible to do but complicated. It's more than using `Selection.activeGameObject` and lots of code. You need a way to distinguish between when the object is actually dragged to the hierarchy and when the object is rearranged in the Hierarchy. Both of these triggers one callback function. You would also need to implement saving and loading of variables since this is an Editor plugin and they will reset anytime you click "Play" button leading to a bug – Programmer Aug 29 '18 at 22:55
  • Unity should add function for that. There is `EditorApplication.hierarchyChanged` but it's also called when the object is re-arranged in the scene. Actually, `Selection.activeGameObject` here is not unrelated because when you drag object from Project to Hierarchy tab, the object is automatically selected, therefore in `Selection.activeGameObject`. It can be used with `EditorApplication.hierarchyChanged` – Programmer Aug 30 '18 at 18:09
  • why is this so important to be in the scene editor ? – Mightee Sep 06 '18 at 12:51
  • @Mightee - that's very obvious, as you position things while creating a scene – Fattie Sep 06 '18 at 14:43
  • @Fattie dont get me wrong but cant you just hard code the z value in the prefab before you put it in the scene ? I think you are approaching your problem in a different way. Whats your actual problem that you are trying to solve ? – Mightee Sep 07 '18 at 06:42
  • Hi @Mightee , it's very obvious that this could be useful in endless ways, cheers. (The " zposition" is just a simple example, as it says.) BTW you can't "hard code a value in a prefab", you can drop a prefab in a scene anywhere you wish - try moving your mouse around just before letting go. – Fattie Sep 07 '18 at 07:28
  • @Fattie, oh so you are trying to create some kind of a third party scene editor ? by the way, have you checked this https://docs.unity3d.com/Manual/RunningEditorCodeOnLaunch.html ? – Mightee Sep 07 '18 at 07:47
  • howdy again @Mightee . Examples are very obvious. You may want to assign a random color, texture, character to each of a certain item you drop. You may want to snap them to a grid, snap them to a certain item or height, or, associate them with some other item in the scene. Very straightforward. Notice the "random rotations" example in the edit below, cheers. – Fattie Sep 07 '18 at 07:53
  • FYI running editor code on launch (they mean when you *launch Unity*) is irrelevant, thanks. – Fattie Sep 07 '18 at 07:55

4 Answers4

10

You can add a custom window editor which implements OnHierarchyChange to handle all the changes in the hierarchy window. This script must be inside the Editor folder. To make it work automatically make sure you have this window opened first.

using System.Linq;
using UnityEditor;
using UnityEngine;

public class HierarchyMonitorWindow : EditorWindow
{
    [MenuItem("Window/Hierarchy Monitor")]
    static void CreateWindow()
    {
        EditorWindow.GetWindow<HierarchyMonitorWindow>();
    }

    void OnHierarchyChange()
    {
        var addedObjects = Resources.FindObjectsOfTypeAll<MyScript>()
                                    .Where(x => x.isAdded < 2);

        foreach (var item in addedObjects)
        {
            //if (item.isAdded == 0) early setup
            
            if (item.isAdded == 1) {
                
                // do setup here,
                // will happen just after user releases mouse
                // will only happen once
                Vector3 p = transform.position;
                item.transform.position = new Vector3(p.x, 2f, p.z);
            }

            // finish with this:
            item.isAdded++;
        }
    }
}

I attached the following script to the box:

public class MyScript : MonoBehaviour {
    public int isAdded { get; set; }
}

Note that OnHierarchyChange is called twice (once when you start dragging the box onto the scene, and once when you release the mouse button) so isAdded is defined as an int to enable its comparison with 2. So you can also have initialization logic when x.isAdded < 1

Bizhan
  • 16,157
  • 9
  • 63
  • 101
  • **This is the correct answer! Hooray!** Well maybe there's another way, but this works perfectly. Note that you should check `if(item.isAdded == 1)` before doing the setup, or, it will do it twice (which may or may not be harmless). – Fattie Sep 07 '18 at 08:09
  • Hooray! Sent a bounty! – Fattie Sep 07 '18 at 08:14
  • @Fattie thanks for the correction and the bountiful bounty =) – Bizhan Sep 07 '18 at 08:15
  • notice also @Bizhan the answer by ikkentim which partially works, it is a useful quick solution I guess in some cases – Fattie Sep 07 '18 at 08:25
2

You could thy this:

using UnityEngine;
#if UNITY_EDITOR
using UnityEditor;
#endif

[ExecuteInEditMode]
public class PrintAwake : MonoBehaviour
{
    #if UNITY_EDITOR
    void Awake()  .. Start() also works perfectly
    {
        if(!EditorApplication.isPlaying)
            Debug.Log("Editor causes this Awake");
    }
    #endif
}

See https://docs.unity3d.com/ScriptReference/ExecuteInEditMode.html


Analysis:

  1. This does in fact work!

  2. One problem! It happens when the object starts to exist, so, when you are dragging it to the scene, but before you let go. So in fact, if you specifically want to adjust the position in some way (snapping to a grid - whatever) it is not possible using this technique!

So, for example, this will work perfectly:

using UnityEngine;
#if UNITY_EDITOR
using UnityEditor;
#endif

[ExecuteInEditMode]
public class PrintAwake : MonoBehaviour
{
    #if UNITY_EDITOR
    void Start() {
        if(!EditorApplication.isPlaying) {
            Debug.Log("Editor causes this START!!");
            RandomSpinSetup();
            }
    }
    #endif
    private void RandomSpinSetup() {
        float r = Random.Range(3,8) * 10f;
        transform.eulerAngles = new Vector3(0f, r, 0f);
        name = "Cube: " + r + "°";
    }
}

enter image description here

Note that this works correctly, that is to say it does "not run" when you actually Play the game. If you hit "Play" it won't then again set random spins :)

Great stuff

Fattie
  • 27,874
  • 70
  • 431
  • 719
ikkentim
  • 1,639
  • 15
  • 30
  • When playing in the editior, dragging a prefab with this component into the scene hierarchy will not do the random spins either. Therefore this is not useful for people who have a big project running and would like to sandbox-add existing prefabsto see things work. Am I right? – thomsky Jul 19 '23 at 15:09
1

Have a similar issue - wanted to do some stuff after object was dragged into scene (or scene was opened with already existed object). But in my case gameobject was disabled. So I couldn't use neither Awake, nor Start. Solved via akin of dirty trick - just used constructor for my MonoBehaviour class. Unity blocks any attempts to use most of API inside MonoBehaviour constructors, but we could just wait for some time, for example via EditorApplication.delayedCall. So code looks like this:

public class ExampleClass: MonoBehaviour
{

//...
// some runtime logic
//... 
   
#if UNITY_EDITOR

        //Constructor 
        ExampleClass()
        {
            EditorApplication.delayCall += DoSomeStuffForDisabledObjectAfterCreation;
        }

        void DoSomeStuffForDisabledObjectAfterCreation()
        {
            if (!isActiveAndEnabled)
            {
                //Some usefull stuff
            }
        }

#endif

}
-1

Monobehaviours have a Reset method, that only gets called in Editor mode, whenever you reset or first instantiate an object.

public class Example : MonoBehaviour
{
    void Reset()
    {
        transform.position =
           new Vector3(transform.position.x, 2.22f, transform.position.z);
        Debug.Log("Hi ...");
    }
}
Fattie
  • 27,874
  • 70
  • 431
  • 719
Paul-Jan
  • 16,746
  • 1
  • 63
  • 95
  • 1
    Hmm - good one but ......... that **almost works**. Unfortunately it does NOT call the script when you add the prefab to the scene. It only calls the script when you *add that component* to an existing game object (or, explicitly click the small "Reset" button...) – Fattie Aug 29 '18 at 07:20
  • (Thus from the manual ... *"Reset is called when the user hits the Reset button in the Inspector's context menu or when adding the component the first time."* Unfortunately and sadly that is different from what you say, *"...or first instantiate"*) – Fattie Aug 29 '18 at 07:21
  • Oh, good point, that's not a solution to your problem then! I'll leave this (wrong) answer as a warning to others. – Paul-Jan Aug 29 '18 at 07:24