Skip to content

Instantly share code, notes, and snippets.

@empika
Last active July 17, 2021 02:01
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save empika/dc5b650fb8516c2f9489eae50aac4c1b to your computer and use it in GitHub Desktop.
Save empika/dc5b650fb8516c2f9489eae50aac4c1b to your computer and use it in GitHub Desktop.
SceneViewEditorBoilerplate
using System.Collections.Generic;
using UnityEditor;
using UnityEditor.SceneManagement;
using UnityEngine;
using UnityEngine.SceneManagement;
public class SceneViewEditorBoilerplate : ScriptableObject
{
public const string SVE_IS_ENABLED = "SVE_IS_ENABLED";
private bool isLeftMouseDragging = false;
private bool isRightMouseDragging = false;
private bool isMiddleMouseDragging = false;
private List<Vector2> mousePositions = new List<Vector2>();
private GUIStyle layerLabelStyle;
// Some singleton business so that we only ever have one editor tool initialised at once
public static SceneViewEditorBoilerplate Instance
{
get
{
if(instance == null)
{
SceneViewEditorBoilerplate[] editor = Resources.FindObjectsOfTypeAll<SceneViewEditorBoilerplate>();
if(editor != null && editor.Length > 0)
{
instance = editor[0];
for(int i = 1; i < editor.Length; i++)
{
GameObject.DestroyImmediate(editor[i]);
}
}
}
return instance;
}
set
{
instance = value;
}
}
private static SceneViewEditorBoilerplate instance;
[MenuItem("Tools/My Scene View Editor %#o", false, 2005)]
public static void InitTool()
{
if (instance == null)
{
EditorPrefs.SetBool(SVE_IS_ENABLED, true);
instance = ScriptableObject.CreateInstance<SceneViewEditorBoilerplate>();
instance.hideFlags = HideFlags.DontSave;
EditorApplication.delayCall += instance.Initialize;
}
else
{
CloseTool();
}
SceneView.RepaintAll();
}
public static void CloseTool()
{
foreach(SceneViewEditorBoilerplate editor in Resources.FindObjectsOfTypeAll<SceneViewEditorBoilerplate>())
editor.Close();
Tools.current = Tool.Move;
}
public void Close()
{
removeListeners();
EditorPrefs.SetBool(SVE_IS_ENABLED, false);
DestroyImmediate(this);
}
public void Initialize()
{
Debug.Log("Initializing SceneViewEditor");
// Set up all our callbacks
SceneView.duringSceneGui -= OnSceneGUI;
SceneView.duringSceneGui += OnSceneGUI;
EditorApplication.update -= Update;
EditorApplication.update += Update;
Undo.undoRedoPerformed -= RepaintSceneView;
Undo.undoRedoPerformed += RepaintSceneView;
EditorApplication.playModeStateChanged -= playmodeStateChanged;
EditorApplication.playModeStateChanged += playmodeStateChanged;
EditorSceneManager.sceneClosing -= sceneClosing;
EditorSceneManager.sceneClosing += sceneClosing;
EditorSceneManager.sceneOpened -= sceneOpened;
EditorSceneManager.sceneOpened += sceneOpened;
layerLabelStyle = new GUIStyle();
layerLabelStyle.alignment = TextAnchor.LowerCenter;
layerLabelStyle.fontSize = 18;
layerLabelStyle.normal.textColor = Color.green;
// Set our tool to None... this is only a visual thing in the toolbar
// See the click handling below on how to actually bypass unity's tools
Tools.current = Tool.None;
instance = this;
Update();
RepaintSceneView();
}
private void OnDestroy()
{
removeListeners();
}
private void removeListeners()
{
SceneView.duringSceneGui -= OnSceneGUI;
EditorApplication.update -= Update;
Undo.undoRedoPerformed -= RepaintSceneView;
EditorSceneManager.sceneClosing -= sceneClosing;
EditorSceneManager.sceneOpened -= sceneOpened;
}
private void playmodeStateChanged(PlayModeStateChange state)
{
if (state == PlayModeStateChange.EnteredEditMode)
{
// Do some stuff here. For example I have a bunch of objects in the sceneview that I set to hidden (tile brush etc)
// I need to respawn those when we are editing
Update();
RepaintSceneView();
}
else if (state == PlayModeStateChange.ExitingEditMode)
{
// Do some stuff here. For example I have a bunch of objects in the sceneview that I set to hidden (tile brush etc)
// I need to remove those when we are playing
}
}
private void sceneClosing(Scene scene, bool removingScene)
{
OnDestroy();
}
private void sceneOpened(Scene scene, OpenSceneMode mode)
{
Initialize();
}
void RepaintSceneView()
{
SceneView.RepaintAll();
}
public void Update()
{
if (EditorApplication.isPlaying)
{
return;
}
// Do some updating stuff here if you need to
}
public void OnSceneGUI(SceneView sceneView)
{
if (EditorApplication.isPlaying)
{
return;
}
bool isCurrentView = sceneView == SceneView.lastActiveSceneView;
if (isCurrentView)
{
Handles.BeginGUI();
DrawSceneGUI();
Handles.EndGUI();
}
HandleUtility.Repaint();
}
public void DrawSceneGUI()
{
Rect screenRect = SceneView.lastActiveSceneView.position;
GUI.Label(new Rect(screenRect.width / 2, 10, 200, 40), "Scene View Editor", layerLabelStyle);
// This is kinda like the update in the normal monobehaviours, but for the sceneview
// The mouse handling gets quite complicated so we just handle them all, all the time
handleClick(screenRect);
handleRightClick();
}
private void handleClick(Rect screenRect)
{
Event e = Event.current;
if (screenRect.Contains(e.mousePosition))
{
// This control id stuff is pretty confusing and i still don't 100% understand it
// We get a control id from unity, to say we're doing something
// Don't know why we check GUIUtility.hotControl != 0, but if it is then we want to tell it our
int controlId = GUIUtility.GetControlID (FocusType.Passive);
if (e.type == EventType.MouseDown && Event.current.button == 0 && GUIUtility.hotControl != 0)
{
GUIUtility.hotControl = controlId;
// This is the start of the drag, for whatever reason unity's EventType.MouseDrag doesnt quite work
// how you'd expect so we want to track this ourselves
isLeftMouseDragging = true;
}
else if (e.type == EventType.MouseUp && Event.current.button == 0 && GUIUtility.hotControl == controlId)
{
if (mousePositions.Count == 0)
{
// We got a click! We're not dragging or anything so just do something normal!
// ...
// or we can check for modifiers first...
if (e.control)
{
// we got a ctrl click
}
else
{
// just a normal click
}
// We need to set the hotcontrol to 0 to let unity know we're all done
GUIUtility.hotControl = 0;
}
// We're no longer dragging
isLeftMouseDragging = false;
mousePositions.Clear();
// This is where we consume the mouse click, so that unity doesnt do it's normal stuff with selecting or moving etc
e.Use();
RepaintSceneView();
}
else if (e.type == EventType.MouseDrag && Event.current.button == 0 && GUIUtility.hotControl == controlId)
{
// Are we dragging... like we set earlier
if (isLeftMouseDragging)
{
// Yes we are, so add this mouse position to the list
mousePositions.Add(e.mousePosition);
// We're dragging so we can do stuff here. In my tile map, I create a tile here based on the position.
// clear those mouse positions
mousePositions.Clear();
// This is where we consume the mouse drag, so that unity doesnt do it's normal stuff with selecting or moving etc
e.Use();
RepaintSceneView();
}
}
}
}
private void handleRightClick()
{
Event e = Event.current;
if (e.type == EventType.MouseDown && Event.current.button == 1)
{
isRightMouseDragging = false;
}
else if (e.type == EventType.MouseDrag && Event.current.button == 1 && e.command )
{
isRightMouseDragging = true;
// Note that here the command key is pressed and we are dragging, I don't consume the event with e.Use().
// This is so that can do the normal camera orbit in the scene view, but it requires the command key to be pressed,
// I want a right click to select the tile in the tile map without affecting the camera at all
}
else if (e.type == EventType.MouseDrag && Event.current.button == 1)
{
isRightMouseDragging = true;
// This is where we consume the mouse drag, so that unity doesnt do it's normal stuff with selecting or moving etc
e.Use();
}
else if (e.type == EventType.MouseUp && Event.current.button == 1)
{
if (isRightMouseDragging == false)
{
// This is just a normal right click
}
isRightMouseDragging = false;
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment