
1. 项目概述从2D到3D的思维跃迁如果你已经用Godot引擎做过一些2D小游戏感觉GDScript语法也熟了节点树也玩得转了那么是时候推开另一扇门了。这个项目或者说这份学习笔记记录的正是一次从Godot的2D舒适区向更具挑战性的3D游戏设计领域的进阶探索。核心不是简单地教你放几个3D模型而是理解在Godot这套以2D起家、如今在3D领域也愈发强大的引擎里构建一个完整3D体验所需要的系统性思维转变和工具链掌握。为什么是Godot做3D几年前可能还会有人质疑但到了今天Godot 4.x版本在3D方面的进步是实实在在的。它的开源免费特性、相对轻量的体量、以及越来越完善的PBR基于物理的渲染管线和支持让独立开发者和小团队有了一个非常诱人的选择。不过从2D到3D绝不仅仅是多了一个Z轴那么简单。你需要面对的是从Sprite到MeshInstance的转变从简单的position向量到复杂的空间变换Transform从TileMap到网格导航NavigationMesh从CanvasLayer到视口Viewport与后期处理Post-Processing的整套新逻辑。这份内容适合已经掌握Godot 2D基础渴望拓展技能树的开发者。我们将不局限于某个单一功能而是围绕一个可玩的3D原型项目拆解其中涉及的核心模块场景搭建、角色控制、物理交互、光照与渲染、以及用户界面适配。你会看到很多2D里的概念在3D中有其对应的、但更复杂的实现。比如2D里你用一个Area2D检测碰撞3D里就是Area3D2D的Camera2D在3D里变成了需要精心调控的Camera3D还多了视野FOV、近远裁剪面等参数。理解这些对应关系是平滑过渡的关键。2. 核心设计思路与架构规划在动手写第一行代码之前花时间规划你的3D项目架构至关重要。2D项目或许可以“边做边想”但3D项目由于资源更重、坐标系更复杂、性能考量更多一个清晰的蓝图能避免后期大量返工。2.1 确定项目类型与核心循环首先明确你要做什么类型的3D游戏是第一人称探索、第三人称动作、俯视角解谜还是低多边形的休闲游戏类型决定了你技术方案的重心。例如第一人称FPS/探索核心是Camera3D与角色身体的分离处理、鼠标视角控制、射线检测RayCast用于交互与射击。第三人称动作/RPG核心是角色模型与动画混合树AnimationTree、摄像机跟随逻辑常使用SpringArm或CameraPivot、锁定目标系统。俯视角/等距视角核心可能是固定的摄像机角度、基于网格或导航网格的移动、以及如何处理3D模型在2D视角下的排序与交互。我们的示例将聚焦于一个基础的第三人称角色控制器这是很多3D游戏的起点。核心游戏循环可以定义为玩家输入 → 控制角色移动与镜头 → 与环境地形、物体发生物理交互 → 触发游戏逻辑如拾取物品、触发机关→ 更新UI反馈。2.2 场景树Scene Tree结构设计Godot的节点树是组织逻辑的骨架。一个清晰的3D主场景树可能如下所示Main (Node3D) ├── WorldEnvironment (世界环境天空、雾效、全局光照设置) ├── Sun (DirectionalLight3D 主方向光) ├── Terrain/Floor (StaticBody3D 或 MeshInstance3D 地面) ├── Player (CharacterBody3D 玩家角色) │ ├── MeshInstance3D (角色模型) │ ├── CollisionShape3D (碰撞形状如胶囊体) │ ├── AnimationPlayer (动画播放器) │ ├── AnimationTree (动画树用于复杂状态混合) │ └── CameraPivot (Node3D 摄像机旋转支点) │ └── Camera3D (跟随摄像机) ├── Props (Node3D 所有可交互道具的父节点) │ ├── Pickup_Coin (RigidBody3D 或 Area3D 可拾取硬币) │ └── Crate (RigidBody3D 可推动的木箱) └── UI (CanvasLayer 用户界面层) ├── HealthBar (ProgressBar) └── InteractionPrompt (Label)这种结构将功能模块化Player作为一个完整的子场景PackedScene可以独立测试和复用。CameraPivot作为Camera3D的父节点是一个经典技巧通过旋转Pivot来实现围绕玩家的镜头旋转而摄像机本身只负责偏移距离和角度。2.3 资源管理与导入流程3D项目严重依赖外部资源模型.gltf, .glb, .fbx、纹理.png, .jpg、音频等。Godot的导入系统需要正确配置。模型导入将FBX或GLTF文件拖入Godot文件系统。关键是在导入Import面板设置网格Meshes勾选“生成碰撞体Create Collision Sibling”Godot会自动为复杂网格生成简化的凸包Convex或精确的凹包Concave碰撞体这对于静态环境物体非常方便。动画Animations如果模型带骨骼动画确保导入动画并设置好循环模式Loop Mode。材质Materials选择“导入材质”或“继承材质”。对于PBR工作流确保纹理Albedo, Normal, Roughness, Metallic能正确关联。纹理导入对于法线贴图Normal Map和粗糙度/金属度贴图必须在导入设置中将模式Mode从“颜色Color”改为对应的“法线Normal”或“粗糙度Roughness”否则引擎无法正确识别。场景组织建议在项目根目录创建清晰的文件夹如models/,textures/,audio/,scenes/,scripts/。将导入后的模型实例化为可复用的场景如res://scenes/props/Crate.tscn而不是直接使用导入的原始资源。注意Godot对GLTF 2.0格式的支持通常比FBX更好尤其是动画和材质。建议工作流是在3D建模软件Blender等中完成制作导出为GLTF或GLB再导入Godot能获得最保真的效果和更少的兼容性问题。3. 第三人称角色控制器深度实现这是3D游戏的核心交互模块。我们将基于CharacterBody3D节点来实现它专为需要复杂碰撞响应如楼梯、斜坡的角色设计。3.1 角色移动与物理创建一个CharacterBody3D节点为其添加一个CollisionShape3D形状用CapsuleShape3D适合人形角色和一个视觉网格MeshInstance3D。然后附上脚本。移动逻辑的核心在_physics_process(delta)函数中extends CharacterBody3D export var speed : float 5.0 export var jump_velocity : float 4.5 export var sprint_multiplier : float 1.5 # 获取重力项目设置 var gravity ProjectSettings.get_setting(physics/3d/default_gravity) func _physics_process(delta): # 处理重力 if not is_on_floor(): velocity.y - gravity * delta # 处理跳跃仅在地面时 if Input.is_action_just_pressed(ui_accept) and is_on_floor(): velocity.y jump_velocity # 获取输入方向 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 is_on_floor(): var current_speed speed if Input.is_action_pressed(sprint): current_speed * sprint_multiplier if direction: velocity.x direction.x * current_speed velocity.z direction.z * current_speed else: # 应用地面摩擦力让角色平滑停止 velocity.x move_toward(velocity.x, 0, current_speed) velocity.z move_toward(velocity.z, 0, current_speed) # 调用move_and_slide()这是关键 move_and_slide()关键点解析transform.basis * Vector3(...)这是将本地坐标系下的输入向量转换到角色的全球坐标系世界空间下。transform.basis代表了节点当前的旋转矩阵。这确保了按“W”键永远是向角色面朝的方向前进而不是固定的世界坐标Z轴负方向。is_on_floor()CharacterBody3D和RigidBody3D在调用move_and_slide()或move_and_collide()后会自动更新地面、墙壁、天花板碰撞状态。这是检测是否着陆的可靠方法。move_and_slide()这个函数不仅处理移动还根据碰撞自动调整速度向量是实现斜坡行走、楼梯、平台边缘检测的魔法所在。它比move_and_collide()更高级更适合角色控制。3.2 鼠标控制摄像机与角色朝向我们希望鼠标左右移动控制角色水平旋转上下移动控制摄像机俯仰但限制角度避免翻转。首先在角色场景中创建结构CharacterBody3D下有一个子节点CameraPivotNode3DCameraPivot下有一个子节点Camera3D。将Camera3D向后上方偏移获得第三人称视角。然后修改脚本添加摄像机控制extends CharacterBody3D # ... 之前的变量 ... export var mouse_sensitivity : float 0.003 export var camera_pitch_limit : Vector2 Vector2(-70, 70) # 俯仰角限制度 onready var camera_pivot $CameraPivot func _ready(): # 隐藏鼠标光标并锁定到窗口中心用于第一人称或自由视角控制 # Input.set_mouse_mode(Input.MOUSE_MODE_CAPTURED) # 对于更常见的第三人称跟随镜头我们可能不需要锁定鼠标而是用鼠标控制旋转。 # 这里我们采用一种混合方式按住右键时旋转镜头和角色。 func _input(event): # 示例按住右键拖动鼠标旋转 if event is InputEventMouseMotion and Input.is_mouse_button_pressed(MOUSE_BUTTON_RIGHT): # 水平旋转角色Y轴 rotate_y(-event.relative.x * mouse_sensitivity) # 垂直旋转摄像机支点X轴并限制角度 camera_pivot.rotate_x(-event.relative.y * mouse_sensitivity) camera_pivot.rotation.x clamp(camera_pivot.rotation.x, deg_to_rad(camera_pitch_limit.x), deg_to_rad(camera_pitch_limit.y))操作心得event.relative是鼠标相对于上一帧的移动量乘以一个灵敏度系数sensitivity来调整控制速度。角色的水平旋转rotate_y直接改变CharacterBody3D的rotation.y这样移动方向direction的计算依赖于transform.basis就能始终保持正确。摄像机的俯仰旋转作用于CameraPivot节点这样摄像机本身只需要处理偏移和角度逻辑更清晰。使用clamp函数限制旋转角度防止摄像机翻转到角色脚下这种奇怪视角。另一种更现代、玩家更习惯的控制方式是鼠标始终控制摄像机自由环视角色朝向自动向移动方向或摄像机朝向插值过渡。这需要更复杂的逻辑通常需要存储一个target_rotation并在_physics_process中用rotation rotation.lerp(target_rotation, delta * rotation_speed)进行平滑插值。3.3 动画状态机集成一个呆立不动的模型是缺乏生气的。我们需要将移动与动画连接。假设你的角色模型带有AnimationPlayer包含了“Idle”待机、“Walk”行走、“Run”奔跑、“Jump”跳跃等动画。设置AnimationTree为角色添加一个AnimationTree节点。在它的属性中将Tree Root设置为AnimationNodeStateMachine并将Active勾选上。创建状态机编辑AnimationTree创建一个简单的状态机包含“Idle”、“Walk”、“Run”状态。使用AnimationNodeBlendSpace2D混合空间可以更平滑地处理从走到跑的过渡。将各个状态连接到AnimationPlayer中对应的动画。在脚本中驱动参数AnimationTree通过参数Parameters来控制。我们可以在代码中根据角色速度、是否在地面等状态来设置这些参数。onready var anim_tree $AnimationTree onready var playback anim_tree.get(parameters/playback) # 获取状态机播放对象 func _physics_process(delta): # ... 之前的移动和重力代码 ... # 计算水平速度大小忽略Y轴 var horizontal_velocity Vector2(velocity.x, velocity.z).length() # 设置AnimationTree的参数 anim_tree.set(parameters/conditions/is_grounded, is_on_floor()) anim_tree.set(parameters/conditions/is_in_air, not is_on_floor()) anim_tree.set(parameters/BlendSpace2D/blend_position, horizontal_velocity / speed) # 假设speed是行走速度 # 根据速度切换状态示例更佳实践是用状态机条件 if is_on_floor(): if horizontal_velocity 0.1: if Input.is_action_pressed(sprint): playback.travel(Run) else: playback.travel(Walk) else: playback.travel(Idle) else: playback.travel(Jump)注意事项动画混合是一个深水区。对于复杂的动作游戏可能需要多层动画状态机Layered State Machine来处理上半身和下半身动作的独立混合比如边移动边射击。AnimationNodeStateMachinePlayback的travel()方法可以安全地在状态间切换引擎会处理过渡。确保动画的根运动Root Motion设置正确。对于Godot通常我们使用代码驱动移动而非动画根运动除非是特定的过场或攻击位移。在导入动画时注意处理动画的缩放和循环属性。4. 3D环境交互与物理系统角色能动之后需要一个世界与之互动。这包括静态环境、可交互的物理物体、以及触发区域。4.1 静态环境与碰撞对于地面、墙壁、房屋等静态物体使用StaticBody3D。它性能最优因为物理引擎知道它不会移动。碰撞形状为StaticBody3D添加一个或多个CollisionShape3D。对于简单形状地面、盒子直接使用BoxShape3D、SphereShape3D等。对于复杂地形有两个选择自动生成在导入复杂网格模型时勾选“生成碰撞体兄弟节点”Godot会创建一个包含ConcaveCollisionShape3D的StaticBody3D。注意凹面Concave碰撞体性能开销大且只能用于静态物体。手动简化用简单的BoxShape3D或CapsuleShape3D组合来近似复杂形状这是最佳性能实践。对于地形可以考虑使用HeightMapShape3D。4.2 动态物理物体木箱、球等可以被推动、击飞的物体使用RigidBody3D。质量与惯性在RigidBody3D属性中设置Mass质量。你可以启用Custom Center of Mass和Custom Inertia来微调物理行为但对于大多数情况默认计算即可。力与冲量你可以通过代码施加力apply_force或冲量apply_impulse来影响它。冲量是瞬间的速度改变适合模拟爆炸、击打效果。睡眠模式为了性能静止的刚体会进入“睡眠”状态。确保Sleeping属性设置合理避免不该动的物体乱动。交互示例 - 推动箱子为了让角色能推动箱子你需要处理角色与刚体的碰撞。一种常见方法是在角色脚本中检测与前方刚体的碰撞然后对该刚体施加一个朝向碰撞法线方向的力。# 在CharacterBody3D的_physics_process中move_and_slide()之后 for i in get_slide_collision_count(): var collision get_slide_collision(i) var collider collision.get_collider() if collider is RigidBody3D: # 计算一个向前的推力 var push_force -collision.get_normal() * 10.0 collider.apply_central_impulse(push_force)4.3 区域检测与触发器Area3D节点用于创建没有物理碰撞响应但可以检测物体进入/离开的区域完美用于触发器。用途检查点、伤害区域、拾取物品、对话触发、场景过渡。信号连接Area3D提供了body_entered、body_exited、area_entered、area_exited等信号。你可以将其连接到脚本中的函数。拾取物品实现创建一个硬币模型其根节点为Area3D添加一个CollisionShape3D用于检测一个MeshInstance3D用于显示。为Area3D附加脚本extends Area3D func _on_body_entered(body): # 确保是玩家拾取 if body.is_in_group(player): # 播放拾取音效、粒子效果 $AudioStreamPlayer3D.play() $Particles.emitting true # 隐藏模型防止重复拾取 $MeshInstance3D.visible false # 通知游戏逻辑例如通过全局信号或直接调用玩家方法 GameManager.add_coin(1) # 设置一个计时器等音效播完再删除节点 await get_tree().create_timer(0.5).timeout queue_free()记得在玩家的CharacterBody3D节点上将其添加到“player”组中在节点面板的“组”选项卡。5. 光照、材质与视觉增强3D游戏的视觉表现力很大程度上取决于光照和材质。Godot 4默认使用前向渲染Forward并支持PBR。5.1 基础光源类型DirectionalLight3D平行光模拟太阳光无限远所有物体接收的光照方向一致。用于主光源塑造基本明暗关系。OmniLight3D点光源从一个点向所有方向发射光线有范围衰减。用于灯泡、火把、爆炸效果。SpotLight3D聚光灯圆锥形光源有方向、角度和衰减。用于手电筒、舞台灯、车灯。环境光通过WorldEnvironment节点下的Environment资源设置。提供均匀的基础照明避免纯黑阴影。可以设置为纯色、天空色或使用天空盒Sky来提供更丰富的环境反射。实操技巧在场景中放置光源时善用间接光Indirect Lighting和反射探针Reflection Probe。Godot 4的全局光照VoxelGI或SDFGI和反射探针可以极大提升场景的真实感。对于静态场景烘焙光照贴图Lightmap是提升性能和质量的终极手段但这需要将场景标记为“可烘焙Bakeable”并使用LightmapGI节点。5.2 标准材质3DStandardMaterial3D这是Godot 4主要的PBR材质。核心属性包括Albedo基础颜色和纹理。决定了物体的基本颜色。Metallic金属度。1.0表示完全金属如金、银0.0表示非金属如塑料、木材。金属材质会强烈反射环境光。Roughness粗糙度。0.0表示完全光滑镜面1.0表示完全粗糙漫反射。它控制高光反射的模糊程度。Normal Map法线贴图。通过RGB通道存储表面细微起伏的方向信息在不增加多边形的情况下增加细节。Emission自发光。让物体自己发光不受场景光照影响。常用于屏幕、霓虹灯、魔法效果。材质设置流程在MeshInstance3D的材质覆盖Material Override或网格资源的表面材质Surface Material中新建一个StandardMaterial3D。将你的纹理图片Albedo, Normal, Roughness/Metallic拖入对应的插槽。对于合并了粗糙度和金属度的ORMOcclusionRoughnessMetallic贴图需要将纹理的通道分别指定给环境光遮蔽AO、粗糙度和金属度。调整Metallic和Roughness参数可以先从0或1的极端值开始感受效果再微调。5.3 后期处理与屏幕空间效果WorldEnvironment节点下的Environment资源也是后期处理的控制中心。抗锯齿Anti-aliasingGodot 4推荐使用快速近似抗锯齿FXAA或时间性抗锯齿TAA。TAA效果更好但可能有轻微拖影在项目设置中启用。屏幕空间环境光遮蔽SSAO在物体接缝和凹陷处添加柔和的阴影增强立体感。适度使用强度过高会显得脏。屏幕空间反射SSR为光滑表面添加反射但仅限于屏幕内的物体。对性能有影响酌情开启。辉光Glow让高亮区域如光源、自发光物体产生光晕效果提升视觉冲击力。可以分层Levels控制不同亮度范围的辉光强度。色调映射Tone Mapping将HDR高动态范围颜色转换到屏幕显示的LDR低动态范围。ACES模式通常能提供更电影化的对比度和色彩。调整Adjustments可以在这里微调饱和度、对比度、亮度或者应用一个查找表LUT进行整体色彩校正。性能警告后期处理效果尤其是SSAO、SSR、辉光是性能消耗大户。在低端设备或移动平台上需要谨慎启用或提供图形质量选项让玩家自行调整。始终在目标硬件上进行性能测试。6. 用户界面与3D世界的结合3D游戏的UI通常分为两部分世界空间UIWorld-Space UI和屏幕空间UIScreen-Space UI。6.1 屏幕空间UICanvasLayer这是最常用的UI如血条、弹药数、小地图、菜单。使用CanvasLayer节点它默认渲染在屏幕最上层。布局使用Control节点如Panel,Label,TextureRect,ProgressBar及其丰富的锚点Anchors和边距Margins系统进行响应式布局。与3D逻辑通信UI通常需要显示3D世界的数据。最佳实践是使用信号Signals或单例Autoload Singletons。信号玩家受伤时发出一个health_changed信号UI层连接这个信号并更新血条。单例创建一个名为GameState或PlayerData的自动加载脚本作为全局数据存储。UI直接读取单例中的数据。# GameState.gd (作为Autoload单例) extends Node var player_health : int 100 signal health_updated(new_health) func take_damage(amount: int): player_health - amount player_health max(player_health, 0) health_updated.emit(player_health) # UI脚本中 func _ready(): GameState.health_updated.connect(_on_health_updated) func _on_health_updated(new_health): $HealthBar.value new_health6.2 世界空间UISubViewport与Sprite3D有时你需要将UI元素如角色头顶的名字、交互提示、血条放置在3D世界中。这需要用到SubViewport和Sprite3D或Label3D。创建SubViewport创建一个SubViewport节点将其大小设置为UI所需的分辨率如256x64。在里面构建你的UI如一个ProgressBar和一个Label。创建ViewportTexture在SubViewport的属性中勾选“透明背景Transparent Bg”。然后在资源面板中点击“新建ViewportTexture”并选择你刚创建的SubViewport。应用到Sprite3D在3D场景中创建一个Sprite3D节点。将上一步创建的ViewportTexture拖拽到Sprite3D的“纹理Texture”属性中。控制显示现在这个Sprite3D显示的就是SubViewport中渲染的UI。你可以通过脚本控制SubViewport中UI的更新如血条变化3D世界中的Sprite3D会实时反映。记得将Sprite3D设置为“Billboard”广告牌模式使其始终面向摄像机。注意事项SubViewport是独立的渲染管线过多使用会影响性能。对于简单的文字提示Godot 4新增的Label3D节点可能是更轻量的选择。7. 性能优化与常见问题排查当你的3D场景变得复杂时性能问题就会浮现。以下是一些关键的优化点和排查思路。7.1 性能瓶颈定位Godot内置了强大的调试器Debugger和性能分析器Profiler。打开分析器运行项目后点击编辑器顶部菜单的“调试Debug” - “分析器Profiler”。观察帧时间重点关注“帧时间Frame Time”图表。一帧的理想时间应在16.67ms60FPS以下。如果某帧突然飙升说明有卡顿。分析各线程Godot是多线程的。查看“物理Physics”、“渲染Render”、“脚本Script”等线程的时间消耗。哪个线程耗时最长就是主要的优化方向。脚本耗时高检查你的_process或_physics_process函数中是否有复杂循环、不必要的计算或频繁的资源加载。渲染耗时高可能是绘制调用Draw Calls太多、过度复杂的着色器、或过高的分辨率/后期效果。物理耗时高场景中物理物体尤其是RigidBody3D过多或碰撞形状过于复杂大量凹面体。7.2 常见优化策略优化方向具体措施效果与说明减少绘制调用使用网格实例MultiMeshInstance3D渲染大量相同物体如草地、树木。将成千上万的独立绘制调用合并为几个性能提升巨大。简化碰撞形状用简单的BoxShape3D、CapsuleShape3D、ConvexPolygonShape3D组合代替复杂的ConcavePolygonShape3D。大幅降低物理引擎计算负担尤其是对动态物体。层级剔除LOD为远处物体使用低多边形版本模型。Godot有LOD节点插件或可手动实现。减少远处物体的顶点数提升渲染效率。遮挡剔除在Godot 4中对静态场景使用烘焙遮挡Baked Occlusion Culling。避免渲染被完全遮挡的物体但对动态物体支持有限。纹理与着色器压缩纹理尺寸如2048x2048降到1024x1024使用纹理图集Texture Atlas。简化自定义着色器代码。减少GPU内存带宽和计算压力。灯光与阴影减少实时阴影投射光源的数量。尽可能使用烘焙光照Lightmap。将OmniLight3D和SpotLight3D的阴影设为“关闭”或“仅静态”。实时阴影是性能杀手烘焙光照是静态场景的最佳选择。粒子系统控制粒子最大数量Amount使用更简单的着色器避免每帧更新Local Coords vs Global Coords。粒子过多或过复杂会迅速拖慢帧率。7.3 常见问题与解决方案实录问题1角色在斜坡上抖动或滑落。原因move_and_slide()默认的floor_max_angle最大地面角度可能太小或者重力/速度计算有误。解决增加floor_max_angle例如设为45度。确保在_physics_process中只有is_on_floor()为false时才应用重力。检查斜坡碰撞体的表面是否平滑。问题2摄像机穿墙或抖动。原因摄像机离角色太近或者没有处理与墙壁的碰撞。解决为Camera3D添加一个RayCast3D节点从摄像机目标点角色向摄像机本身发射射线。如果射线检测到碰撞就将摄像机位置拉回到碰撞点前方一点。这就是常见的“摄像机碰撞检测”功能。问题3导入的3D模型材质显示为紫色。原因着色器编译失败或材质资源丢失。Godot用紫色表示错误材质。解决检查导入模型的材质路径。在Godot中重新指定纹理文件。确保使用的着色器如StandardMaterial3D与你的Godot版本兼容。有时在导入设置中重新选择“材质”选项为“继承”或“新建”可以解决。问题4刚体物理表现不稳定到处乱飞。原因时间步长delta不稳定或者碰撞形状与视觉网格不匹配质量Mass设置不合理。解决物理模拟应在_physics_process中进行它使用固定的时间步长。确保碰撞形状紧密包裹模型但不要陷入内部。为刚体设置合理的质量非零并可以尝试增加物理迭代次数在项目设置 - 物理 - 3D中调整Solver Iterations。问题5游戏在低帧率下物理速度变慢。原因物理计算与帧率耦合。解决永远在物理相关的计算中乘以delta时间增量。velocity和position的变化应该与时间成正比而不是与帧数成正比。确保_physics_process中的移动代码都使用了delta参数。从2D思维切换到3D最大的挑战在于对空间关系的理解和工具链的熟悉。Godot 4让这个过程变得比以往更平顺但依然需要你耐心地搭建场景、调试物理、打磨手感。我个人的体会是从一个非常小的、目标明确的3D原型开始比如“一个方块在平台上跳跃收集星星”把上述每个环节都走通远比一开始就想着做开放世界要实际得多。每当你解决一个3D特有的问题比如让摄像机平滑跟随、让角色在凹凸地面上稳定行走、或者让UI正确地悬浮在物体上方你都会对引擎和3D游戏开发有更深一层的理解。这个过程本身就是最大的乐趣所在。