【Unity编辑器扩展】BoxCollider编辑器扩展

本文介绍了如何使用.NETReflector反编译UnityEditor.dll,找到BoxCollider的编辑器类并创建自定义扩展。通过扩展,实现了点击按钮自动调整BoxCollider以包含对象及所有子对象的功能,简化了游戏物体碰撞盒的设置过程。

内容将会持续更新,有错误的地方欢迎指正,谢谢!
 

BoxCollider编辑器扩展
     
TechX 坚持将创新的科技带给世界!

拥有更好的学习体验 —— 不断努力,不断进步,不断探索
TechX —— 心探索、心进取!

助力快速掌握 BoxCollider 编辑器扩展

为初学者节省宝贵的学习时间,避免困惑!


前言:

  之前在项目中想对一个对象做射线交互,然后给这个对象添加碰撞盒,发现这个对象下面还有很多子对

象,添加的碰撞盒不能完全包含这个对象及它下面的所有子对象,所以对BoxCollider进行了一个编辑器扩

展,实现当点击一个按钮时可以让这个BoxCollider包含该对象及其子对象。

TechX 教程效果:
在这里插入图片描述



一、查找BoxCollider编辑器扩展类

1、安装.net反编译工具.NET Reflector

 
反编译工具.NET Reflector可以去它的官网进行下载。
 
网上也有很多破解教程,可以自行去百度进行搜索,对于大家来说也是比较简单的,这里就不再详细讲解。
 
安装好后是这样的:

在这里插入图片描述
我这里安装的是.NET Reflector11.1版本。
 

2、反编译UnityEditor.dll

 
打开.NET Reflector软件,选择UnityEditor.dll集合进行反编译
 在这里插入图片描述
 
在搜索框中输入BoxCollider进行搜索,可以看到有一个BoxColliderEditor类,

查看右侧代码有CustomEditor(typeof(BoxCollider)),我们知道CustomEditor类

BoxCollider的编辑器类。

 
在这里插入图片描述


二、创建编辑器扩展类BoxColliderExtendEditor


在Unity中创建一个编辑器类BoxColliderExtendEditor,将这个类放到Editor文件夹中


通过反射(Reflection)获取到BoxCollider的编辑器类

private IEnumerator Init()
{
  	Type type = Assembly.GetAssembly(typeof(Editor)).GetType("UnityEditor.BoxColliderEditor", false);
  	yield return new WaitUntil(() => type != null);
  	m_Editor = Editor.CreateEditor(target, type);
}

扩展对象及其所有子对象的边界框Bound

/// <summary>
/// 扩展边界
/// </summary>
private Bounds? CalculateWorldBounds()
{
    var renderers = Target.GetComponentsInChildren<MeshRenderer>(m_IncludeInactive)
                        .Where(r => r != null)
                        .ToArray();

    if (renderers.Length == 0)
    {
        // 尝试查找SkinnedMeshRenderer
        var skinnedRenderers = Target.GetComponentsInChildren<SkinnedMeshRenderer>(m_IncludeInactive)
                                   .Where(r => r != null)
                                   .ToArray();

        if (skinnedRenderers.Length == 0)
            return null;

        return CalculateCombinedBounds(skinnedRenderers.Select(r => r.bounds));
    }

    return CalculateCombinedBounds(renderers.Select(r => r.bounds));
}

private Bounds CalculateCombinedBounds(System.Collections.Generic.IEnumerable<Bounds> boundsCollection)
{
    var boundsArray = boundsCollection.ToArray();

    if (boundsArray.Length == 0)
        return new Bounds(Vector3.zero, Vector3.zero);

    var combinedBounds = boundsArray[0];

    for (int i = 1; i < boundsArray.Length; i++)
    {
        combinedBounds.Encapsulate(boundsArray[i]);
    }

    return combinedBounds;
}

下面贴出完整的代码:

using System;
using System.Reflection;
using UnityEditor;
using UnityEngine;
using System.Collections;
using System.Linq;

[CustomEditor(typeof(BoxCollider))]
public class ColliderExtendEditor : Editor
{
    private bool m_IsFlip;
    private bool m_IncludeInactive = false;
    private Bounds? m_LastCalculatedBounds;

    // 样式定义
    private GUIStyle m_HeaderStyle;
    private GUIStyle m_ButtonStyle;

    private bool isApply;

    private void InitializeStyles()
    {
        m_HeaderStyle = new GUIStyle(EditorStyles.boldLabel)
        {
            fontSize = 12,
            alignment = TextAnchor.MiddleLeft
        };

        m_ButtonStyle = new GUIStyle(GUI.skin.button)
        {
            padding = new RectOffset(10, 10, 5, 5),
            fixedHeight = 25
        };
    }

    private BoxCollider Target => (BoxCollider)target;

    public override void OnInspectorGUI()
    {
        InitializeStyles();

        // 绘制默认的BoxCollider编辑器
        DrawDefaultInspectorFallback();

        // 绘制扩展功能
        DrawExtendedFeatures();
    }

    private void OnSceneGUI()
    {
        if (m_LastCalculatedBounds.HasValue && !isApply)
        {
            Handles.color = new Color(1, 1, 0, 0.8f);

            Vector3 worldCenter = m_LastCalculatedBounds.Value.center;
            Vector3 worldSize = m_LastCalculatedBounds.Value.size;

            if (m_IsFlip)
            {
                Vector3 localCenter = Target.transform.InverseTransformPoint(worldCenter);
                Vector3 localSize = Target.transform.InverseTransformDirection(worldSize);

                Vector3 flipCenter = Target.transform.position + new Vector3(localCenter.z, localCenter.y, -localCenter.x);
                Vector3 flipSize = new Vector3(localSize.z, localSize.y, localSize.x);

                Handles.DrawWireCube(flipCenter, flipSize);
            }
            else
            {
                Handles.DrawWireCube(worldCenter, worldSize);
            }
        }
    }

    private void DrawDefaultInspectorFallback()
    {
        EditorGUILayout.EditorToolbarForTarget(EditorGUIUtility.TrTempContent("Edit Collider"), this);

        EditorGUILayout.Space(2);

        DrawPropertiesExcluding(serializedObject, "m_Script");

        if (serializedObject.hasModifiedProperties)
        {
            serializedObject.ApplyModifiedProperties();
        }
    }

    private void DrawExtendedFeatures()
    {
        EditorGUILayout.Space(15);
        EditorGUILayout.LabelField("Adaptive Size Settings", m_HeaderStyle);

        EditorGUILayout.Space(5);

        // 设置选项
        DrawSettingsSection();

        EditorGUILayout.Space(10);

        // 预览信息
        DrawPreviewSection();

        EditorGUILayout.Space(10);

        // 操作按钮
        DrawActionButtons();
    }

    private void DrawSettingsSection()
    {
        EditorGUILayout.BeginVertical("HelpBox");

        m_IsFlip = EditorGUILayout.ToggleLeft(" Flip Axes (X↔Z)", m_IsFlip);
        m_IncludeInactive = EditorGUILayout.ToggleLeft(" Include Inactive Children", m_IncludeInactive);

        EditorGUILayout.EndVertical();
    }

    private void DrawPreviewSection()
    {
        if (m_LastCalculatedBounds.HasValue)
        {
            EditorGUILayout.BeginVertical("HelpBox");

            EditorGUILayout.LabelField("Last Calculated Bounds:", EditorStyles.miniBoldLabel);

            var bounds = m_LastCalculatedBounds.Value;
            EditorGUILayout.LabelField($"Center: {bounds.center}", EditorStyles.miniLabel);
            EditorGUILayout.LabelField($"Size: {bounds.size}", EditorStyles.miniLabel);
            EditorGUILayout.LabelField($"Extents: {bounds.extents}", EditorStyles.miniLabel);

            EditorGUILayout.EndVertical();
        }
    }

    private void DrawActionButtons()
    {
        // 计算边界按钮
        using (new EditorGUILayout.HorizontalScope())
        {
            GUI.color = new Color(0.2f, 0.8f, 0.2f, 1f);
            if (GUILayout.Button("Calculate Bounds", m_ButtonStyle))
            {
                CalculateAndPreviewBounds();
            }
            GUI.color = Color.white;

            GUI.enabled = m_LastCalculatedBounds.HasValue;
            GUI.color = new Color(0.9f, 0.6f, 0.1f, 1f);
            if (GUILayout.Button("Apply", m_ButtonStyle))
            {
                ApplyCalculatedBounds();
            }
            GUI.color = Color.white;
            GUI.enabled = true;
        }

        // 快速应用按钮
        EditorGUILayout.Space(5);

        GUI.color = new Color(0.1f, 0.6f, 0.9f, 1f);
        if (GUILayout.Button("Quick Adaptive Size", m_ButtonStyle))
        {
            CalculateAndApplyBounds();
        }
        GUI.color = Color.white;
    }

    private void CalculateAndPreviewBounds()
    {
        m_LastCalculatedBounds = CalculateWorldBounds();

        if (!m_LastCalculatedBounds.HasValue)
        {
            EditorUtility.DisplayDialog("No Renderers Found",
                "No MeshRenderers found in children. Please ensure the object has MeshRenderer components.",
                "OK");
            return;
        }

        isApply = false;

        // 在Scene视图中高亮显示计算出的边界
        SceneView.RepaintAll();
    }

    private void CalculateAndApplyBounds()
    {
        var bounds = CalculateWorldBounds();

        if (!bounds.HasValue)
        {
            EditorUtility.DisplayDialog("No Renderers Found",
                "No MeshRenderers found in children.",
                "OK");
            return;
        }

        ApplyBoundsToCollider(bounds.Value);
        m_LastCalculatedBounds = bounds;
    }

    private void ApplyCalculatedBounds()
    {
        if (m_LastCalculatedBounds.HasValue)
        {
            ApplyBoundsToCollider(m_LastCalculatedBounds.Value);
        }
    }

    private Bounds? CalculateWorldBounds()
    {
        var renderers = Target.GetComponentsInChildren<MeshRenderer>(m_IncludeInactive)
                            .Where(r => r != null)
                            .ToArray();

        if (renderers.Length == 0)
        {
            // 尝试查找SkinnedMeshRenderer
            var skinnedRenderers = Target.GetComponentsInChildren<SkinnedMeshRenderer>(m_IncludeInactive)
                                       .Where(r => r != null)
                                       .ToArray();

            if (skinnedRenderers.Length == 0)
                return null;

            return CalculateCombinedBounds(skinnedRenderers.Select(r => r.bounds));
        }

        return CalculateCombinedBounds(renderers.Select(r => r.bounds));
    }

    private Bounds CalculateCombinedBounds(System.Collections.Generic.IEnumerable<Bounds> boundsCollection)
    {
        var boundsArray = boundsCollection.ToArray();

        if (boundsArray.Length == 0)
            return new Bounds(Vector3.zero, Vector3.zero);

        var combinedBounds = boundsArray[0];

        for (int i = 1; i < boundsArray.Length; i++)
        {
            combinedBounds.Encapsulate(boundsArray[i]);
        }

        return combinedBounds;
    }

    public void ApplyBoundsToCollider(Bounds worldBounds)
    {
        Undo.RecordObject(Target, "Adapt BoxCollider Size");

        Vector3 localCenter = Target.transform.InverseTransformPoint(worldBounds.center);
        Vector3 localSize = Target.transform.InverseTransformDirection(worldBounds.size);

        if (m_IsFlip)
        {
            Target.center = new Vector3(localCenter.z, localCenter.y, -localCenter.x);
            Target.size = new Vector3(localSize.z, localSize.y, localSize.x);
        }
        else
        {
            Target.center = localCenter;
            Target.size = localSize;
        }

        isApply = true;

        // 标记为脏对象以便保存
        EditorUtility.SetDirty(Target);

        Debug.Log($"BoxCollider adapted: Center={Target.center}, Size={Target.size}");
    }
}


TechX —— 心探索、心进取!

每一次跌倒都是一次成长

每一次努力都是一次进步

END

感谢您阅读本篇博客!希望这篇内容对您有所帮助。如果您有任何问题或意见,或者想要了解更多关于本主题的信息,欢迎在评论区留言与我交流。我会非常乐意与大家讨论和分享更多有趣的内容。

如果您喜欢本博客,请点赞和分享给更多的朋友,让更多人受益。同时,您也可以关注我的博客,以便及时获取最新的更新和文章。

在未来的写作中,我将继续努力,分享更多有趣、实用的内容。再次感谢大家的支持和鼓励,期待与您在下一篇博客再见!

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

沐沐森的故事

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值