Skip to content
预制体

预制体

阅读本文大概需要 10 分钟。

预制体是可以帮我们保存我们制作的物体和功能的一个重要途径,并且可以在场景中同时创建出多个预制体实例,然后统一进行修改。熟练的使用预制体,可以让你的游戏开发过程更加迅速便捷!

更多预制体使用见产品文档:预制体功能说明

1. 什么是预制体

这里首先抛出两个游戏开发中常遇到的问题,大家思考一下。

  • 第一个问题,想象一下,当前你正在制作一个塔防游戏,你耗费精力用了一个小时制作了一座非常精美的防守塔,包括多个模型和特效的拼接,并且在脚本中添加了攻击附近敌人的功能。这时候游戏地图中,除了你制作的这座防守塔外,还需要额外 4 个塔,怎么办?聪明的你一定会使用复制粘贴功能来解决这个问题,那么当你复制了 4 座塔后,需求又发生改变了,要求塔的顶部要添加一个火焰特效,这时候你只能一个塔一个塔的去加,非常麻烦,而且这只是 5 座塔,如果有 100 座呢?

  • 第二个问题,再想象一下,如果需要在塔防游戏地图中使用脚本动态的创建一座上面的这种防守塔,你怎么创建?你只能在脚本当中创建防守塔需要的所有物体和特效,然后设置它们之间的父子关系和相对位置,并且添加防御塔的功能,非常麻烦。

这时候预制体就要现身了。预制体是由多个模型、特效、UI、脚本等资源组合成的高级组合资源,预制体资源可以和“本地资源库”中的资源一样直接拖拽到场景使用,也可以通过脚本动态创建预制体,这样就可以帮助我们完美解决上述两个问题。在第一个问题中,你可以创建一座防守塔的预制体,然后将其拖拽到场景中需要的位置,当需要修改时,只需要修改防守塔的预制体,那么地图中的防守塔都会同步该修改,非常方便。在第二个问题中,你可以首先记录下防守塔预制体的 GUID,然后在脚本中,该防守塔就可以和普通物体一样通过 GUID 动态生成,也是十分简便了。

2. 创建预制体

预制体的创建有两种方式:

第一种方式,我们可以在“对象管理器”面板中通过右键生成一个预制体,这里示例,将一个宝箱与金币设置父子级关系后,生成一个预制体,如图:

那么生成的预制体到哪了?在“工程内容”就可以看到我们刚生成的预制体了,而且该预制体可以通过右键复制 ID 进行动态生成,也可以直接拖拽到场景进行使用,如图:

第二种方式,我们可以直接新建预制体,会生成预制体文件夹,在文件夹中包含了我们的预制体文件与预制体用到的脚本和 UI 资源,如图:

可以将自己的预制体文件修改为需要的名称,示例这里修改预制体文件名称为“MyPrefab”,双击该预制体文件,打开预制体编辑器,如图:

可以看到场景上方从“场景”切换到了“预制体”,并且右侧的“对象管理”界面,已经变成了“模板结构”界面了,该界面中只显示了我们预制体的父子级结构,并且根节点为我们的预制体节点,该节点不可以被删除。这里我们可以放上自己喜欢的物体作为子节点,如下图:

预制体创建完成后,单击上图中的“场景”按钮,切换到游戏场景编辑模式中,如果出现保存界面,单击保存即可,如图:

接下来和第一种方式一样,我们就可以随意使用该预制体了,如图:

3. 导出预制体

有些时候我们做成的预制体需要保存下来,供我们别的工程使用,或者传给别人去使用,这时候我们就需要将我们的预制体导出成一个文件了,非常简单,右键单击我们的预制体文件,在菜单中选择导出,然后选择导出位置就可以了,如图:

这里我导出到桌面,如图:

单击“选择文件夹”后,可以在桌面上看到我们导出的预制体了,可以看到,是一个 zip 压缩包,如图:

4. 导入预制体

这里创建一个新的工程,尝试导入我们刚才从旧工程中导出的预制体文件,首先,在“工程内容”窗口中,切换到预制体选项,然后单击“导入预制体”按钮,如图:

在弹出的选择窗口中,选择我们桌面上导出的预制体压缩包,并单击“打开”,如图:

这时候在“工程内容”窗口中,可以看到预制体已经被导入进来,我们可以正常使用,如图:

5. 修改预制体

当预制体摆放在场景后,我们可以在“对象管理”窗口中看到,通过预制体实例化的物体,显示为紫色,如图:

右键单击后,在菜单中包含很多预制体相关操作,我们可以单击“编辑预制体”进入预制体编辑界面,如图:

除此之外,也可以双击该预制体文件进入预制体编辑界面,如图:

在编辑界面,我们尝试添加一个物体,并保存预制体,然后可以看到,之前场景中的预制体实例也全部发生改变了,如图:

6. 动态创建预制体

6.1 创建 S&C 端预制体

有些时候我们需要通过脚本动态生成预制体,例如这里我们需要动态生成上面的“MyPrefab”预制体实例,那么应该怎么做?很简单,首先右键单击预制体文件,在菜单中选择“复制工程内容 ID”,如图:

然后在场景中任意游戏脚本中添加生成代码即可,这里创建了一个脚本为“Test”,并挂载到游戏场景中,如图:

修改脚本代码如下:

ts
@Component
export default class Test extends Script {

    /** 当脚本被实例后,会在第一帧更新前调用此函数 */
    protected onStart(): void {
        //服务端,因为预制体默认是服务端&客户端的,所以生成代码要写到服务端代码中,这样会在服务端中生成并且同步到客户端,如果在客户端代码中生成预制体实例则会失败
        if (SystemUtil.isServer()) {
            //生成预制体,填写的 ID 就为刚才复制的 ID
            let object = GameObject.spawn("8258142749883E47DF83FDBD6F780011")
            //设置预制体实例位置
            object.worldTransform.position = Vector.zero
        }
    }
}
@Component
export default class Test extends Script {

    /** 当脚本被实例后,会在第一帧更新前调用此函数 */
    protected onStart(): void {
        //服务端,因为预制体默认是服务端&客户端的,所以生成代码要写到服务端代码中,这样会在服务端中生成并且同步到客户端,如果在客户端代码中生成预制体实例则会失败
        if (SystemUtil.isServer()) {
            //生成预制体,填写的 ID 就为刚才复制的 ID
            let object = GameObject.spawn("8258142749883E47DF83FDBD6F780011")
            //设置预制体实例位置
            object.worldTransform.position = Vector.zero
        }
    }
}

运行后,效果如图:

6.2 创建纯 C 端预制体

有时候我们的预制体没必要在服务端中创建,可能仅仅需要创建到客户端上进行显示,这时候我们可以将预制体的类型改为纯 C 端,如图:

这时候如果想动态创建该预制体,就需要修改上面代码如下:

ts
@Component
export default class Test extends Script {

    /** 当脚本被实例后,会在第一帧更新前调用此函数 */
    protected onStart(): void {
        //纯客户端的预制体生成代码需要写在客户端中,才会正常显示
        if (SystemUtil.isClient()) {
            //生成预制体,填写的 ID 就为刚才复制的 ID
            let object = GameObject.spawn("8258142749883E47DF83FDBD6F780011")
            //设置预制体实例位置
            object.worldTransform.position = Vector.zero
        }
    }
}
@Component
export default class Test extends Script {

    /** 当脚本被实例后,会在第一帧更新前调用此函数 */
    protected onStart(): void {
        //纯客户端的预制体生成代码需要写在客户端中,才会正常显示
        if (SystemUtil.isClient()) {
            //生成预制体,填写的 ID 就为刚才复制的 ID
            let object = GameObject.spawn("8258142749883E47DF83FDBD6F780011")
            //设置预制体实例位置
            object.worldTransform.position = Vector.zero
        }
    }
}

7. 预制体更换模型

有时候,我们可能需要更换预制体中的模型,例如在一个预制体中,有一个正方体物体,如图:

这时候如果需要更换预制体模型,最简单的方式就是直接更换物体网格,这里选中正方体,在属性中可以看到网格属性,如图:

这时候只需要从资源库拖拽过来一个新的物体到网格处,即可成功替换模型,如图:

可以看到场景中物体也发生变化了,如图: