PackedScene.instantiate
2026/4/14大约 7 分钟
最后更新日期:2026-04-16
最后同步日期:2026-04-15 | Godot 官方原文 — PackedScene.instantiate
PackedScene.instantiate
定义
PackedScene.instantiate 就像用模具批量生产饼干——你先设计好一块饼干的模具(场景文件 .tscn),然后每次调用 instantiate 就相当于"用模具压出一块新饼干"。每块饼干都长得一模一样,但它们是独立的个体,可以各自修改而不影响彼此。
在 Godot 中,场景文件(.tscn)就是"模具",它定义了一个节点树的蓝图:包含哪些节点、它们的属性是什么、节点之间怎么关联。PackedScene.instantiate 的工作就是按照这个蓝图,在内存中创建一份活生生的节点树实例。你可以把实例添加到场景树中,让它出现在游戏画面上。
一句话总结
instantiate 的作用是"从场景文件创建一个真正的节点实例"——就像从设计图纸造出一栋真正的房子。
函数签名
C#
// 非泛型版本——返回 Node 基类,需要手动转换类型
public Node Instantiate(GenEditState editState = GenEditState.Disabled)
// 泛型版本(推荐)——直接返回指定类型的节点
public T Instantiate<T>(GenEditState editState = GenEditState.Disabled) where T : classGDScript
PackedScene.instantiate(edit_state: int = 0) -> Node参数说明
| 参数 | 类型 | 必需 | 说明 |
|---|---|---|---|
| editState | GenEditState / int | 否 | 实例的编辑状态,控制实例化时是否携带编辑器信息。一般游戏运行时用默认值即可 |
编辑状态(GenEditState)
| 状态 | 值 | 说明 |
|---|---|---|
GenEditState.Disabled / GEN_EDIT_STATE_DISABLED | 0 | 默认值。普通实例,不包含编辑器信息,游戏运行时使用 |
GenEditState.Enabled / GEN_EDIT_STATE_ENABLED | 1 | 启用编辑器信息,实例可以被编辑器修改。仅用于编辑器插件 |
GenEditState.Main / GEN_EDIT_STATE_MAIN | 2 | 主场景实例,用于编辑器的主编辑视图中 |
返回值
| 类型 | 说明 |
|---|---|
| Node (C# 非泛型) / T (C# 泛型) / Node (GDScript) | 返回场景根节点的实例。这是一个"活"的节点对象,可以修改属性、调用方法、添加到场景树中。如果场景文件无效,返回 null |
代码示例
基础用法
最简单的用法——加载一个场景文件并创建实例:
C#
using Godot;
public partial class BasicInstantiate : Node
{
public override void _Ready()
{
// 加载场景文件("模具")
var enemyScene = ResourceLoader.Load<PackedScene>("res://scenes/enemy.tscn");
// 用模具压出一个实例
var enemy = enemyScene.Instantiate<Node2D>();
// 设置实例的位置
enemy.Position = new Vector2(100, 200);
// 把实例添加到场景树中,让它出现在画面上
AddChild(enemy);
GD.Print("敌人实例已创建,名称: " + enemy.Name);
// 运行结果: 敌人实例已创建,名称: Enemy
}
}GDScript
extends Node
func _ready() -> void:
# 加载场景文件("模具")
var enemy_scene = ResourceLoader.load("res://scenes/enemy.tscn")
# 用模具压出一个实例
var enemy = enemy_scene.instantiate()
# 设置实例的位置
enemy.position = Vector2(100, 200)
# 把实例添加到场景树中,让它出现在画面上
add_child(enemy)
print("敌人实例已创建,名称: %s" % enemy.name)
# 运行结果: 敌人实例已创建,名称: Enemy实际场景
在游戏中动态生成敌人是最常见的 instantiate 用途。下面的例子展示了一个完整的敌人生成器,支持配置生成间隔和随机位置:
C#
using Godot;
public partial class EnemySpawner : Node2D
{
[Export] public PackedScene ExEnemyScene;
[Export] public float ExSpawnInterval = 3.0f;
[Export] public int ExMaxEnemies = 10;
private Timer _spawnTimer;
private int _aliveCount = 0;
public override void _Ready()
{
if (ExEnemyScene == null)
{
GD.PrintErr("请先在编辑器中设置 ExEnemyScene!");
return;
}
// 创建循环计时器
_spawnTimer = new Timer();
_spawnTimer.WaitTime = ExSpawnInterval;
_spawnTimer.OneShot = false;
AddChild(_spawnTimer);
_spawnTimer.Timeout += OnSpawnTimerTimeout;
_spawnTimer.Start();
GD.Print($"敌人生成器已启动,每 {ExSpawnInterval} 秒检查一次");
// 运行结果: 敌人生成器已启动,每 3 秒检查一次
}
private void OnSpawnTimerTimeout()
{
// 检查是否超过最大数量
if (_aliveCount >= ExMaxEnemies)
{
GD.Print("敌人数量已达上限,跳过本次生成");
return;
}
// 实例化敌人
var enemy = ExEnemyScene.Instantiate<CharacterBody2D>();
enemy.Position = new Vector2(
(float)GD.RandRange(-300, 300),
(float)GD.RandRange(-200, 200)
);
// 监听敌人死亡信号
// (假设敌人脚本有一个 died 信号)
AddChild(enemy);
_aliveCount++;
GD.Print($"生成了第 {_aliveCount} 个敌人,位置: {enemy.Position}");
// 运行结果: 生成了第 1 个敌人,位置: (156, -88)
// 运行结果: 生成了第 2 个敌人,位置: (-234, 145)
}
}GDScript
extends Node2D
@export var ex_enemy_scene: PackedScene
@export var ex_spawn_interval: float = 3.0
@export var ex_max_enemies: int = 10
var _spawn_timer: Timer
var _alive_count: int = 0
func _ready() -> void:
if ex_enemy_scene == null:
push_error("请先在编辑器中设置 ex_enemy_scene!")
return
# 创建循环计时器
_spawn_timer = Timer.new()
_spawn_timer.wait_time = ex_spawn_interval
_spawn_timer.one_shot = false
add_child(_spawn_timer)
_spawn_timer.timeout.connect(_on_spawn_timer_timeout)
_spawn_timer.start()
print("敌人生成器已启动,每 %s 秒检查一次" % ex_spawn_interval)
# 运行结果: 敌人生成器已启动,每 3.0 秒检查一次
func _on_spawn_timer_timeout() -> void:
# 检查是否超过最大数量
if _alive_count >= ex_max_enemies:
print("敌人数量已达上限,跳过本次生成")
return
# 实例化敌人
var enemy = ex_enemy_scene.instantiate()
enemy.position = Vector2(
randf_range(-300, 300),
randf_range(-200, 200)
)
add_child(enemy)
_alive_count += 1
print("生成了第 %d 个敌人,位置: %s" % [_alive_count, enemy.position])
# 运行结果: 生成了第 1 个敌人,位置: (156, -88)
# 运行结果: 生成了第 2 个敌人,位置: (-234, 145)进阶用法
使用 [Export] PackedScene 在编辑器中配置场景引用,以及批量实例化多个不同场景来构建复杂关卡:
C#
using Godot;
using System.Collections.Generic;
public partial class LevelBuilder : Node2D
{
// 在编辑器中配置多种场景预制件
[Export] public PackedScene ExPlayerScene;
[Export] public PackedScene ExWallScene;
[Export] public PackedScene ExCoinScene;
[Export] public PackedScene ExGoalScene;
public override void _Ready()
{
// ===== 示例 1:实例化玩家 =====
if (ExPlayerScene != null)
{
var player = ExPlayerScene.Instantiate<CharacterBody2D>();
player.Position = new Vector2(50, 300);
AddChild(player);
GD.Print("玩家已创建");
}
// ===== 示例 2:批量实例化墙壁 =====
if (ExWallScene != null)
{
for (int i = 0; i < 5; i++)
{
var wall = ExWallScene.Instantiate<StaticBody2D>();
wall.Position = new Vector2(i * 64, 400);
AddChild(wall);
}
GD.Print("5 面墙壁已创建");
}
// ===== 示例 3:用数组模式批量实例化金币 =====
if (ExCoinScene != null)
{
var coinPositions = new List<Vector2>
{
new(100, 200),
new(200, 150),
new(300, 200),
new(400, 100)
};
int coinIndex = 0;
foreach (var pos in coinPositions)
{
var coin = ExCoinScene.Instantiate<Area2D>();
coin.Position = pos;
AddChild(coin);
coinIndex++;
}
GD.Print($"已放置 {coinIndex} 枚金币");
}
// ===== 示例 4:实例化终点标记 =====
if (ExGoalScene != null)
{
var goal = ExGoalScene.Instantiate<Area2D>();
goal.Position = new Vector2(500, 300);
AddChild(goal);
GD.Print("终点标记已创建");
}
GD.Print("--- 关卡构建完成 ---");
// 运行结果:
// 玩家已创建
// 5 面墙壁已创建
// 已放置 4 枚金币
// 终点标记已创建
// --- 关卡构建完成 ---
}
}GDScript
extends Node2D
# 在编辑器中配置多种场景预制件
@export var ex_player_scene: PackedScene
@export var ex_wall_scene: PackedScene
@export var ex_coin_scene: PackedScene
@export var ex_goal_scene: PackedScene
func _ready() -> void:
# ===== 示例 1:实例化玩家 =====
if ex_player_scene != null:
var player = ex_player_scene.instantiate()
player.position = Vector2(50, 300)
add_child(player)
print("玩家已创建")
# ===== 示例 2:批量实例化墙壁 =====
if ex_wall_scene != null:
for i in range(5):
var wall = ex_wall_scene.instantiate()
wall.position = Vector2(i * 64, 400)
add_child(wall)
print("5 面墙壁已创建")
# ===== 示例 3:用数组模式批量实例化金币 =====
if ex_coin_scene != null:
var coin_positions = [
Vector2(100, 200),
Vector2(200, 150),
Vector2(300, 200),
Vector2(400, 100)
]
var coin_index = 0
for pos in coin_positions:
var coin = ex_coin_scene.instantiate()
coin.position = pos
add_child(coin)
coin_index += 1
print("已放置 %d 枚金币" % coin_index)
# ===== 示例 4:实例化终点标记 =====
if ex_goal_scene != null:
var goal = ex_goal_scene.instantiate()
goal.position = Vector2(500, 300)
add_child(goal)
print("终点标记已创建")
print("--- 关卡构建完成 ---")
# 运行结果:
# 玩家已创建
# 5 面墙壁已创建
# 已放置 4 枚金币
# 终点标记已创建
# --- 关卡构建完成 ---注意事项
instantiate只是创建,不会自动显示:调用instantiate后,节点只存在于内存中,还不会出现在画面上。你必须手动调用AddChild()将其添加到场景树中,它才会被渲染和执行逻辑。- 每个实例都是独立的:同一个
PackedScene可以多次instantiate,每次都会创建一个全新的独立节点树。修改其中一个实例不会影响其他实例,也不会影响原始场景文件。 [Export] PackedScene是最佳实践:在编辑器的 Inspector 面板中拖入场景引用,比在代码中硬编码路径("res://scenes/enemy.tscn")更灵活、更安全。修改场景文件路径时不需要改代码。- 实例化后
_Ready()会延迟调用:instantiate本身不会触发_Ready(),只有当你用AddChild()把实例加入场景树后,_Ready()才会被调用。所以设置实例属性(如位置、速度)应该在AddChild()之前完成。 - 大量实例化时注意性能:一次性实例化几十上百个场景可能会造成短暂卡顿。如果需要大量生成(如子弹、粒子),考虑使用对象池(Object Pool)模式,预先创建一批实例并循环利用。
- C# 差异:C# 推荐使用泛型版本
Instantiate<T>()直接获得目标类型(如Instantiate<CharacterBody2D>()),避免手动类型转换。GDScript 中instantiate()返回Node,通常可以直接作为对应类型的节点使用,Godot 的动态类型系统会自动处理。
