公司动态
Godot 3D游戏开发入门:从2D思维到3D实战的完整指南
1. 项目概述从2D到3D的思维跃迁如果你已经跟着官方的“你的第一个2D游戏”教程走了一遍看着那个小方块在屏幕上躲避“Creeps”心里大概会想“嗯2D也就这样了。” 恭喜你你已经跨过了从“完全不会”到“能做个东西”的第一道坎。但游戏世界的魅力有一大半藏在第三个维度里。从平面到立体绝不仅仅是多了一个Z轴那么简单它意味着思维方式、工具使用和设计理念的全面升级。“Godot基础入门_8.3D游戏开发基础”这个标题听起来像是系列教程的第八部分但它实际上是一个全新的起点。很多开发者卡在从2D转向3D的关口不是因为3D本身有多难而是因为用2D的思维去理解3D就像用地图去理解地球仪总有些地方对不上。这篇内容就是要帮你完成这个关键的思维转换把你在2D中学到的节点、场景、脚本等核心概念无缝映射到3D空间中并建立起一套专属于3D的工作流。简单来说这部分内容要解决的核心问题是“我已经会用Godot做2D游戏了现在怎么用同样的引擎、相似的逻辑去创造一个可以走进去的世界”它适合所有完成了2D入门对那个蓝色小方块和红色敌人感到意犹未尽想要挑战更广阔空间的开发者。我们将不再满足于平面的左右上下而是要深入探讨摄像机如何跟随、灯光如何塑造氛围、模型如何导入与操控以及如何让一个角色在三维空间中自然地移动和交互。2. 核心概念解析3D与2D的本质差异在2D里一个Sprite2D节点加上一张图片你的角色就有了形象。在3D里事情变得稍微复杂但也更有趣。让我们先抛开具体的操作看看支撑起Godot 3D世界的几个基石概念理解它们后面的路会好走很多。2.1 坐标系从平面到立体的导航图在2D中我们熟悉的是(x, y)坐标系原点(0, 0)通常在屏幕左上角或中心Y轴向下为正。这是纯粹的平面思维。进入3D坐标系变成了(x, y, z)。Godot使用的是右手坐标系X轴向右为正与2D一致。Y轴向上为正这是与2D最大的不同在2D中Y轴通常向下为正而在Godot的3D空间里Y轴指向上方更符合我们对“高度”的直觉。Z轴向前为正深入屏幕方向。实操心得刚开始很容易把Y轴的方向搞混尤其是在处理跳跃velocity.y时。记住“Y向上”可以避免很多诡异的移动bug。你可以通过查看3D视口左下角的坐标轴小工具来随时确认方向。2.2 核心节点构建3D世界的积木2D场景的核心是Node2D及其子类如Sprite2D,CharacterBody2D。在3D中对应的基石是Node3D。所有需要在3D空间中具有位置、旋转、缩放的物体都必须继承自Node3D。以下是几个你最常打交道的Node3D子类MeshInstance3D网格实例这是3D世界的“精灵”。它不包含图像而是包含由顶点、边和面构成的3D模型网格。你需要为它指定一个Mesh资源比如一个立方体、球体或者从Blender导入的复杂角色模型和一个Material材质来定义其外观。Camera3D摄像机2D中你可能用过Camera2D来跟随玩家。在3D中Camera3D就是你的眼睛。它决定了玩家看到的世界是什么样子。透视、视野FOV、近/远裁剪平面等属性都需要在这里设置。没有摄像机3D场景就无法被渲染。Light3D灯光2D游戏可以没有灯光或者只有简单的Light2D但3D世界没有灯光就是一片漆黑。Godot提供了几种灯光DirectionalLight3D平行光模拟太阳光所有光线平行照射用于全局照明。OmniLight3D点光源像灯泡一样向所有方向发光。SpotLight3D聚光灯像手电筒或舞台聚光灯有照射方向和范围角度。CharacterBody3D / RigidBody3D角色体/刚体这是2D中CharacterBody2D和RigidBody2D的3D版本。CharacterBody3D用于需要精确控制移动如玩家角色、NPC的物体通过代码调用move_and_slide()或move_and_collide()来移动。RigidBody3D则用于模拟物理效果的物体如箱子、球受重力、碰撞等物理力影响。2.3 空间与变换理解物体的位置、旋转和缩放在2D中一个节点的变换Transform由positionVector2、rotation弧度和scaleVector2描述。在3D中这个概念被扩展为Transform3D。一个Transform3D本质上是一个4x4矩阵它同时编码了平移Translation物体在世界空间中的位置origin属性是一个Vector3。旋转Rotation物体绕X、Y、Z轴的旋转。在Godot中旋转通常用四元数Quaternion的basis属性来表示因为它能避免“万向节死锁”。但你也可以通过rotation属性欧拉角Vector3来直观地设置。缩放Scale物体沿三个轴的缩放比例。注意事项直接修改rotation欧拉角虽然直观但在进行复杂旋转如让物体同时绕多个轴旋转或插值动画时可能会遇到问题。对于平滑旋转和动画更推荐使用rotation属性进行简单设置或使用Basistransform.basis进行更高级的操作。新手可以先用rotation等熟悉后再深入四元数。3. 3D场景搭建实战从零创建一个可探索的场景理论说再多不如动手搭一个。让我们一步步创建一个简单的3D平台跳跃场景你会直观地感受到上述概念是如何落地的。3.1 创建基础场景与环境新建3D场景在Godot编辑器中点击“场景” - “新建场景”。在弹出的创建节点对话框中选择Node3D作为根节点命名为World。Node3D是3D场景的通用根节点就像2D场景的Node2D。添加地面在场景树中选中World节点右键添加子节点搜索并添加一个StaticBody3D静态刚体3D。将其重命名为Ground。静态刚体不会移动但能与动态物体发生碰撞非常适合地面、墙壁。为地面添加视觉选中Ground节点为其添加一个子节点MeshInstance3D。在检查器面板中点击Mesh属性旁边的[空]选择New BoxMesh。一个白色的立方体会出现在视口中。调整地面大小和位置选中这个MeshInstance3D在检查器中找到其Mesh资源展开。将Size属性从默认的(2, 2, 2)修改为(20, 0.5, 20)。这样我们就得到了一个长宽20个单位、厚0.5单位的平台。然后在Ground节点的Transform中将Position的Y设置为-1让地面下沉一点方便后续放置玩家。添加世界环境一个空的3D空间是漆黑的。我们需要添加WorldEnvironment节点。在场景树中选中根节点World添加子节点WorldEnvironment。在检查器中点击Environment属性旁的[空]选择New Environment。Godot会提供一个默认的带天空盒和基本光照的环境。你可以立即在3D视口中看到效果。3.2 创建并控制玩家角色现在我们来制作一个可以在3D平台上移动的玩家角色。创建玩家场景点击“场景” - “新建场景”。这次创建一个CharacterBody3D节点作为根保存为player.tscn。CharacterBody3D是我们控制移动逻辑的绝佳选择。添加视觉和碰撞为CharacterBody3D添加一个子节点MeshInstance3D命名为Visual。为其创建一个New SphereMesh球体网格作为玩家的身体。为了能与其他物体碰撞我们需要碰撞形状。选中CharacterBody3D根节点在检查器面板的底部点击CollisionShape3D下的[空]选择New SphereShape3D。调整Shape的Radius使其略小于视觉球体例如视觉球体半径0.5碰撞形状半径0.45这样看起来更自然。编写移动脚本选中CharacterBody3D根节点点击检查器上方的“添加脚本”按钮。使用默认设置GDScript附加到节点。我们将编写一个基础的第三人称移动脚本。extends CharacterBody3D # 移动参数 export var speed : float 5.0 export var jump_velocity : float 4.5 export var mouse_sensitivity : float 0.002 # 获取摄像机节点引用 onready var camera_pivot $CameraPivot onready var camera $CameraPivot/Camera3D var gravity ProjectSettings.get_setting(physics/3d/default_gravity) func _ready(): # 锁定鼠标到游戏窗口用于鼠标控制视角 Input.set_mouse_mode(Input.MOUSE_MODE_CAPTURED) func _unhandled_input(event): # 鼠标移动事件用于控制摄像机旋转 if event is InputEventMouseMotion and Input.get_mouse_mode() Input.MOUSE_MODE_CAPTURED: # 水平旋转左右看作用于摄像机支点 camera_pivot.rotate_y(-event.relative.x * mouse_sensitivity) # 垂直旋转上下看作用于摄像机本身并限制角度避免翻转 camera.rotate_x(-event.relative.y * mouse_sensitivity) camera.rotation.x clamp(camera.rotation.x, deg_to_rad(-60), deg_to_rad(60)) func _physics_process(delta): # 添加重力 if not is_on_floor(): velocity.y - gravity * delta # 处理跳跃 if Input.is_action_just_pressed(jump) and is_on_floor(): velocity.y jump_velocity # 获取输入方向 - 基于摄像机的朝向进行转换 var input_dir Input.get_vector(move_left, move_right, move_forward, move_back) var direction (camera_pivot.global_transform.basis * Vector3(input_dir.x, 0, input_dir.y)).normalized() # 在地面上时应用移动 if direction: velocity.x direction.x * speed velocity.z direction.z * speed else: # 如果没有输入逐渐停止水平移动 velocity.x move_toward(velocity.x, 0, speed) velocity.z move_toward(velocity.z, 0, speed) # 调用move_and_slide来移动角色并处理碰撞 move_and_slide()设置输入映射在脚本中我们引用了move_left,move_right,move_forward,move_back,jump这些输入动作。你需要去“项目” - “项目设置” - “输入映射”中创建它们。例如move_left: 按键 A / 左箭头。move_right: 按键 D / 右箭头。move_forward: 按键 W / 上箭头。move_back: 按键 S / 下箭头。jump: 按键 Space空格。设置摄像机回到player.tscn场景。为CharacterBody3D添加一个子节点Node3D命名为CameraPivot。这个节点将负责水平Y轴旋转。然后为CameraPivot添加一个子节点Camera3D。调整Camera3D的位置比如将它的Position设为(0, 1, 3)让它位于玩家身后上方。在Camera3D的检查器中勾选Current属性使其成为活动摄像机。将玩家实例化到主场景回到之前创建的World场景。从文件系统面板中将player.tscn拖放到场景树中World节点下。调整其实例的位置确保它在地面之上例如Position (0, 1, 0)。现在运行场景你应该可以用WASD移动球体用鼠标控制视角按空格键跳跃了3.3 添加一些简单的3D物体为了让场景不那么空旷我们添加一些障碍物或收集品。创建立方体障碍物在World场景中添加一个RigidBody3D节点命名为Obstacle。为其添加一个MeshInstance3D子节点并赋予New BoxMesh。为其RigidBody3D添加一个CollisionShape3D形状为New BoxShape3D。复制几个Obstacle节点随意摆放在地面上。由于它们是RigidBody3D当你用玩家角色球去撞击它们时它们会根据物理规则被撞飞。创建收集品金币新建一个场景根节点为Area3D保存为coin.tscn。Area3D可以检测与其他物体的重叠非常适合做触发区域。为Area3D添加一个MeshInstance3D子节点使用New CylinderMesh圆柱体或导入一个金币模型并缩放至合适大小。添加一个CollisionShape3D形状为New SphereShape3D。为Area3D添加脚本用于检测与玩家的重叠并销毁自身extends Area3D func _on_body_entered(body): # 假设只有玩家角色会被命名为“Player” if body.name Player: # 或者用组group来判断更健壮 # 这里可以触发得分增加、播放音效等 print(Coin collected!) queue_free() # 销毁金币- 记得在Area3D节点的检查器中连接到body_entered信号到这个脚本的函数。在主场景World中实例化几个金币放在地面上方。4. 3D资源导入与处理让世界丰富起来不可能所有模型都用基本几何体。从外部3D建模软件如Blender、Maya、3ds Max导入模型是3D开发的常态。4.1 导入流程与最佳实践支持的格式Godot主要支持glTF 2.0.gltf或.glb格式这是其官方推荐的格式支持网格、材质、动画、骨骼等完整数据。FBX和OBJ格式也支持但可能丢失一些信息或需要额外处理。从Blender导出glTF如果你使用Blender安装官方的glTF 2.0导出插件是最佳选择。导出时注意应用变换在导出前选中所有物体按CtrlA选择“应用全部变换”缩放、旋转、位置。这能确保模型以正确的比例和朝向导入Godot。Y向上确保导出设置中Z轴朝前还是Y轴朝前与Godot设置匹配。Godot默认是Y向上Z向前与Blender一致Blender也是Y向上Z向前。保持默认通常即可。包含材质勾选导出材质选项。在Godot中导入直接将.gltf或.glb文件拖入Godot的文件系统面板。Godot会自动将其作为PackedScene资源导入。你可以像打开其他场景一样双击打开它或者将其拖入你的主场景作为实例。导入设置选中导入的glTF文件在导入面板中可以进行详细设置生成碰撞体对于静态环境物体可以勾选“创建碰撞体”-colGodot会自动根据网格生成简化的碰撞形状通常是凸包分解。对于复杂形状手动创建CollisionShape3D可能更高效。创建导航网格如果你的角色需要AI寻路可以勾选“创建导航网格”。材质处理Godot会尝试将Blender中的原理化BSDF材质转换为自己的StandardMaterial3D。你可能需要微调一些参数如金属度、粗糙度来达到最佳效果。4.2 材质与着色器基础模型有了还得好看。StandardMaterial3D是Godot默认的PBR基于物理的渲染材质它模拟真实世界的光线交互。Albedo反照率物体的基础颜色或纹理。可以理解为漫反射颜色。Metallic金属度控制材质是金属1.0还是非金属0.0。金属材质会强烈反射环境非金属则更漫反射。Roughness粗糙度控制表面的粗糙程度。0.0像镜子般光滑1.0则完全漫反射。Normal Map法线贴图通过RGB通道存储表面细微起伏的方向信息在不增加模型面数的情况下增加细节。Emission自发光让材质自己发光用于灯泡、屏幕等效果。实操心得对于刚接触PBR的开发者一个快速上手的技巧是先从Albedo和Roughness调起。给一个基础颜色然后通过一张灰度图控制粗糙度亮的地方粗糙暗的地方光滑就能立刻获得不错的质感。金属度可以稍后调整它对视觉影响很大调错了会显得很假。5. 光照、阴影与环境氛围3D渲染的灵魂在于光影。没有好的光照再精致的模型也显得平淡。5.1 布置基础三点光这是经典的人物/静物布光法在Godot中同样适用主光Key Light添加一个DirectionalLight3D作为主要光源模拟日光。调整其旋转让阴影方向符合你的场景氛围。强度可以设为1.0或更高。补光Fill Light添加一个OmniLight3D或另一个较弱的DirectionalLight3D放在主光相对的位置用于照亮主光产生的阴影区域降低对比度。强度设为0.3到0.5。轮廓光/背光Rim Light在物体后方添加一个SpotLight3D或OmniLight3D用于勾勒物体轮廓使其从背景中分离出来。强度可以稍高但范围要控制好。5.2 全局光照GI与反射仅靠直接光阴影会死黑物体缺乏环境间的光线交互。Godot提供了几种全局光照方案SDFGISigned Distance Field Global Illumination这是Godot 4.x的默认推荐方案。它实时计算场景的全局光照和反射效果不错且性能相对较好。在WorldEnvironment的Environment资源中启用即可。适合大多数动态场景。VoxelGI体素全局光照需要预烘焙质量很高但烘焙耗时且对动态物体支持有限。适合静态为主的场景。LightmapGI光照贴图需要预烘焙能产生非常高质量的静态光照和阴影性能开销极低。但无法影响动态物体且需要为模型生成第二套UV用于光照贴图。对于反射除了GI自带的粗糙反射你还可以使用ReflectionProbe反射探针。将其放置在光滑物体如金属球、水池附近它会捕获周围环境的立方体贴图提供精确的镜面反射。5.3 后期处理与雾效在WorldEnvironment-Environment-Post-Process中可以开启一系列后期效果来提升画面质感SSAO屏幕空间环境光遮蔽在物体缝隙和接触处添加柔和的阴影极大地增强立体感。SSIL屏幕空间间接光照增强间接光照的效果。自动曝光Auto Exposure模拟人眼对亮暗环境的适应过程。辉光Glow让明亮区域产生光晕效果。色调映射Tonemap将HDR高动态范围颜色映射到屏幕显示范围ACES是电影感很强的选择。添加Fog雾效可以增强场景的纵深感并隐藏远处的景物加载。可以调整雾的颜色、起始距离和浓度。6. 常见问题与排查技巧实录从2D转到3D你会遇到一堆2D里没有的“坑”。这里记录一些我踩过的以及社区里常见的问题。6.1 模型看不见/是粉色的问题导入的模型在场景中显示为洋红色Missing Texture颜色。原因1材质丢失或路径错误。Godot找不到材质引用的纹理图片。解决检查材质球是否正常加载。在文件系统面板中确保纹理文件已成功导入。有时需要重新指定材质中的纹理路径。原因2渲染模式或着色器错误。解决检查模型的材质是否使用了当前渲染后端不支持的着色器特性例如在移动端渲染器上使用了过于复杂的功能。尝试将材质替换为新建的StandardMaterial3D。6.2 摄像机控制诡异翻转、不跟随手柄问题用鼠标或手柄控制摄像机时上下或左右方向相反或者移动不流畅。解决方向相反在摄像机旋转代码中检查event.relative.x和event.relative.y前面的正负号。rotate_y(-event.relative.x * sensitivity)是常见做法如果方向反了去掉负号或交换x/y。万向节死锁如果你直接使用欧拉角rotation属性进行复杂的叠加旋转比如同时绕多个轴旋转后再绕其中一个轴旋转可能会遇到。尽量将水平旋转Y轴赋予一个父节点如我们例子中的CameraPivot将垂直旋转X轴赋予摄像机本身并限制其角度。手柄输入手柄的右摇杆输入是Input.get_vector(look_right, look_left, look_down, look_up)。记得在输入映射中正确设置这些动作并在_process中处理而不是_unhandled_input。6.3 碰撞检测失灵或物体穿透问题玩家或物体穿过了本该碰撞的物体。原因1速度过快。在单次物理帧中移动距离超过了碰撞体的厚度。解决对于高速移动的物体如子弹使用move_and_collide()并处理碰撞信息或者使用射线检测RayCast3D先行判断。也可以增加碰撞体的margin边界容差属性。原因2碰撞层Layer和遮罩Mask未设置。解决在CollisionObject3D如CharacterBody3D,RigidBody3D,StaticBody3D,Area3D的检查器中有Collision分区。Layer定义了这个物体“属于哪一层”Mask定义了这个物体会“与哪一层发生碰撞”。确保相互需要碰撞的物体一方的Layer在另一方的Mask中被勾选。这是3D物理中非常关键且容易忽略的设置原因3碰撞形状CollisionShape3D大小或位置不正确。解决在场景编辑器中选中带有CollisionShape3D的节点在3D视口上方点击“调试”菜单勾选“可见碰撞形状”。这样就能看到绿色的碰撞体轮廓检查其是否与视觉模型匹配。6.4 性能问题帧率低下问题场景稍复杂帧率就急剧下降。排查与优化绘制调用Draw Calls打开“调试器”面板的“监视器”页签查看“渲染/帧时间”和“渲染/绘制调用数”。单个模型一个材质算一次绘制调用。数量过多是性能杀手。优化使用MultiMeshInstance3D来批量渲染大量相同的物体如草地、树木。合并使用相同材质的静态网格在3D建模软件中完成。面数Polycount在“调试” - “可见几何体视图”中可以查看三角形数量。远处或小的物体使用高模是浪费。优化使用LOD细节层次。Godot 4支持自动生成LOD或在建模时制作不同精度的模型根据距离切换。实时阴影和灯光每盏产生阴影的灯光都是性能消耗大户。优化减少动态阴影的使用。尽可能使用烘焙光照LightmapGI。将灯光设置为不投射阴影Shadow Enabled取消勾选或降低阴影贴图的分辨率和距离。过度绘制Overdraw半透明物体叠加会导致同一像素被多次绘制。优化合理安排渲染顺序不透明的物体先画半透明的后画并尽量减少重叠。6.5 从2D项目迁移时的心理调整坐标系时刻牢记Y轴向上。所有涉及“向上”的向量如重力方向、跳跃速度在3D中通常是Vector3(0, -1, 0)和velocity.y。旋转2D只有一个旋转轴绕Z轴。3D有三个轴操作更复杂理解Basis和四元数需要时间初期先用rotation属性没问题。节点查找$NodeName的路径查找逻辑不变但你的节点树现在深度可能更大因为多了很多用于组织3D变换的父节点如我们例子中的CameraPivot。UI2D的UIControl节点默认在3D场景中是不可见的。你需要将UI放在一个独立的2D场景中或者使用SubViewportSubViewportContainer将UI渲染到纹理再在3D中显示。更常见的做法是UI层作为一个单独的2D场景覆盖在3D视口之上。从“Dodge the Creeps”的2D平面到如今可以自由旋转视角、在立体空间中跳跃穿梭的3D世界这一步的跨越充满了挑战但也带来了无与伦比的创作自由。3D开发的门槛确实更高涉及的概念更庞杂但Godot用其一致的节点场景架构和相对友好的工具链尽可能平滑了这个过渡。关键在于别想着一口吃成胖子从搭一个方块平台、放一个球开始逐步添加灯光、导入模型、设置物理每完成一小步你对这个立体世界的掌控力就增加一分。当你能流畅地操控角色在你亲手搭建的3D场景中奔跑跳跃时那种成就感是2D无法比拟的。这只是一个起点后面还有动画、粒子、高级着色器、网络联机等无数山峰等待攀登但有了这份3D基础你已经拿到了通往更广阔游戏世界的门票。