Unity教程 | 简化碰撞几何体

发表于2017-04-29
评论0 4k浏览
本篇教程将展示如何为导入的3D模型自动生成简化版的碰撞体。教程将以Google SketchUp为例,但其中的概念及代码也适用于其它建模工具。

简介

现代游戏大多模型精良,通常一个人物模型需要渲染数千个顶点及三角面。但角色的碰撞会以简化过后的Mesh进行计算。例如,Unity自带的标准角色碰撞器就是一个胶囊碰撞器。球体、立方体、胶囊体及一些简单几何体之间的碰撞计算非常高效。也可以单独计算并处理3D模型每个面之间的碰撞,Unity就专门设计了MeshCollider组件来处理这种类型的碰撞。但它有着较大的性能消耗,对整个游戏体验会带来较大的影响。

第一步 难题

现在游戏中比较标准的解决办法就是,对物理对象采用简化的碰撞网格。这就需要在开发过程中进行额外的步骤。除了创建3D模型之外,还需要创建碰撞网格并将其置于正确的位置。使用某些软件可以大幅度简化该流程。但如果使用Google SketchUp这样的3D建模工具就不行了。从SketchUp中导入模型至Unity只能动态生成MeshCollider。



对于这个问题,一种可能的解决方法是自动添加所有您需要的几何体。首先需要了解的是,一旦从SketchUp导入模型,Unity将保留其组件的层级结构。SketchUp支持组和组件的概念,它们作为几何实体(顶点及三角面)的容器,组合成为模型。组和组件在导入Unity时都会被转换为游戏对象。对于组件,Unity会保留SketchUp中的命名再加上一个增量编号。如果Sketchup模型包含两个Table组件的实例,Unity会将它们命名为Table #1 和Table #2。



第二步 解决方案

这个简易教程的核心思想就是迭代导入游戏对象,并且只为某些特定的组件添加碰撞器。因为导入的模型包含许多嵌套的游戏对象,所以需要以递归(recursion)的形式遍历它们。递归流程如下:

  • 从代表所导入模型的游戏对象开始
  • 迭代它所包含的游戏对象
    • 如果当前游戏对象满足标准(以Table为前缀命名),那就为其添加一个Collider并结束;
    • 如果不满足标准,那就继续迭代它的子节点并重复步骤2。



C#代码如下:
[C#] 纯文本查看 复制代码
public void CreateCollidersRecursively (Transform root)
{
    // [2] Iterates over all children of "root"
    foreach (Transform child in root)
    {
        // [A] Check if we need to create a collider
        if (child.name.StartsWith("Table") )
        {
            child.gameObject.AddComponent();
            return;
        }
  
        // [B] Repeats the process for each child
        CreateCollidersRecursively(child);
    }
}


使用AddComponent添加BoxCollider,与在检视面板中手动添加碰撞器效果一致,Unity会自动调整碰撞器大小直至与3D网格边框匹配。它会为游戏对象生成包围盒,并且适用于大部分情况。

第三步  自定义编辑器

在C#脚本的Start方法中调用CreateCollidersRecursively进行测试:
[C#] 纯文本查看 复制代码
public Transform yourModel;
void Start ()
{
    CreateCollidersRecursively(yourModel);
}


该方法最大的问题是每次启动游戏时都会执行代码,也可以在游戏启动之前就执行,以减少游戏加载时间。实现该功能需要在编辑器中执行脚本,而非游戏启动后执行。Unity支持使用自定义脚本来扩展编辑器的功能及外观,可以创建自定义编辑器脚本实现该功能。在函数前定义MenuItem属性,以便在运行游戏之前就可以直接从编辑器调用。

[C#] 纯文本查看 复制代码
using UnityEngine;
...
[MenuItem("Our Game/Generate Colliders")]
static void ColliderMenu ()
{
    CreateCollidersRecursively(myModel);
}


这个新的设置可以生成碰撞器且不影响最终的玩家体验。如果有多个模型,可以将它们放在同一个游戏对象下,并从该游戏对象上调用此脚本。



第四步 改进

删除之前的碰撞器。多次运行CreateCollidersRecursively 函数会反复添加多次碰撞器。这会导致许多问题。因此,应该确保脚本在创建新的碰撞器之前删除所有已存在的碰撞器。


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public void CreateCollidersRecursively (Transform root)
{
    // Iterates over all children of "root"
    foreach (Transform child in root)
    {
        // Check if we need to create a collider
        if (child.name.StartsWith("Table") )
        {
            // Deletes all the previous colliders
            Collider [] colliders = root.gameObject.GetComponents();
            foreach (Collider collider in colliders)
                Destroy (collider);
   
            // Creates a new colliders
            child.gameObject.AddComponent();
            return;
        }
   
        // Repeats the process for each child
        CreateCollidersRecursively(child);
    }
}
初始化预制件。使用该技术可以自动初始化那些SketchUp无法原生支持的对象。灯光、音源、收集物、敌人等等,它们都可以在函数递归步骤中创建。例如,下面的代码在找到名称前缀为“Light”的组时,就从预制件创建光源。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public GameObject lightPrefab;
   
public void CreateCollidersRecursively (Transform root)
{
    // Iterates over all children of "root"
    foreach (Transform child in root)
    {
        // Check if we need to create a collider
        if (child.name.StartsWith("Table") )
        {
            // ...
        }
   
        // Creates a light
        if (child.name.StartsWith("Light") )
        {
            GameObject light = Instantiate(lightPrefab, root.position, Quaternion.identity) as GameObject;
            light.transform.parent = root;
        }
        
        // ...
    }
}
总结

本教程简单介绍了如何在Unity中循环遍历3D模型的组件,用于自动添加碰撞器并初始化一些预制件,例如:光源、音源及收集物等等。
原文作者: Alan Zucconi

如社区发表内容存在侵权行为,您可以点击这里查看侵权投诉指引

0个评论