├── .gitattributes ├── .gitignore ├── Editor.meta ├── Editor ├── BoneRenderer.Editor.asmdef ├── BoneRenderer.Editor.asmdef.meta ├── BoneRendererEditorMenu.cs ├── BoneRendererEditorMenu.cs.meta ├── BoneRendererInspector.cs ├── BoneRendererInspector.cs.meta ├── BoneRendererUtil.cs └── BoneRendererUtil.cs.meta ├── README.md ├── Resources.meta ├── Resources ├── Shaders.meta └── Shaders │ ├── BoneHandles.shader │ └── BoneHandles.shader.meta ├── Runtime ├── BoneRenderer.Runtime.asmdef ├── BoneRenderer.Runtime.asmdef.meta ├── BoneRenderer.cs └── BoneRenderer.cs.meta ├── Scripts.meta ├── package.json └── package.json.meta /.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 | # Autogenerated Jetbrains Rider plugin 20 | [Aa]ssets/Plugins/Editor/JetBrains* 21 | 22 | # Visual Studio cache directory 23 | .vs/ 24 | 25 | # Gradle cache directory 26 | .gradle/ 27 | 28 | # Autogenerated VS/MD/Consulo solution and project files 29 | ExportedObj/ 30 | .consulo/ 31 | *.csproj 32 | *.unityproj 33 | *.sln 34 | *.suo 35 | *.tmp 36 | *.user 37 | *.userprefs 38 | *.pidb 39 | *.booproj 40 | *.svd 41 | *.pdb 42 | *.mdb 43 | *.opendb 44 | *.VC.db 45 | 46 | # Unity3D generated meta files 47 | *.pidb.meta 48 | *.pdb.meta 49 | *.mdb.meta 50 | 51 | # Unity3D generated file on crash reports 52 | sysinfo.txt 53 | 54 | # Builds 55 | *.apk 56 | *.unitypackage 57 | 58 | # Crashlytics generated file 59 | crashlytics-build.properties 60 | 61 | -------------------------------------------------------------------------------- /Editor.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 1f7e300bed1f24643b133c5479b427da 3 | folderAsset: yes 4 | DefaultImporter: 5 | externalObjects: {} 6 | userData: 7 | assetBundleName: 8 | assetBundleVariant: 9 | -------------------------------------------------------------------------------- /Editor/BoneRenderer.Editor.asmdef: -------------------------------------------------------------------------------- 1 | { 2 | "name": "BoneRenderer.Editor", 3 | "rootNamespace": "", 4 | "references": [ 5 | "GUID:73b2785bb16270b4abdb3ab39f47288a" 6 | ], 7 | "includePlatforms": [ 8 | "Editor" 9 | ], 10 | "excludePlatforms": [], 11 | "allowUnsafeCode": false, 12 | "overrideReferences": false, 13 | "precompiledReferences": [], 14 | "autoReferenced": true, 15 | "defineConstraints": [], 16 | "versionDefines": [], 17 | "noEngineReferences": false 18 | } -------------------------------------------------------------------------------- /Editor/BoneRenderer.Editor.asmdef.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 741ac6313c417e84cb747ae1f9ceeaea 3 | AssemblyDefinitionImporter: 4 | externalObjects: {} 5 | userData: 6 | assetBundleName: 7 | assetBundleVariant: 8 | -------------------------------------------------------------------------------- /Editor/BoneRendererEditorMenu.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using UnityEditor; 3 | using UnityEngine; 4 | 5 | namespace BoneRenderer.Editor 6 | { 7 | 8 | public static class BoneRendererMenu 9 | { 10 | 11 | 12 | [MenuItem("Bone Renderer/Setup", false, 13)] 13 | public static void BoneRendererSetup() 14 | { 15 | var selection = Selection.activeTransform; 16 | if (selection == null) 17 | return; 18 | 19 | BoneRendererSetup(selection); 20 | } 21 | 22 | public static void BoneRendererSetup(Transform transform) 23 | { 24 | var boneRenderer = transform.GetComponent(); 25 | if (boneRenderer == null) 26 | boneRenderer = Undo.AddComponent(transform.gameObject); 27 | else 28 | Undo.RecordObject(boneRenderer, "Bone renderer setup."); 29 | 30 | var animator = transform.GetComponent(); 31 | var renderers = transform.GetComponentsInChildren(); 32 | var bones = new List(); 33 | if (animator != null && renderers != null && renderers.Length > 0) 34 | { 35 | for (int i = 0; i < renderers.Length; ++i) 36 | { 37 | var renderer = renderers[i]; 38 | for (int j = 0; j < renderer.bones.Length; ++j) 39 | { 40 | var bone = renderer.bones[j]; 41 | if (!bones.Contains(bone)) 42 | { 43 | bones.Add(bone); 44 | 45 | for (int k = 0; k < bone.childCount; k++) 46 | { 47 | if (!bones.Contains(bone.GetChild(k))) 48 | bones.Add(bone.GetChild(k)); 49 | } 50 | } 51 | } 52 | } 53 | } 54 | else 55 | { 56 | bones.AddRange(transform.GetComponentsInChildren()); 57 | } 58 | 59 | boneRenderer.transforms = bones.ToArray(); 60 | 61 | if (PrefabUtility.IsPartOfPrefabInstance(boneRenderer)) 62 | EditorUtility.SetDirty(boneRenderer); 63 | } 64 | } 65 | 66 | } 67 | 68 | -------------------------------------------------------------------------------- /Editor/BoneRendererEditorMenu.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 0b8048d0bc2e1e149ac0afa03946f531 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Editor/BoneRendererInspector.cs: -------------------------------------------------------------------------------- 1 | using UnityEngine; 2 | using UnityEditor; 3 | 4 | namespace BoneRenderer.Editor 5 | { 6 | [CustomEditor(typeof(BoneRenderer))] 7 | [CanEditMultipleObjects] 8 | public class BoneRendererInspector : UnityEditor.Editor 9 | { 10 | static readonly GUIContent k_BoneSizeLabel = new GUIContent("Bone Size"); 11 | static readonly GUIContent k_BoneColorLabel = new GUIContent("Color"); 12 | static readonly GUIContent k_BoneShapeLabel = new GUIContent("Shape"); 13 | static readonly GUIContent k_TripodSizeLabel = new GUIContent("Tripod Size"); 14 | 15 | SerializedProperty m_DrawBones; 16 | SerializedProperty m_BoneShape; 17 | SerializedProperty m_BoneSize; 18 | SerializedProperty m_BoneColor; 19 | 20 | SerializedProperty m_DrawTripods; 21 | SerializedProperty m_TripodSize; 22 | 23 | SerializedProperty m_Transforms; 24 | 25 | public void OnEnable() 26 | { 27 | m_DrawBones = serializedObject.FindProperty("drawBones"); 28 | m_BoneSize = serializedObject.FindProperty("boneSize"); 29 | m_BoneShape = serializedObject.FindProperty("boneShape"); 30 | m_BoneColor = serializedObject.FindProperty("boneColor"); 31 | 32 | m_DrawTripods = serializedObject.FindProperty("drawTripods"); 33 | m_TripodSize = serializedObject.FindProperty("tripodSize"); 34 | 35 | m_Transforms = serializedObject.FindProperty("m_Transforms"); 36 | } 37 | 38 | public override void OnInspectorGUI() 39 | { 40 | serializedObject.Update(); 41 | 42 | 43 | EditorGUILayout.BeginHorizontal(); 44 | EditorGUILayout.PropertyField(m_DrawBones, k_BoneSizeLabel); 45 | using (new EditorGUI.DisabledScope(!m_DrawBones.boolValue)) 46 | EditorGUILayout.PropertyField(m_BoneSize, GUIContent.none); 47 | EditorGUILayout.EndHorizontal(); 48 | 49 | using (new EditorGUI.DisabledScope(!m_DrawBones.boolValue)) 50 | { 51 | EditorGUI.indentLevel++; 52 | EditorGUILayout.PropertyField(m_BoneShape, k_BoneShapeLabel); 53 | EditorGUILayout.PropertyField(m_BoneColor, k_BoneColorLabel); 54 | EditorGUI.indentLevel--; 55 | } 56 | 57 | EditorGUILayout.BeginHorizontal(); 58 | EditorGUILayout.PropertyField(m_DrawTripods, k_TripodSizeLabel); 59 | using (new EditorGUI.DisabledScope(!m_DrawTripods.boolValue)) 60 | EditorGUILayout.PropertyField(m_TripodSize, GUIContent.none); 61 | EditorGUILayout.EndHorizontal(); 62 | 63 | EditorGUI.BeginChangeCheck(); 64 | EditorGUILayout.PropertyField(m_Transforms, true); 65 | bool boneRendererDirty = EditorGUI.EndChangeCheck(); 66 | 67 | if (Event.current.type == EventType.ValidateCommand && Event.current.commandName == "UndoRedoPerformed") 68 | boneRendererDirty = true; 69 | 70 | serializedObject.ApplyModifiedProperties(); 71 | 72 | if (boneRendererDirty) 73 | { 74 | for (int i = 0; i < targets.Length; i++) 75 | { 76 | var boneRenderer = targets[i] as BoneRenderer; 77 | boneRenderer.ExtractBones(); 78 | } 79 | } 80 | } 81 | } 82 | } 83 | 84 | -------------------------------------------------------------------------------- /Editor/BoneRendererInspector.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 57f16d8976d4e8c428e85de746b2c31e 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Editor/BoneRendererUtil.cs: -------------------------------------------------------------------------------- 1 | using System.Collections; 2 | using System.Collections.Generic; 3 | using UnityEngine; 4 | using UnityEditor; 5 | 6 | namespace BoneRenderer.Editor 7 | { 8 | using System; 9 | using UnityEditor.Experimental.SceneManagement; 10 | using UnityEditor.SceneManagement; 11 | using UnityEngine.Rendering; 12 | using BoneShape = BoneRenderer.BoneShape; 13 | using UnityObject = UnityEngine.Object; 14 | 15 | [InitializeOnLoad] 16 | public static class BoneRendererUtil 17 | { 18 | private class BatchRenderer 19 | { 20 | const int kMaxDrawMeshInstanceCount = 1023; 21 | 22 | public enum SubMeshType 23 | { 24 | BoneFaces, 25 | BoneWire, 26 | Count 27 | } 28 | 29 | public Mesh mesh; 30 | public Material material; 31 | 32 | private List m_Matrices = new List(); 33 | private List m_Colors = new List(); 34 | private List m_Highlights = new List(); 35 | 36 | public void AddInstance(Matrix4x4 matrix, Color color, Color highlight) 37 | { 38 | m_Matrices.Add(matrix); 39 | m_Colors.Add(color); 40 | m_Highlights.Add(highlight); 41 | } 42 | 43 | public void Clear() 44 | { 45 | m_Matrices.Clear(); 46 | m_Colors.Clear(); 47 | m_Highlights.Clear(); 48 | } 49 | 50 | private static int RenderChunkCount(int totalCount) 51 | { 52 | return Mathf.CeilToInt((totalCount / (float)kMaxDrawMeshInstanceCount)); 53 | } 54 | 55 | private static T[] GetRenderChunk(List array, int chunkIndex) 56 | { 57 | int rangeCount = (chunkIndex < (RenderChunkCount(array.Count) - 1)) ? 58 | kMaxDrawMeshInstanceCount : array.Count - (chunkIndex * kMaxDrawMeshInstanceCount); 59 | 60 | return array.GetRange(chunkIndex * kMaxDrawMeshInstanceCount, rangeCount).ToArray(); 61 | } 62 | 63 | public void Render() 64 | { 65 | if (m_Matrices.Count == 0 || m_Colors.Count == 0 || m_Highlights.Count == 0) 66 | return; 67 | 68 | int count = System.Math.Min(m_Matrices.Count, System.Math.Min(m_Colors.Count, m_Highlights.Count)); 69 | 70 | Material mat = material; 71 | mat.SetPass(0); 72 | 73 | MaterialPropertyBlock propertyBlock = new MaterialPropertyBlock(); 74 | CommandBuffer cb = new CommandBuffer(); 75 | 76 | Matrix4x4[] matrices = null; 77 | 78 | int chunkCount = RenderChunkCount(count); 79 | for (int i = 0; i < chunkCount; ++i) 80 | { 81 | cb.Clear(); 82 | matrices = GetRenderChunk(m_Matrices, i); 83 | propertyBlock.SetVectorArray("_Color", GetRenderChunk(m_Colors, i)); 84 | 85 | material.DisableKeyword("WIRE_ON"); 86 | cb.DrawMeshInstanced(mesh, (int)SubMeshType.BoneFaces, material, 0, matrices, matrices.Length, propertyBlock); 87 | Graphics.ExecuteCommandBuffer(cb); 88 | 89 | cb.Clear(); 90 | propertyBlock.SetVectorArray("_Color", GetRenderChunk(m_Highlights, i)); 91 | 92 | material.EnableKeyword("WIRE_ON"); 93 | cb.DrawMeshInstanced(mesh, (int)SubMeshType.BoneWire, material, 0, matrices, matrices.Length, propertyBlock); 94 | Graphics.ExecuteCommandBuffer(cb); 95 | } 96 | } 97 | } 98 | 99 | static List s_BoneRendererComponents = new List(); 100 | 101 | private static BatchRenderer s_PyramidMeshRenderer; 102 | private static BatchRenderer s_BoxMeshRenderer; 103 | 104 | private static Material s_Material; 105 | 106 | private const float k_Epsilon = 1e-5f; 107 | 108 | private const float k_BoneBaseSize = 2f; 109 | private const float k_BoneTipSize = 0.5f; 110 | 111 | private static int s_ButtonHash = "BoneHandle".GetHashCode(); 112 | 113 | static BoneRendererUtil() 114 | { 115 | BoneRenderer.onAddBoneRenderer += OnAddBoneRenderer; 116 | BoneRenderer.onRemoveBoneRenderer += OnRemoveBoneRenderer; 117 | SceneVisibilityManager.visibilityChanged += OnVisibilityChanged; 118 | 119 | SceneView.duringSceneGui += DrawSkeletons; 120 | } 121 | 122 | private static Material material 123 | { 124 | get 125 | { 126 | if (!s_Material) 127 | { 128 | Shader shader = (Shader)EditorGUIUtility.LoadRequired("BoneHandles.shader"); 129 | s_Material = new Material(shader); 130 | s_Material.hideFlags = HideFlags.DontSaveInEditor; 131 | s_Material.enableInstancing = true; 132 | } 133 | 134 | return s_Material; 135 | } 136 | } 137 | 138 | private static BatchRenderer pyramidMeshRenderer 139 | { 140 | get 141 | { 142 | if (s_PyramidMeshRenderer == null) 143 | { 144 | var mesh = new Mesh(); 145 | mesh.name = "BoneRendererPyramidMesh"; 146 | mesh.subMeshCount = (int)BatchRenderer.SubMeshType.Count; 147 | mesh.hideFlags = HideFlags.DontSave; 148 | 149 | // Bone vertices 150 | Vector3[] vertices = new Vector3[] 151 | { 152 | new Vector3(0.0f, 1.0f, 0.0f), 153 | new Vector3(0.0f, 0.0f, -1.0f), 154 | new Vector3(-0.9f, 0.0f, 0.5f), 155 | new Vector3(0.9f, 0.0f, 0.5f), 156 | }; 157 | 158 | mesh.vertices = vertices; 159 | 160 | // Build indices for different sub meshes 161 | int[] boneFaceIndices = new int[] 162 | { 163 | 0, 2, 1, 164 | 0, 1, 3, 165 | 0, 3, 2, 166 | 1, 2, 3 167 | }; 168 | mesh.SetIndices(boneFaceIndices, MeshTopology.Triangles, (int)BatchRenderer.SubMeshType.BoneFaces); 169 | 170 | int[] boneWireIndices = new int[] 171 | { 172 | 0, 1, 0, 2, 0, 3, 1, 2, 2, 3, 3, 1 173 | }; 174 | mesh.SetIndices(boneWireIndices, MeshTopology.Lines, (int)BatchRenderer.SubMeshType.BoneWire); 175 | 176 | s_PyramidMeshRenderer = new BatchRenderer() 177 | { 178 | mesh = mesh, 179 | material = material 180 | }; 181 | } 182 | 183 | return s_PyramidMeshRenderer; 184 | } 185 | } 186 | 187 | private static BatchRenderer boxMeshRenderer 188 | { 189 | get 190 | { 191 | if (s_BoxMeshRenderer == null) 192 | { 193 | var mesh = new Mesh(); 194 | mesh.name = "BoneRendererBoxMesh"; 195 | mesh.subMeshCount = (int)BatchRenderer.SubMeshType.Count; 196 | mesh.hideFlags = HideFlags.DontSave; 197 | 198 | // Bone vertices 199 | Vector3[] vertices = new Vector3[] 200 | { 201 | new Vector3(-0.5f, 0.0f, 0.5f), 202 | new Vector3(0.5f, 0.0f, 0.5f), 203 | new Vector3(0.5f, 0.0f, -0.5f), 204 | new Vector3(-0.5f, 0.0f, -0.5f), 205 | new Vector3(-0.5f, 1.0f, 0.5f), 206 | new Vector3(0.5f, 1.0f, 0.5f), 207 | new Vector3(0.5f, 1.0f, -0.5f), 208 | new Vector3(-0.5f, 1.0f, -0.5f) 209 | }; 210 | 211 | mesh.vertices = vertices; 212 | 213 | // Build indices for different sub meshes 214 | int[] boneFaceIndices = new int[] 215 | { 216 | 0, 2, 1, 217 | 0, 3, 2, 218 | 219 | 0, 1, 5, 220 | 0, 5, 4, 221 | 222 | 1, 2, 6, 223 | 1, 6, 5, 224 | 225 | 2, 3, 7, 226 | 2, 7, 6, 227 | 228 | 3, 0, 4, 229 | 3, 4, 7, 230 | 231 | 4, 5, 6, 232 | 4, 6, 7 233 | }; 234 | mesh.SetIndices(boneFaceIndices, MeshTopology.Triangles, (int)BatchRenderer.SubMeshType.BoneFaces); 235 | 236 | int[] boneWireIndices = new int[] 237 | { 238 | 0, 1, 1, 2, 2, 3, 3, 0, 239 | 4, 5, 5, 6, 6, 7, 7, 4, 240 | 0, 4, 1, 5, 2, 6, 3, 7 241 | }; 242 | mesh.SetIndices(boneWireIndices, MeshTopology.Lines, (int)BatchRenderer.SubMeshType.BoneWire); 243 | 244 | s_BoxMeshRenderer = new BatchRenderer() 245 | { 246 | mesh = mesh, 247 | material = material 248 | }; 249 | 250 | } 251 | 252 | return s_BoxMeshRenderer; 253 | } 254 | } 255 | 256 | private static Matrix4x4 ComputeBoneMatrix(Vector3 start, Vector3 end, float length, float size) 257 | { 258 | Vector3 direction = (end - start) / length; 259 | Vector3 tangent = Vector3.Cross(direction, Vector3.up); 260 | if (Vector3.SqrMagnitude(tangent) < 0.1f) 261 | tangent = Vector3.Cross(direction, Vector3.right); 262 | tangent.Normalize(); 263 | Vector3 bitangent = Vector3.Cross(direction, tangent); 264 | 265 | float scale = length * k_BoneBaseSize * size; 266 | 267 | return new Matrix4x4( 268 | new Vector4(tangent.x * scale, tangent.y * scale, tangent.z * scale, 0f), 269 | new Vector4(direction.x * length, direction.y * length, direction.z * length, 0f), 270 | new Vector4(bitangent.x * scale, bitangent.y * scale, bitangent.z * scale, 0f), 271 | new Vector4(start.x, start.y, start.z, 1f)); 272 | } 273 | 274 | static void DrawSkeletons(SceneView sceneview) 275 | { 276 | var gizmoColor = Gizmos.color; 277 | 278 | pyramidMeshRenderer.Clear(); 279 | boxMeshRenderer.Clear(); 280 | 281 | for (var i = 0; i < s_BoneRendererComponents.Count; i++) 282 | { 283 | var boneRenderer = s_BoneRendererComponents[i]; 284 | 285 | if (boneRenderer.bones == null) 286 | continue; 287 | 288 | PrefabStage prefabStage = PrefabStageUtility.GetCurrentPrefabStage(); 289 | if (prefabStage != null) 290 | { 291 | StageHandle stageHandle = prefabStage.stageHandle; 292 | if (stageHandle.IsValid() && !stageHandle.Contains(boneRenderer.gameObject)) 293 | continue; 294 | } 295 | 296 | if (boneRenderer.drawBones) 297 | { 298 | var size = boneRenderer.boneSize * 0.025f; 299 | var shape = boneRenderer.boneShape; 300 | var color = boneRenderer.boneColor; 301 | var nubColor = new Color(color.r, color.g, color.b, color.a); 302 | var selectionColor = Color.white; 303 | 304 | for (var j = 0; j < boneRenderer.bones.Length; j++) 305 | { 306 | var bone = boneRenderer.bones[j]; 307 | if (bone.first == null || bone.second == null) 308 | continue; 309 | 310 | DoBoneRender(bone.first, bone.second, shape, color, size); 311 | } 312 | 313 | for (var k = 0; k < boneRenderer.tips.Length; k++) 314 | { 315 | var tip = boneRenderer.tips[k]; 316 | if (tip == null) 317 | continue; 318 | 319 | DoBoneRender(tip, null, shape, color, size); 320 | } 321 | } 322 | 323 | if (boneRenderer.drawTripods) 324 | { 325 | var size = boneRenderer.tripodSize * 0.025f; 326 | for (var j = 0; j < boneRenderer.transforms.Length; j++) 327 | { 328 | var tripodSize = 1f; 329 | var transform = boneRenderer.transforms[j]; 330 | if (transform == null) 331 | continue; 332 | 333 | var position = transform.position; 334 | var xAxis = position + transform.rotation * Vector3.right * size * tripodSize; 335 | var yAxis = position + transform.rotation * Vector3.up * size * tripodSize; 336 | var zAxis = position + transform.rotation * Vector3.forward * size * tripodSize; 337 | 338 | Handles.color = Color.red; 339 | Handles.DrawLine(position, xAxis); 340 | Handles.color = Color.green; 341 | Handles.DrawLine(position, yAxis); 342 | Handles.color = Color.blue; 343 | Handles.DrawLine(position, zAxis); 344 | } 345 | } 346 | } 347 | 348 | pyramidMeshRenderer.Render(); 349 | boxMeshRenderer.Render(); 350 | 351 | Gizmos.color = gizmoColor; 352 | } 353 | 354 | 355 | private static void DoBoneRender(Transform transform, Transform childTransform, BoneShape shape, Color color, float size) 356 | { 357 | Vector3 start = transform.position; 358 | Vector3 end = childTransform != null ? childTransform.position : start; 359 | 360 | GameObject boneGO = transform.gameObject; 361 | 362 | float length = (end - start).magnitude; 363 | bool tipBone = (length < k_Epsilon); 364 | 365 | int id = GUIUtility.GetControlID(s_ButtonHash, FocusType.Passive); 366 | Event evt = Event.current; 367 | 368 | switch (evt.GetTypeForControl(id)) 369 | { 370 | case EventType.Layout: 371 | { 372 | HandleUtility.AddControl(id, tipBone ? HandleUtility.DistanceToCircle(start, k_BoneTipSize * size * 0.5f) : HandleUtility.DistanceToLine(start, end)); 373 | break; 374 | } 375 | case EventType.MouseMove: 376 | if (id == HandleUtility.nearestControl) 377 | HandleUtility.Repaint(); 378 | break; 379 | case EventType.MouseDown: 380 | { 381 | if (HandleUtility.nearestControl == id && evt.button == 0) 382 | { 383 | #if UNITY_2019_3_OR_NEWER 384 | if (!SceneVisibilityManager.instance.IsPickingDisabled(boneGO, false)) 385 | #endif 386 | { 387 | GUIUtility.hotControl = id; // Grab mouse focus 388 | HandleClickSelection(boneGO, evt); 389 | evt.Use(); 390 | } 391 | } 392 | break; 393 | } 394 | case EventType.MouseDrag: 395 | { 396 | if (!evt.alt && GUIUtility.hotControl == id) 397 | { 398 | #if UNITY_2019_3_OR_NEWER 399 | if (!SceneVisibilityManager.instance.IsPickingDisabled(boneGO, false)) 400 | #endif 401 | { 402 | DragAndDrop.PrepareStartDrag(); 403 | DragAndDrop.objectReferences = new UnityEngine.Object[] { transform }; 404 | DragAndDrop.StartDrag(ObjectNames.GetDragAndDropTitle(transform)); 405 | 406 | GUIUtility.hotControl = 0; 407 | 408 | evt.Use(); 409 | } 410 | } 411 | break; 412 | } 413 | case EventType.MouseUp: 414 | { 415 | if (GUIUtility.hotControl == id && (evt.button == 0 || evt.button == 2)) 416 | { 417 | GUIUtility.hotControl = 0; 418 | evt.Use(); 419 | } 420 | break; 421 | } 422 | case EventType.Repaint: 423 | { 424 | Color highlight = color; 425 | 426 | bool hoveringBone = GUIUtility.hotControl == 0 && HandleUtility.nearestControl == id; 427 | #if UNITY_2019_3_OR_NEWER 428 | hoveringBone = hoveringBone && !SceneVisibilityManager.instance.IsPickingDisabled(transform.gameObject, false); 429 | #endif 430 | 431 | if (hoveringBone) 432 | { 433 | highlight = Handles.preselectionColor; 434 | } 435 | else if (Selection.Contains(boneGO) || Selection.activeObject == boneGO) 436 | { 437 | highlight = Handles.selectedColor; 438 | } 439 | 440 | if (tipBone) 441 | { 442 | Handles.color = highlight; 443 | Handles.SphereHandleCap(0, start, Quaternion.identity, k_BoneTipSize * size, EventType.Repaint); 444 | } 445 | else if (shape == BoneShape.Line) 446 | { 447 | Handles.color = highlight; 448 | Handles.DrawLine(start, end); 449 | } 450 | else 451 | { 452 | if (shape == BoneShape.Pyramid) 453 | pyramidMeshRenderer.AddInstance(ComputeBoneMatrix(start, end, length, size), color, highlight); 454 | else // if (shape == BoneShape.Box) 455 | boxMeshRenderer.AddInstance(ComputeBoneMatrix(start, end, length, size), color, highlight); 456 | } 457 | 458 | } 459 | break; 460 | } 461 | } 462 | 463 | public static void OnAddBoneRenderer(BoneRenderer obj) 464 | { 465 | s_BoneRendererComponents.Add(obj); 466 | } 467 | 468 | public static void OnRemoveBoneRenderer(BoneRenderer obj) 469 | { 470 | s_BoneRendererComponents.Remove(obj); 471 | } 472 | 473 | public static void OnVisibilityChanged() 474 | { 475 | foreach (var boneRenderer in s_BoneRendererComponents) 476 | { 477 | boneRenderer.Invalidate(); 478 | } 479 | } 480 | 481 | public static void HandleClickSelection(GameObject gameObject, Event evt) 482 | { 483 | if (evt.shift || EditorGUI.actionKey) 484 | { 485 | UnityEngine.Object[] existingSelection = Selection.objects; 486 | 487 | // For shift, we check if EXACTLY the active GO is hovered by mouse and then subtract. Otherwise additive. 488 | // For control/cmd, we check if ANY of the selected GO is hovered by mouse and then subtract. Otherwise additive. 489 | // Control/cmd takes priority over shift. 490 | bool subtractFromSelection = EditorGUI.actionKey ? Selection.Contains(gameObject) : Selection.activeGameObject == gameObject; 491 | if (subtractFromSelection) 492 | { 493 | // subtract from selection 494 | var newSelection = new UnityEngine.Object[existingSelection.Length - 1]; 495 | 496 | int index = Array.IndexOf(existingSelection, gameObject); 497 | 498 | System.Array.Copy(existingSelection, newSelection, index); 499 | System.Array.Copy(existingSelection, index + 1, newSelection, index, newSelection.Length - index); 500 | 501 | Selection.objects = newSelection; 502 | } 503 | else 504 | { 505 | // add to selection 506 | var newSelection = new UnityEngine.Object[existingSelection.Length + 1]; 507 | System.Array.Copy(existingSelection, newSelection, existingSelection.Length); 508 | newSelection[existingSelection.Length] = gameObject; 509 | 510 | Selection.objects = newSelection; 511 | } 512 | } 513 | else 514 | Selection.activeObject = gameObject; 515 | } 516 | } 517 | } 518 | 519 | -------------------------------------------------------------------------------- /Editor/BoneRendererUtil.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 2f7e39ec8fa64d44ca7aeb4ea2447d85 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # UnityBoneVisualizer 2 | 3 | Visualize your character's bone.骨骼可视化! 4 | 5 | **(Editor Only)** 6 | **(仅限编辑器内)** 7 | 8 | **This is part of Animation Rigging which you can find in Package manager->Animation Rigging** 9 | 10 | **该插件是AnimationRigging里的一部分,我只是做了剥离。你可以直接在PackageManager里的AnimationRigging找到对应源码** 11 | 12 | ![Image2](https://i.ibb.co/jJBkq24/image2.png) 13 | 14 | ## Install 安装 15 | 16 | ### 1.Via Unitypackage 通过unitypackage安装 17 | 18 | Go [this](https://github.com/Shaun-Fong/UnityBoneVisualizer/releases) link download "**.unitypackage" and import to unity. 19 | 20 | 到该[链接](https://github.com/Shaun-Fong/UnityBoneVisualizer/releases)下载“**.unitypackage”并导入unity即可。 21 | 22 | ### 2.Via Unity Package Manager 通过Unity Package Manager安装(推荐) 23 | 24 | Requires a version of unity that supports path query parameter for git packages(Unity >= 2019.3.4f1) 25 | 26 | 需要Unity版本大于2019.3.4f1 27 | 28 | ![images](https://user-images.githubusercontent.com/46207/79450714-3aadd100-8020-11ea-8aae-b8d87fc4d7be.png) 29 | 30 | Click 'Add package from git URL' and add `https://github.com/Shaun-Fong/UnityBoneVisualizer.git?path=UnityBoneVisualizer` then Click 'Add' button. 31 | 32 | 点击“Add package from git URL” 并填入`https://github.com/Shaun-Fong/UnityBoneVisualizer.git?path=UnityBoneVisualizer`,然后等待编译完成即可使用。 33 | 34 | 35 | 36 | ## Usage 使用 37 | 38 | Make sure to use the unity toolbar to add renderer component.(Bone Renderer->Setup) 39 | 40 | 确保使用Unity中的工具栏来添加对应的渲染组件(工具栏Bone Renderer->Setup),这样会直接自动添加所有子对象并进行渲染。 41 | 42 | -------------------------------------------------------------------------------- /Resources.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 5f5c71465b26e114ba7c53bd3769a914 3 | folderAsset: yes 4 | DefaultImporter: 5 | externalObjects: {} 6 | userData: 7 | assetBundleName: 8 | assetBundleVariant: 9 | -------------------------------------------------------------------------------- /Resources/Shaders.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: e4f4b09c43cc51d42995679095ad6dbf 3 | folderAsset: yes 4 | DefaultImporter: 5 | externalObjects: {} 6 | userData: 7 | assetBundleName: 8 | assetBundleVariant: 9 | -------------------------------------------------------------------------------- /Resources/Shaders/BoneHandles.shader: -------------------------------------------------------------------------------- 1 | Shader "Hidden/BoneHandles" 2 | { 3 | Properties 4 | { 5 | _Color ("Color", Color) = (1,1,1,1) 6 | } 7 | SubShader 8 | { 9 | Tags { "Queue" = "Transparent" "RenderType"="Transparent" "ForceSupported" = "True" } 10 | 11 | Lighting Off 12 | Blend SrcAlpha OneMinusSrcAlpha 13 | ZWrite Off 14 | Cull Back 15 | Fog { Mode Off } 16 | ZTest Always 17 | 18 | Pass 19 | { 20 | CGPROGRAM 21 | #pragma vertex vert 22 | #pragma fragment frag 23 | #pragma multi_compile_instancing 24 | #pragma multi_compile __ WIRE_ON 25 | 26 | #include "UnityCG.cginc" 27 | 28 | struct appdata 29 | { 30 | float4 vertex : POSITION; 31 | UNITY_VERTEX_INPUT_INSTANCE_ID 32 | }; 33 | 34 | struct v2f 35 | { 36 | float4 vertex : SV_POSITION; 37 | UNITY_VERTEX_INPUT_INSTANCE_ID 38 | }; 39 | 40 | UNITY_INSTANCING_BUFFER_START(Props) 41 | UNITY_DEFINE_INSTANCED_PROP(float4, _Color) 42 | UNITY_INSTANCING_BUFFER_END(Props) 43 | 44 | v2f vert (appdata v) 45 | { 46 | v2f o; 47 | UNITY_SETUP_INSTANCE_ID(v); 48 | UNITY_TRANSFER_INSTANCE_ID(v, o); 49 | o.vertex = UnityObjectToClipPos(v.vertex); 50 | return o; 51 | } 52 | 53 | fixed4 frag (v2f i) : SV_Target 54 | { 55 | UNITY_SETUP_INSTANCE_ID(i); 56 | fixed4 col = UNITY_ACCESS_INSTANCED_PROP(Props, _Color); 57 | 58 | #ifdef WIRE_ON 59 | col.a = 1.0f; 60 | #endif 61 | 62 | return col; 63 | } 64 | ENDCG 65 | } 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /Resources/Shaders/BoneHandles.shader.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 4d6121ae9d0dc17429730b9d2a85016b 3 | ShaderImporter: 4 | externalObjects: {} 5 | defaultTextures: [] 6 | nonModifiableTextures: [] 7 | userData: 8 | assetBundleName: 9 | assetBundleVariant: 10 | -------------------------------------------------------------------------------- /Runtime/BoneRenderer.Runtime.asmdef: -------------------------------------------------------------------------------- 1 | { 2 | "name": "BoneRenderer.Runtime", 3 | "rootNamespace": "", 4 | "references": [], 5 | "includePlatforms": [], 6 | "excludePlatforms": [], 7 | "allowUnsafeCode": false, 8 | "overrideReferences": false, 9 | "precompiledReferences": [], 10 | "autoReferenced": true, 11 | "defineConstraints": [], 12 | "versionDefines": [], 13 | "noEngineReferences": false 14 | } -------------------------------------------------------------------------------- /Runtime/BoneRenderer.Runtime.asmdef.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 73b2785bb16270b4abdb3ab39f47288a 3 | AssemblyDefinitionImporter: 4 | externalObjects: {} 5 | userData: 6 | assetBundleName: 7 | assetBundleVariant: 8 | -------------------------------------------------------------------------------- /Runtime/BoneRenderer.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using UnityEngine; 3 | 4 | namespace BoneRenderer 5 | { 6 | [ExecuteInEditMode] 7 | public class BoneRenderer : MonoBehaviour 8 | { 9 | #if UNITY_EDITOR 10 | public enum BoneShape 11 | { 12 | Line, 13 | Pyramid, 14 | Box 15 | }; 16 | 17 | public struct TransformPair 18 | { 19 | public Transform first; 20 | public Transform second; 21 | }; 22 | 23 | public BoneShape boneShape = BoneShape.Pyramid; 24 | 25 | public bool drawBones = true; 26 | public bool drawTripods = false; 27 | 28 | [Range(0.01f, 5.0f)] 29 | public float boneSize = 1.0f; 30 | 31 | [Range(0.01f, 5.0f)] 32 | public float tripodSize = 1.0f; 33 | 34 | public Color boneColor = new Color(0f, 0f, 1f, 0.5f); 35 | 36 | [SerializeField] 37 | private Transform[] m_Transforms; 38 | 39 | private TransformPair[] m_Bones; 40 | 41 | private Transform[] m_Tips; 42 | 43 | public Transform[] transforms 44 | { 45 | get { return m_Transforms; } 46 | set 47 | { 48 | m_Transforms = value; 49 | ExtractBones(); 50 | } 51 | } 52 | 53 | public TransformPair[] bones { get => m_Bones; } 54 | 55 | public Transform[] tips { get => m_Tips; } 56 | 57 | public delegate void OnAddBoneRendererCallback(BoneRenderer boneRenderer); 58 | public delegate void OnRemoveBoneRendererCallback(BoneRenderer boneRenderer); 59 | 60 | public static OnAddBoneRendererCallback onAddBoneRenderer; 61 | public static OnRemoveBoneRendererCallback onRemoveBoneRenderer; 62 | 63 | void OnEnable() 64 | { 65 | ExtractBones(); 66 | onAddBoneRenderer?.Invoke(this); 67 | } 68 | 69 | void OnDisable() 70 | { 71 | onRemoveBoneRenderer?.Invoke(this); 72 | } 73 | 74 | public void Invalidate() 75 | { 76 | ExtractBones(); 77 | } 78 | 79 | public void Reset() 80 | { 81 | ClearBones(); 82 | } 83 | 84 | public void ClearBones() 85 | { 86 | m_Bones = null; 87 | m_Tips = null; 88 | } 89 | 90 | public void ExtractBones() 91 | { 92 | if (m_Transforms == null || m_Transforms.Length == 0) 93 | { 94 | ClearBones(); 95 | return; 96 | } 97 | 98 | var transformsHashSet = new HashSet(m_Transforms); 99 | 100 | var bonesList = new List(m_Transforms.Length); 101 | var tipsList = new List(m_Transforms.Length); 102 | 103 | for (int i = 0; i < m_Transforms.Length; ++i) 104 | { 105 | bool hasValidChildren = false; 106 | 107 | var transform = m_Transforms[i]; 108 | if (transform == null) 109 | continue; 110 | 111 | if (UnityEditor.SceneVisibilityManager.instance.IsHidden(transform.gameObject, false)) 112 | continue; 113 | 114 | if (transform.childCount > 0) 115 | { 116 | for (var k = 0; k < transform.childCount; ++k) 117 | { 118 | var childTransform = transform.GetChild(k); 119 | 120 | if (transformsHashSet.Contains(childTransform)) 121 | { 122 | bonesList.Add(new TransformPair() { first = transform, second = childTransform }); 123 | hasValidChildren = true; 124 | } 125 | } 126 | } 127 | 128 | if (!hasValidChildren) 129 | { 130 | tipsList.Add(transform); 131 | } 132 | } 133 | 134 | m_Bones = bonesList.ToArray(); 135 | m_Tips = tipsList.ToArray(); 136 | } 137 | #endif // UNITY_EDITOR 138 | } 139 | } 140 | 141 | -------------------------------------------------------------------------------- /Runtime/BoneRenderer.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: a712abfe15455734182b8c58db92044e 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Scripts.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 691ad30e9534fea4798d430945fe5026 3 | folderAsset: yes 4 | DefaultImporter: 5 | externalObjects: {} 6 | userData: 7 | assetBundleName: 8 | assetBundleVariant: 9 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "com.shaunfong.bonevisualizer", 3 | "displayName": "UnityBoneVisualizer", 4 | "version": "1.0.0", 5 | "unity": "2019.3", 6 | "unityRelease": "1f1", 7 | "description": "BoneVisualizer", 8 | "keywords": [ 9 | "Bone","Visualizer" 10 | ], 11 | "type": "library", 12 | "hideInEditor": false 13 | } 14 | -------------------------------------------------------------------------------- /package.json.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: d6b837e40ecaa3047a0d44679f84f487 3 | PackageManifestImporter: 4 | externalObjects: {} 5 | userData: 6 | assetBundleName: 7 | assetBundleVariant: 8 | --------------------------------------------------------------------------------