Godot CSG Blockout 3.0:画量试玩冻结的白盒关卡流程

📅 发布时间:2026/10/7 3:02:32
Godot CSG Blockout 3.0:画量试玩冻结的白盒关卡流程
做关卡原型的时候最怕的不是画得慢而是画完以后没法验证手感改一版要重摆一堆方块。很多团队在 Godot 里做白盒关卡会用 CSG 节点临时拼几何体可一旦场景复杂起来节点树乱、尺寸靠猜、试玩时要自己接角色最后还要手动把 CSG 烘焙成 Mesh流程非常割裂。本文要聊的 CSG Blockout 3.0就是一套把“画、量、试玩、冻结”串起来的关卡白盒工作流用 Godot 原生 CSG 快速搭几何原型在编辑器里直接控制尺寸和比例切到游戏场景里验证跑动手感最后把 CSG 冻结成正式 Mesh 资产交给下一阶段。不管你是刚接触 Godot 的新手还是已经在用 CSG 做项目预演的开发者这篇文章都会给你一条完整可落地的操作路径。1. 为什么关卡原型要用 CSG而不是直接堆模型1.1 CSG 是什么Blockout 又是做什么的CSG 全称是 Constructive Solid Geometry也就是构造实体几何。在 Godot 里它表现为一组可以直接放在 3D 场景中的节点比如CSGBox3D、CSGCylinder3D、CSGSphere3D这些节点自带几何体并且支持布尔运算。你可以把一个立方体当作基础块再放一个圆柱体作为减去的“钻头”两者组合起来就能形成挖空、开洞、拼接等复杂外形。Blockout 这个词在关卡设计中通常指“白盒原型”也就是用最简单的几何体把地图的体量、空间关系、通行路线先搭出来。CSG Blockout 3.0 这个名字背后其实是一套围绕白盒流程设计的插件工作流它把编辑器的原生 CSG 能力封装成了更顺手的功能让开发者能够像画草图一样快速布置关卡基底同时不丢失尺寸、碰撞和后续烘焙能力。为什么不用美术同学做好的正式模型来搭原型因为正式模型的制作周期长改一个门洞位置可能要重新建模、重新导入、重新摆场景。而 CSG 节点是在编辑器里实时生成的改一个size属性门洞立刻变宽玩家角色马上能跑进去。这个“实时可改”的特性决定了 CSG 是关卡原型阶段性价比最高的工具。1.2 Blockout 3.0 的核心工作流画、量、试玩、冻结CSG Blockout 3.0 把整个白盒流程拆成了四个阶段这也是插件名称里的四个关键词画用 CSGBox3D、CSGCylinder3D 等原生节点快速搭出墙体、平台、障碍物、出入口。量在编辑器里确认每个方块的尺寸、间距、层高让原型比例接近真机效果。试玩挂上临时角色控制器直接在 CSG 场景里跑跳、碰撞验证玩法可行性。冻结把 CSG 节点烘焙成 ArrayMesh生成正式静态网格和碰撞体进入后续资源流程。这四个阶段不是各自独立的而是连续的一条管线。你先画再量量完试玩手感不对就回去改画的部分确认没问题再冻结。由于 CSG 天然支持实时修改和布尔运算这条管线在编辑器内可以反复循环直到关卡结构稳定下来。2. 环境准备与插件安装2.1 Godot 版本与项目设置本文示例以 Godot 4.x 为准使用的节点和脚本接口也基于 4.x 的 API。如果你还在用 Godot 3.x部分节点名称会不同例如CSGBox对应 4.x 的CSGBox3D烘焙方法也有差异。这里建议直接使用 Godot 4.2 或更新的稳定版本官方对 3D 场景、CSG 节点和编辑器插件的支持会更完整。新建项目时选择“3D Scene”模板即可。项目名称可以取BlockoutDemo。进入项目后在项目设置中确认主场景和渲染器默认的 Forward 渲染器可以满足 CSG 预览和烘焙需求。如果你的机器性能较弱也可以切到 Mobile 渲染器但要注意材质效果和烘焙结果可能不同。在编写脚本前还需要在项目设置中开启插件。插件开启方式如下顶部菜单Project - Project Settings - Plugins找到 CSG Blockout 对应的条目并勾选 Enable。如果插件是通过 AssetLib 或手动 ZIP 拷贝到res://addons目录的编辑器会提示重新加载项目重启后插件才会生效。2.2 创建插件目录与基础场景结构手动安装插件时目录结构大概是res://addons/csg_blockout/ ├── plugin.cfg ├── editor/ │ ├── blockout_dock.gd │ └── blockout_dock.tscn ├── scripts/ │ └── csg_builder.gd └── icons/ └── blockout_icon.svgplugin.cfg是插件注册文件Godot 会读取它来启用插件脚本。打开后大致如下[plugin] nameCSG Blockout 3.0 descriptionCSG level blockout: draw, measure, playtest, freeze authorYourName version3.0 scripteditor/blockout_dock.gd实际插件版本以你下载到本地为准这里只是演示插件文件的基本结构。启用插件后编辑器右侧通常会出现一个 Blockout 面板用于快捷创建 CSG 节点、切换测量模式、执行烘焙命令。2.3 CSG 节点家族一览Godot 4 的 CSG 节点分成两类一类是实际生成形状的节点另一类是操作类型的节点。常用形状节点CSGBox3D立方体最常用来搭墙体。CSGSphere3D球体适合做柱子、圆形柱子。CSGCylinder3D圆柱适合做柱子、管道。CSGPolygon3D通过路径挤出多边形可以做异形墙体或斜面。CSGMesh3D把任意 Mesh 变为 CSG 形状可以参与布尔运算。常用操作节点CSGCombiner3D组合器把多个子 CSG 节点合并成一个整体。CSGShape3D所有 CSG 形状节点的基类提供布尔模式参数。CSGModifier3D修改器不影响几何体只影响组合器内的布尔结果。在实际关卡原型中我常以CSGCombiner3D作为父节点把多个CSGBox3D或CSGCylinder3D放进去再通过设置每个子节点的operation来决定是加还是减。这样生成的网格在视觉上是完整一体在节点树上又保留了修改能力。3. 画用原生 CSG 快速布置关卡原型3.1 拖出第一块 CSGBox在一个空 3D 场景里先从左侧节点面板创建一个CSGBox3D。把它放在原点附近这时候你会看到场景中出现一个带线框的立方体默认尺寸在三个轴各是 2 米。选中节点后检查器面板会有Size属性直接改 X、Y、Z 的数值就能把墙体拉长、把平台压扁。为了让墙面更好区分可以给 CSGBox3D 添加一个标准材质# 文件路径res://scripts/assign_material.gd extends Node3D export var box_path: NodePath func _ready() - void: var box : get_node_or_null(box_path) as CSGBox3D if box null: return var mat : StandardMaterial3D.new() mat.albedo_color Color(0.6, 0.75, 0.9) mat.metallic 0.0 mat.roughness 1.0 box.material mat这段脚本的作用是把一个默认材质赋给 CSG 方块。CSG 节点的material属性会作用于整个形状不需要在每个面上单独设置。白盒阶段用灰度或淡蓝色比较合适既能看清几何体又不会影响后续正式材质的判断。3.2 通过代码生成 CSG 模块如果关卡规模比较大手拖节点效率太低。这时候可以直接用 GDScript 生成 CSG 模块。比如我需要快速生成一面墙体墙的尺寸是长 8 米、高 3 米、厚 0.4 米并且自动放到指定位置# 文件路径res://scripts/csg_wall.gd extends Node3D func create_wall(length: float, height: float, thickness: float, position: Vector3) - CSGBox3D: var wall : CSGBox3D.new() wall.size Vector3(length, height, thickness) wall.position position wall.name Wall_%s_%s_%s % [length, height, thickness] return wall func _ready() - void: var wall : create_wall(8.0, 3.0, 0.4, Vector3(0, 1.5, 0)) add_child(wall)把这段脚本挂到场景根节点上运行场景后就能看到一面墙出现在原点前方。这种代码化生成方式特别适合做程序化房间、随机迷宫或者建筑模板你可以把这些方法封装进csg_builder.gd之后在不同的场景里复用。要注意一个细节CSG 节点只有在进入场景树后才会计算几何体所以在_ready()中创建后马上读取网格数据可能拿不到完整结果。如果需要立即使用烘焙或碰撞最好等一帧或者使用await get_tree().process_frame。3.3 布尔运算与合并节点关卡里常见的门洞和窗口可以用CSGCombiner3D配合布尔运算实现。先创建一个CSGCombiner3D作为父节点然后添加两个子节点一个CSGBox3D作为墙体主体operation 保持 Union也就是相加。一个CSGBox3D作为门洞“挖空体”operation 设置为 Subtraction。具体操作如下在场景树中右键创建CSGCombiner3D然后在其下创建子节点CSGBox3D。第一个 Box 的Size设置为墙体的长宽高第二个 Box 的Size设置为门洞尺寸并把它移动到门洞位置。最后把第二个 Box 的Operation改成Subtraction门洞就出现了。# 文件路径res://scripts/csg_door.gd extends CSGCombiner3D func _ready() - void: var wall : CSGBox3D.new() wall.size Vector3(6, 3, 0.3) wall.position Vector3.ZERO add_child(wall) var door : CSGBox3D.new() door.size Vector3(1.2, 2.2, 0.8) door.position Vector3(-1.5, 0, 0) door.operation CSGShape3D.OPERATION_SUBTRACTION add_child(door)布尔运算在编辑器里是实时的调整door.position时门洞位置会立刻更新。这个特性非常关键因为玩法测试时如果发现门洞太窄只需改一个数值就能重新试跑。使用布尔运算时有几点要注意CSGCombiner3D的子节点如果是普通CSGBox3D即使位置超出组合器包围盒只要属于该组合器节点树就会被纳入布尔计算。减法节点需要保证体积和墙体有实际交叠否则几何体不会发生变化。大量嵌套 CSG 组合器会让编辑器计算变慢建议一个区域只用一个组合器不要无限嵌套。4. 量在编辑器里精准把控尺寸与比例4.1 为什么测量很重要白盒原型的“量”不是简单看数字而是要建立一套可以校验的空间尺度体系。很多刚接触关卡制作的开发者会凭感觉把墙体高度设为 2.5走廊宽度设为 4结果跑起来才发现角色跳不上台阶、走廊宽得让人物显得很小。原因是 Godot 的 3D 场景中一个单位通常对应一米但这套换算关系只有在你知道角色实际高度时才有意义。在 Blockout 3.0 的工作流里测量通常包括三个维度单块几何体的尺寸长度、宽度、高度。几何体之间的相对距离门洞到地面高度、箱子之间的跳台间距。角色在空间中的通行尺寸能否蹲下穿过、能否跳跃越过。这些测量可以直接从检查器的 Transform 和 Size 属性读取但插件化工作流往往会把它们整合成可视化标注。例如在选中一个 CSGBox3D 时面板会显示它的体积和中心点坐标方便你快速判断体量。4.2 网格吸附与坐标对齐要量得准首先要让 CSG 节点“贴住”网格。Godot 顶部工具栏有吸附开关开启后移动节点时会按设定步长对齐。建议把步长设置为 0.5 或 1 米房间内部件尽量使用整数尺寸这样后续拼接不会出现 0.1 米级别的缝隙。还有一种常用做法是让 CSG 节点的尺寸对应网格间距。比如地面砖块尺寸设为 1x1x0.2墙体厚度设为 0.4。这样在俯视图中你能快速用坐标推算整关卡的尺寸。例如从 x0 到 x6 之间放了 6 块地砖那这段走廊的长度就是 6 米。对齐时要注意 CSGBox 的中心点位置。CSGBox3D 的position表示盒子的几何中心而不是底面中心。所以把墙体放到地面上时Y 坐标必须是“墙体高度的一半”而不是 0。如果你需要一座高 3 米的墙地面在 Y0那么墙体中心应该在 Y1.5。代码里做对齐时可以这样处理# 文件路径res://scripts/csg_align.gd extends Node3D func place_floor(center_x: float, center_z: float, width: float, depth: float) - void: var floor : CSGBox3D.new() floor.size Vector3(width, 0.2, depth) floor.position Vector3(center_x, 0.1, center_z) add_child(floor) func place_wall(center_x: float, center_z: float, length: float, height: float, thickness: float) - void: var wall : CSGBox3D.new() wall.size Vector3(length, height, thickness) wall.position Vector3(center_x, height / 2.0, center_z) add_child(wall)这里place_floor把砖块的 Y 中心设为 0.1因为砖块厚 0.2底面正好贴着地平面。place_wall则把 Y 中心设为墙高的一半。这种规则统一后整个场景的摆放逻辑会非常清晰。4.3 临时测量工具的脚本实现除了看尺寸还可以做一个简单脚本用来实时显示两个 CSG 节点之间的距离。这个工具在白盒阶段尤其有用例如要验证两个跳台之间是否满足角色跳跃距离。脚本思路# 文件路径res://scripts/measure_distance.gd extends Node3D export var from_node: Node3D export var to_node: Node3D func _process(_delta: float) - void: if from_node null or to_node null: return var diff: Vector3 to_node.global_position - from_node.global_position var horizontal : Vector2(diff.x, diff.z).length() print(水平距离: %.2f 米 % horizontal) print(垂直高差: %.2f 米 % diff.y)把这段脚本挂到场景里再把两个需要测量的 CSG 节点拖到导出属性中运行场景后控制台会实时输出距离。这比在编辑器里逐个读取 Transform 要直观很多。测量阶段还有一个容易被忽略的环节检查角色碰撞体的尺寸。很多堵墙、夹缝的碰撞问题根源不是 CSG 几何体画错了而是角色胶囊体半径或高度超出了通道尺寸。所以做“量”的时候最好在场景里放一个临时角色用它来校验墙缝宽度和门槛高度。5. 试玩在原型里跑起来验证玩法可行性5.1 快速搭建临时角色控制器白盒关卡画完后必须立刻进入试玩阶段。Godot 自带的CharacterBody3D加上碰撞体和摄像机可以快速组成一个第一人称角色。下面是一个最简版本# 文件路径res://scripts/player.gd extends CharacterBody3D export var move_speed: float 5.0 export var jump_speed: float 4.5 var gravity: float 9.8 func _physics_process(delta: float) - void: if not is_on_floor(): velocity.y - gravity * delta if Input.is_action_just_pressed(ui_accept): velocity.y jump_speed var input_dir : Input.get_vector(ui_left, ui_right, ui_up, ui_down) var direction : (transform.basis * Vector3(input_dir.x, 0, input_dir.y)) if direction: velocity.x direction.x * move_speed velocity.z direction.z * move_speed else: velocity.x move_toward(velocity.x, 0, move_speed) velocity.z move_toward(velocity.z, 0, move_speed) move_and_slide()这个脚本只依赖内置的 Input Map。你需要确保项目设置里已经定义了ui_left、ui_right、ui_up、ui_down和ui_accept这几个动作否则移动和跳跃不会生效。角色节点结构如下CharacterBody3D ├── CollisionShape3D胶囊体 ├── Camera3D └── Scriptplayer.gd胶囊体的高度建议设为 1.8半径 0.4这样比较接近常规成年人比例。CSG 关卡白盒应该保证大部分通道宽度大于 1.2 米高度大于 2.2 米否则试玩时会觉得压抑。5.2 让 CSG 正确产生碰撞CSG 节点默认会生成碰撞体吗答案是需要看节点属性和项目设置。在 Godot 4 中CSG 节点本身允许在检查器中设置collision_layer和collision_mask但要真正让角色与之碰撞通常需要烘焙成 StaticBody3D或者依靠编辑器里生成的 CSG 碰撞支持。如果你想让 CSG 直接参与物理碰撞可以在 CSG 节点的属性中开启Use Collision相关选项具体名称随着版本差异略有不同。我的建议是白盒阶段直接烘焙一个临时 StaticBody3D 出来角色测试时使用这个碰撞体。这样既保证碰撞稳定又不会因为布尔运算实时计算而拖慢物理帧率。在编辑器中可以选中 CSG 根节点然后使用CSGShape3D.bake_static_mesh()生成一个 StaticBody3D 临时碰撞体。脚本化的方式可以这样写# 文件路径res://scripts/freeze_csg.gd extends Node3D export var csg_root: CSGShape3D func _ready() - void: if csg_root null: return var static_body : csg_root.bake_static_mesh() static_body.name Baked_Collision add_child(static_body)这段代码会保留原 CSG 节点同时在场景中生成一个名为Baked_Collision的静态碰撞体。试玩阶段使用它等到最终冻结时再替换成正式 Mesh 和正式 StaticBody3D。5.3 试玩时要注意的细节角色在 CSG 表面移动时偶尔会出现多个 CSG 节点相交导致物理抖动的问题常见原因是柱子和墙壁明明是拼在一起的却没有合并组合器。试玩阶段如果发现角色卡在某条接缝处优先检查该区域是否有互相重叠但未合并的 CSGBox3D。另一种可能是一个 CSG 减法节点把地面挖出了断层从视觉上很难看出来但角色脚底会掉进洞。试玩阶段还要顺带验证跳台间距和出生点。最好在场景中放一个Marker3D作为玩家出生点通过脚本让角色在运行时移动到 Marker 位置。这样每次试玩都能从同一位置出发避免手动操作不一致。6. 冻结从 CSG 原型烘焙成正式网格6.1 为什么需要“冻结”CSG 节点虽然方便但不会一直留在最终游戏场景里。原因主要有几个CSG 的实时布尔运算比较消耗资源场景中节点多了编辑器会卡顿。CSG 节点在运行时仍需计算布尔结构如果包含大量减法运算渲染和物理性能都不理想。正式游戏通常需要更精确的 Mesh、LOD、碰撞简化、材质 UV这些 CSG 节点无法完全满足。所以白盒验证完成后要把 CSG 冻结成 ArrayMesh作为正式关卡资产的起点。这一步在 CSG Blockout 工作流里叫“冻结”。6.2 烘焙网格的脚本化流程Godot 的CSGShape3D提供了bake_mesh()方法可以返回一个 ArrayMesh。接下来只需要把它挂到MeshInstance3D上然后替换掉或隐藏原来的 CSG 节点。# 文件路径res://scripts/csg_to_mesh.gd extends Node3D export var csg_root: CSGShape3D export var keep_csg: bool false func freeze() - void: if csg_root null: return var mesh : csg_root.bake_mesh() var mesh_instance : MeshInstance3D.new() mesh_instance.name Baked_Mesh mesh_instance.mesh mesh mesh_instance.transform csg_root.global_transform get_parent().add_child(mesh_instance) mesh_instance.global_transform csg_root.global_transform if not keep_csg: csg_root.queue_free()运行这段脚本后原来的 CSG 节点会被移除场景中留下一个普通MeshInstance3D。需要特别注意的是如果 CSG 根节点内部包含多个子 CSG 节点bake_mesh()会把它们合并成一个完整的网格外部自然只看到一个 Mesh 实例。烘焙结果也可以导出到磁盘方便作为 reusable 资产# 文件路径res://scripts/export_mesh.gd extends Node3D export var csg_root: CSGShape3D export var export_path: String res://assets/baked_wall.tres func export_frozen_mesh() - void: if csg_root null: return var mesh : csg_root.bake_mesh() ResourceSaver.save(mesh, export_path) print(已导出到: , export_path)这里保存的是 ArrayMesh 资源也可以另存为.obj或.glb但建议优先保存.tres因为它能保留 Godot 引擎内部的 Mesh 信息和材质的关联关系。6.3 烘焙后的碰撞与性能优化冻结之后CSG 节点自带的实时几何体不再参与物理你需要单独为MeshInstance3D生成碰撞体。常见做法是添加一个StaticBody3D再挂上CollisionShape3D并把网格交给 Trimesh 碰撞形状。这一步可以手动在检查器里完成选中MeshInstance3D右键选择 “Create Trimesh Collision”Godot 会自动生成 StaticBody3D 和 CollisionShape3D。如果希望脚本化处理可以这样# 文件路径res://scripts/add_collision.gd extends Node3D export var mesh_instance: MeshInstance3D func setup_trimesh_collision() - void: if mesh_instance null: return var static_body : StaticBody3D.new() static_body.name StaticBody3D var collision : CollisionShape3D.new() var shape : ConcavePolygonShape3D.new() var arrays : mesh_instance.mesh.surface_get_arrays(0) if arrays.is_empty(): return var vertices : arrays[Mesh.ARRAY_VERTEX] var indices : arrays[Mesh.ARRAY_INDEX] shape.set_faces(indices) shape.data vertices collision.shape shape static_body.add_child(collision) mesh_instance.add_child(static_body)这段代码是思路演示Godot 引擎有些版本中 ConcavePolygonShape3D 的构造方式可能略有差异。建议优先使用编辑器右键自动生成碰撞因为这里面的索引和顶点处理已经由引擎封装好了脚本化只是为了批量处理。性能方面白盒阶段可以直接把 CSG 节点冻结成 Mesh然后合并多个 Mesh 为一个场景。这样可以减少 Draw Call还能让烘焙后的网格在运行时保持良好的帧率。但不要一股脑把整个地图都合并成一张 Mesh因为后续如果需要修改个别墙体合并后的 Mesh 很难再局部替换。7. 常见报错与排查思路在 CSG Blockout 3.0 的实际使用中以下问题出现频率最高。问题现象常见原因解决思路门洞没有显示出来减法节点的 Operation 设置成了 Union选中挖空体检查 Operation 属性是否为 Subtraction角色穿墙掉出场景CSG 未生成碰撞体为 CSG 根节点 bake static mesh或生成 Trimesh Collision编辑器卡顿严重场景中 CSG 节点过多且未合并减少组合器个数把已经冻结的 CSG 换成 Mesh烘焙后的 Mesh 材质丢失原 CSG 节点的 material 未正确赋值在冻结前确认每个形状节点的 material 都存在且有效CSG 减法出现奇怪接缝挖空体和主体边缘重合让减法节点轻微超出主体表面不要完全贴合插件面板按钮是灰色当前没有选中 CSG 根节点或组合器选中场景中的 CSGCombiner3D 或任意 CSGShape3D角色在接缝处抖动多个 CSG 节点重叠且没有合并把相邻区域并入同一个 CSGCombiner3D冻结后感觉碰撞比视觉大ConcavePolygonShape3D 生成精度问题删除旧的碰撞体重新生成并检查 CollisionShape3D 的尺寸排查 CSG 问题时有一个通用思路先确认节点是否属于正确的组合器再检查布尔模式和尺寸交叠最后看是否烘焙成功。很多人喜欢直接在运行场景里调试但 CSG 的很多几何计算只在编辑器预览中可见运行时的表现可能与编辑器不一致所以排查顺序应该是“编辑器里看到一切正常 → 烘焙 → 运行时再测试”。8. 关卡 Blockout 的最佳实践8.1 命名与目录规范白盒阶段虽然没有正式资产那么严格但命名规范越早建立后期交接越省事。推荐用以下规则CSG 根节点命名按区域划分例如Blockout_Arena、Blockout_Hub。子节点名称包含几何信息和尺寸特征例如Wall_8x3x0.4、Door_1.2x2.2。烘焙后的 Mesh 统一放在res://assets/blockout/目录下文件名带_baked后缀。试玩用的角色控制器单独放在scenes/tests/不要放进最终场景。这样的好处是你可以在后续制作正式关卡时直接引用这些名称不用再重新回忆“这块板子原来是干嘛的”。8.2 材质与层级约定白盒阶段不要让每个 CSG 节点都拥有独立材质。应该尽量使用一个公共的 Blockout 材质颜色可以区分功能区域例如地面浅灰色。墙体淡蓝色。可交互物体浅绿色。危险区域淡红色。这样做视觉上很清楚而且冻结成 Mesh 后多个 Mesh 可以共用同一个材质减少后续材质调整的复杂度。层级上也推荐保持简单。单个关卡区域不要超过两到三层 CSG 嵌套。CSG 组合器的嵌套层级越深编辑器重算的开销越大出现算子失效的概率也越高。8.3 从 Blockout 到正式资产的交接流程白盒完成后正式资产制作并不等于推翻重来。建议把冻结出的 Mesh 作为空间骨架美术同学在保留坐标和碰撞数据的基础上替换成最终模型。如果正式模型体积比白盒大那么关卡设计者应该回到 Blockout 阶段先调整盒子尺寸而不是直接让美术硬塞。交接时至少需要导出以下内容带坐标标注的 Blockout 场景文件。冻结好的 Mesh 资源或.glb文件。玩家通行路径和出生点位置。一份简单的尺寸清单包括最小通道、层高、障碍跳跃距离。碰撞体版本和渲染版本是否使用同一套 Mesh 的说明。这样交接后即使原关卡设计师暂时不在美术和程序也能按照尺寸清单完成正式关卡替换。9. 总结与后续学习路线顺着“画、量、试玩、冻结”这四个词你实际上已经掌握了一套完整的 CSG 关卡原型管线。画让 CSG 节点帮你快速堆出空间量让每个几何体在上帝视角下都对应清晰尺寸试玩让角色碰撞和移动手感反过来检验关卡冻结则让临时原型变成可交接的正式 Mesh 资产。接下来值得深入研究的方向有三个。第一个是 CSG 的布尔运算细节尤其是多个组合器之间的差集和交集这会影响你挖隧道、做窗户的熟练度。第二个是更多样的场景交互比如把冻结出的 Mesh 直接接入导航系统用来生成 NavMesh这样 AI 敌人也会在白盒地图里走动。第三个是程序化关卡生成把本文里的代码化 CSG 创建方式和随机地图算法结合起来。如果你正在做一个体量较大的 3D 游戏强烈建议在项目开工第一天就建立白盒关卡目录许下一个“全地图先用 CSG 跑通玩法再替换正式美术”的规矩。这个规矩虽然朴素但真的能帮你避开很多后期返工的大坑。动手建一个测试场景试着用 CSG 搭一个包含走廊、房间、门洞和平台的小地图然后跑一遍完整的 Blockout 流程你会发现四个阶段之间切换得非常自然。