概述
这是用于在 Godot 编辑器/运行时中自动生成地牢的 GDScript 脚本集合。
特性
-
可设置地牢整体大小、房间数量、房间与通道的最小/最大宽度/高度
-
基于种子(Seed)生成布局
-
预先创建外部/内部布局数据,并使用地牢的基本平铺(Tiling)图案进行装饰和对象放置
-
可可视化设置每种图案的最小/最大出现次数、出现频率及相邻方向
-
可按层/图案设置单元格占用状态、图案覆盖率及是否允许覆盖
通过设置多层,可以实现多层次的装饰效果
使用说明
虽然设置项较多,但一旦下载示例项目,您将能够更顺畅地理解。
1. 下载示例项目
-
从仓库下载所需文件。
https://github.com/ggg-shirokishi/procedural-layout-tools -
将
addons和scripts目录放置在项目根目录下。
2. 将布局生成器节点添加到场景中
-
在任意场景中添加
Node2D,并附加scripts目录下的布局脚本room_rayout_generator。 -
在检查器(Inspector)中设置
room_rayout_generator的各种参数。- 地图整体尺寸:
width,height - 房间数量:
room_count - 房间尺寸范围:
room_w_min / room_w_max,room_h_min / room_h_max - 通道宽度及通道宽度参数:
corridor_widthcorridor_width_min / maxcorridor_width_randomize_each_corridor- 启用
corridor_width_randomize_each_corridor后,通道宽度将在corridor_width_min / max之间随机变化。
- 启用
- 允许对角线通道设置:
connectivity_allow_diagonal- 启用
connectivity_allow_diagonal后,房间将以对角线方式连接。
- 启用
- 种子:
seed
- 地图整体尺寸:
-
启用
editor_auto_generate/editor_live_update后,在编辑器中修改各种参数时将立即反映效果。
3. 设置 TileMapLayer 和 TerrainAtlasPatternPlacer
-
在场景中添加
TileMapLayer和Node2D(用于执行放置脚本),并将scripts目录中的terrain_atlas_pattern_placer附加到Node2D上。- 放置目标图块层与放置用
Node2D之间是 1:1 的关系,可根据需要添加多个。 - 层数没有下限或上限,但在大多数情况下,至少需要两组
TileMapLayer+terrain_atlas_pattern_placer,分别用于外部(Terrain 图块放置)和内部。
(在示例项目中为TerrainTileLayer / TerrainTilePlacer和InteriorBaseLayer / InteriorBasePlacer)
- 放置目标图块层与放置用
-
为
TileMapLayer设置地形TileSet,并注册 Terrain/Pattern。- 本系统从图块层图块集(TileSet)中读取 Terrain/Pattern。
请在图块层侧进行图块集的设置。 - Terrain 的说明请参考以下链接。
https://docs.godotengine.org/ja/4.x/tutorials/2d/using_tilesets.html#creating-terrain-sets-autotiling
- 本系统从图块层图块集(TileSet)中读取 Terrain/Pattern。
-
在附加了
TerrainAtlasPatternPlacer脚本的节点中,设置以下项目。
TerrainAtlasPatternPlacer 的主要设置
layout_node:指向上一节中添加的room_rayout_generator节点的路径target_layer_path:此 Placer 操作的TileMapLayer的路径target_kind:此层处理的单元格类型(WALLS/FLOORS)
※WALLS:外部/不可通行的地形(通常放置 Terrain)
FLOORS:内部placement_type:使用 Terrain 绘制还是 Pattern 粘贴(TERRAIN/PATTERN)clear_before_place:每次重新放置时是否清空图层register_used_cells_to_layout:是否将使用的单元格注册到布局侧的used_cells_mask中
※启用此选项后,可防止后述的only_place_on_unoccupied为 On 且execution_order较大的层/图案进行重叠粘贴。only_place_on_unoccupied:是否限制为仅放置在未使用单元格上auto_update_on_layout_signal:收到布局更新信号时是否自动重新粘贴pattern_coverage_ratio:图案整体的粘贴密度execution_order:当存在多个TerrainAtlasPatternPlacer时的处理顺序
数值越小,处理优先级越高。结合only_place_on_unoccupied和register_used_cells_to_layout使用,以控制重叠粘贴。
4. 图案详细设置(编辑器扩展检查器)
将图块层关联到 TerrainAtlasPatternPlacer 后,检查器中将添加图块集中图案列表面板。
您可以为每个图案设置以下项目。
-
pattern_required_cells:必需接地单元格
(图案内部“希望始终接触边缘的单元格”)
结合后述的pattern_adjacent_dirs进行设置。
※可通过勾选缩略图下的单元格进行设置。 -
pattern_weights:出现频率 -
pattern_min_counts,pattern_max_counts:最小/最大出现次数(默认无限制) -
pattern_adjacent_dirs:相邻方向
(要求与上下左右哪个方向接触的单元格)示例:
将pattern_required_cells设置为上方 2 个单元格,将pattern_adjacent_dirs设置为“上”,则仅会放置在确保上方 2 个单元格相邻的地形上。 -
pattern_register_used_cells_override:单元格占用注册覆盖
可按图案设置是否占用单元格。- 继承全局设置(直接沿用
TerrainAtlasPatternPlacer的设置) - 强制开启
- 强制关闭
- 继承全局设置(直接沿用
-
pattern_only_place_on_unoccupied_override:仅放置在未使用单元格覆盖- 继承全局设置(直接沿用
TerrainAtlasPatternPlacer的设置) - 强制开启
- 强制关闭
- 继承全局设置(直接沿用
大致设置完上述内容后,按下 run_on_editor_button 即可生成布局。
运行时重新生成设置(可选)
- 若希望在运行过程中通过按键重新生成地牢,请将
RuntimeLayoutKeyboardController对应的脚本附加到Node上。
主要设置
layout_node_path:布局生成器节点input_action_name:用于重新生成的 InputMap 动作名称(例如regen_dungeon等)randomize_layout_size/randomize_room_count/randomize_room_size/randomize_corridor_width:是否在每次运行时随机化各种参数move_target_node_path:重新生成后需要移动的目标节点(如玩家)move_target_tilemap_layer_path:用于地板判定的TileMapLayermove_after_generation:重新生成后是否将目标移动到空闲单元格zoom_camera_path:希望根据布局调整limit的相机
在游戏开始后按下指定动作键,
- 更改种子
- 重新生成布局
- 根据需要移动玩家/调整相机
将自动依次执行。
多层装饰
通过让多个 TerrainAtlasPatternPlacer 共享同一个布局节点,并使用 execution_order 控制处理顺序,可以实现如下多层装饰:
- 第 1 层:外部(墙壁/地板的基础图块)
- 第 2 层:内部(柱子、窗户、横梁等图案)
- 第 3 层:对象(家具、瓦砾、装饰品)
通过切换各层的
register_used_cells_to_layoutonly_place_on_unoccupied
或使用图案侧的覆盖设置,可以实现
- “不破坏基础层,在其上叠加”
- “仅覆盖部分区域”
等控制效果。
提示/注意事项
-
图块层和图块集的大小必须统一。
-
若希望随机放置游戏对象:
可向图块集添加场景集合(Scene Collection),将对象放置在图块上,然后将其纳入图案并重新放置。
(可在示例项目中的GameObjectLayer或InteriorBaseLayer等处确认相关设置。)
各脚本详情
room_rayout_generator.gd
使用概要
这是一个附加到 Node2D 上使用的专用布局生成器。
它不会向 TileMap 写入任何数据。
生成结果将作为属性保存。
grid:int二维数组
CELL_WALL = 0,CELL_FLOOR = 1,CELL_DOOR = 2rooms:Array[Rect2i](房间矩形)centers:Array[Vector2i](房间中心)room_id_grid: 每个单元格的房间 ID(用于区分不同房间)corridor_id_grid: 每个单元格的通道 IDused_cells_mask: 从外部(Placer)注册的“已使用单元格”掩码
布局生成的外部 API
generate_now()
同步执行布局生成,完成后emitgeneration_finished(success: bool)。generate_async() -> Signal
异步执行生成。内部检查_is_generating以进行忙闲状态检查,执行带await的处理,最终通过相同的generation_finished信号返回结果。
信号
layout_updated(grid, rooms, centers)
当网格和房间信息更新时触发。generation_finished(success)
通知整体生成是否成功。
TerrainAtlasPatternPlacer 等将引用生成的 grid,并通过
register_used_cells() / get_free_cells()
共享“布局上的空闲单元格/已使用单元格”。
在编辑器中,通过 editor_auto_generate / editor_live_update / editor_generate_button,
- 可控制场景加载时的自动生成
- 可控制在检查器中修改参数时的自动重新生成
主要变量/属性
输出/状态
signal layout_updated(grid: Array, rooms: Array, centers: Array)
向外部通知当前布局信息的信号。signal generation_finished(success: bool)
生成完成时,通知成功/失败。const CELL_WALL: int = 0
网格中表示“墙壁”的值。const CELL_FLOOR: int = 1
网格中表示“地板”的值。const CELL_DOOR: int = 2
网格中表示“门预定单元格”的值。var rng: RandomNumberGenerator
随机数生成实例。由seed和_seed_internal初始化种子。var _is_generating: bool
异步生成中的标志。在generate_async期间变为true,完成后变为false。var grid: Array
grid[y][x]的二维数组。值为上述CELL_*之一。var rooms: Array[Rect2i]
生成成功的房间矩形数组。var centers: Array[Vector2i]
各房间中心单元格坐标的数组。var room_id_grid: Array
room_id_grid[y][x]存储该单元格属于哪个房间 ID(若非房间则为-1等)。var corridor_id_grid: Array
corridor_id_grid[y][x]存储通道的 ID(若非通道则为-1等)。var _next_corridor_id: int
用于分配通道 ID 的内部计数器。var used_cells_mask: Array
used_cells_mask[y][x] = bool的二维数组,表示“是否由 TilePlacer 侧注册为已使用单元格”。
通过register_used_cells(cells: Array[Vector2i])设为true。
通过get_free_cells(kind: int)仅返回false的单元格作为“空闲单元格”。
地图尺寸/房间参数
@export var width: int = 80
布局的宽度(单元格数)。通过_set_width()防止设置为 8 以下的值,编辑器中更改时会自动重新生成。@export var height: int = 60
布局的高度(单元格数)。通过_set_height()确保不会低于 8。@export var cell_padding: int = 1
外周边距。确保外框周围不放置房间。@export var room_count: int = 18
尝试生成的房间目标数量。由于冲突等原因,实际生成数量可能少于该值。@export var room_w_min: int = 5/room_w_max: int = 14
房间宽度(单元格数)的最小值/最大值。通过 setter 确保room_w_min <= room_w_max。@export var room_h_min: int = 4/room_h_max: int = 12
房间高度(单元格数)的最小值/最大值。同样通过 setter 保持一致性。
通道参数
@export var corridor_width: int = 1
通道宽度的基准值。若不使用随机宽度,则使用此固定宽度。@export var corridor_width_min: int = 0
随机通道宽度的最小值。@export var corridor_width_max: int = 1
随机通道宽度的最大值。@export var corridor_width_randomize_each_corridor: bool = true
当为true时,每个通道(每个段)从
corridor_width_min到corridor_width_max范围内单独随机决定宽度。@export var corridor_use_diagonal_path: bool = false
当为true时,允许在连接房间中心的通道挖掘中使用对角线路径(形成之字形移动路径)。@export var connectivity_allow_diagonal: bool = false
当为true时,在连通性检查(确认所有房间是否连接)中,将对角线(8 方向)也视为“已连接”。
随机数/重试/异步
var _seed_internal: int = 123456
实际传递给 RNG 的内部种子。@export var seed: int = 123456
布局生成的公开种子。通过 setter 更新_seed_internal,若编辑器中editor_live_update为true,则立即重新生成。@export var max_retry: int = 25
房间生成或通道连接失败时,更改种子并重试的最大尝试次数。@export var async_yield_rows: int = 6
异步生成时,每隔多少行通过await process_frame等分割处理。
数值越大,单次处理越多,但会长时间阻塞主线程。
日志/编辑器用
@export var log_enabled: bool = true
当为true时,通过_log()输出日志。@export var log_verbosity: int = 1
日志的详细程度。数值越大,日志越详细。@export var editor_auto_generate: bool = true
场景加载时等自动生成交布局的标志。@export var editor_live_update: bool = true
在编辑器中修改参数时,是否立即自动重新生成。@export var editor_generate_button: bool = false
检查器上“仅生成一次”触发用的标志。
仅在设置为true的瞬间调用_generate_editor_safe(),随后立即恢复为false。
terrain_atlas_pattern_placer.gd(TerrainAtlasPatternPlacer)
使用概要
这是带有 class_name TerrainAtlasPatternPlacer 的 Node2D 脚本。
其作用是
“引用 RoomLayoutGenerator 的 grid,并在指定的 TileMapLayer 上粘贴 Terrain 或 Pattern”。
主要前提:
layout_node应引用持有grid并emitlayout_updated/generation_finished的节点,如room_rayout_generator.gd。target_layer_path指定要粘贴的TileMapLayer。
粘贴目标单元格类型:
- 当
target_kind = WALLS时,处理CELL_WALL单元格 - 当
target_kind = FLOORS时,处理CELL_FLOOR + CELL_DOOR单元格
粘贴方法:
- 当
placement_type = TERRAIN时
使用set_cells_terrain_connect()绘制 Terrain。 - 当
placement_type = PATTERN时
使用TileSet的TileMapPattern进行粘贴。
其他行为:
- 若
auto_update_on_layout_signal为true,则在收到layout_node的layout_updated/generation_finished时自动重新放置。 - 通过
register_used_cells_to_layout,决定是否将放置中使用的单元格注册到layout_node.register_used_cells(cells)并反映到used_cells_mask中。 - 通过
only_place_on_unoccupied,可限制为“仅在布局上和 TileMapLayer 上均未使用的单元格”上放置。
此外,可通过pattern_*_override按图案覆盖此行为。 - 当多个 Placer 共享同一布局时,可通过
execution_order控制执行顺序(按数值从小到大执行)。
信号
signal placement_finished(success: bool)
放置处理完成时emit。
主要变量/属性
基本/类型
signal placement_finished(success: bool)
通知放置是否完成。const CELL_WALL: int = 0/CELL_FLOOR: int = 1/CELL_DOOR: int = 2
布局网格的值定义。与RoomLayoutGenerator侧对应。enum TargetKind { WALLS, FLOORS }
指定处理哪种单元格类型。enum PlacementType { TERRAIN, PATTERN }
Terrain 绘制或 Pattern 粘贴的模式。
布局引用/目标层
@export var layout_node: NodePath
布局生成节点(如RoomLayoutGenerator等)的路径。
通过 setter_set_layout_node缓存到内部的_layout_ref中。var _layout_ref: Node
实际的布局节点引用。var _grid: Array
用于缓存当前正在使用的布局grid的内部数组。@export var target_layer_path: NodePath
放置目标TileMapLayer的路径。var _target_layer: TileMapLayer
实际图块放置目标层的引用。
执行控制/联动
@export var clear_before_place: bool = true
当为true时,在开始粘贴前执行TileMapLayer.clear()。@export var register_used_cells_to_layout: bool = true
当为true时,将放置中使用的单元格列表传递给layout_node.register_used_cells(cells)。@export var only_place_on_unoccupied: bool = false
当为true时,仅将“布局上未使用且 TileMapLayer 上为空”的单元格作为放置目标。
结合布局侧的get_free_cells(kind)和 TileMapLayer 的单元格检查进行判定。@export var run_on_editor_button: bool = false
编辑器上的按钮触发标志。仅在设置为true的瞬间调用_set_run_on_editor_button(),- 执行
layout.generate_now() - 执行随后的
place_now()相关处理
完成后标志恢复为false。
- 执行
@export var auto_update_on_layout_signal: bool = true
当为true时,与layout_node的layout_updated/generation_finished信号自动联动。@export var auto_update_in_editor_only: bool = true
当为true时,仅在编辑器执行时自动更新,游戏运行时不自动更新。@export var auto_update_debounce_frames: int = 1
自动更新时,积累多少帧进行批量更新。
即使为0,也会至少等待 1 帧。@export var log_enabled: bool = true
是否输出日志。@export var log_verbosity: int = 1
日志的详细程度。@export var execution_order: int = 0
指定引用同一layout_node的多个 Placer 之间的执行顺序。
按数值从小到大执行,若数值相同,则按instance_id升序执行。var _pending_auto_update_local: boolvar _last_used_cells: Array[Vector2i]var _last_pattern_force_register: bool
用于保持批量更新挂起状态和最近一次放置信息的内部变量。
粘贴模式/Terrain
@export var target_kind: TargetKind = TargetKind.WALLS
指定布局中处理哪种单元格类型(墙壁或地板)。@export var placement_type: PlacementType = PlacementType.TERRAIN
选择 Terrain 模式或 Pattern 模式。@export var terrain_set_index: int = 0
要使用的 Terrain 集索引。@export var terrain_index: int = 0
在上述集合中要使用的 Terrain 索引。
Pattern 相关设置
@export var pattern_indices: PackedInt32Array = PackedInt32Array()
要使用的TileMapPattern索引列表。若为空,则目标为所有可用图案。@export var pattern_avoid_overlap: bool = true
当为true时,确保图案之间互不重叠地放置。@export var pattern_coverage_ratio: float = 0.1
目标单元格整体中,希望用图案覆盖的比例(0.0〜1.0)。@export var pattern_weights: Dictionary = {}
各图案的出现权重。- 键:
pattern_index - 值:
float(权重)
- 键:
@export var pattern_adjacent_dirs: Dictionary = {}
各图案的相邻方向位掩码。- 键:
pattern_index - 值:
int位掩码- 位值:
1 = U(上),2 = R,4 = D,8 = L
- 位值:
- 键:
@export var pattern_required_cells: Dictionary = {}
各图案的“必需接地单元格”定义。- 键:
pattern_index - 值:
Array[Vector2i](图案内部单元格坐标数组)
- 键:
@export var pattern_min_counts: Dictionary = {}
各图案的最低放置次数。@export var pattern_max_counts: Dictionary = {}
各图案的最大放置次数。若为-1或未设置,则表示“无上限”。@export var pattern_register_used_cells_override: Dictionary = {}
register_used_cells_to_layout的图案级覆盖设置。- 值:
0 = Inherit(继承全局设置) - 值:
1 = Force On(始终注册为已使用单元格) - 值:
2 = Force Off(始终不注册为已使用单元格)
- 值:
@export var pattern_only_place_on_unoccupied_override: Dictionary = {}
only_place_on_unoccupied的图案级覆盖设置。- 值:
0 = Inherit(继承全局设置) - 值:
1 = Force On(此图案仅粘贴到“未使用单元格”) - 值:
2 = Force Off(此图案也可粘贴到已使用单元格)
- 值:
terrain_pattern_placer_inspector.gd + plugin.gd / plugin.cfg
使用概要
通过 plugin.cfg 和 plugin.gd,
“Terrain Pattern Tools”插件将注册到 Godot 编辑器中。
plugin.cfg 的内容:
name="Terrain Pattern Tools"description="Custom inspector for TerrainAtlasPatternPlacer (pattern weights / adjacency)."script="plugin.gd"
plugin.gd 继承自 EditorPlugin,在 _enter_tree() 中
- 通过
Script.new()创建terrain_pattern_placer_inspector.gd - 通过
add_inspector_plugin(_insp)注册检查器扩展。
在 _exit_tree() 中,相反地通过 remove_inspector_plugin(_insp) 移除。
在项目设置中启用此插件后,
选择附加了 TerrainAtlasPatternPlacer 的节点时,
- 除了标准检查器外,还将添加按图案的设置 UI。
可编辑的添加 UI 内容
针对每个 TileMapPattern:
- 原始图块外观的缩略图
- 以网格形式显示图案内部单元格布局的网格缩略图
- 出现权重(
SpinBox) - 相邻方向(
U/R/D/L复选框) - 必需接地单元格(通过点击图案内单元格进行切换的按钮组)
- 最低设置数(
Min) - 最大设置数(
Max,-1表示无限制) register_used_cells_to_layout的图案级覆盖(Reg)only_place_on_unoccupied的图案级覆盖(Unocc)
这些都会直接写入 TerrainAtlasPatternPlacer 侧的以下属性:
pattern_weightspattern_adjacent_dirspattern_required_cellspattern_min_countspattern_max_countspattern_register_used_cells_overridepattern_only_place_on_unoccupied_override
class PatternPreviewControl extends Control
用于图案预览绘制的专用 Control 类。
var tileset: TileSet
预览使用的TileSet。var pattern: TileMapPattern
预览目标的图案。var preview_size: Vector2
滚动视图内部的显示大小。
内部生成 TileMapLayer,通过 set_cell() 等粘贴 pattern,并通过 _draw() 直接显示外观。
字典访问辅助函数
- 通过
_get_dict_safe(placer, "pattern_weights")等获取各Dictionary,在 UI 操作时更新并
像placer.set("pattern_weights", d)这样写回。
runtime_rayout_controller.gd(RuntimeLayoutKeyboardController)
使用概要
这是附加到 Node 上的运行时控制器。
作用:
- 当指定的输入动作被按下时,
- 随机更新布局节点的
seed, - (可选地)随机化布局参数(宽度/高度/房间数量/房间尺寸/通道宽度),
- 触发
layout_node侧的generate_now()以重新生成布局。
- 随机更新布局节点的
- 重新生成后,可执行以下操作:
- 将
move_target_node_path指定的节点移动到“空闲单元格”之一。 - 自动调整
zoom_camera_path指定相机的limit_*以匹配布局外框。
- 将
前提
layout_node_path指定附加了room_rayout_generator.gd的Node2D。
输入监视
- 在
_process()中监视Input.is_action_just_pressed(input_action_name),
若按下动作,则调用_regenerate_with_random_seed()。
移动/相机
- 若
move_after_generation为true,则在布局更新后通过_move_target_to_free_cell()进行移动。 - 若设置了
zoom_camera_path,则通过_update_camera_limits()重新计算
limit_left/right/top/bottom。
主要变量/属性
基本设置
const CELL_WALL: int = 0/const CELL_FLOOR: int = 1
布局网格的值定义(与RoomLayoutGenerator相同)。enum MoveTargetKind { MOVE_ON_FLOORS, MOVE_NEAR_WALLS }
表示选择移动目标单元格类型的枚举。MOVE_ON_FLOORS: 移动到地板单元格上。MOVE_NEAR_WALLS: 优先选择靠近墙壁的地板单元格等作为候选的逻辑。
@export var layout_node_path: NodePath
布局节点(RoomLayoutGenerator)的路径。@export var input_action_name: String = "dungeon_regen"
用于触发布局重新生成的InputMap动作名称。@export var log_enabled: bool = true
当为true时,通过print()/push_warning()等输出内部处理日志。var layout_node: Node = null
实际的布局节点引用。在_ready()中从layout_node_path获取。var _rng: RandomNumberGenerator = RandomNumberGenerator.new()
用于每次布局重新生成的随机数 RNG。
布局参数随机化
@export var randomize_layout_size: bool = false
当为true时,每次重新生成时随机更改布局的宽度和高度。@export var layout_width_range: Vector2i = Vector2i(80, 80)
width的随机范围。(x = min,y = max)@export var layout_height_range: Vector2i = Vector2i(60, 60)
height的随机范围。@export var randomize_room_count: bool = false
当为true时,每次重新生成时随机更改room_count。@export var room_count_range: Vector2i = Vector2i(18, 18)
room_count的随机范围。@export var randomize_room_size: bool = false
当为true时,每次重新生成时随机更改房间尺寸范围。@export var room_width_range: Vector2i = Vector2i(5, 14)
房间宽度的随机源范围。内部通过抽取两次值来确定min/max。@export var room_height_range: Vector2i = Vector2i(4, 12)
房间高度的随机源范围。@export var randomize_corridor_width: bool = false
当为true时,每次重新生成时随机更改通道宽度。@export var corridor_width_range: Vector2i = Vector2i(0, 3)
通道宽度的随机源范围。从中抽取两个值以确定min/max,或用于选择固定宽度。
移动目标/相机相关
@export var move_target_node_path: NodePath
布局更新后移动的目标节点路径(如玩家角色等)。@export var move_target_tilemap_layer_path: NodePath
与布局对应的TileMapLayer的路径。
从中判断哪些单元格是地板或墙壁,以获取移动目标候选单元格。@export var move_target_kind: MoveTargetKind = MoveTargetKind.MOVE_ON_FLOORS
通过上述枚举指定移动目标单元格的选择方式。@export var move_after_generation: bool = true
当为true时,在布局重新生成后执行_move_target_to_free_cell()。@export var cell_size: Vector2 = Vector2(16.0, 16.0)
布局网格 1 个单元格的像素大小。
用于从grid上的坐标求取世界坐标。@export var zoom_camera_path: NodePath
重新生成后自动调整limit_left/right/top/bottom的
Camera2D/ZoomCamera2D的路径。




