
关卡原型还在手工拉方块试试这四步画、量、试玩、冻结如果你做过 3D 游戏关卡设计一定经历过这样的阶段策划需求刚出来玩法方向还没有确定美术资源还在制作中但你需要马上验证一个动线、一个跳台距离、一个掩体高度是否成立。传统方案是在编辑器里一个个拉 Box 节点调整尺寸后再复制几十个手动拼出墙壁、楼梯和平台。拼到一半发现走廊太窄只能全部删除重来。如果项目用的是 Godot还有一个更顺手的原生选择CSG 节点。配合专门为关卡原型设计的插件可以把“建模—摆放—测试—固化”整个流程压缩到分钟级。这篇文章要讲的就是 CSG Blockout 3.0。它不负责帮你烘焙高模也不替代美术流程而是把 Godot 自带的 CSG 能力包装成一套适合关卡白盒的工作流画网格、量距离、快速试玩、冻结成静态网格体。读完你应该能判断它适不适合你的项目以及怎么在项目里落地。1. 为什么用 CSG 做 Blockout先解决白盒关卡效率问题关卡设计的早期阶段通常被称为 Blockout、Graybox 或者白盒。它的目标不是好看而是用最廉价的几何体把“可玩性”验证出来这个房间多大才不空旷这个跳台之间的间距合不合理玩家从掩体 A 移动到掩体 B 需要多久在 Godot 里没有专门的白盒编辑器常见的做法有两类。第一类是直接用 MeshInstance3D 加 BoxMesh。麻烦的地方在于BoxMesh 不适合频繁编辑。改一次尺寸要重新创建资源堆叠多个方块后如果需要做房间内掏空很难用布尔运算组合它们。你只能在一块一块地摆放而且没有自动布尔功能。第二类是去外部建模软件比如 Blender 或 SketchUp画好再导入 Godot。这种方式做大规模场景很快但它脱离了 Godot 编辑器需要来回切换窗口而且“量距离”“试玩”这些环节还是在 Godot 里做导入导出链路会打断思路。CSG 节点是 Godot 自带的一套“程序化建模”方案。你可以把它理解成在编辑器里直接摆放“乐高积木”积木与积木之间用相加、相减、相交三种逻辑自动组合。这正好弥补了前两种方式的短板它不依赖外部软件创作过程始终留在 Godot 里。它可以实时修改基本形状和布尔运算适合快速迭代。它天然支持“一键加上碰撞”原型阶段不需要手动维护 CollisionShape3D。它可以用脚本批量生成和修改适合程序化搭建。但直接使用原生 CSG 做白盒也有几个不顺手的地方原生 CSG 节点没有测量工具没有快速进入试玩的按钮也不能一键把整套 CSG 场景“冻结”成共享网格。于是 CSG Blockout 这类插件就有了存在价值。我的一个基本判断CSG Blockout 3.0 这类工具降低的不是美术成本而是“关卡想法到可玩场景”之间的等待成本。它解决的是验证速度的问题而不是渲染效果的问题。2. 核心概念CSG 节点、Blockout 工作流和布尔运算2.1 CSG 到底是个什么东西CSG 全称 Constructive Solid Geometry中文一般叫“构造实体几何”。在 Godot 里它表现为一组 3D 节点CSGBox3D立方体CSGSphere3D球体CSGCylinder3D圆柱CSGPolygon3D拉伸多边形CSGMesh3D把任意 Mesh 作为 CSG 基础形状CSGCombiner3D组合多个 CSG 节点并可以在子节点上设置布尔操作CSGTorus3D圆环Godot 4 中可用这些节点可以互相嵌套。子节点通过operation属性决定它对父节点的影响可选项通常有Union并集合并两个形状。Subtraction差集从父形状中挖掉当前形状。Intersection交集只保留两个形状重叠的部分。在实际关卡设计里使用最多的是 Union 和 Subtraction。Union 用来把四面墙拼成房间Subtraction 用来在实体块中掏出门洞和窗口。2.2 Blockout 工作流是什么Blockout 这个词源自游戏行业的“灰盒Graybox”。核心思路是在投入美术资源之前先用灰色方块验证一套关卡数据。数据包括尺寸、距离、高度、覆盖范围、玩家路径。一个典型的 Blockout 工作流包含五个环节环节传统做法CSG Blockout 思路快速搭形手动缩放 Box画布绘制和参数化 CSG 摆放尺寸检查靠眼睛估或建模软件量编辑器内直接量动线试玩需要先加碰撞和玩家控制器CSG 自动碰撞一键进试玩固化导出需要重建网格和碰撞体冻结为 Mesh Collision后续替换结构要重做保留坐标和层级结构原地替换这套流程的意义在于从“想法”到“上手试玩”只隔几步而不是隔一次漫长的建模周期。2.3 为什么是“原生 CSG”“原生”这个词强调了一个事实CSG Blockout 3.0 并不是引入了新的网格系统或渲染管线。它建立在 Godot 自带的CSGShape3D、CSGBox3D等节点之上。这意味着几个好处只要你的 Godot 工程能正常使用 3D 节点插件就能用。没有引擎版本断裂风险因为 Godot 4 对 CSG 节点的支持已经进过一轮完整完善。你可以半途退出插件只留下原生的 CSG 节点工程不会被人为绑定到某个付费编辑器。这一点对团队协作尤其重要。用什么插件是个人偏好但导出到其他同事手里时插件必须尽量轻量。如果你以前没碰过 Godot CSG先不要把“CSG”想得太高大上。它就是一种节点化的布尔建模工具能被脚本控制也能被编辑器里的鼠标操作控制。3. CSG Blockout 3.0 核心能力画、量、试玩、冻结插件的功能从标题就能看出来画、量、试玩、冻结。下面把每个能力对应的关卡设计痛点拆开讲。3.1 画像绘图一样摆放基础体而不是手工拉坐标原生 CSG 节点虽然操作方便但在编辑器里摆放时仍然要先“添加节点 → 改大小 → 改位置”。画几个简单形状还好如果要画一面锯齿形墙壁或一段楼梯重复操作会非常耗时。Blockout 插件中的“画”功能一般会提供类似“绘制点 → 拉伸成墙/地面”的交互方式。你不需要关心每一个 CSG 节点的尺寸参数而是用鼠标在 3D 视口中确定范围工具自动生成对应的CSGBox3D或CSGPolygon3D。这一步解决的问题是把“搭积木”变成“画草图”。就像在建模软件里用画笔工具画线条而不是手动精确输入每个点坐标。进入具体场景比如你要快速在场景里拉出一段 20 米长、2 米高的墙体手动方式创建CSGBox3D把 size 设为 (20, 2, 0.2)再移动位置。Blockout 画法在视口里点两点工具生成长方体并自动把长边对准两点方向。看起来只是省了两次输入但累积到几十面墙时效率差异很明显。3.2 量测量距离和高差替代“肉眼估算法”关卡设计中最常见的需求是确认空间尺度。跳跃距离、射击距离、房间宽度都直接影响玩法。在原生 Godot 中没有内置的编辑器测量工具不少开发者只能靠经验估算或者临时创建一个长条碰撞体当尺子。CSG Blockout 3.0 通常会把“距离测量”做成编辑器内的小工具。选中两个点或两个节点工具显示它们之间的直线距离和垂直高度差。这个功能的价值被低估了。关卡动线调整中最耗时的不是“移动一个跳台”而是“从多个角度验证移动后的距离是否合理”。一个显示实时读数的测量框比你在心里默念“这个大概三米吧”可靠得多。如果你跑题时担心“我看到的距离和玩家实际跑的距离不一样”也可以反过来依赖测量结果判断试玩的时候记录体验感觉编辑器里测量得到精确数据两者对照后你对关卡尺度的敏感度会快速提高。3.3 试玩在原型场景里立刻变成玩家视角搭好白盒并不代表关卡成立。很多工作室在白盒阶段就能跑动、跳跃、观察视线遮挡说明他们把“试玩”提前到了非常靠前的位置。原生 CSG 节点本身支持use_collision属性。勾选后生成的形状自动拥有碰撞体。也就是说布局完成的一瞬间这个场景就已经具备物理可玩性缺的只是一个玩家控制器。CSG Blockout 的“试玩”模式一般是在编辑器里提供一个快捷入口按下某个快捷键就进入“玩家模式”临时挂载一个第一人称或第三人称控制器让你立刻在白盒里跑起来检查高度、宽度、动线是否合理。这里有个容易误解的点不是“试玩”这个功能帮你控制了碰撞而是 CSG 原生的碰撞属性帮你省掉了手动添加碰撞体的过程。插件的贡献是帮你把“测试”这个动作缩短到一次按键。3.4 冻结把 CSG 网格固化成可烘焙、可导出的 MeshCSG 节点有一个天然短板它在引擎内部是按“形状列表实时合成为网格”的方式工作的。每变换一次都会触发网格重建。如果场景里有上百个 CSG 节点编辑时的实时卡顿和生产环境的性能都不理想。“冻结”解决的就是这个问题。它的本质是把一组 CSG 形状合并后的结果转换为一个共享的ArrayMesh。用静态碰撞体StaticBody3D替代 CSG 自动碰撞可选。隐藏或移除原 CSG 节点只保留最终网格。冻结后编辑器的实时计算负担消失场景网格可以进入光照贴图烘焙流程也可以被导出或复制到其他场景中使用。更关键的是冻结不应该是终点而是白盒阶段结束的标志。一旦关卡动线通过验证你就该冻结场景后续再替换美术资源时网格数据仍然保持设计时的几何结构。流程图可以概括为画 → 量 → 试玩 → 调整 → 冻结 → 交给美术替换。这正好对应插件的四个按钮也对应白盒关卡的生命周期。我的建议是不要在关卡还处于草稿阶段就去冻结。冻结应该发生在“数据已经验证、之后不会再频繁改形状”的时候。否则每改一次形状就要重新冻结一次反而多了一道工序。4. 环境准备与插件安装4.1 引擎版本建议GitHub 插件版本号里出现“3.0”大概率对应 Godot 4.x 系列。Godot 3.x 的 CSG 系统虽然存在但编辑器整体架构和插件兼容方式差别太大不建议在新项目中使用旧版 CSG Blockout。如果你当前的项目还是 Godot 3建议先规划引擎升级再考虑这个工作流。如果你正在新建项目直接使用 Godot 4 即可。由于插件具体版本要求可能随更新变化这里不做死板版本承诺建议以项目实际使用的 Godot 版本为准。判断是否兼容的方法很简单进入插件仓库的README或 Release 页面查看它标注的引擎版本。4.2 安装方式一从 AssetLib 安装Godot 自带 AssetLib 插件商店。打开编辑器点击顶部“AssetLib”标签搜索“CSG Blockout”。搜索到插件后点击“Download”。Godot 会要求选择下载目录下载完成后点击“Install”选择要安装到的项目点击“Install Edit”。安装完成后插件文件会出现在项目的addons目录下。4.3 安装方式二从 GitHub 下载 ZIP如果你在 AssetLib 中搜不到最新版本可以到 GitHub 仓库的 Release 页面下载 ZIP 包。下载后解压把addons文件夹里的csg_blockout具体文件夹名以实际仓库为准复制到你项目的addons目录下。4.4 启用插件完成安装后在 Godot 顶部菜单选择“项目 → 项目设置”切换到“插件”选项卡找到 CSG Blockout把状态设置为“启用”。启用后编辑器界面右侧或顶部会出现对应的工具面板。不同版本的界面位置可能不同但核心逻辑一致你会在某个工具条上看到画、量、试玩、冻结这四组操作入口。如果插件加载失败先检查文件是否放进了正确的项目目录。是否在“项目设置 → 插件”里被启用。Godot 版本与插件要求是否匹配。5. 入门实操用 CSG 原生能力搭一段白盒走廊在深入插件用法之前先手动创建一次纯原生的 CSG 白盒这样你能理解插件在背后做了哪些自动化操作。目标搭出一段带门洞的走廊。5.1 创建 CSG 根节点打开任意 3D 场景先创建一个根节点。推荐用CSGCombiner3D作为根。CSGCombiner 不会自己产生几何体但可以统一管理子节点的布尔运算。5.2 添加地面和墙体在 CSGCombiner3D 节点下添加子节点CSGBox3D作为地面尺寸设为(10, 0.2, 4)。CSGBox3D作为一面侧墙尺寸设为(10, 3, 0.2)位置移动到y1.5, z-2。需要墙体时按住 Ctrl 或 Shift 在场景树中多选节点再复制可以快速铺出四块墙。5.3 用减法留出门洞现在我们已经有一面完整的墙但它没有门洞。门洞是用 Subtraction 操作实现的。在墙体节点下再加一个CSGBox3D把它的operation设置为Subtraction。调整它的位置和尺寸让它像一把“冲头”嵌在墙里布尔运算会自动把它掏空。在上帝视角观察时你可能觉得根本看不见这把“冲头”因为它是被减去的空形状。在场景树中选中它它才会高亮显示。实际操作中把减数和被减数放到同一个父节点下是必要的。子系列中的操作对象必须保持“兄弟关系”而且共享同一个 CSG 根布尔运算才会正确。5.4 自动碰撞在走廊搭建完成后为每个 CSG 节点勾选use_collision。也可以直接给根节点CSGCombiner3D勾选生成的碰撞体将覆盖子节点合并后的形状。从这一刻起你已经拥有了一个带物理碰撞的可跑动走廊全程没有借助外部建模软件。这就是“原生 CSG 做关卡原型”的最基础打开方式。5.5 代码方式快速生成走廊为了说明 CSG 的可脚本化能力这里提供一份最小脚本。把以下脚本挂到一个空 Node3D 节点上运行后会自动生成一条走廊# 文件路径scene/blockout_generator.gd extends Node3D export var corridor_length: float 10.0 export var corridor_width: float 4.0 export var corridor_height: float 3.0 func _ready() - void: _build_corridor() func _build_corridor() - void: var combiner : CSGCombiner3D.new() add_child(combiner) var floor_box : CSGBox3D.new() floor_box.size Vector3(corridor_length, 0.2, corridor_width) floor_box.position.y -corridor_height / 2.0 combiner.add_child(floor_box) var wall_1 : CSGBox3D.new() wall_1.size Vector3(corridor_length, corridor_height, 0.2) wall_1.position Vector3(0, 0, -corridor_width / 2.0) combiner.add_child(wall_1) var wall_2 : CSGBox3D.new() wall_2.size Vector3(corridor_length, corridor_height, 0.2) wall_2.position Vector3(0, 0, corridor_width / 2.0) combiner.add_child(wall_2) # 共享合并并启用碰撞 combiner.use_collision true脚本逻辑很简单创建 CSGCombiner3D添加三个 Box最后统一开启碰撞。多节点组成的形状会自动合并不需要额外写布尔逻辑。这个脚本演示的是原生 API。CSG Blockout 插件大概率在背后做的事情和这段代码类似只是把它变成了面板上的手动画布和拖拽操作。6. 进阶三个实用脚本让 Blockout 效率再翻一倍插件解决的是“编辑器内操作”的效率。但如果你的关卡需要批量处理或者是程序化生成级别还是需要通过脚本调用 CSG API 和网格 API。下面是三个比较实用的例子。6.1 生成一段带门洞的墙体第一个脚本演示如何用循环生成一面不断出现门洞的墙体。做法主体用 Union门洞用 Subtraction。# 文件路径scene/wall_with_doors.gd extends Node3D export var wall_length: float 20.0 export var wall_height: float 3.0 export var wall_depth: float 0.3 export var door_width: float 1.2 export var door_height: float 2.2 export var door_interval: float 4.0 func _ready() - void: var combiner : CSGCombiner3D.new() add_child(combiner) var solid : CSGBox3D.new() solid.size Vector3(wall_length, wall_height, wall_depth) combiner.add_child(solid) var x : -wall_length / 2.0 door_interval / 2.0 while x wall_length / 2.0: var door : CSGBox3D.new() door.size Vector3(door_width, door_height, wall_depth * 1.2) door.position Vector3(x, door_height / 2.0 - wall_height / 2.0, 0) door.operation CSGShape3D.OPERATION_SUBTRACTION combiner.add_child(door) x door_interval combiner.use_collision true注意减法体要略大于墙体厚度wall_depth * 1.2防止浮点精度造成残留面片。这个细节在手工建模时也会遇到。6.2 一键冻结把 CSG 场景转换为静态网格冻结的核心是把一个 CSG 形状的合并结果转换为Mesh再塞进一个MeshInstance3D。Godot 的 CSG 节点提供get_meshes()方法返回一个包含材质和网格数据的数组。下面脚本演示一个最小冻结工具。它会把指定 CSG 节点变成静态网格体并保留碰撞# 文件路径tools/freeze_csg.gd tool extends EditorScript # 在编辑器中运行把当前选中的 CSG 节点冻结为 Mesh StaticBody func _run() - void: var selection : EditorInterface.get_selection().get_selected_nodes() if selection.is_empty(): print(请先在场景树中选择一个 CSG 节点) return var csg : selection[0] if not csg is CSGShape3D: print(选中的节点不是 CSG 节点) return freeze(csg) func freeze(csg: CSGShape3D) - void: var meshes : csg.get_meshes() # meshes 的格式为 [材质数组, ArrayMesh]不同版本细节略有差异 if meshes.is_empty(): return var mesh: ArrayMesh meshes[1] var parent : csg.get_parent() if parent null: return var mesh_instance : MeshInstance3D.new() mesh_instance.name csg.name _frozen mesh_instance.mesh mesh parent.add_child(mesh_instance) mesh_instance.global_transform csg.global_transform var static_body : StaticBody3D.new() static_body.name csg.name _static static_body.add_child(mesh_instance) parent.add_child(static_body) static_body.global_transform csg.global_transform # 为静态网格生成碰撞体 mesh_instance.create_trimesh_collision() # 关闭 CSG 节点避免双倍碰撞和持续计算 csg.visible false csg.process_mode Node.PROCESS_MODE_DISABLED print(%s 已冻结为 %s % [csg.name, mesh_instance.name])这段代码的重点是get_meshes()和create_trimesh_collision()。前者把程序化生成的几何体“拍平”成普通网格后者给静态网格附加一个 Trimesh 碰撞。冻结完成后你看到的是网格而不是 CSG 实时合成结果场景负担明显降低。注意不同 Godot 小版本中get_meshes()的返回值结构可能略有差异。使用前最好打印一次meshes数组确认索引对应的元素是什么。插件本身如果提供了“冻结”按钮内部逻辑大约就是这个流程。6.3 在编辑器中测量两点距离原生 Godot 编辑器没有测量工具但我们可以写一个简短的tool脚本读取两个选中节点的位置输出距离和高差# 文件路径tools/measure_nodes.gd tool extends EditorScript func _run() - void: var nodes : EditorInterface.get_selection().get_selected_nodes() if nodes.size() ! 2: print(请同时选中两个节点) return var a: Node3D nodes[0] if nodes[0] is Node3D else null var b: Node3D nodes[1] if nodes[1] is Node3D else null if a null or b null: print(选中的节点不是 Node3D) return var delta : b.global_position - a.global_position var horizontal : Vector2(delta.x, delta.z).length() var distance : delta.length() var height : delta.y print(水平距离: %.2f % horizontal) print(垂直高差: %.2f % height) print(直线距离: %.2f % distance)这个脚本可以作为你理解“量”这一功能的最小实现。插件如果做得更完善会直接在 3D 视口里画一条带刻度的线而不是打印日志。6.4 试玩的实现思路“试玩”本质上是“监测到一个快捷键 → 在玩家位置生成 CharacterBody3D → 接管相机”。如果你需要在非插件环境自己实现可以用编辑器快捷方式挂载一个带玩家控制脚本的场景实例。这里不贴完整玩家控制器但给出关键思路# 文件路径tools/spawn_player.gd tool extends EditorScript func _run() - void: var player : load(res://scenes/player.tscn).instantiate() get_scene().add_child(player) get_scene().get_viewport().get_camera_3d().current false # player 内部会持有并激活自己的相机换成 CSG Blockout 插件的场景你按下试玩快捷键它会在当前视角位置生成一个临时的 Player 节点你可以在白盒里走、跳、蹲。退出试玩后Player 节点被销毁场景回到编辑状态。7. 运行结果与效果验证把手动搭建和脚本生成两条路线跑通后如何确认“没做错”7.1 验证场景树结构在运行场景前先检查场景树根节点应该是CSGCombiner3D或其他 CSG 节点。所有参与合并的 CSG 节点必须挂在同一个 CSG 根下。Subtraction 节点有正确的operation属性。根节点的use_collision为打开状态。如果门洞没有正确掏空通常是因为减法节点和主体没有共享同一个父级或者节点顺序不正确。CSG 的组合计算是父子嵌套递归的Structure 层级不对时结果会不完整。7.2 验证试玩功能运行场景按 F5 或点击运行按钮。移动时能和墙壁发生碰撞说明碰撞体生成成功。如果可以直接穿过地面和墙基本可以确定use_collision没有打开或者碰撞层的Layer与玩家碰撞层不匹配。假如你发现 CSG 墙和玩家之间会有“抖动”先看是不是玩家物体嵌入了墙体。CSG 会在重叠位置产生内部面表现形式就是穿模。解决方法是不要让减法体恰恰好贴在表面略微超过厚度。7.3 验证冻结结果冻结后场景树里应该出现一个新的 MeshInstance3D或 StaticBody3D原 CSG 节点处于隐藏或禁用状态。运行冻结后的场景确认玩家无法穿过网格。网格的材质和 CSG 原型阶段的材质一致或已经被你替换成占位材质。场景的运行时帧率不再受 CSG 节点数量影响。如果你发现冻结后纹理拉伸或 UV 异常不要惊讶。get_meshes()生成的网格 UV 来自 CSG 内部自动展开通常不会适配你额外指定的复杂材质。白盒阶段大多数使用的是纯色材质所以问题不明显。若在冻结后要做美术替换推荐重新从外部导入模型而不是继续依赖这个自动网格。8. 常见问题与排查思路问题现象可能原因排查方式解决方案CSG 节点没有生成形状CSG 没有任何子节点时某些 CSG 自身可以显示但部分节点需要子节点才能产生结果查看场景树是否为空添加 Box 或 Sphere 子节点布尔运算没有效果减法/交集节点没有正确定位或没有挂在同一个 CSG 根节点下在 3D 视口中高亮被减节点调整 operation 和父子关系运行后玩家直接穿过墙体use_collision未开启或碰撞层设置不对检查 CSG 属性和玩家碰撞层在 CSG 根上开启use_collision核对碰撞层CSG 节点数量多时编辑器卡顿CSG 每次编辑都会重新合成网格打开性能分析查看 Mesh 生成耗时早点进入冻结阶段减少 CSG 常驻节点数量门洞边缘出现闪烁/重叠面减法体与被减体表面重合CSG 生成过程中保留了两层面片放大减法体尺寸把减法体深度设置为主体厚度的 1.1~1.2 倍冻结后没有碰撞冻结脚本没有生成 Trimesh 碰撞检查 MeshInstance3D 的碰撞子节点是否出现调用create_trimesh_collision()或手动添加 CollisionShape3D冻结后材质显示异常CSG 的材质索引和数组网格不匹配打印 mesh surface 数量手动为冻结后网格指定材质或用 SurfaceTool 重算 UV插件按钮不生效插件版本与 Godot 版本不匹配查看编辑器输出日志有无报错更新版本或改用旧版插件真实项目里最容易踩的坑有两个一是 CSG 场景一直不冻结节点数量累积到几十上百编辑器实时卡顿运行帧率断崖二是忘掉碰撞层导致试玩时“踩空”。前者属于流程节奏问题解决办法是约定“冻结节点”。后者属于基础配置问题建议在项目初始化时统一规划碰撞层位。9. 最佳实践与工程建议9.1 明确 Blockout 生命周期在团队中引入 CSG 工作流时要先定义清楚“什么时候算 Blockout 完成”玩法验证通过。动线、视野、跳跃距离数据记录完毕。关卡拓扑结构不再频繁变化。美术替换已经开始。满足这些条件后立刻执行冻结把 CSG 节点转换为静态网格。后续美术修改应该在冻结后的网格上替换资源而不是继续拖 CSG 节点。9.2 场景层级与命名规范CSG 场景的节点层级会影响布尔运算和后续替换效率。推荐按“区域 → 结构 → 细节”组织层级节点类型命名示例根CSGCombiner3DLevel_01_Blockout结构CSGCombiner3DRoom_A_Walls细节CSGBox3DWall_North_Segment_01减法CSGBox3DDoor_North_01命名清晰的好处是冻结脚本可以通过节点名批量识别“哪些是减法体、哪些是主体”后续程序化替换也更安全。9.3 不要用 CSG 做最终场景CSG 适合原型不适合最终交付。原因有几个CSG 网格在运行时是实时生成的性能低于预烘焙网格。CSG 的 UV 展开不适合复杂材质表现。CSG 节点数量多时编辑器和游戏进程都承受不必要的计算。CSG 的物理碰撞是网格碰撞复杂碰撞检测比凸包碰撞要昂贵。正确做法是用 CSG 做原型冻结后交给美术制作最终资源然后用最终资源替换冻结后的占位网格。9.4 碰撞层与分组管理在白盒阶段就建立碰撞层规范1Player2World3Interactable4EnemyCSG 根节点统一设置到 World 层玩家 CharacterBody3D 设置到 Player 层碰撞掩码只包含 World。这样可以避免试玩时被原型的临时几何体干扰。如果你的项目已经有层位规范以项目现有规范为准。9.5 版本管理与回滚CSG 场景文件本质上也是场景树和普通场景一样会被版本控制系统追踪。但 CSG 节点数量多时tscn文件内容会膨胀合并冲突会变麻烦。建议一个关卡的白盒单独放在一个场景文件里不要全部塞进 Main 场景。冻结前的版本和冻结后的版本用不同文件名比如level_01_blockout.tscn和level_01_frozen.tscn方便回滚。如果团队有版本管理操作 CSG 频繁改动的当天晚上及时提交避免冲突时回溯困难。9.6 注意 CSG 的性能上限在 Godot 4 中CSG 的实时代码合成性能比 Godot 3 有明显改善但它仍然不是用来搭建超大型开放世界的方案。判断性能是否达标的参考如果编辑器在选中 CSG 节点后出现明显卡顿或者运行帧率在低端机器上低于目标帧数说明节点规模已经过大需要冻结或拆分场景。这里没有给出具体节点数量因为性能瓶颈和几何复杂度、物理碰撞数量都有关系。更稳妥的判断方式是定期在目标配置的机器上做一次运行测试以实际帧率和编辑器操作流畅度为准。9.7 用 CSG 做“平面图推演”很多关卡概念阶段只需要俯视平面图判断走廊和房间的拓扑关系。用 CSG 加TopView相机可以快速切换视角是一种灵活的空间推演方法。项目里如果经常要做这种平面推演可以在场景中保留一个固定的 Camera3D 节点预设顶部视角位置。试玩时切换到第一人称视角推演时切换回顶部视角。这种相机切换也应该被收纳进 GDScript 工具中避免反复手动调整相机位置。10. 总结与后续学习方向CSG Blockout 3.0 的价值严格来说不是“新功能”而是把 Godot 已经具备的 CSG 能力组合成了一套符合关卡原型设计习惯的工作流。画、量、试玩、冻结四个动作恰好覆盖白盒关卡从搭建到固化的完整周期。如果你本身一直在用原生 CSG 做原型这个插件只是帮你省去了重复流程如果你还没试过 CSG那就更值得从原生 CSG 入手先在引擎里手动搭一次走廊再引入插件。两种方式并不冲突反而能加深理解。下一步的学习方向可以分三条路线深入 CSG 节点Godot 官方文档提供了完善的 CSG 节点说明重点理解operation和use_collision的属性细节。程序化关卡生成用脚本批量创建 CSG 形状探索随机迷宫、房间序列等生成式玩法。团队工作流管理把“冻结”脚本集成到一键构建流程中让策划、程序、美术在同一个版本控制流程中并行工作。最后给一个项目提醒CSG 是原型利器但一定要提前规划“退出机制”——也就是冻结。一个关卡如果始终停留在 CSG 阶段性能问题和美术替换问题会在项目后期集中爆发。如果你团队里的关卡设计还没有固定流程建议从下一个小原型开始加入“冻结”这道工序。收藏好这篇打开 Godot先用原生 CSG 搭一段走廊再试试 Blockout 插件的四个按钮。跑通一次完整流程后你就知道以后做关卡原型该走哪条路了。