U
18 / 22 · 9 分钟
常见问题

Instantiate:生成 prefab 并复用它

简短回答

怎么用代码生成物体?

把要生成的东西做成 prefab,在一个 public 字段里持有对它的引用,然后调用 Instantiate(prefab, position, rotation)。把 Prefab 从 Project 面板拖进那个字段——拖成 Hierarchy 里那个场景实例,是一半生成器 bug 的根源。

生成物体就是一个 Instantiate 函数,外加一个决定它灵不灵的习惯:你要生成的得是 Project 面板里的 prefab,而不是躺在场景里的那份拷贝。

先把 prefab 做出来

  1. 1

    在场景里把物体搭起来,一直搭到它正是你想生成的那个样子 —— mesh、material、collider、脚本,全都配齐。

  2. 2

    把它从 Hierarchy 拖进 Assets/Prefabs,名字会变成蓝色:现在它是 prefab 了,存在磁盘上。

  3. 3

    把留在场景里的那份删掉。prefab 才是正本;场景里那份只会让开局时白白多站一个敌人。

Instantiate

Spawner.cs — drag the PREFAB into the slot, not the scene object
using UnityEngine;

public class Spawner : MonoBehaviour
{
    public GameObject prefab;
    public Transform parent;        // optional, keeps the Hierarchy tidy
    public float interval = 1.5f;

    float next;

    void Update()
    {
        if (Time.time < next) return;
        next = Time.time + interval;

        Vector3 where = transform.position + Random.insideUnitSphere * 3f;
        where.y = transform.position.y;

        GameObject spawned = Instantiate(prefab, where, Quaternion.identity, parent);

        // Instantiate returns the new copy. Configure that, never the prefab —
        // writing to prefab.something edits the asset on disk.
        spawned.name = "Enemy " + Time.frameCount;
    }
}

人们常忘的三件事

  • Instantiate 会复制一切,包括 prefab 的 scale 和它的子对象。生成出来的物体尺寸不对,那就是 prefab 的尺寸不对。

  • 传入 parent 能让 Hierarchy 保持整洁,但对象会继承父级的 scale。要么挂到一个 scale 为 1 的父对象下,要么不带父对象生成,之后再调用 SetParent(parent, true)。

  • Quaternion.identity 表示不做旋转。在那里改写 transform.rotation,生成的每个物体都会和 spawner 朝向一致 —— 而这通常才是你真正想要的。

Destroy,以及 spawner 为什么拖垮帧率

每一次 Instantiate 都是一次分配,每一次 Destroy 都给垃圾回收器留下一堆垃圾。每帧都在跑的 spawner,在手机上用不了一分钟就开始卡顿。子弹和特效 —— 这些要出现上百次的东西 —— 要复用,别反复创建。

The smallest pool that is worth having
using System.Collections.Generic;
using UnityEngine;

public class SimplePool : MonoBehaviour
{
    public GameObject prefab;
    readonly Queue<GameObject> idle = new();

    public GameObject Get(Vector3 at)
    {
        GameObject go = idle.Count > 0 ? idle.Dequeue() : Instantiate(prefab);
        go.transform.position = at;
        go.SetActive(true);
        return go;
    }

    // Call this instead of Destroy.
    public void Release(GameObject go)
    {
        go.SetActive(false);
        idle.Enqueue(go);
    }
}

做出这个的那一课

用代码生成方块网格

这篇指南单独成篇,是一份可以直接照着做的配方。在课程里,同样的东西会作为贯穿全部五个章的那个项目的一部分来搭建 —— 基础,第 08 课。