Skip to content

Instantly share code, notes, and snippets.

@Arakade
Created December 22, 2018 14:29
Show Gist options
  • Star 7 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save Arakade/dc1d6623cafaab07aeee92a8a1cf661f to your computer and use it in GitHub Desktop.
Save Arakade/dc1d6623cafaab07aeee92a8a1cf661f to your computer and use it in GitHub Desktop.
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
Copy link

Awesome!

@kiborg
Copy link

kiborg commented Jan 20, 2019

How to use it?

@rafiqrap
Copy link

where to use it please ?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment