Drawing to the scene from an EditorWindow
Asked Answered
D

5

0

I'm getting my feed wet with extending the Unity tools. Long story short I want to make a custom grid that I can use to snap specific objects. Right now I'm trying to make a window that shows you the grid that you can define. My issue here is that this the line drawing doesn't seem to update in a window very well.

If I toggle it on or off... doesn't update. If it's on and I select a viewport, it draw, but If I change a number, it does not update. If I change a number and select a viewport, it now draws the previous iteration as well as the last one.

I'm not even sure if I'm using the correct draw functions here. drawing gizmos didn't seem to work. As well, I got a memory leak error from this. So, any suggestions as to a better method or a way to force a draw update in the viewports.

Here is the code I have so far.

class TileWindow extends EditorWindow
{
    var showGrid:boolean=false;
    var xTiles:int=24;
    var zTiles:int=24;
    var tileSize:float=1;
    var xOffset:float=0;
    var zOffset:float=0;
    var topLeft:Vector3;
    var topRight:Vector3;
    var botLeft:Vector3;
    var botRight:Vector3;

    @MenuItem ("Window/Tile View")
    static function ShowWindow () {
        EditorWindow.GetWindow (TileWindow);
    }

    function OnGUI () {
        showGrid=EditorGUILayout.Toggle("Grid View",showGrid);
        tileSize=EditorGUILayout.FloatField("Tile Size",tileSize);
        xTiles=EditorGUILayout.FloatField("X Tiles",xTiles);
        zTiles=EditorGUILayout.FloatField("Z Tiles",zTiles);
    }

    function OnInspectorUpdate () {
        xOffset=-(xTiles*tileSize)/2;
        zOffset=-(zTiles*tileSize)/2;

        if (showGrid){
            for (i=0;i<xTiles;i++){
                for (j=0;j<zTiles;j++){
                    topLeft= Vector3(xOffset+(tileSize*i),0,zOffset+(tileSize*j));
                    topRight=Vector3(xOffset+(tileSize*i+tileSize),0,zOffset+(tileSize*j));
                    botLeft= Vector3(xOffset+(tileSize*i),0,zOffset+(tileSize*j+tileSize));
                    botRight=Vector3(xOffset+(tileSize*i+tileSize),0,zOffset+(tileSize*j+tileSize));
                    Debug.DrawLine(topLeft,topRight,Color.red);
                    Debug.DrawLine(botLeft,botRight,Color.red);
                    Debug.DrawLine(topLeft,botLeft,Color.red);
                    Debug.DrawLine(topRight,botRight,Color.red);

                }
            }
        }
    }
}

Detoxicate answered 9/1 at 19:6 Comment(0)
K
0

Oliver’s answer is not entirely correct but is the correct approach. This is how I solved this problem:

// Window has been selected
void OnFocus()
{
	// Remove delegate listener if it has previously
	// been assigned.
	SceneView.onSceneGUIDelegate -= this.OnSceneGUI;

	// Add (or re-add) the delegate.
	SceneView.onSceneGUIDelegate += this.OnSceneGUI;
}
  
void OnDestroy()
{
	// When the window is destroyed, remove the delegate
	// so that it will no longer do any drawing.
	SceneView.onSceneGUIDelegate -= this.OnSceneGUI;
}
  
void OnSceneGUI(SceneView sceneView)
{
	// Do your drawing here using Handles.

	Handles.BeginGUI();
	// Do your drawing here using GUI.
	Handles.EndGUI();    
}

Once you have registered your OnSceneGUI method as a delegate listener to the onSceneGuiDelegate event in SceneView, it will be called whenever the editor draws the scene (so once for each window).

This will continue after the window is destroyed, so it must be release in OnDestroy.

The strange part is how the delegate is released and then register in OnFocus. Unfortunately EditorWindow has no Awake/Start type event. OnFocus is as a next-best. OnFocus can be called multiple times during a Window’s lifetime, and the delegate should not be registered more than once. There is no way to check whether the delegate has already been registered, but removing it first will solve this.

Kickstand answered 9/1 at 19:7 Comment(4)

You should use OnEnable() to register your delegates. And delegates can be checked https://mcmap.net/q/9272/-has-an-event-handler-already-been-added

Vitriolic

Thanks for the tip on checking delegates. I didn't check OnEnable, but it's not documented as a message received by EditorWindow. Does it work anyway?

Kickstand

Thats a cool snippet, didn't know I can work with the SceneView inside EditorWindow too.

Coagulase

If you got TargetParameterCountException error, then try to use static on void OnSceneGUI(SceneView sceneView){} to be static void OnSceneGUI(SceneView sceneView) {} don't know if this is will help or not, just figuring out when I got that exception while using the same method as describe above for drawing GUI elements on SceneView while not on Play Mode.

Durning
A
0

Well, the editor works a bit different than the game runtime. The GUI and the sceneview is only drawn if it's needed. Every EditorWindow have a Repaint function. The SceneView is also derived from EditorWindow. The SceneView class is not documented yet, but if you use visual studio (c#) you can view all members.

Just in case that you can't find it:

SceneView.lastActiveSceneView.Repaint();

Arabist answered 23/3 at 11:37 Comment(2)

HandleUtility.Repaint() also works great.

Isothere

@Arabist I've been trying to solve this related question (https://forum.unity.com/threads/prefab-mode-changes-only-one-of-several-prefab-copies-until-the-others-are-manually-selected.613735/) for quite a while. I'll really appreciate your help.

Fulgurate
P
0

I used the Delegate function of SceneView to manage this.
C# Code (should work in JavaScript…):

//Inside OnGUI or Update, does not make any difference.
if(SceneView.onSceneGUIDelegate != this.OnSceneGUI)
{
    SceneView.onSceneGUIDelegate += this.OnSceneGUI;
}

When you insert this you can have a simple OnSceneGUI() function in you EditorWindow class. BUT: The OnSceneGUI function has to have one parameter of the type SceneView. Like so:

public void OnSceneGUI (SceneView scnView)
{
	Handles.Button (new Vector3 (500.0f, 0, 500.0f), Quaternion.LookRotation (Vector3.up), 500.0f, 0.0f, Handles.RectangleCap);
}

The SceneView passed each call is the SceneView that is currently drawing, so you can draw different GUIs for each SceneView.

As you See, you can even use the Handles’ functions. If you intend to use regular GUI functions you first have to start a GUI Block with the corresponding Handle functions BeginGUI and EndGUI.

Pectase answered 9/1 at 19:9 Comment(5)

I tried to upvote this answer but for some reason Unity Answers doesn't permit me to do anything. I'm actually surprised I'm allowed to comment. Anyway, great answer, you've helped me solve my problem. I'd just like to make a small correction, however. The != operator does not do what your code implies. A better way to do this would be to declare a flag to keep track of whether you have registered your delegate yet. When I used your method Unity crashed - I attribute the reason to an infinitely extending list of delegate listeners in SceneView (one per call to OnSceneGUI).

Kickstand

Well, that would then be just a matter of adding the delegate correctly. As I just now found out you can just remove it before adding it, if it was not already assigned it will just be ignored. See here: https://mcmap.net/q/9273/-how-do-i-find-out-if-a-particular-delegate-has-already-been-assigned-to-an-event

Pectase

I've now adopted using OnFocus and OnLostFocus to add and remove the listener, however this is not necessarily the appropriate solution for all situations.

Kickstand

That might work, too. It would at least save you the check in every GUI/Update call. However that will then only update the scene if the editor window in question is active (Do update calls make it to an inactive window? It's been a while since I last worked with Editor modification...). But still, that might be just the right thing for some cases, depending on what you want to do, if it is a permenent "addon" for the scene view or just a demonstration/visualization of what is going on in the editor.

Pectase

Yeah, the OnSceneGUI is fired when the window is inactive and even after it is destroyed. I'm going to add a new answer with my solution I've come to (using both your and my approach). Thanks for the tip.

Kickstand
K
0

Oliver’s answer is not entirely correct but is the correct approach. This is how I solved this problem:

// Window has been selected
void OnFocus()
{
	// Remove delegate listener if it has previously
	// been assigned.
	SceneView.onSceneGUIDelegate -= this.OnSceneGUI;

	// Add (or re-add) the delegate.
	SceneView.onSceneGUIDelegate += this.OnSceneGUI;
}
  
void OnDestroy()
{
	// When the window is destroyed, remove the delegate
	// so that it will no longer do any drawing.
	SceneView.onSceneGUIDelegate -= this.OnSceneGUI;
}
  
void OnSceneGUI(SceneView sceneView)
{
	// Do your drawing here using Handles.

	Handles.BeginGUI();
	// Do your drawing here using GUI.
	Handles.EndGUI();    
}

Once you have registered your OnSceneGUI method as a delegate listener to the onSceneGuiDelegate event in SceneView, it will be called whenever the editor draws the scene (so once for each window).

This will continue after the window is destroyed, so it must be release in OnDestroy.

The strange part is how the delegate is released and then register in OnFocus. Unfortunately EditorWindow has no Awake/Start type event. OnFocus is as a next-best. OnFocus can be called multiple times during a Window’s lifetime, and the delegate should not be registered more than once. There is no way to check whether the delegate has already been registered, but removing it first will solve this.

Kickstand answered 9/1 at 19:7 Comment(4)

You should use OnEnable() to register your delegates. And delegates can be checked https://mcmap.net/q/9272/-has-an-event-handler-already-been-added

Vitriolic

Thanks for the tip on checking delegates. I didn't check OnEnable, but it's not documented as a message received by EditorWindow. Does it work anyway?

Kickstand

Thats a cool snippet, didn't know I can work with the SceneView inside EditorWindow too.

Coagulase

If you got TargetParameterCountException error, then try to use static on void OnSceneGUI(SceneView sceneView){} to be static void OnSceneGUI(SceneView sceneView) {} don't know if this is will help or not, just figuring out when I got that exception while using the same method as describe above for drawing GUI elements on SceneView while not on Play Mode.

Durning
L
0

Here’s a more compact version using OnEnable/OnDisable instead, which is more reliable

void OnEnable () { SceneView.onSceneGUIDelegate += this.OnSceneGUI; }
void OnDisable () { SceneView.onSceneGUIDelegate -= this.OnSceneGUI; }
void OnSceneGUI ( SceneView sceneView )
{
    Handles.BeginGUI();
    // GUI Code here
    Handles.EndGUI();
}
Leprous answered 9/1 at 19:10 Comment(1)

Delighted to find a Freya Holmér answer that nails it. Massive fan.

Capo
T
0

SceneView.onSceneGUIDelegate is deprecated now. You should use SceneView.duringSceneGui instead.

Towhee answered 9/1 at 19:11 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.