├── .gitattributes ├── .gitignore ├── LICENSE └── readme.md /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # This .gitignore file should be placed at the root of your Unity project directory 2 | # 3 | # Get latest from https://github.com/github/gitignore/blob/master/Unity.gitignore 4 | # 5 | /[Ll]ibrary/ 6 | /[Tt]emp/ 7 | /[Oo]bj/ 8 | /[Bb]uild/ 9 | /[Bb]uilds/ 10 | /[Ll]ogs/ 11 | /[Mm]emoryCaptures/ 12 | 13 | # Never ignore Asset meta data 14 | !/[Aa]ssets/**/*.meta 15 | 16 | # Uncomment this line if you wish to ignore the asset store tools plugin 17 | # /[Aa]ssets/AssetStoreTools* 18 | 19 | # TextMesh Pro files 20 | [Aa]ssets/TextMesh*Pro/ 21 | 22 | # Autogenerated Jetbrains Rider plugin 23 | [Aa]ssets/Plugins/Editor/JetBrains* 24 | 25 | # Visual Studio cache directory 26 | .vs/ 27 | 28 | # Gradle cache directory 29 | .gradle/ 30 | 31 | # Autogenerated VS/MD/Consulo solution and project files 32 | ExportedObj/ 33 | .consulo/ 34 | *.csproj 35 | *.unityproj 36 | *.sln 37 | *.suo 38 | *.tmp 39 | *.user 40 | *.userprefs 41 | *.pidb 42 | *.booproj 43 | *.svd 44 | *.pdb 45 | *.mdb 46 | *.opendb 47 | *.VC.db 48 | 49 | # Unity3D generated meta files 50 | *.pidb.meta 51 | *.pdb.meta 52 | *.mdb.meta 53 | 54 | # Unity3D generated file on crash reports 55 | sysinfo.txt 56 | 57 | # Builds 58 | *.apk 59 | *.unitypackage 60 | 61 | # Crashlytics generated file 62 | crashlytics-build.properties 63 | 64 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Park June Chul 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | UnityHack 2 | ==== 3 | A set of snippets to extend Unity Editor. 4 | 5 | Before you start 6 | ---- 7 | All of snippets from this repository only work inside Unity Editor.
8 | You must place your code inside `Editor` directory to avoid build errors. 9 | 10 | Bootstrap your plugin 11 | ---- 12 | It all begins with bootstrapping code. This step is pretty easy because Unity already has API for this. 13 | ```cs 14 | [InitializeOnLoadMethod] 15 | public static void Initialize() { 16 | /* ... Your code goes here ... */ 17 | } 18 | ``` 19 | 20 | Input 21 | ---- 22 | Because of `Input` class does not work on Unity Editor, you have to write special code to handle it. 23 | 24 | __Handling global editor input__ 25 | ```cs 26 | System.Reflection.FieldInfo info = typeof(EditorApplication).GetField("globalEventHandler", System.Reflection.BindingFlags.Static | System.Reflection.BindingFlags.NonPublic); 27 | EditorApplication.CallbackFunction value = (EditorApplication.CallbackFunction)info.GetValue(null); 28 | value += HandleUnityEvent; 29 | info.SetValue(null, value); 30 | ``` 31 | ```cs 32 | static void HandleUnityEvent() 33 | { 34 | var e = Event.current; 35 | 36 | if (e.type == EventType.KeyDown && e.keyCode == KeyCode.LeftControl) 37 | { 38 | /* Do your stuffs here */ 39 | } 40 | } 41 | ``` 42 | 43 | __Handling scene window input__
44 | If you want to execute your code __ONLY__ inside the scene view, try this instead of `globalEventHandler`. It also seems less scary. 45 | ```cs 46 | SceneView.duringSceneGui += HandleSceneEvent; 47 | ``` 48 | ```cs 49 | static void HandleSceneEvent() { 50 | var e = Event.current; 51 | 52 | if (e == null) return; 53 | if (e.type == EventType.KeyDown && e.keyCode == KeyCode.LeftControl) 54 | { 55 | /* Do your stuffs here */ 56 | } 57 | } 58 | ``` 59 | 60 | 61 | Property change detection 62 | ---- 63 | There's no official API to get notification when your changed the property. However, this can be done with `Undo.postprocessModifications` since every changes will be recorded to `Undo`. 64 | 65 | ```cs 66 | Undo.postprocessModifications += OnPostProcessModifications; 67 | ``` 68 | ```cs 69 | private static UndoPropertyModification[] OnPostProcessModifications(UndoPropertyModification[] propertyModifications) 70 | { 71 | foreach (var m in propertyModifications) 72 | { 73 | if (m.currentValue.target is Text textTarget) { 74 | if (m.currentValue.propertyPath == "m_FontData.m_FontSize") { 75 | /* User has changed `FontSize` from `Text` Component. */ 76 | } 77 | } 78 | } 79 | } 80 | ``` 81 | 82 | Rendering inside SceneView 83 | ---- 84 | `OnDrawGizmo` is a traditional way to draw something inside SceneView. However, you must have a `GameObject` to receive `OnDrawGizmo` callback from Unity.
85 | You can also use `SceneView.duringSceneGui` to draw your stuffs without active `GameObject`. 86 | 87 | ```cs 88 | SceneView.duringSceneGui += OnDrawSceneGUI; 89 | ``` 90 | ```cs 91 | static void OnDrawSceneGUI() 92 | { 93 | /* Do your stuffs! */ 94 | } 95 | ``` 96 | 97 | __Render Texts__
98 | There are APIs for drawing `Rect`, `Line`, `Polygon` and even `Texture`. But they don't have an API to render texts.
99 | You can render texts by below script: 100 | ```cs 101 | public static void DrawString(string text, Vector3 position, Color? color = null, bool showTexture = true, int offsetY = 0) 102 | { 103 | Handles.BeginGUI(); 104 | 105 | var restoreColor = GUI.color; 106 | var viewSize = SceneView.currentDrawingSceneView.position; 107 | if (color.HasValue) GUI.color = color.Value; 108 | var view = SceneView.currentDrawingSceneView; 109 | Vector3 screenPos = view.camera.WorldToScreenPoint( 110 | position + view.camera.transform.forward); 111 | if (screenPos.y < 0 || screenPos.y > Screen.height || screenPos.x < 0 || screenPos.x > Screen.width || screenPos.z < 0) 112 | { 113 | GUI.color = restoreColor; 114 | Handles.EndGUI(); 115 | return; 116 | } 117 | var style = new GUIStyle(GUI.skin.label); 118 | style.alignment = TextAnchor.MiddleCenter; 119 | style.fontSize = 30; 120 | Vector2 size = style.CalcSize(new GUIContent(text)); 121 | GUI.Label(new Rect(screenPos.x - size.x/2, viewSize.height - screenPos.y - offsetY - size.y/2, size.x, size.y), text, style); 122 | GUI.color = restoreColor; 123 | 124 | Handles.EndGUI(); 125 | } 126 | ``` 127 | 128 | Detecting object creation 129 | ---- 130 | 131 | __Detect GameObject creation__ 132 | ```cs 133 | EditorApplication.hierarchyChanged += OnHierarchyChanged; 134 | ``` 135 | ```cs 136 | private static GameObject lastChangedGO; 137 | 138 | private static void OnHierarchyChanged() 139 | { 140 | if (Selection.objects.Length == 0) 141 | return; 142 | var go = Selection.objects[0] as GameObject; 143 | if (go == null) 144 | return; 145 | 146 | if (go == lastChangedGO) 147 | return; 148 | 149 | lastChangedGO = go; 150 | 151 | /* Do your stuff! */ 152 | } 153 | ``` 154 | 155 | __Detect Component creation__ 156 | ```cs 157 | private static GameObject lastChangedGO; 158 | private static int lastGOComponentCount; 159 | 160 | private static void OnHierarchyChanged() 161 | { 162 | if (Selection.objects.Length == 0) 163 | return; 164 | var go = Selection.objects[0] as GameObject; 165 | if (go == null) 166 | return; 167 | 168 | if (go != lastChangedGO) 169 | return; 170 | lastChangedGO = go; 171 | 172 | var comps = go.GetComponents(); 173 | if (comps.Length == lastGOComponentCount) 174 | return; 175 | lastGOComponentCount = comps.Length; 176 | 177 | var addedComponent = comps.Last(); 178 | /* Do your stuff! */ 179 | } 180 | ``` 181 | 182 | Drag and Drop 183 | ---- 184 | 185 | __Asset drag detection__
186 | You can watch `BeginDrag` for your assets with below code: 187 | ```cs 188 | EditorApplication.update += OnUpdate; 189 | ``` 190 | ```cs 191 | static void OnUpdate() 192 | { 193 | if (DragAndDrop.paths.Length > 0 && 194 | !(Selection.activeObject is GameObject)) 195 | { 196 | // DragAndDrop.objectReferences; 197 | } 198 | } 199 | ``` 200 | 201 | Object Picking 202 | ---- 203 | You can retrive gameObjects with a XY position using `HandleUtility.PickGameObject`. This is slightly better than `Physics.Raycast` since it does not require `Collider` on the GameObject. 204 | 205 | ```cs 206 | SceneView.duringSceneGui += HandleSceneEvents; 207 | ``` 208 | ```cs 209 | static void HandleSceneEvents() { 210 | if (e == null) return; 211 | if (e.type == EventType.MouseDown) { 212 | var picked = HandleUtility.PickGameObject( 213 | Event.current.mousePosition, false); 214 | Debug.Log(picked); 215 | } 216 | } 217 | ``` 218 | 219 | __Pick ALL objects with current mouse position__ 220 | ```cs 221 | var ignoreList = new List(); 222 | while (true) 223 | { 224 | var picked = HandleUtility.PickGameObject( 225 | Event.current.mousePosition, false, 226 | ignoreList.ToArray()); 227 | 228 | if (picked == null) 229 | break; 230 | 231 | Debug.Log(picked); 232 | 233 | ignoreList.Add(picked); 234 | } 235 | ``` 236 | --------------------------------------------------------------------------------