ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

Godot 4 3D游戏开发入门:从零构建滚球收集游戏原型

Godot 4 3D游戏开发入门:从零构建滚球收集游戏原型 1. 项目概述为什么是Godot为什么是现在如果你和我一样是个在游戏行业摸爬滚打多年的“老家伙”看着Unity的收费风波、Unreal的庞大身躯心里总会琢磨有没有一个轻量、自由、又能打的引擎能让我们这些独立开发者或小团队把精力真正花在创意上而不是在复杂的管线、高昂的成本和臃肿的编辑器里挣扎Godot就是那个让我眼前一亮的答案。它不是突然冒出来的新秀而是经过多年社区打磨在4.0版本后真正在3D领域站稳脚跟的开源利器。这次我们不谈空洞的概念就从一个最具体的目标开始用Godot从零开始完整地“搓”出一个能跑起来的3D游戏。这个过程我会把我踩过的坑、验证过的技巧以及那些官方文档里不会明说的“潜规则”毫无保留地摊开来讲。无论你是刚接触游戏开发的新手还是从其他引擎转过来的老鸟这篇笔记的目的就是让你能跟着做做出东西并理解每一步背后的“所以然”。2. 核心思路与项目选型定义一个“最小可行游戏”在动手写第一行代码之前最重要的一步是明确我们要做什么。对于第一个3D游戏目标不是复刻《塞尔达传说》而是构建一个“最小可行产品”MVP它能验证核心玩法循环并涵盖3D游戏开发的基础模块。我选择了“3D滚球收集”这个经典原型。它麻雀虽小五脏俱全需要处理3D物理运动、摄像机跟随、用户输入、碰撞检测、UI更新和简单的游戏状态管理。选择Godot 4作为引擎不仅因为其开源免费这对个人和独立开发者至关重要更因为其独特的场景树Scene Tree节点架构让游戏对象的组织逻辑非常清晰学习曲线相对平缓。与Unity的GameObject/Component模式或Unreal的Actor/Component模式相比Godot的“一切皆节点节点可嵌套”的理念在构建复杂游戏对象时往往更直观。2.1 为什么不是Unity或Unreal对于初学者或小型项目Unity曾经是毋庸置疑的起点但其新的运行时费用政策增加了很多不确定性尤其是对成功的个人开发者或小团队。Unreal Engine功能强大画面顶尖但其庞大的体量、复杂的C/蓝图双线学习路径以及5%的收入分成虽然门槛不低对于想快速验证想法的初学者来说显得过于沉重。Godot则完全没有这些顾虑。一个不到100MB的安装包包含了编辑器、脚本语言GDScript类似Python极易上手、调试工具和导出模板。你可以用它做任何你想做的项目无论成功与否都没有财务或法律上的后顾之忧。这就是自由的味道。2.2 工具链准备安装与初识编辑器首先去Godot官网下载最新的稳定版目前是4.2.x。建议下载标准版Standard build它包含了所有功能。安装过程就是解压一个文件夹绿色便携甚至可以放在U盘里随身携带。第一次打开Godot你会看到项目管理器。点击“新建项目”给它起个名字比如“MyFirst3DBall”。选择渲染器如果你的显卡比较新支持Vulkan强烈建议选择“Forward”它能提供更好的光影效果和性能如果遇到兼容性问题再回退到“兼容性”模式。项目路径不要有中文或特殊字符这是一个好习惯。创建后进入主编辑器。界面可能有点陌生但别慌核心就这几块场景面板左上以树形结构展示当前场景的所有节点。这是我们构建游戏世界的地方。视口中间3D/2D视图预览和编辑场景。文件系统面板左下项目资源管理器所有场景、脚本、图片、模型都在这。检查器面板右显示当前选中节点的所有属性和参数修改它们就能改变节点行为。底部面板输出、调试、动画编辑器等会在这里打开。花几分钟拖动各个面板熟悉一下布局。接下来我们开始真正的建造。3. 构建游戏世界场景、节点与第一个3D对象Godot的核心哲学是“场景Scene驱动”。一个场景就是一棵节点树它可以是一个角色、一把武器、一个关卡甚至整个游戏界面。我们将从创建一个简单的游戏关卡场景开始。3.1 创建基础场景与地面在“场景”面板中你看到一个叫“Scene”的根节点。默认是Node3D类型这是所有3D节点的基类。我们先把它重命名为“Level”选中节点按F2。我们需要一个地面。右键点击“Level”节点选择“添加子节点”。在搜索框中输入“MeshInstance3D”添加它。这是一个用于显示3D网格的节点。选中新建的MeshInstance3D节点看右侧的“检查器”面板。找到“Mesh”属性点击旁边的下拉箭头选择“新建BoxMesh”。一个白色的立方体出现在视口中。这个立方体现在是我们的“原型地面”。我们需要把它压扁、放大。在检查器中找到“Transform”下的“Scale”缩放。将Y轴第二个值设为0.2将X和Z轴第一和第三个值设为10。现在你得到了一个扁平的长方形地面。为了让物理生效我们必须给地面添加一个碰撞体。选中MeshInstance3D节点点击检查器顶部的“添加子节点”按钮或右键节点选择“CollisionShape3D”。Godot会自动为其创建一个CollisionShape3D子节点。选中这个CollisionShape3D节点在检查器中点击“Shape”属性旁的下拉箭头选择“新建BoxShape3D”。你会看到一个绿色的线框包裹住地面这代表了物理碰撞的体积。关键点碰撞形状Shape的尺寸默认是(1,1,1)它基于父节点MeshInstance3D的缩放。因为我们把地面网格缩放了(10, 0.2, 10)所以碰撞盒也会自动同步这个缩放比例完美匹配可视网格。这是Godot一个非常方便的特性。注意在3D游戏中确保碰撞体与可视网格基本匹配至关重要否则会出现“视觉上没碰到但游戏判定碰撞了”或者“穿模”的诡异情况。对于简单形状Godot的自动同步很好用对于复杂模型可能需要手动调整碰撞形状或使用多个简单形状组合。3.2 创建我们的主角可控制的球体主角是一个受物理控制的球体。回到场景根节点“Level”右键添加一个新的子节点类型为“CharacterBody3D”。这是Godot 4中用于处理角色移动的推荐节点它内部集成了碰撞检测和物理响应比直接用RigidBody3D刚体更容易控制。将其重命名为“PlayerBall”。选中“PlayerBall”节点为其添加一个MeshInstance3D子节点。将其Mesh属性设为“新建SphereMesh”。你可以调整球体的半径在Mesh资源属性里比如设为0.5。同样为“PlayerBall”节点添加一个CollisionShape3D子节点。将其Shape属性设为“新建SphereShape3D”。调整SphereShape3D的半径使其与可视球体匹配例如0.5。现在我们需要让球体动起来。这需要脚本。选中“PlayerBall”节点点击检查器上方类似纸卷的图标“附加脚本”。保持默认设置语言GDScript模板默认点击“创建”。这会创建一个关联到该节点的脚本文件。4. 编写核心逻辑移动、摄像与交互双击新创建的脚本文件Godot的内置脚本编辑器会打开。我们将在这里编写控制逻辑。4.1 玩家移动脚本解析我们先编写一个基础的、使用物理力的移动脚本。请替换脚本模板中的内容为以下代码extends CharacterBody3D # 定义可调节的参数 export var speed 10.0 export var jump_velocity 8.0 export var mouse_sensitivity 0.002 # 获取摄像机节点 onready var camera_pivot $CameraPivot onready var camera $CameraPivot/Camera3D 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: # 左右移动鼠标控制水平旋转绕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(-60), deg_to_rad(60)) # 按ESC键释放鼠标 if event.is_action_pressed(ui_cancel): if Input.get_mouse_mode() Input.MOUSE_MODE_CAPTURED: Input.set_mouse_mode(Input.MOUSE_MODE_VISIBLE) else: Input.set_mouse_mode(Input.MOUSE_MODE_CAPTURED) func _physics_process(delta): # 每帧物理步进时调用处理移动和物理逻辑 # 初始化速度向量 var direction Vector3.ZERO # 获取基于玩家当前朝向的输入方向 if Input.is_action_pressed(move_forward): direction - transform.basis.z if Input.is_action_pressed(move_backward): direction transform.basis.z if Input.is_action_pressed(move_left): direction - transform.basis.x if Input.is_action_pressed(move_right): direction transform.basis.x # 归一化方向向量防止斜向移动更快 if direction.length() 0: direction direction.normalized() # 应用水平速度 velocity.x direction.x * speed velocity.z direction.z * speed # 处理跳跃仅在地面时 if is_on_floor() and Input.is_action_just_pressed(jump): velocity.y jump_velocity # 应用重力如果不在跳跃状态 if not is_on_floor(): velocity.y - 9.8 * delta # 简单模拟重力 # 调用父方法执行移动和碰撞检测 move_and_slide()代码要点解析export关键字这是Godot 4的一个强大功能。它将变量暴露在编辑器的检查器中你可以在不修改代码的情况下直接在编辑器里调整速度、跳跃力等参数实现快速迭代。_ready()场景加载完成后调用一次。这里我们锁定鼠标实现常见的“鼠标控制视角”操作。_unhandled_input(event)处理未消耗的输入事件。我们用它来捕获鼠标移动并计算摄像机的旋转。clamp函数用于限制俯仰角度防止摄像机翻转过头。_physics_process(delta)在固定的物理时间步长默认每秒60次中调用。这是处理移动、力和碰撞的最佳位置。transform.basis.z和transform.basis.x这代表了该节点局部坐标系的前方-Z和右方X。通过将输入映射到这些方向上我们实现了“按W向前走”是基于玩家当前朝向的而不是固定的世界坐标。move_and_slide()CharacterBody3D的核心方法。它根据velocity向量移动角色并自动处理与场景中其他CollisionShape3D的碰撞和滑动。调用后is_on_floor()等方法的状态会被更新。4.2 配置输入映射脚本里用到了move_forward、jump等动作Action我们需要在项目设置中定义它们。点击编辑器顶部菜单项目 - 项目设置。切换到“输入映射”标签页。在“动作”输入框输入“move_forward”点击“添加”。然后点击其旁边的“”号添加一个键盘事件选择“W”键。同样地为“move_backward”添加“S”键“move_left”添加“A”键“move_right”添加“D”键。添加“jump”动作并绑定“空格键”。添加“ui_cancel”动作Godot已有默认但检查一下通常绑定“Esc”键。4.3 设置摄像机我们的脚本引用了CameraPivot和Camera3D节点现在来创建它们。在场景中选中“PlayerBall”节点。右键添加一个子节点类型为“Node3D”重命名为“CameraPivot”。这个节点将负责摄像机的俯仰旋转。选中“CameraPivot”节点右键添加一个子节点类型为“Camera3D”重命名为“Camera3D”。选中“Camera3D”节点在检查器中调整它的位置。将“Transform”下的“Position”的Y值设为1.0让摄像机在球体上方1米处Z值设为-3.0让摄像机在球体后方3米处。这样就是一个经典的第三人称跟随视角。重要为了让摄像机平滑跟随而不是僵硬地绑在球上我们可以使用一个简单的插值。修改脚本在_physics_process函数的最后move_and_slide()调用之后添加以下代码# 平滑摄像机跟随可选让摄像机更柔和地跟随球体 var target_position global_transform.origin target_position.y 1.0 # 摄像机高度偏移 camera_pivot.global_transform.origin camera_pivot.global_transform.origin.lerp(target_position, delta * 10.0)lerp方法是线性插值让摄像机的位置每一帧向目标位置球体位置偏移移动一小段距离从而实现平滑的跟随效果避免剧烈抖动。5. 丰富游戏性收集物、UI与游戏状态现在我们的球可以滚了但游戏还需要目标和反馈。5.1 创建可收集的硬币在场景根节点“Level”下创建一个新的RigidBody3D节点重命名为“Coin”。为它添加MeshInstance3D子节点使用“新建CylinderMesh”作为网格。在网格属性中将“顶部半径”和“底部半径”设为0.5“高度”设为0.2让它看起来像个硬币。可以再添加一个OmniLight3D点光源作为子节点赋予一点发光效果。为“Coin”添加CollisionShape3D形状使用“新建CylinderShape3D”调整尺寸匹配网格。我们希望硬币被碰到后就消失并计分。为“Coin”节点附加一个新脚本。extends RigidBody3D # 定义一个信号当硬币被收集时发出 signal collected func _on_body_entered(body): # 当有物理体进入碰撞区域时调用 # 检查进入的是否是玩家球体 if body.is_in_group(player): # 发出收集信号 emit_signal(collected) # 播放一个简单的消失动画例如缩放至0 var tween create_tween() tween.tween_property(self, scale, Vector3.ZERO, 0.2) tween.tween_callback(queue_free) # 动画结束后从场景中删除自己在场景中选中“Coin”的根节点在检查器的“节点”标签页点击“组”旁边的编辑按钮为硬币添加一个组比如“coin”。方便以后用代码查找所有硬币。我们需要让玩家球体属于“player”组。选中“PlayerBall”节点同样在“节点”标签页为其添加“player”组。最后我们需要在“PlayerBall”的脚本中连接硬币的信号并处理得分。在_ready()函数末尾添加func _ready(): Input.set_mouse_mode(Input.MOUSE_MODE_CAPTURED) # 查找场景中所有的硬币并连接它们的收集信号 for coin in get_tree().get_nodes_in_group(coin): coin.collected.connect(_on_coin_collected) func _on_coin_collected(): # 增加分数这里我们先打印到控制台后面会连接到UI print(Coin collected!) # 在实际项目中这里会更新一个全局的分数变量并通知UI更新5.2 创建用户界面UIGodot的UI系统基于控件Control节点。首先我们需要一个独立的UI场景。在文件系统面板右键选择“新建场景”。创建一个CanvasLayer节点作为根。CanvasLayer可以确保UI渲染在所有3D内容之上。重命名根节点为“UILayer”。然后为其添加一个Control子节点命名为“MainUI”。选中“MainUI”在检查器中找到“布局”下拉菜单选择“全矩形”。这样UI就会铺满整个屏幕。现在添加显示元素。右键点击“MainUI”添加一个Label子节点重命名为“ScoreLabel”。在检查器中调整其“文本”为“分数0”。你可以拖动它在视口中的位置或者使用“锚点”和“边距”进行精确定位推荐。例如将锚点的水平预设设为“左上”垂直预设也设为“左上”然后调整上边距和左边距为20像素。再添加一个Button节点重命名为“RestartButton”。设置其文本为“重新开始”并调整位置比如锚定在中心。暂时先隐藏它取消勾选检查器顶部的“可见”属性当游戏结束时才显示。为UI场景保存命名为“UI.tscn”。回到我们的主场景“Level”。在场景根节点下添加一个子节点类型为“Node”作为UI的挂载点重命名为“UI”。然后点击检查器中的“场景”属性旁边的链接图标选择我们刚保存的“UI.tscn”场景。这样就将UI场景实例化到了主场景中。我们需要在“PlayerBall”脚本中引用UI并更新分数。首先在脚本顶部声明一个变量onready var ui $”../UI/UILayer/MainUI”路径根据你的节点结构调整。然后修改_on_coin_collected函数var score 0 onready var score_label ui.get_node(“ScoreLabel”) # 获取分数标签节点 func _on_coin_collected(): score 1 score_label.text “分数%d” % score # 检查是否收集完所有硬币 if score 5: # 假设有5个硬币 _on_game_won() func _on_game_won(): print(“You Win!”) # 显示重新开始按钮 var restart_button ui.get_node(“RestartButton”) restart_button.visible true # 可以暂停游戏物理 get_tree().paused true最后为“RestartButton”添加按下信号的处理。在UI场景中选中“RestartButton”在检查器的“节点”标签页切换到“信号”子标签找到“pressed()”信号双击连接选择“MainUI”节点创建一个新函数。在该函数中写入get_tree().reload_current_scene()。这会重新加载当前场景实现重启。6. 打磨与发布优化、调试与导出6.1 环境与光照目前我们的场景只有默认的灰色环境光。为了让游戏看起来更舒服我们需要设置世界环境。在主场景中选中根节点“Level”。在检查器中找到“世界环境”属性World Environment点击下拉箭头选择“新建Environment”。点击新建的Environment资源进行编辑。你可以调整“背景”模式为“纯色”或“天空”并设置颜色。在“光照”部分可以启用“环境光”并设置其颜色和能量这相当于一个全局的基础光照能照亮所有物体的背光面。为了更好的效果可以添加一个DirectionalLight3D平行光节点到场景中模拟太阳光。调整它的旋转角度照亮场景。6.2 调试技巧与常见问题问题球体移动“滑冰感”太强停不下来。原因CharacterBody3D的move_and_slide()默认会保留一部分速度。我们在_physics_process中直接设置了velocity.x和velocity.z但没有处理减速。解决在应用输入速度前先给水平速度一个衰减。可以在计算direction后添加# 水平速度衰减模拟摩擦力 velocity.x lerp(velocity.x, 0.0, delta * 5.0) velocity.z lerp(velocity.z, 0.0, delta * 5.0) # 然后再 velocity.x direction.x * speed ...lerp在这里用于平滑地将速度向0衰减。问题摄像机旋转时球体也跟着乱转。原因我们的摄像机旋转代码是直接作用在PlayerBall节点rotate_y和CameraPivot节点上的。rotate_y会旋转整个球体节点导致其前进方向改变。解决这是设计选择。对于“坦克式”控制移动方向独立于视角应该将水平旋转应用在一个独立的“YawPivot”节点上而球体模型作为其子节点。对于“第一人称/第三人称”控制移动方向跟随视角当前做法是合理的。根据你的游戏类型调整节点结构。问题导出项目时报错或找不到模板。解决首次导出前需要下载导出模板。点击编辑器顶部“项目” - “导出”。在“导出预设”中添加一个预设如“Windows Desktop”。在右侧的“功能”中选择“Export With Debug”调试导出或“Export”发布导出。底部会提示“模板缺失”点击“下载”按钮Godot会自动下载对应平台的导出模板。确保下载的模板版本与你的Godot引擎版本完全一致。6.3 导出为可执行文件点击编辑器右上角的“导出项目”按钮或通过“项目”-“导出”菜单。确保你已经为你的目标平台如Windows添加了导出预设并下载了模板。在预设中你可以配置应用名称、图标、文件包含规则等。点击“导出项目”按钮选择一个输出路径和文件名例如MyFirst3DBall.exe。Godot会打包所有资源并生成可执行文件。运行它你的第一个Godot 3D游戏就诞生了7. 进阶思考与项目扩展完成这个基础原型后你已经走过了Godot 3D开发的核心路径场景搭建、节点组织、物理交互、输入处理、脚本逻辑、UI连接和项目导出。但这仅仅是开始。基于这个框架你可以尝试以下扩展让游戏变得更完整添加敌人与生命值创建另一个CharacterBody3D作为敌人为其编写简单的AI如朝玩家移动。为玩家添加生命值变量在碰撞到敌人时减少并在UI上显示。设计多个关卡将当前的“Level”场景保存为“Level1.tscn”。然后新建场景“Level2.tscn”设计不同的地形和障碍。创建一个“GameManager”单例Autoload来管理关卡切换和全局状态如总分数。加入音效使用AudioStreamPlayer3D节点为硬币收集、跳跃等动作添加空间音效使用AudioStreamPlayer节点添加背景音乐。优化性能对于大量重复的物体如硬币考虑使用MultiMeshInstance3D进行合批渲染。使用Godot的性能分析器调试器 - 分析器查看帧时间和Draw Call。探索更高级的特性尝试Godot的着色器语言GLSL或Godot自带的着色器语言来制作特效使用动画播放器AnimationPlayer为角色添加跑动、跳跃动画或者用导航网格NavigationRegion3D为敌人实现更复杂的路径寻找。Godot的魅力在于它用一个相对简洁的体系覆盖了从原型到完整产品的大多数需求。它的文档和活跃的社区是宝贵的学习资源。记住学习游戏开发最好的方式就是动手去做遇到问题拆解问题然后解决它。从这个滚动的球开始去构建你想象中的世界吧。
返回列表