Created
December 22, 2018 14:29
-
-
Save Arakade/dc1d6623cafaab07aeee92a8a1cf661f to your computer and use it in GitHub Desktop.
Unity3D 2018.3 Editor script to find PhysX CCD problems
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
//////////////////////////////////////// | |
// Find PhysX components that will trigger the Unity 2018 complaint about | |
// kinematic with Continuous Collision Detection: | |
// | |
// ERROR: [Physics.PhysX] RigidBody::setRigidBodyFlag: kinematic bodies with CCD enabled are not supported! CCD will be ignored. | |
// | |
// Use the script to find troublesome components in the scene or in the project | |
// then manually change them to a valid value, likely ContinuousSpeculative | |
// | |
// See https://docs.unity3d.com/ScriptReference/CollisionDetectionMode.ContinuousSpeculative.html | |
// | |
// Author: Rupert Key, @Arakade | |
// Date: 2018/12/20 | |
// License: CC BY-SA https://creativecommons.org/licenses/by-sa/4.0/ | |
//////////////////////////////////////// | |
using System.Collections.Generic; | |
using System.IO; | |
using System.Linq; | |
using JetBrains.Annotations; | |
using UnityEditor; | |
using UnityEngine; | |
namespace UGS { | |
public sealed class PhysXCCDEditorHelper : EditorWindow { | |
[MenuItem("Tools/UGS/PhysX CCD Helper")] | |
static void Init() { | |
var window = GetWindow<PhysXCCDEditorHelper>(); // Get existing open window or if none, make a new one | |
window.Show(); | |
} | |
public void OnGUI() { | |
if (GUILayout.Button("Find bad PhysX in scene")) { | |
populateList(true); | |
} | |
if (GUILayout.Button("Find bad PhysX under selected (inc. in project)")) { | |
populateList(false); | |
} | |
using (new EditorGUILayout.VerticalScope()) { | |
using (var scrollView = new EditorGUILayout.ScrollViewScope(scrollPos, GUILayout.Width(position.width), GUILayout.Height(position.height - EditorGUIUtility.singleLineHeight * 2))) { | |
scrollPos = scrollView.scrollPosition; | |
using (new EditorGUI.DisabledScope(true)) { | |
foreach (var r in problems) { | |
r.onGUI(); | |
} | |
} | |
} | |
} | |
} | |
private void populateList(bool inScene) { | |
problems.Clear(); | |
if (inScene) { | |
problems.AddRange(FindObjectsOfType<Rigidbody>().Where(isProblem).Select(rb => new Result(rb))); | |
Debug.LogFormat("Found {0} problems", problems.Count); | |
} else { | |
processObject(Selection.activeObject); | |
Debug.LogFormat(Selection.activeObject, "Found {0} problems under {1}", problems.Count, Selection.activeObject); | |
} | |
} | |
private void processObject(Object o) { // recursive | |
// Debug.LogFormat(o, "Processing {0}", o); | |
switch (o.GetType().FullName) { | |
case "UnityEngine.GameObject": | |
var rbs = ((GameObject) o).GetComponentsInChildren<Rigidbody>(true).Where(isProblem).ToArray(); | |
if (rbs.Length > 0) { | |
problems.Add(new Result((GameObject) o, rbs)); | |
} | |
break; | |
case "UnityEngine.Rigidbody": | |
// Debug.LogFormat(o, "{0}", o); | |
problems.Add(new Result((Rigidbody) o)); | |
break; | |
case "UnityEditor.DefaultAsset": | |
var guids = getAssetGUIDsInDirectory(o as DefaultAsset); | |
if (null == guids || 0 >= guids.Length) | |
break; | |
guids = guids.Distinct().ToArray(); // uniquify | |
foreach (var guid in guids) { | |
var asset = AssetDatabase.LoadMainAssetAtPath(AssetDatabase.GUIDToAssetPath(guid)); | |
processObject(asset); | |
} | |
break; | |
default: | |
Debug.LogWarningFormat(o, "{0} is of unknown type:\"{1}\"", o, o.GetType()); | |
break; | |
} | |
} | |
private static bool isProblem(Rigidbody rb) { | |
var collisionDetectionMode = rb.collisionDetectionMode; | |
return rb.isKinematic && collisionDetectionMode != CollisionDetectionMode.Discrete && collisionDetectionMode != CollisionDetectionMode.ContinuousSpeculative; | |
} | |
[CanBeNull] | |
private static string[] getAssetGUIDsInDirectory(DefaultAsset directory) { | |
var pathOrig = AssetDatabase.GetAssetPath(directory); | |
var path = pathOrig; | |
if (path.StartsWith("Assets")) { // should always be true | |
path = path.Substring(6); // cut "Assets" from front since already in Application.applicationPath | |
} | |
path = Application.dataPath + path; // should now be absolute | |
if (!Directory.Exists(path)) | |
return null; | |
//Debug.LogFormat("Searching in \"{0}\" (which is \"{1}\")", pathOrig, path); | |
return AssetDatabase.FindAssets("t:GameObject", new[] {pathOrig}); // TODO: Why doesn't t:Rigidbody work? | |
} | |
private readonly List<Result> problems = new List<Result>(); | |
private sealed class Result { | |
private readonly Rigidbody[] rbs; | |
private readonly GameObject go; | |
public Result(Rigidbody rb) { | |
Assert.IsNotNull(rb); | |
rbs = new []{ rb }; | |
go = rb.gameObject; | |
} | |
public Result(GameObject go, Rigidbody rb) { | |
Assert.IsNotNull(go); | |
Assert.IsNotNull(rb); | |
this.go = go; | |
rbs = new []{ rb }; | |
} | |
public Result(GameObject go, Rigidbody[] rbs) { | |
Assert.IsNotNull(go); | |
Assert.IsNotNull(rbs); | |
Assert.IsTrue(rbs.Length > 0); | |
this.go = go; | |
this.rbs = rbs; | |
} | |
public void onGUI() { | |
foreach (var rb in rbs) { | |
EditorGUILayout.ObjectField(rb.name, go, typeof(GameObject), true); | |
} | |
} | |
} | |
private Vector2 scrollPos; | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
How to use it?