Godot Beginner
How to make a Video Game - Godot Beginner Tutorial
好多人都推荐从这个开始入门,看起来就是做了一个 2d 横版跳台游戏,感觉可以。
从 2d 游戏开始,容易实现,容易获得满足感,容易坚持下去,嗯。
基础
先是介绍了一些基础概念,也就是 Sprites、Models、Textures、Sounds 这些 Assets,游戏引擎自然是把它们结合在一起做游戏的,不是让你造轮子的。
你别说,用做饭比喻还真是很合理。
那既然是 beginner,assets 就全用 brackey 提供的好了。
直接创建项目在左下角的文件系统建好游戏目录下需要的各种文件再导入资产。
NOTE
Godot 中实现一切的逻辑都以 Node 为基础。使用 Scene 把你的一些 Node 打包成为实现一个功能的模块。封装你的 Node 为 Scene,再把不同的 Scene 组合,最终形成你的游戏。
角色
在引擎的场景栏中创建一个 2D 场景的 root。保存后 F5 运行,需要选择一个 Main Scene,非常合理。
选择当前 Scene 作为节点运行,然后自然是无事发生。
我们需要添加我们的人物。关掉窗口,创建一个新场景,然后添加一个 CharacterBody2D 节点;我们要创建一个可以运动的角色,所以再给这个节点添加一个 AnimatedSprite2D ,在右侧的检查器中选择 Animation 然后添加一个 Sprite Frame,之后引擎的下方就会打开一个动画窗口了。
选择“从精灵表中添加帧”,选择主角的那张图,调整一下 grid,选择 idle 的 4 帧添加。F 将摄像机中心放到角色身上,再滚滚轮放大。
Godot 默认会让材质有一些滤镜效果,但对于像素画来说没必要,在 project setting 中把纹理过滤关掉。这样就可以播放动画了,看起来效果不错;可以把 fps 改高一些。
之后移动一下人物的位置。
会看到引擎提示节点没有 shape,因为这是一个物理节点,要有一个 shape,再 CharacterBody2D 下增加一个 CollisionShape2D,改一下形状,不需要特别精准。
之后去 Game scene,拖出 player,我们还需要一个摄像机,还是添加 Node Camera2D,改一改 zoom,拖动焦点到人物身上。F5 运行即可看到 idle 动画
这里记得在 sprite animation 里打开加载后自动播放。
之后,为了让角色移动,需要添加一些脚本,给 player 添加一个使用默认移动模板的 gd 脚本。
直接 F5 运行,我们的角色就掉下去了,因为我们还没有地面,合理。
切回游戏,添加一个 Node StaticBody2D,通常用于作为地面之类的静态碰撞,给他也添加一个碰撞 shape. 给他添加一个 CollisionShape2D,选择 WorldBoundaryShape2D,它会在某个方向上无限延展。
现在角色不会掉下去了,可以操控,之后可以改一下角色的速度、跳跃高度。
世界
创建 2D 世界的最快方法是使用 tiles,在 grid 中摆放 tiles 即可(想起以前做马里奥的改版了)。
首先需要添加一个 Node TileMap,但发现 TileMap 现在是 Deprecated,推荐使用 TileMapLayer,看了文档,看起来是多个 TileMapLayer 可以实现和 TileMap 相同的效果。
不过在这里想必可以忽略 TileMapLayer 和 TileMap 的区别,毕竟从说明上看,我一个 Layer 也可以当 完整 Map 对不对。
设置好 TileSet 就可以准备画地图了。然后地图什么的随便画画。
让我怀念起小时候玩 wii 的马里奥改版了。
完成后运行,人物就掉下去了。我们需要给 Tile 添加物理碰撞,在 Tile Set 下增加 physics layer。之后在下面的 Tile Set 窗口中绘制即可。C/F 两个快捷键切换清除、绘制模式
最后,让摄像机跟踪人物即可,只需要把摄像机拖拽作为人物的子 Node,可以开启 Position Smoothing.
平台
可以移动或者静止。
新建一个 scene,增加 AnimatebleBody2D,如果想让一个 Node 移动那么就用这个。再给他添加一个 Sprite2D,添加纹理。我们有好多平台,可以自己定义显示的部分。在 Region 中。
编辑后,给他一个碰撞 shape 即可。
之后,保存 scene,回到 Game 中把他拖拽出来,it works!
不过他现在并不能从下面跳上去,改一下碰撞的属性即可。
发现角色的图层在平台下面,虽然可以通过改变 Node 的上下顺序修复这个,不过还是改角色的 z-index 比较好。切到角色的 scene,之后修改 Ordering。
如果我们想要一个移动的平台呢?
增加一个平台,给他一个 AnimationPlayer,新建动画,Godot 的这个 Node 非常强大,可以移动几乎所有东西。
之后切换到平台创建关键帧即可。
但他现在是一个 loop,我们把他改成 ping-pong。还可以修改动画长度。
最后,记得设置他为 autoplay。
可拾取物品
我们来增加一个可以拾取的金币。
新建一个 scene,增加 Area2D Node,它可以检测碰撞,但是并不会实际阻碍我们的其他实体。给他增加一个 AnimatedSprite2D,从精灵表添加帧,设置金币的长宽然后拖拽。
设置 10FPS,记得选择自动播放。
添加 CollisionShape2D 即可保存。
添加到游戏后,我们撞到他没什么反应,这是自然,我们需要添加一个脚本。
再 coin 这个 scene 中添加一个 script,这次选择默认模板即可。
默认有两个函数,_ready 会在游戏开始时执行,嗯,可以用来初始化。不过我们并不需要这俩函数,只想在人物碰到金币时触发一些事件。
我们需要使用信号。godot 有很多内置的 signal。
我们在 Area2D Node 中可以看到很多 Signal,我们双击 body_entered 然后点击 connect。
不过不管哪个 Body 碰到金币都会触发,我们只希望角色触发,可以通过改变他们的碰撞 Layer 解决,把主角的 Layer 放到 2,至于 coin 的 Layer,我们还不需要它处于其他 Layer,只希望他在 Layer 2 检测碰撞,所以修改 Mask。
我们需要在碰撞时销毁金币,调用 queue_free() 即可。
死亡 1.0
现在我们的角色就算从悬崖上掉下去也什么都不会发生。
首先我们需要限制摄像机,角色掉下去的时候不能一直下坠。可以简单的在摄像机的 inspector 里设置 limit,配合 ruler 测量即可。
然后要设置一个 killzone,角色调出地图就会死亡。
新建一个 scene,增加 Area2D ,记得修改碰撞的 mask,只检测角色。我们需要给他增加一个区域,毕竟我们想复用不同形状的 killzone。
把他增加到我们的 Game scene 中,给他一个碰撞,选择 WorldBoundaryShape2D. 之后调整他的位置。
回到 killzone,给他一个脚本,这一次模板为 Empty。然后回到节点中,给他一个 signal body_entered。
我们并不想碰到就直接重开,给一点延迟,所以给 killzone 一个 Timer Node,设置下时间,以及 oneshot。
代码中增加 @onready var timer = $Timer,这样就可以调用我们的 Timer 了。
Node 都是树形结构,例如我们在 Game 里想调用 Camera,那么此时他的路径就是
$Player/Camera
所以我们要做的是在角色与 killzone 碰撞的时候启动 Timer,然后再节点中再选择一个信号,即 timeout,这样在计时器结束后调用这个 callback。
这个 callback 的任务就是重新开始游戏。
世界 2.0
我们可以把游戏中的物品放在一起,单纯的创建一个基础的 Node,然后把金币、平台等分类放好。
之后,可以给 TileMap 增加一层,用于绘制背景。之前画好的已经是一层了,给他一个名字;之后新建一个即可,记得调整顺序,确保背景优先绘制。
不过我们用的是 TileMapLayer,根据文档也很简单,明显官方的意思是让你主体、背景分开;所以我们可以新建一个 TileMapLayer 然后调整他在主地图的上面,保证他处于背景。之后重新创建 TileSet 绘制即可。然后可以跟之前一样,新建一个普通的 Node 把两个 TileMapLayer 都放进去。
敌人
创建 scene,增加一个普通的 Node2D,给他一个 AnimatedSprite2D 然后做设置 Sprite Frames,先做一个 idle 的动画。
我们可以复用之前的 killzone,给他一个符合史莱姆形状的碰撞即可。把他拖拽到游戏中即可。
为了让我们的史莱姆移动,当然可以使用现成的 Node,但我们可以使用一个脚本,让他一直移动直到撞墙,再向相反方向移动即可。
脚本选择默认的模板,我们需要修改的是 _process,因为它每一帧都会执行,我们也需要每一帧移动我们的史莱姆。
它的参数是 delta,指示距离上一帧的时间,我们可以用 速度 * delta 即可让史莱姆的速度恒定,不管帧数高低。
还需要控制它的方向,我们需要检测碰撞,给史莱姆增加一个 RayCast2D Node。
左右方向都添加一个,然后就可以跟之前的 Timer 一样,引入到脚本里。
只要移动时让方向改变即可。
那如何让他的动画方向改变?AnimatedSprite2D 提供了 Flip H 选项,方便快捷,在脚本里控制即可。
死亡 2.0
我们给死亡加一些慢动作效果。
在 Killzone 的 Timer 中调整时间尺度,修改脚本。
此外,我们还想让角色死亡时调出地图,就像马里奥那样,在碰撞函数的参数里已经能获取到角色的引用了。
角色 2.0
目前我们的角色都用的 godot 移动脚本模板,打开角色的脚本。
可以发现其中的函数是 _physics_process,它默认固定每秒执行 60 次,跟 _process 不同。
我们可以重新绑定一些输入热键。godot 提供了 Actions 系统方便我们自己创建动作并且绑定一些按键。
项目设置里选择 input map,然后我们就可以添加动作了,比如跳跃、移动等等。然后点击旁边的 + 即可设置监听的按键。
然后回到脚本,发现 godot 提供了一些默认的动作,我们把它们更换成刚刚添加的动作即可。
之后,要修正角色移动时的朝向。这一步和之前的史莱姆差不多。
来到角色的 scene 增加两个动作,run、jump
还是回到脚本,让 godot 播放动作
文本
添加一个 Label 节点即可,可以修改成像素字体,适合我们的风格。
分数
这需要一个类似 Game Manager 的东西,我们可以创建一个普通的 Node,重命名为 Game Manager。
我们并不需要一个 Node 2D,因为他并不需要旋转或者改变位置什么的。
增加一个脚本,这次我们要自己实现一个函数。
但是如何调用这个函数呢?我们需要在捡起金币的时候调用这个函数。
在金币的脚本中如何操作呢?如果使用普通的路径那你会得到 $"../../GameManager",这个非常奇怪,因为 GameManager 在金币节点的高层。
不推荐使用这种路径。
我们让 Game Manager 节点成为 unique,这样就可以使用 %GameManager 直接访问这个单例。
音频
需要一个 AudioStreamPlayer2D 播放音乐,此外,他默认不是 Loop,要修改一下。双击音乐资产即可。
可以在音频选项卡增加 Bus,让音频在上面播放,即可分类调整音乐的大小。
但发现每次死亡,音乐都会重置,比较快的修复方法是将音频制作成一个 scene,然后在项目设置中导入,作为全局效果。
对于拾取金币,我们的做法类似。
但注意,金币被销毁了那他的音效自然也不会被播放。
这里可以给金币一个 AnimationPlayer,然后在 AnimatedSprite2D 中,给可见度一个关键帧,毕竟吃了金币就不需要它可见了。
godot 会自动帮我们创建一个 Reset track。
对于 CollisionShape2D 也如此,给 disabled 一个关键帧。
给你的音效的 Play 一个关键帧,可以在下面的选项卡中方便的修改。
选择 Reset 轨道即可看到初始状态,选择我们增加的 pickup 就可以看到拾取后的状态。非常方便。
我们把关键帧移动到开头,在播放完动画和音效后,我们添加轨道 Call Method Track,godot 允许我们调用方法,非常 cool,
我们在 Coin 上调用 queue_free() 即可,而我们什么代码都不需要写。
最后一步,在脚本中移除 queue_free 而是调用 animation_player.play("pickup")
导出
首先需要下载导出模板,因为他们比较大,所以没有被包含在 godot 中,需要你手动下载一次。
然后在项目中选择导出即可。
very ez
结语
跟着做了一遍,感觉 godot 做 2D 游戏还是非常方便的。不知道做 3D 是什么情况。
可以先 2D 练习一下吧。
这让我回忆起来儿时做马里奥改版的日子,下次可以尝试复刻一个类似马里奥的游戏试试。