Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
Unity3D 2018.3 Editor script to find PhysX CCD problems
////////////////////////////////////////
// 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;
}
}
@MiraiTunga

This comment has been minimized.

Copy link

commented Jan 5, 2019

Awesome!

@kiborg

This comment has been minimized.

Copy link

commented Jan 20, 2019

How to use it?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.