Godot 2D游戏开发笔记

本篇笔记是对[想在2025年做游戏?用Godot做出你的第一个2D游戏吧:安装Godot_哔哩哔哩_bilibili]的总结

Part0 系统

游戏引擎及导出模板下载官网

设置语言:gamemanager界面右上角Settings

创建新项目:左上角“+创建”

(进入项目之后)

​ 左下角:文件系统 可将外部文件直接拖入以导入 导入的文件可双击预览

​ 左上角:节点管理(场景树)

​ 右侧:检查器

​ 右上角:运行项目

​ 顶部:切换2D 3D 脚本(Script)

​ 顶部下方:切换对节点的选择、移动、旋转、锁定等操作

​ 鼠标中键:拖动视角

​ 鼠标滚轮:缩放视角

​ 中间蓝色框:默认渲染区域

文件管理

​ 游戏场景(.tscn)放在Scenes文件夹

​ 代码文件(.gd)放在Scripts文件夹

设置窗口缩放

项目->项目设置->显示->窗口->拉伸->模式,可选择让场景随窗口放大缩小

viewport 像素吸附

canvas_items 不限像素

管理导出模板

编辑器->管理导出模板->下载或导入

导出项目

项目->导出->添加,选择操作系统->启用内嵌PCK

Part1 搭建场景

添加一个”Node2D“作为根节点

在该Node2D根节点下

​ 添加”Sprite2D“节点作为场景

​ 在右侧的检查器中,将所需的场景拖入”Texture“中

像素风格设置:左上角项目->项目设置->渲染->纹理->将默认纹理过滤设置为Nearest

设置主场景:第一次运行项目时会弹出窗口,将背景场景设置为主场景即可

添加摄像机:Camera2D(粉色方框)

​ 在右侧检查器中,改变”Zoom“的数值以改变方框大小

Part2 创建玩家并添加动画

点击顶部下方场景栏的加号”+“,新建一个场景

添加节点“CharacterBody2D”作为玩家的根节点

在该节点下添加”AnimatedSprite2D“节点

左侧会出现一个黄三角警告,需要在右侧检查器中,设置"SpriteFrames"图片帧

点击Animation->SpriteFrames右边的<空>,新建SpriteFrames,再点击创建好的SpriteFrames即可打开之

在下方的SpriteFrames栏中,上侧的网格图标可分割并添加数个图片,组成一个动画(如”idle“待机动画)

​ 动画帧率可调

​ 可设置游戏开始时就自动播放某一动画

将玩家场景添加到主场景:回到主场景(背景场景)左侧节点栏上方,用链接图标即可将某一场景添加到主场景

Part3 添加代码

为玩家添加代码:选中玩家场景的根节点,点击右上侧一个有绿色加号的图标

​ 使用Node:Default模板

​ 可通过”创建“新代码或”加载“已有代码的方式将代码挂载到某一节点

Part4 操控玩家

为玩家设置速度(附2之5,6)

以键盘操控玩家移动

​ 接收输入信号:项目->项目设置->输入映射->添加新动作并分配键位

实时检测键盘的输入(附2之8)

将玩家的速度赋值为输入与速度的乘积,形如:

velocity=Input.get_vector(”left“,"right","up","down")*move_speed

Part5 播放动画

在角色的脚本中,声明一个类型为AnimatedSprite2D的变量animator,并使其暴露在检测器中(附2之10)

在检测器中,给animator的“Animator”赋值(可拖动或选择)为Part2中创建的AnimatedSprite2D节点(在场景树中)

在特定情况下播放动画,如在角色速度为0时播放待机(idle)动画,否则播放跑步动画,形如:

if velocity == Vector2.ZERO:
	animator.play("idle")
else:
	animator.play("run")

Part6 添加碰撞体-场景边界空气墙

在主场景中,添加一个"StaticBody2D"节点

​ 在其之下添加"CollisionShape2D"节点

​ 在右侧的检查器中为其添加“Shape”,其中的“WorldBoundaryShape2D”适合用来做空气墙,检查器中的“Transform”->"Position"可用于精确调整位置

可将所有空气墙整合到一个Node2D节点下,并锁定,以使场景整洁

Part7 添加碰撞体-角色

在角色场景的根节点下,添加一个“CollisionShape2D”节点

​ 在右侧的检查器中添加“Shape”,用合适的图形概括角色外形。

Part8 创建敌人

新建场景,以“Area2D”作为敌人的根节点

​ Area2D是一个物理节点,可检测是否有别的物理节点碰到了它

用“AnimatedSprite2D”为敌人添加动画,方法同前

用“CollisionShape2D”为敌人添加碰撞体,方法同前

回到主场景,注意不要选中任何节点,将敌人的场景链接到主场景中

Area2D下没有velocity参数,但可以通过改变position来控制敌人的移动,形如:

position += Vector2(-100,0) * delta

(若不加“* delta”则会导致按帧改变位置,使移速过快)

解决玩家与怪的叠放次序问题:

​ 按照物体在y轴上的坐标来渲染:选中根节点->在右侧检查器中选择"Ordering"->勾选 Y Sort Enabled

Part9 碰撞检测

来到敌人场景->右侧节点面板下找到信号栏->选择body_entered(body: Node2D)->选择敌人节点并连接

自动创建了如下函数

func _on_body_entered(body: Node2D) -> void:

每次body entered信号被触发,该函数下语句便会被执行

需判断敌人碰到的是玩家而非他物:

if body is CharacterBody2D: #body指碰到的物体

Part10 游戏结束并刷新场景

实现”碰到敌人就输掉游戏“

来到玩家代码,添加函数:

func game_over():
	get_tree().reload_current_scene() #重新加载当前场景

回到敌人代码

if body is CharacterBody2D: #body指碰到的物体
	body.game_over() #body即代指玩家,可直接调用写好的game_over函数

设置游戏结束时玩家的动画,在game_over函数中:

await get_tree().create_timer(3).timeout #等待3秒倒计时

可使用bool型变量记录游戏是否结束

添加失败动画,方法同前

在game_over函数中,播放该动画:

animator.play("game_over")

总结如下:

func game_over():
	animator.play("game_over")
	await get_tree().create_timer(3).timeout
	get_tree().reload_current_scene()

Part11 不断发射子弹

新建场景,Area2D作根节点,Sprite2D作子弹图片,CollisionShape2D作碰撞体

添加脚本:子弹移动同前

每隔一段时间就发射子弹

回到玩家节点,添加“Timer”节点,在右侧的检查器中,不勾选“One Shot”以使循环执行,勾选“Autostart”以使在游戏开始时自动执行

在Timer的节点->信号栏中,双击“timeout()“,将其连接到玩家的代码上,可将其命名成”_on_fire“

可在Timer节点的检查器中对这个倒计时循环进行精细的控制

声明一个类型为”PackedScene“的变量来储存子弹的场景,命名为”bullet_scene“

在_on_fire函数下,新声明一个局部变量bullet_node来存子弹场景,并让其生成在玩家边合适的位置:

func _on_fire() -> void
	var bullet_node = bullet_scene.instantiate()
	bullet_node.position = position #前一个position是子弹的,后一个是玩家的
	get_tree().current_scene.add_child(bullet_node)

回到玩家场景的根节点,将右侧检查器中的“Bullet Scene”快速加载为子弹的场景

可在_on_fire函数中自由设计开火条件(如只能站定才能开火,游戏结束后无法开火等)

Part12 性能优化

在子弹的_ready()函数加添加一个倒计时(附2之16)

倒计时结束后摧毁生成的子弹(附2之20)

Part13 消灭敌人-分组功能

给敌人添加被消灭的动画,方法同前,注意死亡动画不需要重复播放

检测敌人被子弹打到

回到敌人场景根节点,添加area_entered(area: Area2D)信号,并连接到敌人的代码

区分敌人和子弹,避免敌人碰到自己人而触发被消灭

回到子弹场景,右侧节点->分组->添加分组并命名为“bullet”

回到敌人代码,判断撞到的是否为子弹:

func _on_area_entered(area: Area2D) ->void
	if area.is_in_group("bullet"):#被子弹撞到了
	#让敌人停止运动
	$CollisionShape2D.queue_free() #删除敌人碰撞体积,防止尸体吞箭
	#摧毁打死敌人的子弹
	#播放死亡动画
	#摧毁死亡的敌人

Part14 不断生成敌人

在主场景的根节点上(重点)新建代码“GameManager”

回到主场景,添加一个Timer节点,勾选Autostart

在该Timer节点的信号面板中,双击timeout(),选中根节点的GameManager以连接,可命名为_spawn_slime()

希望敌人随机生成在一定范围中

​ 范围赋值为随机值(附2之23)

生成敌人和生成子弹代码类似:

@export var slime_scene : PackedScene

func _spawn_slime() -> void:
	var slime_node = slime_scene.instantiate()
	slime_node.position = Vector2(x,randf_range(a,b))
	get_tree().current_scene.add_child(slime_node)

选中场景根节点,在右侧的检查器中看到Slime Scene变量,将其设置成敌人的场景

使敌人生成得越来越快

声明一个类型为Timer的变量spawn_timer,通过控制它来控制生成间隔,记得在检查器中将其设置到连接了生成敌人代码的Timer上。

func _process(delta: float) -> void:
	spawn_timer.wait_time -= 0.2 * delta
	spawn_timer.wait_time = clamp(spawn_timer.wait_time,1,3)

敌人到达左侧边界,则摧毁

if position.x < value:
	quene_free()

Part15 记分

在GameManager代码中,声明一个记分的变量score:

@export var score : int = 0

在敌人被消灭的代码中,获取主场景的根节点(因为GameManager连接在那)

get_tree().current_scene.score += 1

Part16 UI

回到主场景,添加"CanvasLayer"节点,出现的蓝色大框为其渲染区

在CanvasLayer之下,添加“Label”节点,在右侧检查器的Text中编辑文字,可自由设置文字参数

如:Theme Overrides中可设置字体、颜色、大小等

在其下Fonts可快速加载某一字体文件(.ttf)

显示分数

来到GameManager代码,声明一个类型为Label的变量

@export var score_label : Label

将score节点拖动到GameManager检查器中的score_label中

在process函数中实时显示分数:

score_lable.text = "Score: " + str(score)

显示Game Over

创建好节点后,先点右侧眼睛按钮让它隐藏

在玩家死亡代码中,告诉GameManager播放gameover

get_tree().current_scene.show_game_over()

在GameManager中设置show_game_over函数,显示gameover

@export var game_over_label: Label
func show_game_over()
	game_over_label.visible = true

在检查器中将game over label设置成gameover节点

Part17 音乐音效

需要循环播放的,要在预览界面勾选“循环”并重新导入

音效

来到玩家场景,添加节点“AudioStreamPlayer”

将音效文件拖入节点右侧检查器中,可调节其参数

可使用拖动的方式,将音效节点拖到对应的代码函数中,使其按需播放

可用.play()或.stop()来控制其播放与停止

.playing 表示它正在播放,是一种状态

背景音乐

在主场景添加节点“AudioStreamPlayer”,将BGM文件拖入检查器,勾选Autoplay

令失败重启不影响BGM播放

让BGM节点存在于主场景节点之外,使用godot自动加载功能,与主场景同时存在

将BGM保存为一个场景,而非节点

左上角项目->项目设置->全局->自动加载->BGM场景->+添加

附1 快捷键

2D界面中按F 使选中的节点居中

ctrl+A 新建节点

ctrl+Z 撤回

ctrl+D 复制并粘贴

ctrl+S 保存

ctrl+X 剪切整行代码

旋转中按住ctrl 磁吸

F5 运行项目

F8 退出运行

附2 系统函数与参数

func _ready() -> void:

该函数下的语句会在游戏开始运行时或被加入场景时被执行

print()

输出括号中的内容

func _process(delta: float) -> void:

该函数下的语句会在游戏的每一帧被执行(基于电脑可能有所不同)

若改成 _physics_process则会以固定60帧/秒运行

extends CharaceterBody2D

出现在代码顶端,表示该代码是由CharaceterBody2D类型延申而来,可用其下的参数

velocity = v

CharacterBody2D特有的参数,给节点速度赋值v

Vector2(x,y)

二维向量,能表示在x轴,y轴上的数值

move_and_slide()

CharacterBody2D特有的功能,可让该节点以设置好的velocity来移动

Input.get_vector(negative_x,positive_x,negative_y,positive_y)

获取左,右,上,下四个方向上的输入

var a : int = 1

声明a是一个值为1的整型变量

@export

将某个变量暴露在左侧检查器中

Vector2.ZERO

向量值为0,可用于判断速度是否为零,形如velocity == Vector2.ZERO

animator.play()

animator为自定义的AnimatedSprite2D类型变量,该函数功能为播放括号中指定的动画

position

节点的世界坐标

func _on_body_entered(body: Node2D) -> void:

Area2D的函数,每次body entered信号被触发(碰到物体),该函数下语句便会被执行

get_tree().reload_current_scene()

重新加载当前场景

get_tree().create_timer(t).timeout

创建倒计时,括号中的t为具体的时间

bullet_scene.instantiate()

bullet_scene为自定义的PackedScene类型变量,该函数可生成一个bullet_scene节点

get_tree().current_scene.add_childe(node)

将第二个括号中的node节点变成当前场景的子节点

return

提前结束函数

queue_free()

摧毁当前节点

area.is_in_group("group1")

这个area场景属于分组“group1”

area.queue_free()

摧毁area节点

randf_range(a,b)

a~b之间随机生成一个数(含a不含b)

clamp(value,min,max)

将value的值限制在min和max之间

附3 报错

1.indented

与缩进有关的报错

posted @ 2025-09-08 17:35  Fish4174  阅读(193)  评论(1)    收藏  举报