Last active
June 14, 2018 18:00
-
-
Save jonagill/16de46ec9716cf78b5841a15f0f17a4b to your computer and use it in GitHub Desktop.
Tool to track what collisions are happening in a Unity game
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#if UNITY_EDITOR | |
using System; | |
using System.Collections; | |
using System.Collections.Generic; | |
using System.Linq; | |
using UnityEditor; | |
using UnityEngine; | |
[InitializeOnLoad] | |
public class CollisionDebugger : EditorWindow | |
{ | |
private const int MAX_SAMPLES = 300; | |
static CollisionDebugger() | |
{ | |
EditorApplication.playModeStateChanged += OnPlaymodeStateChange; | |
EditorApplication.update += OnUpdate; | |
} | |
[MenuItem("Window/Collision Debugger")] | |
public static void CreateWindow() | |
{ | |
var window = GetWindow<CollisionDebugger>("Collision Debugger"); | |
window.autoRepaintOnSceneChange = true; | |
window.Show(); | |
} | |
public enum CollisionEvent | |
{ | |
Collision_Enter, | |
Collision_Stay, | |
Collision_Exit, | |
Trigger_Enter, | |
Trigger_Stay, | |
Trigger_Exit, | |
} | |
private class FrameSample | |
{ | |
public string name; | |
public int frame; | |
public float timestamp; | |
public int collisionEventCount; | |
public int triggerEventCount; | |
public Dictionary<CollisionEvent, CollisionInfo[]> collisionEvents; | |
} | |
private class CollisionInfo | |
{ | |
public GameObject gameObject; | |
public Collider otherCollider; | |
} | |
private static readonly List<FrameSample> frameSamples = new List<FrameSample>(MAX_SAMPLES); | |
private static readonly Dictionary<CollisionEvent, List<CollisionInfo>> cachedCollisionEvents = new Dictionary<CollisionEvent, List<CollisionInfo>>(); | |
public static void ReportCollision(CollisionEvent eventType, GameObject gameObject, Collider otherCollider) | |
{ | |
List<CollisionInfo> events; | |
if (!cachedCollisionEvents.TryGetValue(eventType, out events)) | |
{ | |
events = new List<CollisionInfo>(); | |
cachedCollisionEvents[eventType] = events; | |
} | |
events.Add(new CollisionInfo() | |
{ | |
gameObject = gameObject, | |
otherCollider = otherCollider | |
}); | |
} | |
private static void OnUpdate() | |
{ | |
if (Application.isPlaying && !EditorApplication.isPaused) | |
{ | |
// Don't ever exceed the allocated number of samples | |
if (frameSamples.Count == frameSamples.Capacity) | |
{ | |
frameSamples.RemoveAt(0); | |
} | |
var frameSample = CollectFrameSample(); | |
frameSamples.Add(frameSample); | |
ClearCollisionBuffers(); | |
} | |
} | |
private static void OnPlaymodeStateChange(PlayModeStateChange change) | |
{ | |
if (change == PlayModeStateChange.EnteredPlayMode) | |
{ | |
frameSamples.Clear(); | |
} | |
} | |
private static FrameSample CollectFrameSample() | |
{ | |
// Copy all the cached collision events from this frame into a new collection | |
var collisionEvents = new Dictionary<CollisionEvent, CollisionInfo[]>(); | |
var orderedEvents = cachedCollisionEvents.OrderBy(pair => pair.Key); | |
foreach (var events in orderedEvents) | |
{ | |
collisionEvents.Add(events.Key, events.Value.ToArray()); | |
} | |
var collisionEventCount = collisionEvents | |
.Where(e => | |
e.Key == CollisionEvent.Collision_Enter || | |
e.Key == CollisionEvent.Collision_Stay || | |
e.Key == CollisionEvent.Collision_Exit) | |
.Select(e => e.Value.Length) | |
.Sum(); | |
var triggerEventCount = collisionEvents | |
.Where(e => | |
e.Key == CollisionEvent.Trigger_Enter || | |
e.Key == CollisionEvent.Trigger_Stay || | |
e.Key == CollisionEvent.Trigger_Exit) | |
.Select(e => e.Value.Length) | |
.Sum(); | |
var frame = Time.frameCount; | |
var frameName = string.Format( | |
"Frame {0} (C: {1}, T: {2})", | |
frame, | |
collisionEventCount, | |
triggerEventCount | |
); | |
return new FrameSample() | |
{ | |
name = frameName, | |
frame = frame, | |
timestamp = Time.time, | |
collisionEventCount = collisionEventCount, | |
triggerEventCount = triggerEventCount, | |
collisionEvents = collisionEvents, | |
}; | |
} | |
private static void ClearCollisionBuffers() | |
{ | |
foreach (var pair in cachedCollisionEvents) | |
{ | |
pair.Value.Clear(); | |
} | |
} | |
private static void AttachDebugReporters() | |
{ | |
var rigidbodies = FindObjectsOfType<Rigidbody>(); | |
foreach (var rigidbody in rigidbodies) | |
{ | |
if (!rigidbody.GetComponent<DebugCollisionReporter>()) | |
{ | |
rigidbody.gameObject.AddComponent<DebugCollisionReporter>(); | |
} | |
} | |
} | |
private FrameSample detailSample; | |
private void Awake() | |
{ | |
if (Application.isPlaying) | |
{ | |
AttachDebugReporters(); | |
} | |
} | |
private void OnGUI() | |
{ | |
using (new EditorGUILayout.VerticalScope()) | |
{ | |
using (new EditorGUILayout.VerticalScope(GUILayout.ExpandHeight(true))) | |
{ | |
var sampleToRender = detailSample; | |
if (DebugCollisionReporter.Count == 0) | |
{ | |
EditorGUILayout.LabelField("No tracked rigidbodies"); | |
} | |
else if (detailSample == null) | |
{ | |
detailSample = DrawListGUI(); | |
} | |
else | |
{ | |
if (GUILayout.Button("Return")) | |
{ | |
detailSample = null; | |
} | |
DrawDetailedGUI(sampleToRender); | |
} | |
if (sampleToRender != detailSample) | |
{ | |
// Reset the scroll view if we've changed focus | |
detailedScrollViewPos = Vector2.zero; | |
} | |
} | |
EditorGUILayout.Separator(); | |
GUI.enabled = Application.isPlaying; | |
if (GUILayout.Button("Refresh Tracked Rigidbodies")) | |
{ | |
AttachDebugReporters(); | |
} | |
GUI.enabled = true; | |
EditorGUILayout.Separator(); | |
} | |
} | |
private Vector2 listScrollViewPos; | |
private FrameSample DrawListGUI() | |
{ | |
FrameSample selectedSample = null; | |
using (var scrollView = new EditorGUILayout.ScrollViewScope(listScrollViewPos, "box")) | |
{ | |
EditorGUI.indentLevel++; | |
listScrollViewPos = scrollView.scrollPosition; | |
foreach (var frameSample in frameSamples) | |
{ | |
EditorGUILayout.PrefixLabel(frameSample.name); | |
if (GUILayout.Button("Details")) | |
{ | |
selectedSample = frameSample; | |
} | |
} | |
EditorGUI.indentLevel--; | |
} | |
return selectedSample; | |
} | |
private Vector2 detailedScrollViewPos; | |
private void DrawDetailedGUI(FrameSample sample) | |
{ | |
using (var scrollView = new EditorGUILayout.ScrollViewScope(detailedScrollViewPos, "box")) | |
{ | |
EditorGUI.indentLevel++; | |
detailedScrollViewPos = scrollView.scrollPosition; | |
EditorGUILayout.Separator(); | |
EditorGUILayout.LabelField(sample.name, EditorStyles.boldLabel); | |
EditorGUILayout.Separator(); | |
foreach (var collisionEvent in sample.collisionEvents) | |
{ | |
using (new EditorGUILayout.VerticalScope("box")) | |
{ | |
EditorGUI.indentLevel++; | |
var eventType = collisionEvent.Key; | |
var collisions = collisionEvent.Value; | |
var eventName = eventType.ToString().Replace("_", " "); | |
var title = string.Format("{0} ({1})", eventName, collisions.Length); | |
EditorGUILayout.LabelField(title); | |
EditorGUI.indentLevel++; | |
foreach (var collision in collisions) | |
{ | |
EditorGUILayout.LabelField(string.Format("{0}:", collision.gameObject.name)); | |
using (new EditorGUILayout.VerticalScope()) | |
{ | |
EditorGUILayout.ObjectField("Object", collision.gameObject, typeof(Collider), true); | |
EditorGUILayout.ObjectField("Other", collision.otherCollider, typeof(Collider), true); | |
} | |
EditorGUILayout.Separator(); | |
} | |
EditorGUI.indentLevel--; | |
EditorGUI.indentLevel--; | |
} | |
} | |
EditorGUI.indentLevel--; | |
} | |
} | |
} | |
#endif |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#if UNITY_EDITOR | |
using System.Collections; | |
using System.Collections.Generic; | |
using UnityEngine; | |
/// <summary> | |
/// Debug class that reports collisions on this object to CollisionDebugger | |
/// </summary> | |
public class DebugCollisionReporter : MonoBehaviour | |
{ | |
public static int Count { get; private set; } | |
private void Awake() | |
{ | |
Count++; | |
} | |
private void OnDestroy() | |
{ | |
Count--; | |
} | |
private void OnCollisionEnter(Collision collision) | |
{ | |
CollisionDebugger.ReportCollision( | |
CollisionDebugger.CollisionEvent.Collision_Enter, | |
gameObject, | |
collision.collider); | |
} | |
private void OnCollisionStay(Collision collision) | |
{ | |
CollisionDebugger.ReportCollision( | |
CollisionDebugger.CollisionEvent.Collision_Stay, | |
gameObject, | |
collision.collider); | |
} | |
private void OnCollisionExit(Collision collision) | |
{ | |
CollisionDebugger.ReportCollision( | |
CollisionDebugger.CollisionEvent.Collision_Exit, | |
gameObject, | |
collision.collider); | |
} | |
private void OnTriggerEnter(Collider other) | |
{ | |
CollisionDebugger.ReportCollision( | |
CollisionDebugger.CollisionEvent.Trigger_Enter, | |
gameObject, | |
other); | |
} | |
private void OnTriggerStay(Collider other) | |
{ | |
CollisionDebugger.ReportCollision( | |
CollisionDebugger.CollisionEvent.Trigger_Stay, | |
gameObject, | |
other); | |
} | |
private void OnTriggerExit(Collider other) | |
{ | |
CollisionDebugger.ReportCollision( | |
CollisionDebugger.CollisionEvent.Trigger_Exit, | |
gameObject, | |
other); | |
} | |
} | |
#endif |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment