Instantiate:生成 prefab 并复用它
怎么用代码生成物体?
把要生成的东西做成 prefab,在一个 public 字段里持有对它的引用,然后调用 Instantiate(prefab, position, rotation)。把 Prefab 从 Project 面板拖进那个字段——拖成 Hierarchy 里那个场景实例,是一半生成器 bug 的根源。
生成物体就是一个 Instantiate 函数,外加一个决定它灵不灵的习惯:你要生成的得是 Project 面板里的 prefab,而不是躺在场景里的那份拷贝。
先把 prefab 做出来
- 1
在场景里把物体搭起来,一直搭到它正是你想生成的那个样子 —— mesh、material、collider、脚本,全都配齐。
- 2
把它从 Hierarchy 拖进 Assets/Prefabs,名字会变成蓝色:现在它是 prefab 了,存在磁盘上。
- 3
把留在场景里的那份删掉。prefab 才是正本;场景里那份只会让开局时白白多站一个敌人。
Instantiate
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,在手机上用不了一分钟就开始卡顿。子弹和特效 —— 这些要出现上百次的东西 —— 要复用,别反复创建。
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 课。