打开 Godot 官方演示仓库,如何 10 分钟跑通自己的第一款游戏?零基础五步走到寻路
【免费下载链接】godot-demo-projectsDemonstration and Template Projects项目地址: https://gitcode.com/GitHub_Trending/go/godot-demo-projects
刚把 godot-demo-projects 拉下来,打开就愣住了:2d、3d、mono 一堆文件夹,四十多个演示项目,到底从哪个下手?这是 Godot 引擎官方的演示项目集,每个文件夹都是一个能直接跑起来的小游戏,也是学 Godot 游戏开发最省时间的材料。这篇文章给你一条从零基础到寻路 AI 的具体练习路线。
先看难度再挑演示项目:一条 10 分钟的上手路线
仓库里 demo 从十几个文件到完整工程都有,挑的时候别挑"最酷"的,挑"能完整读懂"的。三个有代表性的候选:
| 演示项目 | 你能学到什么 | 需要先会什么 | 跑通耗时 | 难度 |
|---|---|---|---|---|
| Pong | 场景结构、信号驱动碰撞 | 知道怎么运行工程 | 10 分钟 | ★ |
| 躲避小怪(Dodge the Creeps) | Timer、场景实例化、计分 HUD | 认识节点是什么 | 半小时 | ★★ |
| 有限状态机(FSM) | 用状态机组织角色动作 | 2D 移动能读懂 | 一小时 | ★★★ |
一句话概括这条梯度:Pong 用来"跑通",Dodge 用来理解"一个游戏怎么拼起来",FSM 用来看"角色代码该怎么组织"。3D 的 demo 都依赖模型资源,先放到 3D 那一节再说。
跑通 Pong 只需要四步:
- 本地没有代码就克隆一份:
git clone https://gitcode.com/GitHub_Trending/go/godot-demo-projects- 打开 Godot 编辑器,选"导入",指向
2d/pong里的 project.godot。 - 按 F5 运行。球开始弹、挡板跟鼠标走——恭喜,第一个演示项目已经跑通。
- 打开 pong.tscn 场景,你会看到 Ball、Left、Right、Wall 四个节点,每个都挂着脚本;再把 ball.gd 通读一遍,整个游戏一共就 12 行核心逻辑。
到这里你已经会三件事:导入工程、运行、读场景树。后面所有 demo 都是这个套路。
用 Pong 串讲 2D 游戏的三个核心:移动、碰撞、计分
Pong 小到离谱,但 2D 游戏开发里最关键的三件事——移动、碰撞、计分——它全占了。下面按四步拆开看。
第 1 步:整个游戏就是 4 个节点
场景里没有"主控制器",没有全局管理器,Ball、Left、Right、Wall 各自挂一个脚本,靠信号互相喊话。这个"去中心化"的结构是 Godot 的基本哲学:节点自治,事件通信。
第 2 步:球只干两件事
ball.gd 的_process里真正起作用的就两行,下面是球每帧做的事:
func _process(delta: float) -> void: _speed += delta * 2 position += _speed * delta * direction关键细节在第一行:_speed += delta * 2让球每回合越打越快,reset()里才把它拉回默认值——这就是为什么拉锯几拍之后球会变得很难接。
第 3 步:挡板教会球往哪飞
挡板脚本 paddle.gd 干两件事:按输入上下移动自己,被撞到时改写球的方向:
func _process(delta: float) -> void: var input := Input.get_action_strength(_down) - Input.get_action_strength(_up) position.y = clamp(position.y + input * MOVE_SPEED * delta, 16, _screen_size_y - 16) func _on_area_entered(area: Area2D) -> void: if area.name == "Ball": area.direction = Vector2(_ball_dir, randf() * 2 - 1).normalized()注意这里没有射线检测,碰撞靠 Area2D 的area_entered信号;而球的 y 方向分量每次命中都随机化——手感就是从这种小随机里来的。
第 4 步:加一个计分,四行搞定
原 demo 没做计分,我们补上。wall.gd 里原本只有area.reset(),按下面思路加个计数器:
var score := {"left": 0, "right": 0} func _on_wall_area_entered(area: Area2D) -> void: score["right"] += 1 # 球撞了左墙 → 右边得分 area.reset() # 这里省略了更新 HUD 的文本赋值,核心就这一句存好再运行,球出界时角落的分数就该 +1 了。能改、能跑、能验证,Pong 就算彻底消化:移动是"向量乘 delta",碰撞是"信号",计分是"一个变量"。
从 2D 跨到 3D:你会撞上 3 个问题
先看目标:3d/squash_the_creeps里你可以前后左右移动、跳跃,从怪头顶踩过去得分,相机跟随,角色落地还有压扁的挤压动画。
倒推它为什么能跑:玩家脚本挂在 CharacterBody3D(带碰撞的角色刚体)上,_physics_process里每帧做三件事——把输入变成速度、加跳跃、扣重力,最后交给move_and_slide()处理碰撞:
if is_on_floor() and Input.is_action_just_pressed(&"jump"): velocity.y += jump_impulse velocity.y -= fall_acceleration * delta move_and_slide()最后那行velocity.y -= fall_acceleration * delta前面源码里特意写了注释:重力必须每帧都加,哪怕角色正站在地面上。跳一下就记住这条 2D 到 3D 的转换表,能少走很多弯路:
| 你 2D 里写的 | 3D 里变成 | 为什么变 |
|---|---|---|
| Vector2 | Vector3 | 多一个轴,"上下"成了坐标的一部分 |
| move_and_slide(velocity) | 先设 velocity 属性再调 move_and_slide() | 3D 版一次调用可能分多次子步滑开碰撞 |
| 进信号判断碰撞 | 循环查 get_slide_collision_count() | 子步碰撞不会逐个发信号,得自己遍历 |
Vector2 不够用了,朝向得换套写法
移动输入从两键变四键,direction 变成 Vector3。2D 里翻转贴图就行,3D 里要真正转节点朝向,demo 用的是basis = Basis.looking_at(direction)——把角色的正前方对准移动方向。
🤔 角色为什么会陷进地面或飘在半空
两个高频症状,同一个病根:重力没每帧加,或者只加了"空中"的重力。跳起来的那一帧靠velocity.y += jump_impulse给一个冲量而不是固定高度,落地判断靠is_on_floor(),而它只有在持续碰撞的帧才可靠——所以重力那行绝对不能写进if not on_floor里。
为什么踩怪判定要套一层 for 循环
move_and_slide()在一帧内可能分多次子步推开重叠,每次子步都是一次碰撞记录,但不会发信号。所以踩怪逻辑要循环get_slide_collision_count(),再检查碰撞法线和向上方向的点乘大于 0.1(确认"从头顶踩下"而不是蹭到侧面),命中就压扁怪物、给个反弹速度,然后break防止一只怪算两次分。
物理发顿、边缘发糊:两个调优时真实的坑
角色走起来一"顿"一顿,问题出在哪
做 3D 第一人称时会遇到一个怪现象:代码逻辑完全正确,但角色在低帧率机器上看起来一顿一顿,像定格。原因不在你的移动代码,而在物理和渲染的节奏错位——物理按固定步长跑(默认 60Hz),渲染按显示器刷新率跑(可能是 144Hz),位置只在物理步更新,中间的画面是"冻"的。
3d 文件夹下的 physics_interpolation 演示就是专门讲这个的,它的修复方案是一行工程设置:开启物理插值(项目设置里common/physics_interpolation=true)。引擎会在相邻两个物理步之间自动插值渲染位置,你的代码一行不用改。反过来也看看 2d 里的 physics_platformer:它的玩家干脆是 RigidBody2D,在_integrate_forces回调里手控加速度和摩擦,文件顶部 WALK_ACCEL、WALK_MAX_VELOCITY 那些常量就是"手感"旋钮——把 1000 改成 500 再跑一次,角色立刻变肉。
边缘锯齿和拖影:三个抗锯齿旋钮
画面另一个高频抱怨是锯齿。3d/antialiasing演示把三个选项做成了一个可切换面板,对应三行核心代码:
# MSAA:质量最好,开销高,对透明贴图边缘没效果 get_viewport().msaa_3d = index as Viewport.MSAA # TAA:连高光锯齿都能抹平,但运动物体会有拖影 get_viewport().use_taa = index == 1 # 屏幕空间抗锯齿:快,代价是整幅画面轻微发糊 get_viewport().screen_space_aa = int(index) as Viewport.ScreenSpaceAA实际选择路径:低端机选屏幕空间抗锯齿,高配机要画质选 MSAA,中间档选 TAA——如果 TAA 拖影明显,把渲染缩放降到 75% 再开 FSR 锐化,是 demo 里给出的组合拳。
逆向官方寻路演示:角色到底怎么在网格上找到路
先看效果:在2d/navigation_astar里点一下地面,小飞船绕开所有障碍飞过去,身后留下一条折线路径。
它的实现有个反直觉的地方:寻路逻辑挂在地图上,不在角色身上。pathfind_astar.gd 继承的是 TileMapLayer,里面维护一个 AStarGrid2D——导航网格,简单说就是"角色能走哪些格子"的地图。角色只是照着路径点走。
_obstacle 集合就是"你画了什么"。_ready()里先把地图逻辑变成网格并标记障碍:
_astar.cell_size = CELL_SIZE _astar.update() for pos in get_used_cells(): _astar.set_point_solid(pos) _path = _astar.get_point_path(_start_point, _end_point)前几行的含义:把已画出的格子全部标为实心(不可走),起点终点标成可走之后,一次get_point_path就拿到完整路径,画线交给_draw()。
角色别瞬移过去,要"开"过去
character.gd 的精髓是把角色当辆车:每帧算"理想速度"(指向下一路点的方向乘最大速度),用"理想速度减当前速度"得到转向力,再除以质量 MASS 累加进速度——质量就是惯性,车不会急停。走到距路点小于 ARRIVE_DISTANCE 就算到达,弹出这个点,取下一个。整条路径就是被一辆有惯性的车"开"过去的,所以转弯有弧度,不呆板。
下一步做什么
- 今天就做:按第一节的四步跑通 Pong,把球速常量
DEFAULT_SPEED从 100 改成 60,看拉锯手感差多少。 - 本周内:给 Dodge the Creeps 加一个倒计时 HUD,重点读懂 Timer 信号和
mob_scene.instantiate()的实例化流程。 - 练手 3D:把你自己写的 2D 角色移动照搬进踩怪演示,故意注释掉"每帧加重力"那行,亲眼看看角色怎么飘起来。
- 理解寻路:在导航演示里抹掉一格障碍重跑,确认路径会绕开;再试着把 MASS 改大,观察车感变化。
- 用 C# 的话:翻到
mono/目录,同样的演示有等价实现,对着 GDScript 版看语法差异,比啃教程快。
【免费下载链接】godot-demo-projectsDemonstration and Template Projects项目地址: https://gitcode.com/GitHub_Trending/go/godot-demo-projects
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考