找回密码
 立即注册
查看: 349|回复: 0

Unity编辑器拓展——实现批量最小碰撞盒的自动生成

[复制链接]
发表于 2022-1-3 08:46 | 显示全部楼层 |阅读模式
差不多冬至,一早一晚还是有雨。
       在Unity里,我们经常会用把多个模型用一个空物体装起来,和3dsMax里“组”的功能类似。但如果这个时候给这个空物体添加BoxCollider碰撞体,它是无法生成一个把所有模型包围起来的碰撞体(如图1),需要我们手动在正交视图下手动调整BoxCollider包围盒的大小Size和中心点Center。这个过程做一两次还可以接受,但若需要操作的对象很多,我们就不得不依赖代码来解决问题了。



图1 空物体添加碰撞体时,碰撞体参数并未因子物体变化

首先,我们来分析一下这个问题,最小AABB包围盒的大小取决于模型的网格顶点的分布位置。(AABB包围盒和OBB包围盒区分开,前者是与世界坐标一致方向的最小包围盒,后者是方向性包围盒,本次只考虑最为常见的AABB包围盒)那我们就可以通过获取网格顶点的包围盒来解决这个问题。幸运的是,Unity已经为我们提供了相应的Bound的API:
if (gameObject.GetComponent<MeshRenderer>()) {
            Bounds bound = gameObject.GetComponent<MeshRenderer>().bounds;
    }
那我们F12可以看到Bounds结构体下,有一个Encapsulate(Bounds bounds)的方法:
// 摘要:
//   Grow the bounds to encapsulate the bounds.//大致翻译是扩充包围盒。即传入一个包围盒Bounds,返回扩充的结果。
//
// 参数:
//   bounds:
    public void Encapsulate(Bounds bounds);那对于我们根据这两个API,就可以直接遍历物体下的所有MeshRenderer,然后不断地扩充包围盒Bounds:
    /// <summary>
    /// 获得对象的最小包围盒
    /// </summary>
    public static  Bounds GetLocalBounds(GameObject target)
    {
        Renderer[] mfs = target.GetComponentsInChildren<Renderer>();
        Bounds bounds = new Bounds();
        if (mfs.Length != 0)
        {
            bounds = mfs[0].bounds;
            foreach (Renderer mf in mfs)
            {
                bounds.Encapsulate(mf.bounds);
            }
        }
        return bounds;
    }
完成这一步之后,我们可以得到包围盒bounds的值,有中心点Center、大小Size。我们接下来所需要做的就是把这些参数赋予我们新建的BoxCollider里的Center和Size。
    /// <summary>
    /// 添加碰撞体
    /// </summary>
    /// <param name="gameObject"></param>  
    public static void AddGameObjectCollider(GameObject gameObject)
    {
        Vector3 pos = gameObject.transform.localPosition;
        Quaternion qt = gameObject.transform.localRotation;
        Vector3 ls = gameObject.transform.localScale;

        gameObject.transform.position = Vector3.zero;
        gameObject.transform.eulerAngles = Vector3.zero;
        gameObject.transform.localScale = Vector3.one;
        //获取物体的最小包围盒
        Bounds itemBound = GetLocalBounds(gameObject);

        gameObject.transform.localPosition = pos;
        gameObject.transform.localRotation = qt;
        gameObject.transform.localScale = ls;
        //parent = null;
        if (!gameObject.GetComponent<Collider>())
            gameObject.AddComponent<BoxCollider>();
        if (gameObject.GetComponent<BoxCollider>())
        {
            gameObject.GetComponent<BoxCollider>().size = itemBound.size;

            gameObject.GetComponent<BoxCollider>().center = itemBound.center;
        }
    }上述两个方法,只是实现了这个功能,但我们的标题是批量添加,这就需要考虑到一个问题,批量是指多个同级的添加吗?还是指递归的添加呢?前者来说,只是一个gameObject数组的遍历添加问题,而更多的,我们需要考虑的是子物体的递归添加:
static void AddMultipleBoxCollider()
    {
        for (int i = 0; i < Selection.gameObjects.Length; i++)
        {
            Queue<GameObject> collidersObjects = new Queue<GameObject>();

            collidersObjects.Enqueue(Selection.gameObjects.transform.GetChild(0).gameObject);

            while (collidersObjects.Count != 0)
            {
                GameObject parent = collidersObjects.Dequeue();

                AddGameObjectCollider(parent);

                for (int j = 0; j < parent.transform.childCount; j++)
                {
                    collidersObjects.Enqueue(parent.transform.GetChild(j).gameObject);
                }
            }
        }
    }为了验证所有功能,我们在Assets/Scripts/Editor文件下创建一个BatchGenerateBoxColliders.cs的脚本,把所有的脚本放进去:
using System.Collections;
using System.Collections.Generic;
using UnityEditor;
using UnityEngine;

public class BatchGenerateBoxColliders : Editor
{
    [MenuItem("添加碰撞体/创建")]
    static void AddSingleBoxCollider()
    {
        for (int i = 0; i < Selection.gameObjects.Length; i++)
        {
            AddGameObjectCollider(Selection.gameObjects);
        }
    }

    [MenuItem("添加碰撞体/递归创建")]
    static void AddMultipleBoxCollider()
    {
        for (int i = 0; i < Selection.gameObjects.Length; i++)
        {
            Queue<GameObject> collidersObjects = new Queue<GameObject>();

            collidersObjects.Enqueue(Selection.gameObjects.transform.GetChild(0).gameObject);

            while (collidersObjects.Count != 0)
            {
                GameObject parent = collidersObjects.Dequeue();

                AddGameObjectCollider(parent);

                for (int j = 0; j < parent.transform.childCount; j++)
                {
                    collidersObjects.Enqueue(parent.transform.GetChild(j).gameObject);
                }
            }
        }
    }

    /// <summary>
    /// 添加碰撞体
    /// </summary>
    /// <param name="gameObject"></param>  
    public static void AddGameObjectCollider(GameObject gameObject)
    {
        Vector3 pos = gameObject.transform.localPosition;
        Quaternion qt = gameObject.transform.localRotation;
        Vector3 ls = gameObject.transform.localScale;

        gameObject.transform.position = Vector3.zero;
        gameObject.transform.eulerAngles = Vector3.zero;
        gameObject.transform.localScale = Vector3.one;
        //获取物体的最小包围盒
        Bounds itemBound = GetLocalBounds(gameObject);

        gameObject.transform.localPosition = pos;
        gameObject.transform.localRotation = qt;
        gameObject.transform.localScale = ls;
        //parent = null;
        if (!gameObject.GetComponent<Collider>())
            gameObject.AddComponent<BoxCollider>();
        if (gameObject.GetComponent<BoxCollider>())
        {
            gameObject.GetComponent<BoxCollider>().size = itemBound.size;

            gameObject.GetComponent<BoxCollider>().center = itemBound.center;
        }
    }

    /// <summary>
    /// 获得对象的最小包围盒
    /// </summary>
    public static  Bounds GetLocalBounds(GameObject target)
    {
        Renderer[] mfs = target.GetComponentsInChildren<Renderer>();
        Bounds bounds = new Bounds();
        if (mfs.Length != 0)
        {
            bounds = mfs[0].bounds;
            foreach (Renderer mf in mfs)
            {
                bounds.Encapsulate(mf.bounds);
            }
        }
        return bounds;
    }

}
效果如下:

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

×
懒得打字嘛,点击右侧快捷回复 【右侧内容,后台自定义】
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

小黑屋|手机版|Unity开发者联盟 ( 粤ICP备20003399号 )

GMT+8, 2024-9-22 23:23 , Processed in 0.090225 second(s), 26 queries .

Powered by Discuz! X3.5 Licensed

© 2001-2024 Discuz! Team.

快速回复 返回顶部 返回列表