看视频做的笔记 1
视频地址:【已完结】Godot4零基础入门游戏开发制作教程_哔哩哔哩_bilibili
1 Godot游戏程序启动步骤
①程序初始化,游戏程序开始创建游戏进程,生成场景树与内置服务器。(使用 Godot 编 辑器无法对这一步骤进行控制)
②游戏初始化,游戏程序加载项目设置,进行游戏程序基础信息的设置。
③将场景中的节点实例化,生成与之相关的脚本与资源,使其加入到场景树下,游戏正式开 始运行。
以上3步,可以操作的是第二步:项目设置
2 项目设置
操作: 选择项目设置
项目设置的内容:
①常规设置
▪ 对游戏项目相关的显示、音频、物理等方面进行设置。
②键位映射
③本地化(项目内文本翻译)
④Autoload
▪ 设置自定义单例
⑤Shader_Globals
▪ 添加全局 Shader 变量
⑥插件
▪ 添加或创建插件,拓展编辑器功能。
⑦默认导入设置
▪ 设置文件导入默认产生的特殊文件信息。
项目设置文件与单例
①project.godot
▪ 存放项目设置信息的文件,每个项目都拥有这个文件。加载项目设置就是加载这个文件。
▪ 除项目设置以外,class_name 自定义的类也会被写入这个文件。
▪ 此文件导出后会被放入 pck 文件中。
▪ 多人合作时,要小心此文件的覆盖问题。
②override.cfg
▪ 存放项目设置信息的文件。可以覆盖 project.godot 中重复的设置信息。但需要手动 或使用代码创建。
③ProjectSettings
▪ 与项目设置相关的单例,可以使用代码在这个单例中设置项目设置的信息。并保存为 project.godot 或 override.cfg
▪ 使用代码修改的信息,不会对运行中的游戏造成实质性的影响
3 场景文件的加载与保存
场景文件与场景资源
①场景文件
▪ 一组节点的集合。是节点加载和存储的基本单位。
②场景资源
▪ 场景文件对应的资源对象,再处理后可以生成对应的节点集合。并返回这个节点集合 的根节点。
场景的实例化(加载)
①场景加载的情景
▪ 游戏开始时,场景树自动将主场景实例化
▪ GDS 代码中将场景实例化
②实例化代码
▪ 场景资源的再处理就是场景的实例化。
▪ 一个场景资源调用 instantiate 将生成对应的节点集合,并且将这些节点的根节点作为 返回值返回。
▪ 使一个处于场景树下的节点运行 add_child()函数,并将新生成的根节点作为 add_child 的参数调用,则根节点就会成为这个节点的子节点,新生成的节点也将全 部加入树下。开始行使功能。
③实例化的过程
③ @onready 关键字
▪ 在函数外部使用@onready 来定义变量,可将变量的赋值拖延到 ready 执行的时刻
节点的 owner 属性与场景保存
①节点的 onwer 属性
▪ onwer 是一个节点类型的变量,用来表示某个节点。在一个场景文件实例化所产生的节点集合中所有节点的 owner 属性都指向这次实例化生成的根节点。
②场景的保存途径
▪ 编辑器中保存。
▪ 使用代码在游戏运行时将若干节点保存为场景。
③代码保存的注意事项
▪ 使用代码保存场景,只需要保存节点集合的根节点即可。
▪ 要对保存的节点集合中的节点设置 owner,将 onwer 设为它们保存场景的根节点对象。
4游戏管理者 ——MainLoop 与 SceneTree
SceneTree(场景树)与 MainLoop(主循环)
①场景树的作用 ▪ 场景树是游戏的管理者,它负责进行 Godot 的内置服务器与节点的沟通工作。
▪ 内置服务器是 Godot 中的各种模块,包括计时系统、物理模拟系统、图像绘制系统 等。
▪ 节点是 Godot 中最基本最常用的开发组件。
②主循环与场景树的关系
▪ 场景树类继承自主循环类,场景树是在主循环的基础上,对节点管理进行了扩写。
▪ 虽然存在主循环类,但游戏运行时只存在场景树对象。
MainLoop 的功能
①程序启动后,程序一定会创建一个主循环对象(虽然这个对象一般情况是他的子类场景 树),它包含了初始化、空闲帧同步回调、物理帧同步回调等方法。这个类中不包含与节点相 关的具体操作,我们使用 Godot 制作游戏时也几乎不会遇到需要自行编写 MainLoop 的情况。
②空闲处理与物理处理(Idle Process 与 Physic Process)
▪ 物理处理:Godot 游戏程序中会内置一个物理服务器,用于处理游戏世界内的各种物 理计算。在每一次物理计算之前,这个服务器都会给予主循环一次参与计算的机会, 这就是主循环中的物理处理。默认情况下,物理服务器一秒内会进行 60 次运算,因 此主循环中的物理处理也会每秒进行六十次处理。
▪ 空闲处理:在 Godot 游戏程序相对“空闲”(当某些内置服务器运行结束的时候)的时候执行此处理。引擎会尽可能快的利用空闲时间来绘制新的游戏图像。
③节点中的空闲处理与物理处理
▪ _process(delta) : 空闲帧回调函数 delta 表示距上次空闲帧调用的时间
▪ _physic_process(delta) :物理帧回调函数 delta 表示距上次物理帧调用的时间
▪ call(函数名) :立即调用函数
▪ call_deffer(函数名) : 到下个空闲帧调用函数
SceneTree 的功能
一、场景树
▪ 场景树继承自 Mainloop。
▪ 游戏运行时,将生成一个场景树对象,这个场景树对象将控制游戏中节点
二、场景树的功能
节点内部的 get_tree()函数可以获取场景树
①组(Group)功能
• 组:将若干节点归纳为一组节点,使用场景树命令同组节点调用函数或修改属 性。
• 节点的组操作 :使得节点加入 / 退出组、判断此节点是否处于某小组内
• 场景树的组操作 :命令同一组内的节点调用函数 | 判断某组是否存在(has_group) | 获取某组内的所有节点
例如:调用"A"组里面所有节点的b函数
func _process(delta):
get_tree().call_group("A","b") #A组中的节点都是当前节点的子节点
②游戏暂停
• 当场景树进入暂停后,节点会根据自己的暂停模式来调节自己的状态。
• 节点的暂停模式 :继承 | 正常暂停 | 暂停时运行 | 总是运行 | 从不运行
• 场景树暂停操作 :修改 pause 属性
get_tree().paused = true #SceneTree暂停
process_mode = PROCESS_MODE_PAUSABLE #暂停模式 1
暂停模式可以在界面中选择,第一种是继承模式,和父节点一致
③场景树的其他功能
• 游戏退出
• 创建计时器
• 创建 Tween 动画
• 获取节点总数
• 设置网络连接
• ..... 等等
5 第一个节点——Viewport
一、第一个节点 Viewport
Viewport(视窗)节点是游戏运行时出现的第一个节点,当场景树第一次加载主场景时, 游戏程序将先创建一个 viewport 节点并且将此节点添加到场景树下,然后再将主场景的根节点 作为 Viewport 的子节点添加过去。场景树下的节点都是第一个 viewport 节点的子节点。
Object
|
Node
|
Viewport
|
SubViewport
|
Window
二、Viewport 节点的功能
①Viewport 概述:
这个节点可以在屏幕中创建一个不同的窗口或在另一个窗口中创建子窗口。
游戏世界中出现的第一个 Viewport 节点将会创建一个World2D 与一个World3D 对象, 它们分别代表一个独立运行的 2D 世界和 3D 世界,这个世界中将包括游戏运行时的物理状 态、视觉场景与声音空间。Viewport 节点的子 Node2D 节点与子 Node3D 节点将分别参与 两种世界的构成。
Viewport 节点的子 Camera2D/Camera 节点(2D 摄像机与 3D 摄像机)节点,可以调 整游戏显示的区域,也会改变游戏世界中监听游戏声效的位置坐标。
②高阶应用:
▪ 访问 world_2d(world_3d)属性,直接对游戏世界造成影响或查询游戏世界中物理 / 视觉 / 声音元素的状态。
③常用应用:
▪ 双人游戏时的分屏效果
分屏效果就是用两个 Camera2D(Camera)节点,来显示同一个World2D (World3D)对象内的不同区域内容。而一个 Camera2D 只可以影响到一个 Viewport 节点的显示范围,因此,我们可以创建两个代表相同World2D 对象的 Viewport 节点。
补充:默认情况下,当我们手动新建一个 SubViewport 节点时,这个节点将会创建 一个新的World2D 对象,而不会去创建一个新的World3D 对象。这将导致 Viewport 节 点拥有一个独立的 2D 世界,而它代表的 3D 世界则是最近的上级 Viewport 节点代表的 World3D 对象。
▪ 输入事件的处理
④其他:
▪ 获取屏幕画面截图
▪ 设置 / 获取鼠标位置
▪ 等等 ......(详情请查询文档)
三、输入事件处理(Viewport 节点最常用的功能)
①节点的_input 与_unhandled_input 函数
▪ _input 与_unhandle_input 都是输入事件处理函数,当我们点击鼠标、按下键盘和进 行其他输入设备的操作时,一个对应的输入事件就会被创建,输入事件会在节点中进行传播。此时节点中的_input 函数将根据传播的顺序依次被调用。
▪ 如果在_input 传播中 , 输入事件未被处理掉,则在节点中再进行一次此事件的传播, 此时_unhandle_input 自动被调用。_unhandled_input 的含义即为未处理输入。
▪ 参数 event 可以代表鼠标点击、键盘按压、屏幕触碰、屏幕拖拽等多种输入事件,不 同种类的事件被划分为 InputEvent 的不同子类,它们各自拥有不同的属性,在编写 _input 与_unhandle_input 函数时,要先判断 event 的类型,再进行具体属性的处 理。
▪ 鼠标左键点击代码示范
②Viewport 对 event 的处理。
▪ 将事件设为已处理 ( set_input_as_handle() )
▪ 创建 input 输入事件 ( push_input() )
▪ 创建 unhandleinput 事件 (push_unhandled_input() )
▪ 创建文本输入事 (push_text_input() )
InputEvent的继承图(部分)
Object
|
RefCounted
|
Resource
|
InputEvent
|
InputEventFromWindow
|
InputEventWithModifiers
|
InputEventMouse
|
InputEventMouseButton
|
InputEventMouseMotion
|
InputEventKey
|
InputEventGesture
|
InputEventScreenDrag
|
InputEventScreenTouch
|
InputEventJoypadButton
|
InputEventMIDI
|
InputEventShortcut
示例: Input 鼠标左键按钮按下
(这是关于分屏的结构)
Node 中代码,接受鼠标input
func _input(event):
if event is InputEventMouseButton:
if event.button_index == MOUSE_BUTTON_LEFT:
#print("左键") #这里按下抬起 会触发两次
if event.pressed == true:
print("鼠标左键按下")
#不再向下传递 加上这句 _unhandled_input的最后一个if不会进去
$HBoxContainer/SubViewportContainer2/SubViewport.set_input_as_handled()
func _unhandled_input(event):
if event is InputEventMouseButton:
if event.button_index == MOUSE_BUTTON_LEFT:
#print("左键") #这里还会进入
if event.pressed == true:
print("鼠标左键按下")
手动触发一个鼠标左键按下事件
node中
func _ready():
$HBoxContainer/SubViewportContainer2/SubViewport.world_2d = $HBoxContainer/SubViewportContainer/SubViewport.world_2d #分屏 两个sub的视口一致
#手动添加input
var s = InputEventMouseButton.new()
s.button_index = 1
s.pressed = true
$HBoxContainer/SubViewportContainer/SubViewport.push_input(s)
node的完整代码如下:
extends Node
# Called when the node enters the scene tree for the first time.
func _ready():
$HBoxContainer/SubViewportContainer2/SubViewport.world_2d = $HBoxContainer/SubViewportContainer/SubViewport.world_2d
#手动添加input
var s = InputEventMouseButton.new()
s.button_index = 1
s.pressed = true
$HBoxContainer/SubViewportContainer/SubViewport.push_input(s)
print("node ready")
# Called every frame. 'delta' is the elapsed time since the previous frame.
func _process(delta):
pass
func _input(event):
if event is InputEventMouseButton:
if event.button_index == MOUSE_BUTTON_LEFT:
#print("左键") #这里按下抬起 会触发两次
if event.pressed == true:
print("鼠标左键按下")
#不再向下传递 加上这句 _unhandled_input的最后一个if不会进去
$HBoxContainer/SubViewportContainer2/SubViewport.set_input_as_handled()
func _unhandled_input(event):
if event is InputEventMouseButton:
if event.button_index == MOUSE_BUTTON_LEFT:
#print("左键") #这里还会进入
if event.pressed == true:
print("鼠标左键按下")
因为在SubViewport 中触发的,所以_Input 要写在SubViewport 挂载的脚本中(或者SubViewport 的子节点)
SubViewport中:
extends SubViewport
# Called when the node enters the scene tree for the first time.
func _ready():
print("subViewport ready")
# Called every frame. 'delta' is the elapsed time since the previous frame.
func _process(delta):
pass
func _input(event):
if event is InputEventMouseButton:
if event.button_index == MOUSE_BUTTON_LEFT:
#print("左键") #这里按下抬起 会触发两次
if event.pressed == true:
print("鼠标左键按下11111")
如果在其子节点也添加代码:
Sprite2d中:
extends Sprite2D
# Called when the node enters the scene tree for the first time.
func _ready():
print("sprite2d ready")
# Called every frame. 'delta' is the elapsed time since the previous frame.
func _process(delta):
pass
func _input(event):
if event is InputEventMouseButton:
if event.button_index == MOUSE_BUTTON_LEFT:
#print("左键") #这里按下抬起 会触发两次
if event.pressed == true:
print("鼠标左键按下2222")
那么 输出如下:
sprite2d ready
subViewport ready
鼠标左键按下2222
鼠标左键按下11111
node ready
why?!!!!! 难道是谁先执行了ready 谁先接收吗?