Godot-制作-像素游戏-B站教程-Notes

参考

P1. 最后成果、资源导入、项目设置、角色移动基础

将主场景保存在工作目录下的根目录,而不是子目录.

运行游戏之前要选择一个场景,也就是一个 .tscn 文件, scn 应该是 sceen 的缩写.

需要设置更适合像素风的窗口大小,在 Project -> Project Setting -> Display -> Window 下设置.

如:

还需要修改下面的 Stretch -> Mode -> 2D

场景是 nodes 的集合. nodes 是挂载在场景之下的. nodes 为树状.

字节点的移动是相对于父节点的.

选择 KinematicBody2D 作为角色的节点. 其为物理碰撞体.

StaticBody 用于游戏中不会移动的物体.

RigidBody 用于使用物理系统运动的物体.

一个显示了很多帧的图片,可以通过调整右侧边栏的 Animation -> Hframes 将其合并. Hframes 中的 H 是 Horizontal.

将脚本创建到与图片同一个文件夹并挂载.

脚本基于哪一个节点创建的就继承哪一个节点的对象, 日:

1
extends KinematicBody2D

比如这个脚本挂载到一个 KinematicBody2D node 之下,当场景中的这个节点准备就绪时,ready 函数就运行.

_physic_process(delta) 函数在每次物理引擎更新时调用.

delta 变量是一个恒定常量,指上一帧花费的时间.

任何不懂的代码可以直接在 Search Help 中搜索.

获取输入使用 Input, 要让人物移动,需要使用 move_and_collide(velocity) 函数.

Vector2.ZERO 的含义就是 (0, 0).

P2. 关于delta、优化移动

回调函数 , 回调实际上就是重复执行这个函数.

_physic_processdelta 的值不会变,一直是 1/60 (也就是说固定 1/60 秒调用一次). 要区分其和 _process 中的 delta.

  • _physic_process 和真实时钟同步. 是固定的。
  • _process 和运行的帧率有关

速度乘以 delta 的目的是将速度从 x 每帧变成 x 每秒.

原本,一帧处理完之后,物体才会从一个位置移动到另一个位置.

使用 Vector2 对象的成员函数 normalized(), 返回一个 unit vector.

模拟摩擦力需要用到 move_toward() 成员函数,理解为向这个给定的向量靠近, 后面加速率.

设定最大速度用 limit_length() 成员函数:

1
velocity = velocity.limit_length(MAX_SPEED * delta)

P3. 碰撞、move_and_slide()

给 KinematicBody2D 添加碰撞. 点击 KinematicBody2D 节点然后添加 CollisionShape2D 节点.

在右侧边栏选择形状 CollisionShape2D -> Shape

选中子节点后按住 Alt 可以只移动子节点.

可以给 StaticBody2D 添加 CollisionPolygon2D, 然后可以自己画出形状. (利用上边栏多出的选项)

画出后 CollisionShape 还不可见,需要在左上角边栏里的 Debug -> Visible Collision Shapes 打开.

使用 move_and_collide() 函数不能让你沿着碰撞体滑动. 可以使用 move_and_slide() 解决,但是需要去除 delta.

1
velocity = move_and_slide(velocity)

设置 Window -> Stretch -> Aspect -> keep.

P4. 场景、YSort节点

讲解部分

Godot 可以从已有场景中的节点来创建新的节点. (将一个节点设置为一个单独的 scene, 然后拖拽到新的场景中)

点击一个节点 -> Save Branch as Scene. 先把右侧边栏中的 Transform 下的坐标设置为 (0, 0), 不然你放置的位置是相对原点, 而不是准确的你想要的位置.

将一个物体设置为单独的场景方便管理.

利用 YSort节点 来显示层次关系. 其判断谁在表面谁在下面是通过中心点的 Y 值.


可以看到 Y 值大的在表面.

点击一个节点,然后 Change Type -> Ysort.

步骤总结

在一个节点下添加场景:

  1. 创建一个节点,选中这个节点
  2. 将场景拖拽到地图中,可以看到这个场景挂载到了这个节点下. (一个场景就是节点的集合)

P5. 角色动画-AnimationPlayer

讲解部分

给角色的节点添加 AnimationPlayer 节点.

点击 AnimationPlayer 节点,在下边栏中点击 Animation -> New

Snap 是每一帧的间隔时间, 右上角侧是总时间. 右下角可以调整 timeline.

可以在编辑器中预览动画.

在 Player 脚本内访问 AnimationPlayer 节点.

写在 _ready() 中, 如:

1
2
3
4
var animationPlayer = null

func _ready():
animationPlayer = $AnimationPlayer

$get_node() 的缩写,可以用来获取树中的节点.

或者写成不需要 _ready() 函数的形式:

1
onready var animationPlayer = $AnimationPlayer

可以查看 AnimationPlayer 的文档如何使用 Animation.

使用 AnimationPlayer 类的成员函数 play(), 其中一个参数是动画名:

1
animationPlayer.play("IdleRight")

步骤总结

P6. 利用 AnimationTree 设置角色四方向动画

在 Play 下添加一个节点 AnimationTree, 需要现在右侧边栏中 AnimationTree -> Anim Player 中给它分配一个 AnimationPlayer.

AnimationTree 有很多中结构,需要在右侧边栏中选择 AnimationTree -> Tree Root, 这里选择的是 AnimationNodeStateMachine.

同样要勾选 Animation -> Active -> on, 来 activate 这个 tree.

在下边栏中右键可创建动画.

添加 Blendspace2D, 这个节点的含义大概是将多个动画放在一起,通过一个二维向量来判断播放哪一个. 注意这里的坐标系和二维游戏坐标系不同. 切换到虚线模式.

可以添加几个 Blendspace2D 动画的转换. 即什么条件下从一个动画切换到下一个动画.

如:

1
animationTree.set("parameters/Idel/blend_position", input_vector)

这个的含义大概是设置 “parameters/Idel/blend_position” 这个动画,然后通过 Input_vector 这个向量来设置 前者的值.

set 第一个参数是 AnimationTree 右边栏的参数.

P7. 草地背景和Auto Tile

重新设置一下.

第一个方法设置背景:

设置一个 regon, 点击图片 Import -> Flags -> Repeat -> Enabled -> reimport, 之后先在右边栏 Regon -> Enabled -> on, 然后在下边栏 -> Textureregion

第二个方法,添加一个 TextureRect 节点. 添加图片后,在右边栏设置 Stretch Mode -> Tile, 就会自动平铺.

关于 Tile 的部分,添加一个 TileMap 节点, 先设置一部分,在右边栏 Cell -> Size 两个 16, 然后 TileMap -> Tile Set -> TileSet, 点击这个 TileSet 可以看到 Resource 的参数.

在 Tile 面板中一般添加 Autotile

Bitmask 的作用不太清楚.

Shift + F12 可以将 TileMap 全屏。

P8. Autotile碰撞

给一些 tile 增加碰撞的特性.

P9. 攻击动画和State Machine

讲解部分

enum 是从 0 开始的.

设置键位: Project -> Project Settings -> Input_map, 然后就可以设置, 添加.

同样添加动画.

在动画的最后一帧添加一个 track: Add Track -> Call Method Track, 这一帧用来调用一个函数.

总结步骤

添加并引用动画的步骤:

  1. 创建一个节点, 添加 Sprite 节点 (或 AnimatedSprite 节点) 并添加含有多帧画面的图像
  2. 若是 Sprite 节点,则再添加一个 Animation 节点如 AnimationPlayer, AnimationTree
  3. 设置动画
  4. 在挂载的脚本中引用动画,先用 onready var animatedSprite = $AnimatedSprite 这种形式来获取动画节点 (一个对象),

P10. 信号、在代码中引用场景

讲解部分

ysort 是用父节点来排序的.

_process(delta)_physic_process(delta) 的区别,前者的 delta 不是常量,后者的 delta 是常量.

queue_free() 函数,将节点添加到一个会被 free 的队列中. 注意和 free() 的区别,前者不是立即清除.

简单的动画使用 AnimatedSprite 节点.

AnimatedSprite 节点中的右边栏,切换到 Node 可以看到 Signals

信号可以在某个事件发生时触发, 当一个信号触发时 (连接到一个函数就是当这个函数运行时.)

load() 函数,加载一个场景.

instance() 函数,用场景创建一个节点.

get_tree().current_scene 来获取当前场景的根节点.

节点用 add_child() 成员函数可以添加子节点. 这个子节点的位置默认是 (0, 0), 因此需要添加位置. 如:

1
grassEffect.global_position = global_position

global_position 应该是预处理的变量. 你把代码挂载到哪一个节点,这个位置就是哪一个节点的位置.

Remote 是当前游戏运行的即时场景.

总结步骤

动画播放后消失:

  1. 给节点挂载脚本
  2. 添加信号 animation_finished
  3. 在这个信号提供的函数下写动画播放完毕后要做的事

在一个节点中引用场景的步骤:

  1. 选择一个节点的脚本 (你想把场景加载到这个节点下),加载场景,储存在变量里
  2. 引用该场景,储存在变量里
  3. 把引用添加到任意一个节点下面,作为它的子节点
  4. 设置子节点的位置

P11. 近战攻击的Hurtboxes(伤害检测框)

讲解部分

新建两个场景,使用 Area2D 节点. 其有与位置相关的信号.

分为 Hurtbox 和 Hitbox.

给节点添加 CollisionShape2D 子节点.

一个 Area2D 节点必须要有 CollisionShape2D 节点. 可以用来检测碰撞,检测玩家进入区域等 (查看右边栏的 Signals).

把 Area2D 场景添加到一个节点上.

Hurtbox 加在受到攻击的物体上,Hitbox 加在发出攻击的物体上.

将 Hitbox 绑定到击打动画上, 创建一个 Position2D 节点 (其实际上和 Node2D 差不多, 只不过用的是十字坐标, Node2D 也可以添加 key).

需要确定在某一帧激活 Hitbox

Collision -> Layer 和 Mask 可以在 Project Setting -> Layer Names -> 2d Physics 中命名

注意 Layer 和 Mask 的作用,Layer 是设置当前节点所在那一层, Mask 是设置当前节点会和那一层发生关系, 设置了的则会被处于 Layer 的扫描到 (为什么叫 Mask, 不清楚)

步骤总结

思路: 检测碰撞 -> 在指定事件发生时触发

P13. 蝙蝠小怪和击退效果

讲解部分

击退效果的思路,在遭遇伤害时给一个与攻击方向相同的初速度,然后通过 move_toward() 函数让这个初速度变为零.

onready var swordHitbox = $SwordHisbox 获取的节点,可以访问其中挂载脚本的变量如:

1
swordHitbox.knockback_vector = Vector2.ZERO

关于这两行的理解:

1
2
func _on_Area2D_area_entered(area):
knockback = area.knockback_vector * 200

这里的参数 area 是进入这个 area 的 Area2D 对象. knockback_vector 是这个对象挂载脚本里的变量.

一个节点,挂载了脚本之后,似乎脚本中的变量可以被当作成员变量使用.

P14. 敌人属性、输出变量、Setget、代码结构

创建通用的属性. 就是把一部分节点保存为场景,在其他场景中引用.

export 一个变量,就是将其变为可以在编辑器中直接修改

export 带固定属性的写法:

1
export(int) var test = 1

注意, 在编辑器中获取的数值,是在 ready 的时候更新.

创建信号, 如:

1
signal no_health

发送信号示例:

1
2
3
func _process():
if health <= 0:
emit_signal("no_health")

在一个值改变是调用一个函数, 使用 setget:

1
2
3
4
onready var health = max_health setget set_health

func set_health(value):
health = value

这里,每当 health 的值发生改变,就会调用 set_health() 函数.

注意 “向下调用,向上信号”, 意思是,节点树的上层要传递信息给下层,则使用函数传递,下层向上层传递信息,则使用信号.

一个脚本可以继承另一个脚本,如:

1
expands "res://Overlap/Hitbox.gd"

P15. 小怪死亡效果、Bug修复

设置所有初始动画的 Blend Position 相同.

preload() 函数的使用.

P16. 蝙蝠基础AI

添加检测玩家的区域. 添加 Area2D 节点.

添加 body_entered 和 body_exited 信号.

其信号函数中:

1
func _on_PlayerDetectZone_body_entered(body):

body 应该代表一个场景对象.

使图片水平反转,需要使用 flip_h 成员函数.

P17. 玩家属性、小怪攻击

思路,通过区域检测来判断攻击.

connect() 函数的用法: 信号,对象,对象中的函数

Timer 节点. 有 start() 成员函数,参数是一段时间. 其结束时会和 time_out 信号发生作用.

调整 Monitorable 属性 (或 Monitoring),达到重新发送信号的要求.

有些属性不能在 _physic_process() 运行中直接修改, 需要用 set_deferred() 函数,第一个参数是属性,第二个是值.

自动加载一个场景: Project -> Project Settings -> Autoload -> 选择一个文件 -> add

P18. 玩家生命值UI

给 world 场景添加一个新的节点.

Godot 里大部分 Control 节点都和 UI 有关系. 其 position 和 普通的不同. 其很多属性也和 node2D 不同.

有锚点属性 Anchor, 其默认位于原点. margin 表示 label 的大小.

创建一个 Control 节点,再创建一个 label 子节点. 将其作为场景保存.

关于 clamp() 函数,其将一个数截取. 三个参数,输入,最小,最大.

使用 setget 的好处是,当一个变量改变时,会触发一个函数.

使用 TectureRect 节点, 设置在拉伸时自动重复: 右侧边栏 -> TectureRect -> Stretch Mode -> Tile

通过长度来判断心的个数.

设置生命值不超过最大值可以用 min() 函数, 其返回两个参数中的最小值.

设置 TectureRect 节点右边栏 Expand -> on, 就可以把 Rect -> Size 设置为 0.

定义的信号后面可以加上参数,可以在 emit_signal() 中发送:

1
2
signal test(value)
emit_signal("test", value1)

添加一个静止不动的物体一般使用 StaticBody2D.

P19. 小怪软碰撞、性能监测

创建软碰撞,先创建一个 Area2D 节点,添加一个 CollisionShape2D 子节点.

Area2D 的成员函数 get_overlapping_areas(), 判断是否重叠. 实现软碰撞的思路是让重叠的部分在检测到重叠时移动.

适当增加判断性函数如: is_colliding()

P20. 镜头

添加一个跟随角色的镜头.

在 Player 节点下添加一个 Camera2D 节点. 为了使用这个节点,在右侧边栏 Camera2D -> Current -> on

为了让生命值跟随镜头,添加一个 Canvaslayer 节点,这个节点会跟随镜头, 然后将生命值设置为其子节点. 开启右边栏的 Smoothing -> Enabled -> on 会更好.

解决角色死亡后镜头跳转问题. 将 Camera2D 节点移到外面,在 Player 下添加 RemoteTransform2D 节点,这个节点的作用是,你给它一个路径,就可以让指定路径的节点跟随这个节点. 由于这个 RemoteTransform2D 节点是跟随 Player 的,让 Camera2D 节点跟随这个节点也就可以跟随 Player.

可以调整 Camera 的大小.

P21. 蝙蝠游荡状态

添加一个 Node2D 节点作为控制节点,因为需要这个节点的位置来作为游荡的原始位置.

给这个节点添加一个 Timer 子节点.

可以设置 Timer 自动开始,且运行一次. 右边栏 One Shot -> on, Autostart -> on

rand_range() 函数可以返回范围内的随机值, 如: rand_range(-32, 32)

数组的 shuffle() 函数,随机取出一个值. pop_front() 函数,选取数组的第一个值.

向 Player 移动:

1
var direction = global_position.direction_to(player.global_position)

direction_to() 函数,直译就是朝向,也就是设置当前位置指向 player 位置的方向向量.

P22. 音效、闪烁特效(着色器)

在 Player 节点下添加 AudioStreamPlayer 子节点 (其和 AudioStreamPlayer2D 的区别是,后者在靠近时音量会改变).

在 AnimationPlayer 中的下边栏 Animation 中 Add Track 可以添加 AudioStreamPlayer, 用于分配音频.

没有在同一个节点树中的节点,可以通过 preload() 来加载. 但使用是需要先用 instance() 创建一个对象,然后通过 get_tree().current_scene.add_child() 来将这个场景添加到树中.

闪烁特效同样添加 AnimationPlayer 子节点.

使用 Godot 的 Shader, 点击 Sprite, 然后右边栏下方 Material -> Material -> New ShaderMaterial -> Shader -> NewShader, shader 同样用代码控制,保存以 .shaderl 为后缀的文件.

Godot 的 shader 语言,有分号. 和 C++ 类似.

可操作每个像素的颜色.

在 Shader 中,应该避免使用 if 语句.


Godot-制作-像素游戏-B站教程-Notes
http://example.com/2022/11/13/Godot-制作-像素游戏-B站教程-Notes/
作者
Jie
发布于
2022年11月13日
许可协议