尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

Godot原生CSG关卡白盒工作流:画量试玩冻结四步法

Godot原生CSG关卡白盒工作流:画量试玩冻结四步法 翻开一篇 Godot 关卡原型的教程你会发现很多团队绕开建模软件直接在白盒阶段疯狂复制 CSG 节点。原因很简单在一个游戏想法还没定型的时候让美术去出高模很不划算。这个阶段你需要的是“画得快、量得准、跑得起”的灰盒工具而 Godot 原生 CSG 正好能承担这部分工作。本文围绕 CSG Blockout 这类插件化工作流展开讲透原生 CSG 的画、量、试玩、冻结四步流程适合刚接触 Godot 3D 关卡设计、又想快速验证玩法的开发者。Blockout 这个词源于建筑设计行业指用简单几何体先搭出体量关系游戏行业里通常叫灰盒或白盒。它的目标不是好看而是快速验证空间尺度、动线节奏和关卡结构。Godot 的 CSGConstructive Solid Geometry构造实体几何允许你在编辑器里直接用盒子、圆柱、多边形挤出生效的几何体并通过并集、差集、交集做布尔运算非常适合 Blockout 阶段。1. Blockout 到底在解决什么问题1.1 关卡原型的痛点很多新手做 3D 关卡时第一反应是打开 Blender 建模再导回 Godot。这个流程在初期有几个明显问题一是模型迭代慢墙壁改一个尺寸可能要重新导出二是场景资产混乱工程里堆满final_v7.blend、final_v8.obj三是玩法验证滞后你只有把整个关卡搭完才能跑起来测试。Blockout 阶段需要的几何体并不精细但需要“可交互”“可测量”“可反复修改”。CSG 的优势在于它不是离线建模产物而是场景树里的节点你可以直接拖拽、改参数、加布尔运算所有修改实时反馈在 3D 视口中。Godot 原生 CSG 把这套能力直接内置在引擎里不用额外插件也能用。CSG Blockout 3.0 这类工具的核心就是把这种“随手改几何体”的能力整理成一套可执行的工作流。它大致分成四个动作画、量、试玩、冻结。后续章节会按这个顺序展开你可以把它理解成关卡原型阶段的操作规范而不是某个不可替代的神秘工具。1.2 CSG Blockout 3.0 的四步工作流“画”指的是在场景中快速放置 CSG 基元通过组合器和布尔运算搭出墙壁、地面、门洞、窗洞、楼梯、斜坡等粗结构。这一阶段不需要雕刻细节只关心空间围合与动线雏形。“量”是对灰盒空间进行数值校验门高是否足够、走廊宽度是否合理、跳跃平台之间的距离是多少、整层楼的实际占地范围有多大。测量如果不能快速完成后续玩法数值就会建立在猜的基础上。“试玩”是进入游戏视图的实测阶段给场景加上临时角色控制器、碰撞体和导航网格跑一跑路径看跳跃是否卡头、转角是否过窄、敌人巡逻路线是否被墙阻挡。试玩阶段能暴露大量纸面上看不出来的问题。“冻结”是收尾阶段把已经确定的 CSG 几何体转成静态网格或锁定节点防止后续误改同时提升场景性能。冻结不代表不能改而是明确“这个区域进入只读状态”。1.3 为什么选择 Godot 原生 CSG外部建模软件生成的模型是“死”资源改尺寸要回到软件里重新调整。Godot 的原生 CSG 则是“活”几何体它就是场景中的普通节点支持脚本修改、动态创建、运行时变化。对 Blockout 而言这意味着你可以用代码生成关卡也可以用编辑器手动拼装还可以在测试时临时加挡板或减半个门洞。另一个优势是资产轻量。一个 CSG 节点的本质只是参数加变换信息而不是成百上千的顶点数据。场景文件体积很小版本管理也容易。团队协作时其他人打开工程看到的不是一堆难懂的资源文件而是能直接拖拽的几何节点。原生 CSG 也有自己的边界这一点在第五节会细说。简单来说它适合原型、灰盒、临时遮挡与程序化初稿不适合直接作为最终高精度资产。理解这个边界你才能在工作中用好它。2. 环境准备与项目初始化2.1 版本选择与下载Godot 的 CSG 模块从 3.x 就开始提供在 4.x 中 API 与节点名做了调整。本文以 Godot 4.x 为例3.x 的节点名不同但工作流思路基本一致。建议你使用 4.x 当前稳定版尽量避免用尚未发布的 alpha 或 beta 版本因为 CSG 相关的属性可能会随版本微调。下载后第一次打开编辑器如果你的显卡驱动或网络环境不稳定可能会遇到启动异常。这类问题多半跟 GPU 驱动、编辑器缓存或缺失的系统库有关优先检查显卡驱动是否更新其次是清理godot/editor_data缓存目录。如果你下载的 zip 包在解压阶段损坏也会出现打不开的情况最简单的做法是重新下载并校验压缩包完整性。版本确定后本文示例项目不需要任何额外插件。避免在前期引入太多外部依赖能帮你更清楚地区分“Godot 原生能力”和“第三方插件能力”之间的边界。2.2 创建测试项目打开 Godot新建一个空项目渲染器建议保留默认的 Forward。如果你的电脑配置偏低可在项目设置中调整到兼容模式但要注意 CSG 节点本身受模型复杂度影响较大渲染器对 CSG 编辑体验的影响反而不明显。在项目根目录下建议提前规划好目录结构res:// ├── main.tscn # 主场景 ├── scripts/ │ ├── csg_builder.gd # 代码生成 CSG 关卡 │ ├── measure.gd # 简单 AABB 测量工具 │ ├── player.gd # 临时角色控制器 │ └── bake_csg.gd # CSG 转网格的工具脚本 └── exported/ # 导出的网格与最终资产然后创建一个空场景main.tscn根节点选择Node3D命名为Main。主场景是用于验证整个 Blockout 流程的场景后续所有操作都在这里完成。2.3 输入映射配置试玩阶段需要一个角色在场景里移动跳跃所以先在 Input Map 里配置几个动作move_forward、move_back、move_left、move_right、jump。在 4.x 中可以分别绑定 W/S/A/D、方向键和空格键。如果你不想手动点击面板也可以直接编辑project.godot。以下片段只展示输入动作的命名结构事件绑定因编辑器版本会有序列化差异建议以编辑器面板为准[input] move_forward{ deadzone: 0.5, events: [] } move_back{ deadzone: 0.5, events: [] } move_left{ deadzone: 0.5, events: [] } move_right{ deadzone: 0.5, events: [] } jump{ deadzone: 0.5, events: [] }事件数组留空表示尚未分配具体按键。后续在 Input Map 图形界面里为这些动作绑定按键即可。输入映射是试玩阶段的基础设施没有它角色控制器无法感知操作。3. Godot CSG 核心知识与节点规则3.1 CSG 节点家族Godot 4.x 提供了一批以CSG开头的 3D 节点常用的包括CSGBox3D最基本的长方体。CSGSphere3D、CSGCylinder3D、CSGCone3D、CSGTorus3D基础几何体。CSGPolygon3D二维多边形沿方向或曲线挤出适合做墙体轮廓、楼梯侧板、斜坡。CSGMesh3D把任意网格当作 CSG 几何体参与运算这一步可以把外部模型引入布尔体系。CSGCombiner3D组合容器用于管理多个 CSG 子节点的并集、差集、交集。每个 CSG 节点都是一次几何运算的结果。CSGBox3D本身可以独立使用但在复杂结构里通常作为CSGCombiner3D的子节点。组合器的作用不只是分组它会决定子节点之间的布尔关系。如果你需要快速搭建一整面带窗洞的墙最合理的做法是组合器下先放一个墙体盒体再放若干小的盒体作为“减法体”。组件顺序决定了哪些是加法、哪些是减法。这一规则在下一小节展开。3.2 布尔运算顺序与节点层级CSG 组合器内节点的运算逻辑并不像 Blender 修改器那样有显式操作列表。它遵循的是子节点顺序和类型推导组合器内多个子节点会依次叠加而相邻子节点之间根据“是否被标记为减法体”来执行差集。在编辑器里你可以在CSGShape3D节点上找到操作类型相关属性。把某个子节点设置为差集后它就会从前面的主体中挖去体积。常见问题是减法体没有放在主体之后或者多个减法体互相重叠导致表面出现异常镂空。因此搭建结构时应该把减法体统一放在组合器子节点列表的末尾。以门洞为例正确的节点顺序是CSGCombiner3D ├── 墙体外侧盒体 # 加法 ├── 墙体内侧盒体 # 加法 ├── 门洞减法体 # 差集 └── 窗洞减法体 # 差集这里还有一个容易踩坑的点CSG 的布尔运算依赖封闭几何体如果某个盒体尺寸太小、旋转角度刚好与另一个面共面就容易出现 z-fighting表面闪烁或布尔退化。所以减法体的尺寸不只要“数学上够”还要留出少量余量。比如门洞深度比墙体厚度多 0.05 米能有效减少共面闪烁。3.3 材质、碰撞与运行时行为CSG 节点可以直接在属性面板里设置material。Blockout 阶段通常建议每个功能区域用不同颜色标识例如墙体用灰色、地面用蓝色、危险区域用红色、可跳跃平台用绿色。这样在试玩视角里能快速辨认也能给美术提供明确的区域语义。碰撞方面每个 CSG 节点都有use_collision属性。启用后引擎会为该节点生成 StaticBody 类型的碰撞体。运行时角色踩到地面、撞到墙壁靠的都是这些自动生成的碰撞体。简单关卡可以直接启用复杂关卡要谨慎CSG 布尔运算生成的碰撞网格有时会有缝隙或重叠角色高速移动时可能出现穿墙抖动。材质和碰撞只是 CSG 节点的两种数据输出。需要额外留意的是CSG 节点本身是几何生成器不是最终渲染网格。它在运行时会继续保留参数而且会在每次节点变换变化时重新计算几何体。这意味着你如果频繁移动一个大型 CSG 场景编辑器会卡顿如果在运行时动态改变参数也可能出现性能尖峰。3.4 CSG 的边界适合原型不适合最终资产CSG 的优势是参数化和实时可控它适合的是数据量不大、结构清晰的原型几何。一旦场景里塞满成千上万个 CSG 节点或者单个组合器内做了大量布尔嵌套实时计算会明显变慢文件打开和编辑器响应也会变差。另一个问题是 CSG 生成的多边形拓扑往往不是最优的。布尔运算容易产生较多三角形网格质量远不如手动建模优化过的资产直接拿 CSG 做光照烘焙或高精度碰撞也不算合适。因此行业里的常见做法是原型期放心用 CSG定稿后转换为静态网格再进入后续制作。理解这个边界你就能理解“冻结”这个动作的必要性。冻结不是 CSG 的缺点而是一个严谨工作流的必然收尾。4. 实战用 CSG 搭建一个可试玩关卡4.1 目标关卡设计这一节的例子不做花哨地形只搭一个“三室一走廊”的简单探路场景一个 10×10 的主房间一扇 1.4m 宽的门洞一条 2m 宽的走廊走廊尽头有一个可跳上或绕过的低台。这个结构能覆盖墙体围合、门洞布尔、走廊挤出和平台碰撞四类常见需求。为了让你看清 CSG 的编程式用法我会用 GDScript 生成场景而不是手动在编辑器里拖节点。编程方式的好处是参数一目了然也方便批量调整墙体尺寸。你可以在main.tscn中挂一个脚本节点运行它也可以在编辑器场景树里直接创建Node3D挂脚本。关卡的坐标约定如下地面中心在原点附近墙高 3m墙体厚度 0.2m。主房间尺寸 10m×10m走廊从主房间东侧墙延伸出去。4.2 用代码搭房间框架新建脚本res://scripts/csg_builder.gd内容如下# res://scripts/csg_builder.gd tool extends Node3D export var build_room : false: set(value): _build_room() func _ready() - void: if Engine.is_editor_hint(): call_deferred(_build_room) func _build_room() - void: # 清理旧节点避免重复构建 for child in get_children(): child.queue_free() var combiner : CSGCombiner3D.new() combiner.name MainRoom combiner.use_collision true combiner.collision_layer 1 combiner.collision_mask 1 # 地板 var floor : CSGBox3D.new() floor.name Floor floor.size Vector3(10, 0.2, 10) floor.position Vector3(0, -0.1, 0) combiner.add_child(floor) # 四面墙体 var wall_defs [ {name: NorthWall, size: Vector3(10, 3, 0.2), pos: Vector3(0, 1.5, -5)}, {name: SouthWall, size: Vector3(10, 3, 0.2), pos: Vector3(0, 1.5, 5)}, {name: EastWall, size: Vector3(0.2, 3, 8), pos: Vector3(5, 1.5, 0)}, {name: WestWall, size: Vector3(0.2, 3, 10), pos: Vector3(-5, 1.5, 0)}, ] for def in wall_defs: var wall : CSGBox3D.new() wall.name def[name] wall.size def[size] wall.position def[pos] combiner.add_child(wall) # 门洞减法体 var door : CSGBox3D.new() door.name DoorVoid door.size Vector3(1.4, 2.1, 0.4) door.position Vector3(3.0, 1.05, 5.0) combiner.add_child(door) add_child(combiner)build_room这个导出变量看似没有业务逻辑实际作用是让编辑器检查器上出现一个布尔开关。你把它设为true后脚本会执行一次_build_room()生成完整的 CSG 房间。由于脚本是tool这个构建过程能在编辑器模式下直接运行不需要按播放按钮。代码里有两点需要说明。第一combiner.use_collision true是把整个组合器的碰撞统一打开子节点会自动生成碰撞体。第二减法体DoorVoid放在所有加法体之后并且深度设为 0.4m比墙体厚度 0.2m 略大这是为了避免共面布尔问题。4.3 切出门洞与窗户如果要在南墙上切出一个门洞上一节的脚本已经演示了思路墙体是加法体门洞盒体是减法体。窗户的做法完全一致只需要把减法体尺寸和位置改一下。比如在南墙 1.0m 高度开一个 1.2m×1.0m 的窗洞var window_void : CSGBox3D.new() window_void.name WindowVoid window_void.size Vector3(1.2, 1.0, 0.4) window_void.position Vector3(-2.0, 1.5, 5.0) combiner.add_child(window_void)这一行代码让窗洞从墙里被挖掉。你不需要关心布尔运算的内部实现只要记住两个原则减法体必须在加法体之后减法体的尺寸要在被减方向上留余量。如果你在编辑器中手动操作不要把这些减法体节点拖到加法体之前否则减法体可能不会生效。出现“门洞没挖出来”“窗洞只显示一半”这类问题时第一个检查点就是子节点顺序。4.4 用 CSGPolygon3D 做走廊和斜坡盒子只能做方方正正的结构。走廊如果只想做一笔画出来的轮廓用CSGBox3D一块块拼会很繁琐。CSGPolygon3D可以让你画一条二维路径然后沿路径挤出三维几何。走廊示例在主房间东侧延伸一条长 6m、宽 2m、高 3m 的走廊。走廊的截面是一段 U 型轮廓你可以用 Polygon2D 的思路理解先画出轮廓的二维顶点再设置深度和方向。var corridor : CSGPolygon3D.new() corridor.name Corridor corridor.polygon PackedVector2Array([ Vector2(-1, 0), Vector2(1, 0), Vector2(1, 3), Vector2(-1, 3), ]) corridor.depth 6.0 corridor.position Vector3(5, 0, -3) combiner.add_child(corridor)注意polygon使用的是局部二维坐标depth决定挤出的长度position负责把挤出的几何体放到正确位置。这个走廊本质上是一段“先画截面再拉深”的几何体。如果需要斜坡可以把polygon改成侧面轮廓例如一个直角三角形深度代表路面宽度。CSGPolygon3D在灰盒阶段非常实用因为它比单个盒体更能表达“走线”逻辑。你可以在纸上画好关卡的俯视轮廓然后用CSGPolygon3D把轮廓直接转成墙体天然贴合 Blockout 的“画”这个动作。4.5 测量与校验尺寸“量”这个动作在 Godot 编辑器里看上去很简单点选节点看属性面板里的尺寸。但是关卡里有多个组合器嵌套时单个节点的尺寸并不等于实际整体尺寸。你需要一个工具来测量“整段墙体的 AABB 包围盒”。下面是一个简单的测量脚本它会遍历目标节点下的所有GeometryInstance3D算出整体包围盒并打印长宽高# res://scripts/measure.gd tool extends Node3D export var target: NodePath export var refresh : false: set(value): _measure() func _enter_tree() - void: if Engine.is_editor_hint(): call_deferred(_measure) func _measure() - void: var node : get_node_or_null(target) as Node3D if node null: return var aabb : _calc_aabb(node) print([Measure] size %.3f x %.3f x %.3f % [aabb.size.x, aabb.size.y, aabb.size.z]) print([Measure] origin %s % aabb.position) func _calc_aabb(node: Node3D) - AABB: var result : AABB() var first : true for child in node.find_children(*, GeometryInstance3D, true, false): var gi : child as GeometryInstance3D if gi null: continue var box : gi.get_aabb() var world_box : gi.global_transform * box if first: result world_box first false else: result result.merge(world_box) return result在场景中放一个Node3D挂上这个脚本把target指定为MainRoom然后在检查器里打开refresh编辑器控制台会输出整体尺寸。这比逐个点选节点更可靠尤其适合核验“门洞上方保留的墙体高度”“走廊是否超过了地块边界”这类问题。测量不只是在编辑器里看数字。你应该提前为关卡定好设计规范例如门洞最低 2.1m、走廊最窄 1.8m、跳跃平台间距最大 3m。这类数值可以用一张表记录下来每次测量后对照检查。4.6 配置角色并试玩试玩的前提是场景中有一个能跑的玩家角色。下面是一个最小CharacterBody3D控制器使用你在 2.3 节配置的输入动作# res://scripts/player.gd extends CharacterBody3D export var move_speed : 5.0 export var jump_speed : 4.5 export var gravity : 9.8 func _physics_process(delta: float) - void: # 垂直方向重力与跳跃 if not is_on_floor(): velocity.y - gravity * delta if Input.is_action_just_pressed(jump) and is_on_floor(): velocity.y jump_speed # 水平方向输入向量转为世界方向 var input_dir : Input.get_vector(move_left, move_right, move_forward, move_back) var direction : (transform.basis * Vector3(input_dir.x, 0, input_dir.y)).normalized() 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()创建一个小场景Player.tscn根节点是CharacterBody3D挂上player.gd再添加一个CollisionShape3D使用CapsuleShape3D最后加一个Camera3D作为子节点放到头部高度。将Player.tscn实例化到main.tscn中。运行游戏后你可以在 CSG 房间内移动、跳跃、尝试穿过门洞。试玩阶段重点验证三件事角色能不能顺畅通过所有预留通道跳跃高度和平台距离是否匹配是否有碰撞盲区导致角色卡住。发现问题后直接回编辑器改 CSG 参数再次运行整个循环非常快。5. 试玩之后冻结与交付5.1 冻结前要检查什么当你把原型玩法跑通关卡结构基本稳定就不要继续在 CSG 上做大量细调了。冻结前先过一遍检查清单所有门洞、窗户、通道的尺寸是否符合设计规范。角色能否从开始位置走到关卡中的所有可玩区域。碰撞体是否覆盖了所有需要阻挡玩家的表面。CSG 组合器下是否存在多余的减法体或隐藏节点。冻结的本质是“停止无规则修改”。你可以用编辑器自带的锁定功能把场景树中的 CSG 节点锁起来也可以把组合器节点整体折叠。这个动作在团队协作中尤其重要它向其他人传递一个信号这个区域已经进入交付状态不要随手改参数。如果项目还没有进入最终美术阶段冻结也可以只是逻辑上的在main.tscn中把 CSG 根节点设置为不可选中并给场景添加注释说明。具体形式不重要重要的是流程上明确“从自由搭建切换到了固定交付”。5.2 把 CSG 转成网格的通用方案真正进入性能优化和资产交接阶段时你需要把 CSG 转成静态网格因为静态网格不依赖布尔实时计算加载更快渲染也更稳定。Godot 的 CSG 节点对外提供获取网格数据的接口例如get_meshes()不同版本返回结构有差异使用前应通过打印结果确认格式。下面是一段烘焙脚本的示例思路它会把CSGCombiner3D的解析结果保存为资源# res://scripts/bake_csg.gd tool extends Node3D export var source: CSGCombiner3D export var bake : false: set(value): _bake() func _bake() - void: if source null: return var result source.get_meshes() # result 在不同 Godot 版本中可能是交替数组或嵌套数组 # 这里以交替数组为例result [Transform3D, Mesh, Transform3D, Mesh, ...] if result is Array and result.size() 2: var array_mesh : ArrayMesh.new() for i in range(0, result.size(), 2): var xform: Transform3D result[i] var mesh: Mesh result[i 1] if mesh null: continue var arrays : mesh.surface_get_arrays(0) # 把局部坐标变换烘焙到顶点后更利于后续优化 var baked_arrays : _bake_arrays(arrays, xform) array_mesh.add_surface_from_arrays(Mesh.PRIMITIVE_TRIANGLES, baked_arrays) ResourceSaver.save(array_mesh, res://exported/room_baked.tres) print([Bake] saved to res://exported/room_baked.tres) func _bake_arrays(arrays: Array, xform: Transform3D) - Array: var new_arrays : arrays.duplicate(true) if new_arrays.size() Mesh.ARRAY_VERTEX: var vertices : new_arrays[Mesh.ARRAY_VERTEX] as PackedVector3Array for i in vertices.size(): vertices[i] xform * vertices[i] new_arrays[Mesh.ARRAY_VERTEX] vertices return new_arrays这段代码只提取每个网格的第一个表面适合原型级烘焙。如果你的 CSG 结构包含多材质表面需要扩展为遍历所有表面并把材质一并复制到ArrayMesh中。最终导出的.tres或.res网格资源可以直接拖进场景替换 CSG 节点也可以交给美术作为参考底模。需要注意的是不同 Godot 小版本对get_meshes()返回值格式可能不一致。你在自己的工程里先运行一次并打印result再决定用交替索引还是嵌套索引解析。不要盲目复制本文代码到生产环境。5.3 碰撞体与寻路网格的最终处理CSG 节点启用use_collision后生成的碰撞体在原型阶段够用但冻结转静态网格后建议重新生成碰撞。你可以在MeshInstance3D上添加StaticBody3D然后根据网格数据生成ConcavePolygonShape3D或者对简单结构直接选用BoxShape3D、CapsuleShape3D等体素碰撞。寻路方面如果关卡里有敌人在跑需要在灰盒阶段就让 NavigationAgent 跑通。Godot 的NavigationRegion3D可以从几何体源烘焙导航网格烘焙范围应当覆盖地面和所有可行走平台。CSG 节点参与导航烘焙时要把地面、楼梯、平台与墙体分开处理否则导航网格可能把墙面也当成可行走区域。碰撞和寻路是试玩体验的关键。很多原型看起来正常一跑起来才发现角色会被一个小台阶卡住或者敌人直接穿过墙壁。这类问题在 CSG 阶段修起来非常快拖一个盒体或改一下导航网格参数就能解决。如果拖到美术模型完成后再处理定位和修改成本都会成倍增加。6. 常见问题与排查思路6.1 快速定位表问题现象常见原因解决思路门洞/窗洞没挖出来减法体节点顺序不对把所有减法体移到组合器子节点末尾CSG 表面闪烁两个几何面重合将减法体在对应方向上加 0.05m 余量碰撞体与视觉不一致CSG 更新后碰撞体未重新生成关闭再开启use_collision或手动重建碰撞体编辑器严重卡顿CSG 节点过多或布尔嵌套过深简化几何烘焙部分 CSG 为静态网格角色跳跃穿墙碰撞体是网格形状且存在破面改用简单盒体碰撞或重新烘焙碰撞网格导出的网格没有材质只提取了表面顶点未复制材质遍历所有表面并复制material_override或表面材质这张表覆盖了灰盒阶段最常见的一批问题。如果你遇到的问题不在表内先对照“几何体是否封闭”“节点层级是否合规”“版本是否支持该属性”三个方向排查。6.2 CSG 表面闪烁或裁剪异常表面闪烁的根源通常是共面或极薄几何。比如你想在墙面上贴一个装饰 CSG 盒体或者门洞减法体与墙体刚好共面布尔运算会产生大量退化三角形。视觉上就是某些帧能看到墙后面的内容局部像在“抖动”。处理方法很简单增加减法体的深度让它在被减方向上穿透墙体给装饰性几何体留出 0.01m 级别的小偏移避免完全贴合检查缩放是否出现 0 值。如果你的 CSG 盒子被缩放到某轴为 0也会导致数学上的退化。还有一种情况是多个 CSG 节点嵌套过深。Godot 的 CSG 虽然支持组合器嵌套但是嵌套层级越多内部计算越容易在小数精度上出问题。建议一个组合器内只保留两到三层结构复杂区域拆成多个组合器分别搭建。6.3 碰撞与视觉不一致CSG 节点开启use_collision后碰撞体并不总是实时保持最新的。你移动了墙体节点但碰撞数据可能没有完全刷新于是出现“能看到墙却穿过去”或“看不到墙却被挡住”的情况。遇到这类问题先尝试把use_collision关掉再打开强制重新生成碰撞。如果问题依旧说明 CSG 生成的临时碰撞网格本身存在缝隙或重叠。这是原生 CSG 的已知边界适合原型快速验证不适合追求物理精确度的成品。更稳妥的做法是确定关卡结构后直接使用简单体碰撞。三个盒体凑成一面墙比一个复杂的 CSG 碰撞网格更可靠。物理引擎处理简单体时碰撞检测更快也更稳定。6.4 编辑器卡顿与构建变慢卡顿几乎都和几何复杂度有关。CSG 布尔运算不是免费的每当你拖动一个 CSG 节点引擎都需要重新计算受影响区域的网格。一个大型组合器内堆了几十个盒体拖动时的重算开销会非常明显。处理办法是分层管理。把房间拆成几个组合器主房体一个、走廊一个、楼梯一个。每次修改只影响当前组合器而不是整个场景。另一个办法是临时把不相关的 CSG 组合器隐藏减少视口重绘压力。当场景已经进入冻结阶段不要再保留大量 CSG 节点。把确定下来的结构烘焙成静态网格场景加载速度和编辑器响应都会明显提升。这也是很多人把 Blockout 工具流从“好用”变成“高效”的关键节点。7. 最佳实践把“画量冻”变成团队流程7.1 场景树与命名规范如果没有命名规范一个星期后你自己也会忘记CSGBox3D32到底是墙还是地板。建议为每个 CSG 组合器划分明确区域并在命名中带上结构语义MainRoom └── Wall_North └── Wall_South_WithDoor └── Wall_East └── Wall_West └── Windows/WindowVoid_01 Corridor └── Floor └── Roof └── Wall_Left Stairs └── Step_01 └── Step_02命名规范建立后测量脚本、烘焙脚本、导航网格配置都能按规则自动过滤节点。比如Wall_开头的节点统一走墙体碰撞规则Void结尾的节点不参与导航烘焙。这个思路在团队协作中能省下大量沟通成本。7.2 用颜色与图层管理区域Blockout 阶段不追求美术效果但颜色语义能极大提升团队沟通效率。你可以给 CSG 节点设置不同材质颜色形成一套轻量的“区域语言”灰色普通墙体。蓝色可行走地面。绿色可交互区域例如按钮触发区、安全区。红色危险区域、掉落坑、敌人生成区。黄色待定区域需要策划或美术介入。颜色之外合理使用碰撞层也能改善试玩体验。墙体放在 layer 1角色放在 layer 2触发器放在 layer 3。试玩时角色只与前后左右可见的墙发生碰撞避免多个 CSG 组合器的碰撞体相互干扰。7.3 性能与内存控制一个标准的灰盒关卡不应该同时存在上千个 CSG 节点。如果你的关卡面积很大要把场景拆分成多个场景文件按区域加载。Godot 支持远程场景或子场景实例化在 Blockout 阶段就建立区块加载机制后期换正式美术资产时过渡会更平滑。内存控制主要靠烘焙。CSG 节点本身轻量但运行时生成的网格数据会占资源。冻结后的场景应该导出为静态网格资源并删除或禁用 CSG 生成器。烘焙后的网格还可以进一步合并同类表面减少 Draw Call。尤其注意不要在游戏运行时反复修改大型 CSG 节点。即使 Godot 允许你做动态布尔运算这也不是为高频变化设计的玩法方案。如果确需动态破坏或建造优先考虑只对小块区域做 CSG 更新其他区域保持静态。7.4 从原型到正式资产的交接Blockout 的终点不是“保存一个 CSG 场景”而是把确定的结构、尺寸、碰撞规则和玩法验证结果完整交给下游环节。交接时应包含至少四样东西烘焙后的静态网格、碰撞体配置说明、导航网格结果、一份标注了关键尺寸的测量清单。美术拿到网格后可以基于它做高层级模型关卡策划拿着测量清单调整布怪和触发点程序按碰撞层设计交互逻辑。整个过程不需要重新从空场景开始“猜结构”这就是 Blockout 工作流真正的价值。很多新手项目的问题不是工具不够强而是流程太随意。画完不量、量完不玩、玩完不冻最后所有 CSG 节点堆在场景里变成技术债。你只要严格走完“画、量、试玩、冻结”四个阶段CSG 就会成为开发效率的加速器而不是祸根。8. 总结CSG Blockout 能给你什么回到标题里的插件事CSG Blockout 3.0 这类工具本质上是把 Godot 原生 CSG 包装成一条更可执行的关卡原型流水线。它并不要求你掌握复杂的建模技巧也不要求你写大量美术资源而是用一种偏向工程化的方式组织几何体先画出来再量清楚再跑起来最后冻住。对你来说最值得记住的是三件事。第一Godot 原生 CSG 足够承担绝大多数灰盒需求不要急着导入外部模型也不要急着依赖复杂插件。第二画场景时保持减法体在后的顺序测量时使用 AABB 工具试玩时配置简单角色控制器和导航网格冻结时处理碰撞并烘焙静态网格。第三CSG 是原型工具想清楚它在最终资产流程中的边界不要让它变成场景里的长期性能隐患。如果这篇文章对你有帮助建议把它当作一份配套笔记收藏。下一次搭建新关卡时按照“画、量、试玩、冻结”的顺序走一遍再用第六节的排查表处理异常你会明显感受到原型阶段变快。现在的重点不是继续读更多资料而是打开 Godot先用 CSG 节点画出一个小小的房间再走完整条流程。只有真正跑过一遍这些经验才会变成你自己的开发习惯。
返回列表