DGScript ---c++基础学习笔记

GDScript 是一种专门为 Godot 引擎设计的脚本语言。

Godot 是一款开源的、跨平台的游戏引擎,它使用 GDScript 作为首选的脚本语言来开发游戏逻辑、场景和其他功能。

GDScript 受到 Python 的启发,在语法和结构上与 Python 很相似,使得它易于学习和上手。与其他语言相比,GDScript 在 Godot 引擎中运行的性能良好,并且能够与引擎的 API 紧密集成,使得开发者可以方便地创建复杂的游戏逻辑和交互效果。

使用GDScript,开发者可以编写游戏中的脚本、节点的行为、UI 界面的逻辑等,并且能够直接在 Godot 编辑器中运行和测试代码。因此,GDScript 是学习和使用 Godot 引擎进行游戏开发的重要工具之一。

如果你已经有了 C++ 的基础,学习 GDScript 应该会相对容易一些,因为 GDScript 与 C++ 在语法和概念上有一些共通之处。以下是为什么学习 GDScript 对于有 C++ 基础的人来说可能会比较容易的几个原因: 

1. 面向对象编程概念相似:C++ 和 GDScript 都是面向对象的语言,因此你对 C++ 中的类、对象、继承、多态等概念已经有一定了解,这些概念在 GDScript 中同样适用。

 2. 语法接近:GDScript 的语法与 Python 更接近,但与 C++ 也有一些相似之处,比如使用大括号 `{}` 表示代码块、定义变量时指定类型等。这些相似之处可能会让你更容易上手。

3. 轻量级脚本语言:GDScript 是一种脚本语言,不需要像 C++ 那样需要关心内存管理、编译过程等底层细节,这样可以让你更专注于游戏逻辑和功能的实现。

尽管 GDScript 和 C++ 在某些方面有相似之处,但也有一些差异,例如类型系统、性能等方面。因此,虽然有 C++ 基础可以让学习 GDScript 更容易一些,但仍然需要花一些时间来熟悉 GDScript 的特性和 Godot 引擎的使用方法。通过不断练习和尝试,你将能够更加熟练地运用 GDScript 进行游戏开发。

综上,作为有c++基础的,本文记录博主在学习Godot引擎过程中,遇到的GDSCript的需要记的知识点。

可能有点混乱……

博主的学习方式:

1 视频:1【已完结】Godot4零基础入门游戏开发制作教程_哔哩哔哩_bilibili   (注意加群)

              2 godot 4.x 教程 100集_哔哩哔哩_bilibili  (up另开了一个200集的)

2 图文:1 前言 — Godot Engine (4.x) 简体中文文档 官方中文版文档

              2  GDScript零基础图文入门.pdf(从视频1的群里下载到的,谢谢群友分享)(这个文章里,作者列出了很多学习资源网站,不想麻烦找的看本文末尾)

                

3 工具查询: GODOT帮助文档

                      ChatGPT3.5

正文:

1 float转int,四舍五入

Variant round(x: Variant) 

 将 x 舍入到最接近的整数,中间情况远离 0 舍入。支持的类型:int、float、Vector2、Vector2i、Vector3、Vector3i、Vector4、Vector4i。

round(2.4) # 返回 2
round(2.5) # 返回 3

2 浮点数比较

bool is_equal_approx(a: float, b: float)

如果a和b彼此接近相等,则返回true。
这里,“近似相等”意味着 a 和 b 在彼此的一个小的内部 epsilon 内,该 epsilon 与数字的大小成比例。

相同符号的无穷大值被认为是相等的。

if(is_equal_approx(0.1+0.2,0.3)):
    print("相等") #执行

3 逻辑表达式

not   非(否)

and   与(同时为 true)

or   或(任意为 true)

4 强类型变量

在定义变量时,在变量名后加上冒号和类型来明确变量类型,例如:

var lifeV: int = 100
var name: String = "Conan"

5 推导类型

每次都加上一个冒号和类型会比较麻烦,所以 GDScript 提供了一种语法: :=

var player_Life := 100        # 等于 player_Life: int
var player_Name := "Conan"    # 等于 player_Name: String

6 获取节点  $

$ 语法本质上是一种简写,它的完整写法是一个方法调用,写做 get_node("节点路径"),不过还是 $ 写法更简单实用。 

类似c++中获取控件指针

 var 输入框: LineEdit = $LineEdit
 var 按钮: Button = $Button # 顺手把按钮也拿

节点名可能包含一些奇怪的符号,直接把名字写在 $ 后面会出现语法错误,比如有个节点叫 做 外. 币 巴-伯,这时就可以使用字符串来表示节点名,变成 $"外.币 巴-伯" 即可。

准确来说,$ 符号后面填写的并不是节点名,而是节点路径,例如我们可以使用两个点 .. 表示上一级,或者使用 /root/ 开头表示场景根节点,下面来看几个例子: $"../ABC" 获取和当前脚本所在节点同级的 ABC 节点 $"../../../" 获取自己的父节点的父节点的父节点 $"/root/BFG" 获取场景中最外层的 BFG 节点。

①获取直接子节点

▪ get_child()

②获取直接父节点

▪ get_parent()

▪ get_node("..")

③获取多级子 / 多级父节点

▪ /

④获取所有的直接子节点

▪ get_children()

唯一名称

某些节点需要在其他位置反复通过 为累赘。 $"XXX" 语法访问,这时候 $ 符号后面长长的路径就会成 如果这个节点的名称在场景中是唯一的,那么就可以给这个节点勾选上 唯一名称:

此时即可在代码中通过 %"节点的唯一名称" 语法来获取这个节点,在这种语法中就只填写节 点名即可,不需要节点的路径。

如果节点名称没有空格或者其他特殊符号、没有造成语法歧义的话,可以去除引号,也就是 %节点的唯一名称

7 添加节点 add_child

调用哪个节点的 add_child 就是给那个节点添加子节点。

 $ABC.add_child(新节点) # 给 ABC 节点添加子节点

8 删除节点  free 和 queue_free

一般情况下更建议使用 queue_free 来删除节点,方法名中的 queue 是队列的意思,可 以理解成排队,也就是说这是让节点排队删除,而不是立刻删除。 而 free 则是立刻删除节点,在调用 free 时,Godot 就会立刻删除这个节点

# 这是举例用的错误代码
free()
print(position) #已经删除,这里就会出错

如果我们将 free 换成 queue_free 则可以避免这个报错,Godot 会先将调用 queue_free 的节点记录下来,等咱们的代码执行完毕后,在空闲时间时再将它们删除。

queue_free() 等同于 call_deffer("free")  在空闲帧中调用free

9 Match 条件分支语句

match 语句在判断变量与大量固定值是否相等时,比 if 分支更简洁,不过由于 if 可以实 现同样的功能,所以用的较少。

比如下面的示例判断三月份有几天。

var 月份 = 3
match 月份:
   2:
       print("非闰年该月份有28天")
   4,6,9,11:
       print("该月份有30天")
   _:
       print("该月份有31天")

更多用法可以参阅官方文档相应介绍,可以先认识一下这个语句,但不一定能用到。

10 三元运算符(又称三目运算符)

比如下面的例子,判断玩家在 x 轴的正半轴还是负半轴。

var 玩家的x轴位置 = 100
var 玩家方向 = 1 if 生命值 >=0 else -1

夹在 if 和 else 中间的为条件语句 生命值 >= 0,如果条件语句为真,返回 if 前的 值,否则返回 else 后的值。

11 随机整数 randi_range

int randi_range(from: int, to: int)

它会根据括号里填入的数字生成一个随机 整数,包含这两个数以及两数之间的数

bingoA = randi_range(1,9) #产生1`9之间的随机数

12 数组 Array (或叫集合)

数组就是一堆数据构成的组,在 GDScript 中使用一对方括号表示数组,在方括号中填入要保 存的数据,数据之间用逗号分隔,例如使用数组制作背包:

var 背包: Array = ["水瓶", "钥匙", "金币"]
print(背包[2]) # 显示:金币
print(背包) # 显示:["水瓶", "钥匙", "金币"

获取数组长度 len

var 背包 := ["水瓶", "钥匙", "金币"]
print(len(背包)) # 显示一个数字 

使用 .remove_at() 删除指定位置的元素

var 背包 := ["水瓶", "钥匙", "金币"]
背包.remove_at(1)
print(背包) # 显示:["水瓶", "金币"

一个数组中可以存在不同类型的数据

var 一个数组 := [1, "你好", false

甚至数组内在再含一个数组

 var 又数组 := [[1, 2, 3], [1, 2, 3]]

数组遍历: 可以自行写循环遍历,也可用专门为遍历而生的语法:

这个东西本质上还是个循环,循环次数就是遍历目标的长度,每一轮循环中,都将从遍历目标 里取出一个元素放到元素变量中。

for <元素变量名> in <遍历目标>:
 <代码块>
 var 背包 = ["水瓶", "钥匙", "金币"]
 print("背包中有:")
 for 物品 in 背包:
 print(物品)

有时候我们想直接指定循环次数,例如显示 50 个 hello,这样直接用一个变量和 while 也可 以搞定,但我们可以结合 range 方法和 for 来实现同样的效果:

range 这个方法会根据括号里的数字产生一个数组,里面分别是 0、1、2、3、4...直到括号 里的数字,但不包括那个数,也就是最后一个数字是 49。

for 当前次数 in range(50):
    print("Hello")

不过 GDScript 还给咱们提供了一种简写方式,直接把 和上面的 range(123) 写成 123 即可,例如 range(50) 效果相同的 for 可以写成 

for 当前次数 in 50:

13 str 把任何Variant类型转换为一个String

尽可能以最佳方式将一个或多个任何 Variant 类型的参数转换为一个 String。

var a = [10, 20, 30]
var b = str(a)
print(len(a)) # 输出 3(数组中元素的数量)。
print(len(b)) # 输出 12(字符串“[10, 20, 30]”的长度)。

14 函数返回值 强类型

返回值也支持强类型语法,在参数列表的括号后面使用 -> 来表示返回值的类型

func 输出拼接结果(左边的输入框, 右边的输入框) -> String :
    
    ...

    return ...

15 表示坐标向量  Vector2

     对应的整数版本 Vector2i

16 引用类型数据

数字、字符串、布尔值都是这值类型数据,而数组是引用类型数据

var a = [1, 2]
var b = a
a.append(10)
print(a)
print(b)

这段程序会输出两个 [1, 2, 10]。这是因为数组是引用类型的数据,当执行 并不是这个数组,而是这个数组的引用。

这种引用类型的数据,除了数组外还有很多,例如绝大多数类的实例都是引用类型,自然也就 包括各种节点。

17 节点的生命周期方法

生命周期方法不需要我们手动调用,Godot 会在内部自动调用它们。

_enter_tree :在节点进入到场景树时执行

注意不要和下面的 _ready 搞混,在执行 _enter_tree 生命周期方法时可能还没有子节 点,因为子节点还没有加入到场景树中。

_ready :当节点完全准备好时执行。

完全准备好是指子节点都执行完毕 _enter_tree 方法。

_exit_tree  : 节点离开场景树

顾名思义,当节点离开场景树时执行,且子节点优先执行。

生命周期 - 循环执行周期。下面这两个周期方法会在节点存在时反复执行。

_process 和 _physics_process

_process(空闲帧)  是每个画面帧时执行 。这个方法还需要有个参数,Godot 给的默认参数名是 delta,它表示当前帧和上一帧之间间 隔的时长,单位是秒。

_physics_process  类似画面帧,游戏中进行物理效果模拟时也是一帧一帧进行的,不过这个帧不等于画面帧, Godot 默认是每秒 60 物理帧,同样他也有个 delta 参数,表示上一个物理帧与当前物理帧之间的间隔时长。

画面帧和物理帧就是指 _process 和 _physics_process。

当我们处理一些画面显示相关的逻辑,例如按钮动画、视角移动等,建议使用 _process, 这能保证每次画面刷新时都能看到流畅的画面变化。

如果要处理一些物理相关的逻辑,例如玩家移动、开门关门等,一定要使用 _physics_process,因为物理碰撞、摩擦等运算都是在物理帧进行的,如果某个物体在画面帧 中移动,可能会导致物理帧中处理不到这次移动信息,从而影响物理模拟的真实性。

两个 process 生命周期都有个 delta 参数,使用这个方法可以平衡不同帧率对游戏的影响。

最常用的生命周期就这三个: _ready 、_process 、 _physics_process 。

18 获取输入 Input类

 Input 类专门用于获取玩家的输入信息

比如,方法   bool is_key_pressed(keycode: Key) const    获取玩家按键

另: 与 is_key_pressed() 相比,is_physical_key_pressed() 被推荐用于游戏内的动作,因为无论用户的键盘布局如何,它都会使 W/A/S/D 布局有效。

Godot 定义了一堆 KEY_??? 这样的变量来表示每一个按键,KEY_W 是键盘上的 W 键。具体见 godot的搜索帮助 Key  。

引擎主界面菜单中的 项目 -> 项目设置 -> 输入映射 选项卡,即可看到类似上图的界 面,我们可以在这里添加咱们的按键映射

线性输入 

游戏手柄上有一些可以“输入一半”的键,比如摇杆和扳机,这时候就可以使用 Input.get_action_strength("动作名称") 来获取一个小数数值,范围是 0 ~ 1,表示按键 移动的强度。

成对输入

get_axis  指定两个动作来获取轴的输入,一个是负的,一个是正的。

例如操控船只的加速和减速,我们可以使用 Input.get_axis("反方向动作","正方向动作") 来获取一个 -1 ~ 1 的值。

get_vector  通过指定正负 X 和 Y 轴的四个动作来获取输入向量。 这个方法在获取向量输入时很有用,比如从操纵杆、方向盘、箭头或 WASD。向量的长度被限制为 1,并且有一个圆形的死区,这对于使用向量输入进行运动很有用。

例如玩家的上下左右移动,可以使用 Input.get_vector("-x动 作","+x动作","-y动作","+y动作") 来获取到一个 Vector2 类型的值,其中的 x 和 y 的范围是-1 ~ 1。

19 鼠标输入

可以使用 get_global_mouse_position()  获取鼠标在 2D 世界中的坐标。

如果要获取鼠标的移动速度:

func _input(event):
    if is_instance_of(event,InputEventMouseMotion):
        print(event.velocity)

20 _input 生命周期

void _input(event: InputEvent) virtual

只有在启用输入处理时才会被调用,如果该方法被重写则会自动启用,可以使用 set_process_input()  进行切换。

21 常用节点

  1. Node2D: 2D 场景中的基本节点类型,用于表示 2D 空间中的对象。
  2. Control: 用户界面元素的基本节点类型,用于构建游戏 UI。
  3. Sprite: 用于在 2D 场景中显示 2D 图像或纹理。
  4. AnimatedSprite: 用于播放带有动画的精灵。
  5. Camera2D: 用于定义 2D 场景的相机属性,控制视图的位置和缩放等。
  6. CollisionShape2D: 用于 2D 碰撞检测的节点,可以定义物体的碰撞形状。
  7. Area2D: 用于触发区域检测的节点,可以检测其他节点是否进入或离开区域。
  8. KinematicBody2D: 用于实现基本的 2D 运动和碰撞检测。
  9. RigidBody2D: 用于实现具有物理属性的 2D 物体,例如重力、碰撞反应等。
  10. TileMap: 用于创建 2D 地图,可以快速构建复杂的地图场景。

一些常见节点的一般属性和常用成员:

  1. Node2D:

    • 位置属性:positionrotationscale
    • 父节点和子节点管理:get_parent()add_child(node)remove_child(node)
  2. Control:

    • 用户界面属性:rect_min_sizerect_sizeanchor_leftanchor_top
    • 样式:themecustom_styles
  3. Sprite:

    • 显示的纹理:textureflip_hflip_v
    • 动画:frameframe_coords.
  4. AnimatedSprite:

    • 动画播放:frameanimationspeed
    • 动画控制:play()stop()set_frame().
  5. Camera2D:

    • 视图控制:zoomoffsetrotation
    • 视图限制:limit_leftlimit_rightlimit_toplimit_bottom.
  6. CollisionShape2D:

    • 碰撞形状类型:shapeshape_type
    • 碰撞检测:is_colliding()get_collider()
  7. Area2D:

    • 区域属性:shapemonitoringcollision_layercollision_mask
    • 区域检测:_on_area_entered()_on_area_exited()
  8. KinematicBody2D:

    • 运动控制:move_and_slide()move_and_collide()is_on_floor()
    • 碰撞检测:move()collide_and_slide()
  9. RigidBody2D:

    • 物理属性:massfrictionbounce
    • 物理控制:apply_central_impulse()set_linear_velocity()
  10. TileMap:

    • 地图属性:tile_setcell_sizecustom_tiles
    • 编辑地图:set_cell()get_cell()tile_exists()

一些常见节点类型之间的部分继承关系:

Node
├── CanvasItem
│   ├── Control
│   │   ├── Button
│   │   ├── Label
│   │   └── TextureRect
│   ├── Sprite
│   ├── TextureRect
│   └── ...
├── Spatial
│   ├── Node3D
│   │   ├── Camera
│   │   ├── MeshInstance
│   │   └── Light
│   └── Node2D
│       ├── Sprite
│       ├── Area2D
│       └── ...
├── PhysicsBody2D
│   ├── RigidBody2D
│   │   ├── KinematicBody2D
│   │   └── StaticBody2D
│   └── CollisionObject2D
│       ├── Area2D
│       └── ...
├── Resource
└── ...

Control 节点及其子类节点:

Control
├── AcceptDialog
├── Button
├── CheckBox
├── ConfirmationDialog
├── Container
├── CenterContainer
├── HBoxContainer
├── MarginContainer
├── Menu
├── OptionButton
├── Panel
├── PopUp
└── ...

Node2D 节点及其子类节点:

Node2D
├── Camera2D
├── CollisionObject2D
│   ├── Area2D
│   ├── KinematicBody2D
│   ├── StaticBody2D
│   └── RigidBody2D
├── Position2D
├── RayCast2D
├── Sprite
├── TileMap
└── ...

Sprite 节点及其子类节点:

Sprite
├── AnimatedSprite
├── Particles2D
├── NinePatchRect
├── Polygon2D
├── RectangleShape2D
├── RayCast2D
└── ...

AudioStreamPlayer 节点及其子类节点:

AudioStreamPlayer
├── AudioStreamPlayer2D
└── AudioStreamPlayer3D

上面的示例图表展示了一些节点类型之间的基本继承关系。节点类层次结构在 Godot 引擎中非常庞大和多样化,以满足不同类型的游戏开发需求,因此继承关系也相对复杂。

22 move_and_collide 移动物体

 KinematicCollision2D move_and_collide(motion: Vector2, test_only: bool = false, safe_margin: float = 0.08, recovery_as_collision: bool = false)

沿着运动向量 motion 移动该物体。为了在 Node._physics_process() 和 Node._process() 中不依赖帧速率,motion 应该使用 delta 计算。

返回 KinematicCollision2D,包含停止时的碰撞信息,或者沿运动向量接触到其他物体时的碰撞信息。

23 模板 PackedScene (打包场景)

创建 PackedScene 方法有三:

1. 点击菜单栏[场景] -> [新建场景]后,开始制作你的模板,并保存当前场景。

2. 在任意场景对着节点列表中的某个节点右键,点击 [将分支保存为场景]。

3. 在节点列表中拖拽节点到下面的文件列表中。

Godot 中的一个场景就是个 PackedScene

想要把一个 PackedScene 使用代码创建出来,就需要先在代码中获取到 PackedScene 这个 文件。

使用 load("文件路径") 来读取一个 Godot 资源,这里的文件路径使用 项目中的资源。

load("res://物体/某个packed_scene.tscn")

创造一个 PackedScene

只需要先实例化一个 PackedScene 实例,并调用它的 pack 方法 即可:

var 打包包 := PackedScene.new()
打包包.pack(被打包的节点)

这个 pack 方法就是将某个节点放到这个 PackedScene 中,所以结合节点元数据,我 们就能把分数信息保存到一个 PackedScene 中了

var 节点 := Node.new()
# set_meta 就是添加一条元数据
节点.set_meta("分数", 123)
var 打包包 := PackedScene.new()
打包包.pack(节点)

不需要 add_child  此处的 Node 节点只是存个数据,不需要添加到场景中去。

保存资源

需要使用 ResourceSaver.save 方法:

ResourceSaver.save(打包包, "user://存档.tscn")

这样,那个包含分数元数据的Node就被以 PackedScene 的方式保存到用户目录的 存档.tscn 文件中了。 完整的代码如下:

var 节点 := Node.new()
# set_meta 就是添加一条元数据
节点.set_meta("分数", 123)
var 打包包 := PackedScene.new()
打包包.pack(节点)
ResourceSaver.save(打包包, "user://存档.tscn")
# 删除这个用完了的节点
节点.free()

不要保存引用 不要保存任何实例的引用,在读取时这些引用都会失效。 建议只保存值类型数据,例如 数字、字符串、Vector3、Color 等。

读取存档

因为咱们保存的是个 PackedScene,所以读取存档就是实例化 PackedScene

var 节点 = load("user://存档.tscn").instantiate()
# 显示之前保存的分数
print(节点.get_meta("分数"))
节点.free()

24 instantiate  实例化该场景的节点架构

实例化该场景的节点架构。触发子场景的实例化。在根节点上触发 Node.NOTIFICATION_SCENE_INSTANTIATED 通知。

当我们想要根据PackedScene 创建新物体时,可以调用它的 instantiate 方法,这个方法会返回创建好的节点。

var 保存好的场景 = load("res://物体/某个packed_scene.tscn")
var 新物体 = 保存好的场景.instantiate()
get_parent().add_child(新物体)

25 代码实现信号连接

    有点类似于Qt,connectdisconnect

    例如:

extends Control

func _ready():
    $Button.pressed.connect(当点击按钮) #关联到函数

func 当点击按钮():
    $Label.text = str(int($Label.text) + 3)

$Button.pressed 是 Signal 类型

需要断开连接时

$Button.pressed.disconnect(当点击按钮

自定义信号

在脚本中使用 signal 关键字定义信号,具体格式和定义方法差不多:

signal <信号名>([参数列表])

触发信号,手动触发 emit 关键字

signal 开机完成()

func 开机():
    print("加载中...")
    已经开机 = true
    print("开机完成")
    开机完成.emit()

信号可以向外界反应自身的状态,但这不是节点之间的唯一通信途径,别忘了我们可以直 接使用 <节点变量>.属性或方法 这种形式修改其他节点的属性或是调用其他节点的方法。

使用Callable

#test signal
signal abc


func _ready():
	connect("abc",Callable(slotabc))
	emit_signal("abc")


func slotabc():
	print("abc")

传递参数

signal abc


func _ready():
	connect("abc",Callable(self,"slotabc")) #或者这么写
	emit_signal("abc","123")


func slotabc(str):
	print(str)

26 组 

使用节点对象的 is_in_group 方法判断节点是否属于某个组

func _ready():
    print($Zombie.is_in_group("亡灵生物"))

虽然应该不常用,但如果你想要使用代码操纵节点的组,可以使用 节点添加到一个组中,或使用 add_to_group 方法把 remove_from_group 从组中移除节点

$Zombie.remove_from_group("亡灵生物")
$Zombie.add_to_group("怪物")
print($Zombie.is_in_group("亡灵生物")) # 输出 false
print($Zombie.is_in_group("怪物")) # 输出 true

还有个 get_groups 方法可以获取节点的全部组,一般用不到

27 节点自定义属性

给脚本中的属性变量加上 @export 前缀即可

@export var 玩家名: String = "没名字吗?"
@export var 钱包: int = 5
func _ready():
    # 注意,成员变量是指脚本最外层的变量,不要定义在方法里面!
    pass

给节点加上上面代码后,即可在属性面板看到效果

很多时候,属性导出可以代替掉 load 方法。

属性变量必须使用强类型指定类型或指定上初始值

28 字典 Dictionary

键值对 存储数据

var 玩家信息: Dictionary = {
 "名字": "Rika",
 "年龄": 22,
 "职业": "赤魔法师",
 }

获取元素值的方式也很类似数组,方括号里直接填写键即可,例如显示玩家名字:

print(玩家信息["名字"])

如果不能保证某个键是否存在,可以使用in 关键字(或者叫运算符)进行判断,in的左边 是键,右边是字典,结合 if 关键字:

if "武器" in 玩家信息:
    print("玩家手持 " + 玩家信息["武器"])
else:
    print("玩家没有武器")

如果只是简单的获取值,每次都加这个 if xxx in 也太麻烦了,所以 GDScript 为字典对象 提供了一个方法,叫做 get

print(玩家信息.get("名字"))
 # 等同于
print(玩家信息["名字"])

如果玩家信息中不包含名字键,则 get 方法会返回一个 并停止游戏。 null,而 ["名字"] 索引则会报错 同时,get 方法的第二个参数可以指定一个默认值,若键不存在,则返回这个默认值:

print(玩家信息.get("名字", "无名"))

字典的增加删除

若要向字典中添加数据,直接使用元素赋值语句即可: 玩家信息["武器"] = "小棍子"。如果字典中存在武器键,则会修改对应的值,若没有武器键则会创建这个键并赋值。

若要删除,则调用 erase 方法并传入一个键,例如 玩家信息.erase("武器") 就会删除武 器键值对。

29 类

写在节点上的脚本就是一个类

extends Node2D
 
var 移动速度: int = 100
 
func _physics_process(delta):
    var 移动 := 获取横向移动()
    if 移动 != 0:
        position.x += 移动 * delta * 移动速度

func 获取横向移动() -> int:
    if Input.is_action_pressed("Left"):
        return -1
    if Input.is_action_pressed("Right"):
        return 1
    return 0

在其他代码中,也可以获取当前节点,从而获取其中的属性和方法。

$"/root/玩家".移动速度 = 300 #这样就可以修改玩家的移动速度了
$"/root/玩家".获取横向移动()  #在节点上定义的方法也可以被上述方式访问

命名类: class_name 关键字

内部类:定义类

某些情况下,我 们需要一些小巧的类,我们懒得去再创建一个新的脚本文件了,此时就可以用内部类语法:

class <类名>:
 <类成员(方法、属性、信号等)>

例如我们用这种形式定义一个 伤害来源 类:

class 伤害来源:
    var 攻击者 = null
    var 伤害值 = 0
    var 是魔法吗 = false

这样定义的类被称作内部类,就是说这个伤害来源类是位于当前脚本文件类内部的 类,所以外部脚本想要使用这个伤害来源类时,就需要使用 外部类.内部类 的形式来访问。

假设刚刚定义 伤害来源 类的文件中有一行 class_name 伤害相关,那么外部代码在使用 伤 害来源 类时则需要:

var 伤害 = 伤害相关.伤害来源.new() # 实例化这个类

30 向量归一化

normalized() 

返回该向量缩放至单位长度的结果。等价于 v / v.length()。另见 is_normalized()。

31 封装

也就是不直接访问变量,而是通过函数修改 (set/get事物)

使用<节点>.<属性变量>的方式引用其他节点的属性变量并修改,但这样其 实很危险,例如我们可能会不小心把玩家速度设置成负数,或将玩家的生命值设置过大导致超出生 命上限。

var 移动速度: int = 100
# 假设玩家最低移动速度是 10
func 设置移动速度(新速度:int):
    if 新速度 < 10:
        新速度 = 10
        移动速度 = 新速度
func 获取移动速度() -> int:
    return 移动速度

随后,我们只要保证每次用到 移动速度 时都通过调用 设置移动速度 或 获取移动速度 方法 即可。

肯定有一天我们会忘记 移动速度 属性还有两个对应的访问方法,这时候就需 要用到 GDScript 为我们提供的 set、get 关键字来指定变量的访问方法了:

var 移动速度: int = 100: set = 设置移动速度, get = 获取移动速度

在我们需要使用 移动速度 变量时,依旧按照普通变量的方式使用即可。就是说当执行 $"玩 家".移动速度 = 400 这句代码时,就会自动调用 设置移动速度 方法并将 400 作为参数传入其 中,相应的,执行 print($"玩家".移动速度) 时,实际输出的就是 获取移动速度 方法的返回 值。

var 属性:int = 10:set = _设置属性, get = _获取属性
func _设置属性(新的值:int):
    if 新的值 > 0:
        属性 = 新的值
    else:
        属性 = 新的值 * 新的值
func _获取属性() -> int:
    if 属性 == 0:
        print("巧了,属性值竟然是零")
    return 属性

_设置属性 和 _获取属性 方法作为属性的访问方法,咱一般不希望别人随便使用,所以 起名字的时候给加上下划线前缀来告诉别人没事别用我

简写形式:

var 属性 = 100:
    set(新的值):
        if 新的值 > 0:
            属性 = 新的值
        else:
            属性 = 新的值 * 新的值
    get:
        if 属性 == 0:
            print("巧了,属性值竟然是零")
        return 属性

32  继承

extends 关键字指定了当前类的父类

GDScript 仅支持单继承,就是说子类只能继承自一个父类。 不过,父类还可能继承自另一个父类(爷爷类),所以继承关系可以形成一条长链。

当我们的脚本继承自某个节点类时,我们实际 上就是在一种节点的基础上创造了一种新的节点,并添加了我们的功能,例如玩家移动等效果。

super关键词,引用父类实例并从中调用方法

# 在子类中
func 打电话(电话号码:String):
    print("为什么不试试发短信呢?")

    # 调用父类的打电话方法
    super.打电话(电话号码)

在子类的函数中调用父类同名的函数,直接写super()即可

33 多态

把子类实例存放到父类类型的变量中,并且可以根据父类中的成员名称访 问子类的成员

var 某人的手机:手机 = 新手机.new()
某人的手机.打电话("10086")
# 虽然 某人的手机 是手机类型,但其值是新手机,所以调用的是新手机的打电话方法。
# 某人的手机.发短信("10086","Hello")
# 上面这句注释掉的代码是错误的。
# 虽然新手机能发短信,但是 某人的手机 是老手机类型,不包含发短信方法。

使用多态的目的不是让我们的变量变得花里胡哨,而是规范一类操作

34 全局定义

同时注意自动加载列表中有一个全局变量按钮,当勾选了这个东西时即可在代码中的任意位置 通过前面的名称使用这个节点或脚本的实例,例如现在在任意代码处即可使用:

print(player.global_position)

如果学过其他编程语言中的设计模式,就会明白“自动加载”就是起到了单例模式的作用

35 load

PackedScene 部分提到 load 方法,知道它可以从文件中读取一个 PackedScene。

但实际上,load 方法可以读取任何 Godot 认识的文件,例如脚本、图片、声音以及 PackedScene等等。

例如加载图片并显示在 TextureRect 节点上:

$"TextureRect".texture = load("res://你的图片路径")

或者加载一段声音并播放:

$"AudioStreamPlayer".stream = load("res://你的音频路径")
$"AudioStreamPlayer".play()

36 类型判断

有时候我们需要判断一个实例是否是某个类的实例,此时可以使用 is 关键字

func _on_body_entered(body):
    if body is 敌人:
        print("敌人进来了")
    else:
        print("进来的不是敌人")

37 静态方法

使用 static 关键字标注:

static func 获取生命值高(敌人1, 敌人2):
    if 敌人1.生命值 > 敌人2.生命值:
        return 敌人1
    return 敌人2

如果上面代码所在的文件中定义了类名 class_name 工具,即可在任意代码处使用 工具.获取生命值高 来调用这个方法。

38 格式化字符串

<模版字符串> % <填入值>

例如:

发消息("获得了 %d 点经验,你升到了 %d 级!" % [经验, 等级])

前面字符串中的 %d 我们称之为占位符,此处会被替换成百分号后面的内容,上例中, 变量的值填入到了第一个 %d 的位置, 等级变量的值填入到了第二个 %d 的位置。

39 存档读档

Godot 拥有对 Json 数据的完美支持,如果你学习过 Json,可以尝试使用 Json 保存游戏存 档。

介绍一种纯 Godot 的数据保存方式

元数据

可以理解成给节点添加一些额外的信息,这些信息类似属性,也是由名称和 值来表示的

代码当中,使用 set_meta 函数添加

39 函数式编程   Lambda表达式

函数式编程允许我 们在变量中存放一个方法的引用,并可通过这个变量调用对应的方法

func _ready():
    var f = A
    f.call() # 输出 123
func A():
    print("123")
func B():
    print("666")

同时我们也可以简写,直接将方法定义到变量中,而不用定义新的方法

var hello = func():
    print("Hello")
hello.call()

这种写法一般称为 Lambda 表达式,或者匿名方法。 注意,调用变量中的方法必须要使用 .call,如果方法有参数,则填入到 中即可。

40子节点操作

查找 find_child

用 find_child 来获取藏在子节点内部甚至子子子节点中的某个节点

节点名参数还可以使用 * 和 ? 这种通配符

# 在当前节点的子节点中查找名为 "MyChildNode" 的子节点
var child_node = find_child("MyChildNode", recursive = false)

# 检查是否找到对应的子节点
if child_node != null:
    # 找到了子节点,做进一步处理
    print("找到子节点:", child_node)
else:
    # 没有找到对应的子节点
    print("未找到子节点")

遍历子节点

get_child_count 可以获取当前节点的子节点个数。 get_child 可以根据下标获取一个子节点。 结合 for 语句可以遍历全部子节点:

for i in range(get_child_count()):
    print(get_child(i))

41 字符串操作

  1. 字符串连接

    • + 运算符:用于连接两个字符串。
    • append():将一个字符串附加到另一个字符串的末尾。
  2. 字符串查找

    • find():在字符串中查找指定的子字符串,返回第一次出现的索引位置。
    • rfind():在字符串中反向查找指定的子字符串,返回最后一次出现的索引位置。
    • findn():在字符串中查找所有指定的子字符串。
  3. 字符串替换

    • replace():替换字符串中的指定子字符串。
  4. 字符串切片

    • substr():截取字符串的子串。
    • left() 和 right():截取字符串的左边或右边一定长度的子串。
    • substr_replace():替换字符串中的子串。
  5. 字符串大小写转换

    • to_lower():将字符串转换为小写。
    • to_upper():将字符串转换为大写。
  6. 字符串分割

    • split():将字符串分割为子字符串。
  7. 字符串去除空格

    • trim_prefix() 和 trim_suffix():去除字符串开始或结尾的空格。
    • strip_edges():去除字符串两端的空格。
  8. 字符串格式化

    • format():使用参数替换字符串中的格式说明符。
    • 使用 % 和 format 函数进行格式化字符串。

42 Object类 

Object 是 Godot 中所有类的父类,所有类都继承自 Object。

Object 的重要函数

▪ _init() virtual :初始化虚函数,当一个对象生成时,此函数立即 自动调用

▪ _notification(what: int) virtual:接收通知虚函数,当此节点接收到 notification 的通 知时此函数自动被调用。what 以数字的形式来告知此函数具体是接收到了那一个 notification。

▪ connect/disconnect/is_connect: 信号连接、取消连接、是否存在对应的信号连 接。信号出现在 Object 内部,这代表 Godot 中所有的对象都可以进行信号连接,信 号不是节点的专利。

▪ emit_signal :发射信号。

▪ call 与 call_deffer :调用或在空闲帧中调用函数。对于空闲帧的解释会在后续教程 中进行解释。 ▪ set_script(script) : 设置此对象的 GDS 脚本。Godot 中大部分对象都可以设置脚 本。

▪ free () :从程序中删除此对象,也就是将此对象从内存中移出。Godot 中所有对象都可以执行 free 功能。

43 RefCounted  节点之外的对象

非常适合用于制作游戏中数目庞大的数据对象。如仙侠游戏中 的成百上千的随机 NPC 数据对象。

RefCounted 继承自 Object,它以及它的子类同样具有 Object 的所有功能。 ▪ 它拥有众多的子类。RefCounted 内置了一个独特的引用计数器,要了解掌握这些子 类,就必须先了解掌握 RefCounted 的引用计数器机制。

RefCounted 对象的生成

▪ 类名 .new() (类名由 class_name 进行自定义)

▪ load(“路径”).new()(也可以 preload("路径").new())

RefCounted 的特征及作用

▪ 特征 1:内置引用计数器,当引用计数器归 0 时,此对象自动被程序删除。

▪ 特征 2:在 GDS 中不包含任何内置属性,仅有四个内置函数,也不必像节点一样要频 繁的受场景树的控制,参与服务器的计算工作,相比于节点更加轻量。

▪ 作用 :RefCounted 不但比 Node 占用更少的内存节约更多的电脑的算力、还可以 进行内存的自动管理。非常适合用于制作游戏中数目庞大的数据对象。如仙侠游戏中 的成百上千的随机 NPC 数据对象。

RefCounted 的重要子类

最重要:

▪ Resource——游戏文件与游戏程序的中转站,所有的图片、音频、视频文件都必须 先转为 Resource,然后才可以被节点等对象使用。

了解即可 

▪ Astar2D/Astar3D 等可以用于构建游戏中的寻路数据。

▪ Thread/Mutex/SemaPhore 可以用于构建多线程。

▪ FileAcess/DirectoryAcess 可以用于加载读取计算机内的文件。

▪ 还可以使用某些子类进行 UDP、TCP 或Websocket 的网络通信。

▪ StreamPeer 可以以字节单位对网络传输中收发的数据进行处理。

▪ 此外,它的子类还涉及了正则、JSON、游戏物理、自定义引擎编辑器等领域

44 Resource 资源类

Resource 是游戏文件与游戏程序的中转站。游戏项目内的大部分文件都要先转为 Resource 对象,然后才可以被游戏程序中的节点等对象使用。

需要转为 Resource 的常见文件

• 图片、3D 模型、音频、视频文件

• 场景文件

• 脚本文件

Object
│
├─ RefCounted
│   │
│   └─ Resource

资源供对象的使用的方式

• 多对象使用同一资源本身、适用于图片、3D 模型、音频、视频文件等。

• 资源经过再处理后,生成新对象,供不同对象使用。

特例:• 脚本资源再处理生成的对象在 GDS 中不可见,这个新对象被编写在内置代码中, 当我们使用 set_script 或绑定脚本的对象生成时它也将自动生成。

程序中 Resource 对象的创建

通过路径加载文件生成资源。

①资源随场景启动而自动创建

▪ 将资源文件(可以转化为资源的文件)拖入游戏项目中。

▪ 编辑器对资源文件进行导入,生成特殊文件以及资源文件对应的 import 文件。 import 文件会指向特殊文件。

▪ 游戏启动后借由 import 文件加载特殊文件生成资源对象。

②load 与 preload

如果加载的文件已经被转化为资源,且此资源引用计数器不为 0,则在此加载此文件不会产 生新的资源对象,而是返回一个原先资源对象的引用。

load("文件路径")或 preload("文件路径")

创建新对象

③由类名

.new()创建,不会产生相同引用。

▪ 内置资源类:某些内置资源类也可以使用 new 以外的函数来创建资源对象。部分内置 资源类内部拥有读取与保存文件的函数。

▪ 自定义资源类

load 与 preload 的区别

▪ load 与 preload 的参数都是文件路径,都是字符串形式,但 load 的参数可以是字符 串变量。而 preload 则无法使用变量,这是由于二者执行的时机不一样所导致的。

▪ 当脚本文件转化为脚本资源时,转换部分的程序会自动翻找文件中是否出现了 preload 函数,若出现,则在脚本资源生成的同时进行 preload 资源的加载。这时候 变量还没有出现,因此只能以文本字符串的形式来告知 preload 加载的内容。

▪ load 后可跟随字符串变量,字符串变量的值可以在程序运行时改变,如果改变变量, load 可以加载不同的文件。

▪ preload 后只能跟随固定的字符串,它不会在程序运行时改变。

资源保存的格式

资源对象是文件和程序的中转站,它不是简简单单的文件本身,一个资源对象可能是对一个 或对个文件处理后产生的数据对象。这些处理可以是对若干图片的裁剪、压缩、拼接;一个 资源对象,也可能是对节点某部分功能的记录与描述,比如 Pong 中区域检测时 shape 节点 的形状就也是一个资源对象。要保存这些复杂的信息,Godot 必须准备单独的文件。

▪ tres :文本资源格式。资源的内容以文本的形式储存,资源文件具有可读性。

▪ res :二进制资源格式。简单来说就不是人话,但是程序加载速度更快。

▪ 某些资源会提供特殊格式来保存自己的数据。

Resource 对象的特征

▪ 加载路径产生资源时,如果原先的资源不消失,则不会产生这个文件的新资源对象, 而只返回引用。在任意一处对此资源的修改,都将影响到所有使用此资源的对象

(更新中)

附录:

这里罗列一下文章《GDScript零基础图文入门.pdf》中提到的参考资料。(在此感谢作者分享)

开始

Godot 官网:Godot Engine - Free and open source 2D and 3D game engine

最新正式版下载:Download for Windows - Godot Engine

所有版本下载:Index of /godotengine/ (downloads.tuxfamily.org)

视频教程

完整教程

十分钟制作横版动作游戏|Godot 4 教程《勇者传说》#0_哔哩哔哩_bilibili (B 站视频)

【中字】Godot 4 吸血鬼生存复刻教程【新手】P0:简介_哔哩哔哩_bilibili(B 站视频)

RTS即时战略游戏 (B 站视频)

【Godot教程搬运】2D平台游戏快速入门指南 ~ Godot 4 游戏开发新手教程_哔哩哔哩_bilibili(B 站视频)

【Godot教程搬运】速成班 ~ 如何在 Godot 4 中制作资源收集游戏!_哔哩哔哩_bilibili(B 站视频)

【Godot4】从零开始制作土豆兄弟 #1 | 创建背景_哔哩哔哩_bilibili(B 站视频)

Godot 俯角射击游戏教程(jmbiv) (bilibili.com)(B 站视频)

Godot4.0 2D游戏全要素全技能速成教程(B 站视频)

Godot 制作 Roguelike 游戏系列_哔哩哔哩_bilibili(B 站视频)

经典的俄罗斯方块游戏(B 站视频)

godot 4.x 教程 100集_哔哩哔哩_bilibili(B 站视频)

知识点

【Godot】Control节点实例—背包系统_哔哩哔哩_bilibili(B 站视频)

【转载】【Godot】超简单的2D可破坏地形 - SUPER EASY 2D DESTRUCTIBLE TERRAIN in Godot_哔哩哔哩_bilibili(B 站视频)

将 VS Code 连接到 Godot(B 站视频) 

文档资源

godot官方简体文档

Home :: Godot 4 Recipes (kidscancode.org) :英文网站,本网站收集了各种解决方案和示例,可帮助您制作所需的任何 游戏系统。

A Vegetables Bird's Blog (liuqingwen.me) Liuqingwen 的个人博客:包含众多 Godot 中文资料

资源网站

开源素材的整合站:

国内的开源素材的整合站,制作游戏时不再因为找可商用素材花费大量时 间

godot工坊:国内的 Godot 论坛,包含文档、教程、资源、答疑等板块。

(不知道为什么都没打开)

Download the latest indie games - itch.io:国外的独立游戏交流平台,有很多资源和工具,还能玩其他作者的游戏。

Home · Kenney:国外的资源网站,上千免费资源任你使用,大多是 low poly 或像素风。

Game UI Database | Welcome :国外的游戏 UI 网站,主要用来学习和参考。

Godot Asset Library - The Best Assets, All Free:一个国外的 Godot 资源网站,看起来比 Godot 自带的资源网站高级一些。

Godot Assets Marketplace – All you need in one place (godotmarketplace.com):国外的 Godot 资源市场,类似上一个,包含付费内容

补充:

OpenGameArt.org  一个游戏素材社区,你可以提交素材或在上面找到想要的免费纹理、人物、3D模型、音乐、音效等,非常良心。各素材的许可证可能不同,使用时请遵循许可证。

Home · Kenney 来自国外工作室Kenny,包含100多个免费素材和教程,例如UI,材质,人物,背景,也可以学习教程自己绘制素材。并且他家也开发游戏,有兴趣可以看看。

free3d.com 你可以在Free 3D上找到你想要的3D模型,并且支持下载Blender模型,3DMAX模型,FBX模型等。模型非常精致,但是打开速度有点慢(几乎打不开,建议科学)。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值