Godot 免费跨平台游戏引擎 (四、脚本GDScript)
GDScript是Godot默认的编程语言,使用了类似Python的语法。
extends BaseClass #继承
class_name MyClass, "res://path/to/optional/icon.svg" #带有图标的类定义(可选)
# 变量定义
var a = 5
var s = "Hello"
var arr = [1, 2, 3]
var dict = {"key": "value", 2: 3}
var typed_var: int
var inferred_type := "String"
# 常量
const ANSWER = 42
const THE_NAME = "Charly"
# 枚举
enum {UNIT_NEUTRAL, UNIT_ENEMY, UNIT_ALLY}
enum Named {THING_1, THING_2, ANOTHER_THING = -1}
# 内置的向量类型
var v2 = Vector2(1, 2)
var v3 = Vector3(1, 2, 3)
# 函数
func some_function(param1, param2):
var local_var = 5
if param1 < local_var:
print(param1)
elif param2 > 5:
print(param2)
else:
print("Fail!")
for i in range(20):
print(i)
while param2 != 0:
param2 -= 1
var local_var2 = param1 + 3
return local_var2
# 基于父类/基类的函数重写.
# 如果你仍要调用父类的函数,使用.号。
func something(p1, p2):
.something(p1, p2)
# 内部类
class Something:
var a = 10
# 构造器
func _init():
print("Constructed!")
var lv = Something.new()
print(lv.a)
语言
标识符
标识符区分大小写。
关键词
常见的就列出来,不常见的做一点解释
if、elif、else、for、while、match、break、continue、pass、return、func、const、enum、var
class:定义一个类
extends:定义用当前类扩展什么类
is:测试变量是否扩展给定的类,或者是否是给定的内置类型
as:如果可能,将值转换为给定类型
self:引用当前类实例
tool:在编辑器中执行脚本
signal:定义一个信号民
static:定义一个静态函数
onready:一旦脚本附加到的节点及其子级成为场景树的一部份,就初始化变量。
export:保存一个变量及其附加资源,使其在编辑器中可见和可修改。
setget:为变量定义setter和getter函数.
breakpoint:调试器断点的编辑器助手。
preload:预加载一个类或变量。
yield:协和支持
assert:断言一个条件,如果失败则记录错误。
remote:
master:
puppet:
remotesync:
mastersync:
puppetsync:
PI:圆周率常量
TAU:TAU常量 ?
INF:无穷大常数。
NAN:NAN(不是一个数字)常数
上面没有解释的几个,都是关于网络RPC方面的。
运算符
x[index] 数组索引
x.attribute 属性引用
foo() 函数调用
is 实例类型检查
~ 按位取反
-x 负/一元否定
* / % 乘/除/余数
+ -
<< >> 位移
& ^ | 按位与、异或、或
< > == != >= <=
in 内容测试
! not 布尔非
and && 布尔与
or || 布尔或
if x else 三元if/else
= += -= *= /= %= &= |=
变量
45 基数为10的整数
0x8F51 基数为16整数
0b101100 基数2整数
3.14, 58.1e-10 浮点数(实数)
"Hello" 字符串
"""Hello""" 多行字符串
@"Nodel/Label" ??
$NodePath get_node("NodePath")的快捷方式,类似于获取节点名柄
注释
任何以#开始到行尾的内容视为注释
内置类型
null 空数据类型 bool true/false int 正/负整数 float 浮点实数 String Unicode字符序列
Vector2 二维向类(包括x和y) Rect2 二维矩形(包括postion、size和end字段) Vector3 三维向量(x、y、z) Transform2D 2D变换在3x2矩阵 Plane 3D平面类型的标准形式包含一个 normal 向量字段以及一个 d 标量距离 Quat 四元数是一种用于表示3D旋转的数据类型。 AABB 轴对齐边界框(或3D框)(包含position、size和end) Basis 3x3矩阵被用于3D旋转与绽放。 Transform 3D变换(包含basis(Basis)、origin(Vector3))
Color 颜色(r g b a) NodePath 编译路径,到一个主要用在场景系统中的节点。 RID 资源ID Object 任何非内置类型的基类
Array 任意对象类型的泛型序列,包括其他数组或字典。索引从0开始。负索引表示从尾部开始计数。
var arr = []
arr = [1, 2, 3]
var b = arr[1] # 2.
var c = arr[arr.size() - 1] # 3.
var d = arr[-1] # 与上相同
arr[0] = "Hi!" # 替换 1 为 "Hi!".
arr.append(4) # 序列现在是 ["Hi!", 2, 3, 4].
字典
var d = {4: 5, "A key": "A value", 28: [1, 2, 3]}
d["Hi!"] = 0
d = {
22: "value",
"some_key": 2,
"other_key": [2, 3, 4],
"more_key": "Hello"
}
数据
变量
var 定义
在声明中初始化变量
var my_vector2 := Vector2()
var my_node := Sprite.new()
有效的类型:
内置类型(Array、Vector2、int、String等)
引擎类(Node、Resource、Reference等)
常量名(const MyScript = preload("res://my_script.gd") )
脚本类使用 class_name 关键字声明
转换
如果值是相同类型或转换类型的子类型,则在对象类型之间进行转换会导致相同的对象。如果该值不是子类型,则强制转换操作将产生 null 值。
var my_node2D: Node2D
my_node2D = $Sprite as Node2D
对于内置类型,如果可能,将对其进行强制转换,否则引擎将引发错误。
var my_int: int
my_int = "123" as int
my_int = Vector2() as int
在与场景树进行交互时,强制转换对于获得更好的类型安全也很有用:
var my_sprite := $Character as Sprite
($AnimPlayer as AnimationPlayer).play("walk")
枚举
enum {TILE_BRICK, TILE_FLOOR, TILE_SPIKE, TILE_TELEPORT}
与下相同:
const TILE_BRICK = 0
const TILE_FLOOR = 1
const TILE_SPIKE = 2
const TILE_TELEPORT =3
enum State {STATE_IDLE, STATE_JUMP = 5, STATE_SHOOT = 6 }
与下相同:
const State = {STATE_IDLE, STATE_JUMP = 5, STATE_SHOOT = 6}
函数
变量查找的作用域优先级是:局部->类成员->全局。 self 变量总是可用的,并作为访问类成员的选项提供。
函数可以任何时候 return,默认返回值是 null。
func my_func(a:int, b:String):
pass
# 函数有默认值,可以自动推断类型
func my_func1(int_arg := 42, String_arg := "string"):
pass
# 指定返回类型
func my_int_fun() -> int:
return 0
# void 不返回任何内容
func void_func() -> void:
return
引用函数
与Python不同,若要在过行时按名称引用一个函数,必须使用call或funcref。
my_node.call("my_function", args)
var my_func = funcref(my_node, "my_function")
my_func.call_func(args)
静态函数
函数可以声明为静态,它不能访问实例成员变量或 self。
static func sum2(a, b):
return a + b
语句和控制流程
# 允许将语句放在条件相同的行上
if 1 + 1 == 2: return 2 + 2
else:
var x = 3 + 3
return x
# 三元表达式
# var x = [值] if [条件] else [值]
y += 3 if y < 10 else -1
for x in [5,7,11]:
pass
var dict = {"a":0, "b":1, "c":2}
for i in dict:
print(dict[i])
for i in range(3):
print(i)
for i in range(1,3):
print(i) #1,2
for i in range(2,8,2):
print(i) #2,4,6
for i in 3: #等同于range(3)
print(i)
for i in 2.2: #等同于range(ceil(2.2))
print(i)
match
类似于switch,用于分支程序运行。
match [变量]:
[模式](s):
[程序块]
[模式](s):
[程序块]
match x:
1:
print("...")
2:
print("....")
"test":
print("????")
match typeof(x):
TYPE_REAL:
print("float")
TYPE_STRING:
print("text")
TYPE_ARRAY:
print("array")
match x:
1:
print("...")
2:
print(".....")
_:
print("下划线匹配所有其它情况,类型default")
#绑定模式
match x:
1:
print("...")
2:
print("....")
var new_var:
print("...", new_var)
#数组模式
match x:
[]:
print("空数组")
[1,3,"test",null]:
print("...")
[var stat,_,"test"]:
print ("stat开头,test结尾的数组")
[42,...]:
print("包含以42开头的数组?")
#字典模式
match x:
{}:
print("空字典")
{"name": "Dennis"}:
print("name为Dennis")
{"name": "Dennis", age, var age}:
print("Dennis is ", age, " years old.")
{"name", "age"}:
print("包含name和age")
{"key": "godo", ...}
print("检测key,其它没管")
类
默认情况下,所有脚本都是未命名的类。只能使用路径引用
extends "res://path/character.gd"
var Character = load("res://path/to/character.gd")
var character_node = Character.new()
也要以为类命名,并附图标。
extends Node
class_name Item, "res://interface/icons/item.png"
继承
不允许多重继承。使用 extends 关键字。
extends SomeClass
extends "somefile.gd"
extends "soefile.gd".SomeInnerClass
# 检查给定的实例是否从给定的类继承
const Enemy = preload("enemy.gd")
if (entify is Enemy):
entify.apply_damage()
# 调用基类,在函数面前加.,象其它语言的super关键字。
func some_func(x):
.base_func(args)
默认函数象_init和大多数通知象_enter_tree、_exit_tree、_process、_physics_processt等,将自动调用在所有父类中的函数。无需显示调用它们。
类构造器
# 在类实例化时调用的类构造函数名为_init。
# 如果被继承的类的构造函数接受参数,则将它们传递为:
func _init(args).(parent_args):
pass
#示例
#State.gd
var entify = null
var message = null
func _init(e=null):
entity = e
func enter(m):
message = m
#Idle.gd
extends "State.gd"
#此构造函数将e传入基类
func _init(e=null, m=null).(e):
message = m
内部类
类文件可以包含内部类。内部类使用 class 关键字定义。ClassName.new() 实例化。
class SomeInnerClass:
var a = 5
func print_value_of_a():
print(a)
func _init():
var c = SomeInnerClass.new()
c.print_value_of_a()
类作为资源
存储为文件的类被视为 Resource。必须从磁盘加载它们才能在其他类中访问。
var my_class = load("myclass.gd")
const MyClass = preload("myclass.gd") #预载
func _init():
var a = MyClass.new()
a.some_function()
Setters/getters
函数用于获取类的成员在何时出于何原因被调用。
var 变量 = 值 setget setterfunc, getterfunc
每当变量被外部的源修改时,seeterfunc函数将被调用。这发生在值改变之前,函数将决定如何处理。
var my_var setget my_var_set, my_var_set
func my_var_set(new_value):
my_var = new_value
func my_var_get():
return my_var
也可以只要一部份
var my_var = 5 setget my_var_set
var my_var = 5 setget ,my_var_get
注意,本地访问是不触发函数的。
工具模式
我猜,这是用于改良编辑界面,提供一些适合中于自己的功能。
用tool关键字放在文件顶部
tool
extends Button
func _ready():
print("Hello")
信号
func _ready():
var character_node = get_node("Character")
character_node.connect("health_depleted", self, "_on_Character_health_depleted")
func _on_Character_health_depleted():
get_tree().reload_current_scene()
# 例
signal health_changed
func take_damage(amount):
emit_signal("health_changed",1,2)
func _on_Character_health_changed(old_value,new_value):
...
协程使用yield
调用 yield() 将立即从当前函数返回,并且相同函数的当前冻结状态作为返回值。调用 resume() 将继续执行并返回函数。
#将输出Hello、my dear、 world
func my_func():
print("Hello")
yield()
print("world")
func _ready():
var y = my_func()
print("my dear")
y.resume()
还可以在 yield() 和 resume() 之间传递值
#将打印Hello、world、cheers
func my_func():
print("Hello")
print(yield())
return "cheers"
func _ready():
var y = my_func()
print(y.resume("world"))
协程&信号
使用 yield 的真正优势在于信号结合使用。 yield 可以接受两个参数,对象和信号。收到信号后,将重新开始执行。
yield(get_tree(),"idle_frame") #下一帧继续执行
yield(get_node("AnimationPlayer"),"animation_finished") #播放完动画后继续执行
yield(get_tree().create_timer(5.0),"timeout") #等待5秒然后继续执行
协程自身转换为无效状态时会使用 completed 信号
#my_func 仅在按下两个按钮后继续执行
func my_func():
yield(button_func(), "completed")
print("所有键都按下了")
func button_func():
yield($Button0, "pressed")
yield($Button1, "pressed")
Onready 关键字
使用节点时,通常希望将对场景部份的引用保留在变量中。由于仅在进入活动场景树时才保证要配置场景。因此只有在调用 Node._ready() 时才能获得子节点
var my_label
func _ready():
my_label = get_node("MyLabel")
#将成员变量的初始化推迟到调用 _ready()。以上就可以用一行来替换:
onready var my_label = get_node("MyLabel")
Assert 关键字
可用于检查调试版本中的条件。??暂跳过。
GDScript 动态语言简介
变量与赋值
int a
a = "Hi" #出错
var a
a = "Hi" #没问题
a = 5 #没问题
类似的,在函数定义时
func print_value(int value)
func print_value(value)
指针和引用
func use_class(instance): #不关心类的类型
instance.use() #任何有use方法的类
func do_something():
var instance = SomeClass.new() #引用创建
use_class(instance) #作为引用传递
数组
var array = [10, "hello", 40, 60]
array.resize(3)
use_array(array) #用为引用传递
var array = []
array.append(4)
array.pop_front()
字典
字典是动态的,键可以在任何一点添加或删除
d["monther"] = "Rebecca"
d.erase("name")
var d = {
name = "John",
age = 22
}
print("Name:",d.name)
d.mother = "Caroline"
For & while
for i in range(10,0,-1)
for key in dict
鸭子类型 ?
GDScript 导出
可导出类成员,它们的值会与它们所附加的资源一起保存。也可以在属性编辑器中进行编辑。
extends Button
export var number = 5 #对于赋值的情况,编辑器会推断类似
export(int) var number
export(Texture) var character_face
export(Resource) var resource
export(int, "Warrior", "Magician", "Thief") var character_class
export(String, "Rebecca", "Mary") var character_name
export(String, "中国", "美国") var GuoJia #变量不能为中文,选项可以为中文
enum NameEnum{THING_1, THING_2, ANOTHE = -1}
export(NameEnum) var x
export(String, FILE) var f #一个带路径文件
export(String, DIR) var f #一个目录
export(String, FILE, "*.txt") var f #带类型过滤的文件
export(String, FILE, GLOBAL, "*.png") var tool_image #这只能用于工具中
export(String, DIR, GLOBAL) var tool_dir
export(String, MULTILINE) var text #多行文本
export(int, 20) var i #从0到20
export(int, -10, 20) var j #从-10到20
export(float, -10, 20, 0.2) var k #浮点数,从-10到20,间隔0.2
export(float, EXP, 100, 1000, 20) var l #y=exp(x)
export(float, EASE) var transition_speed #编辑时显示ease函数的可视化表示形式
export(Color, RGB) var col
export(Color, RGBA) var col
export(NodePath) var node #节点选择
导出位标志
用作位标志的整数可以在一个属性中存储多个布尔值,有点类似于枚举。但是用8421码的方式进行存储。即Fire=1,Water=2,Earth=4,Win=8
export(int, FLAGS, "Fire", "Water", "Earth", "Wind") var spell_elements = 0
导出数组
导出的数组在所有实例之间都是 共享的。
export var a = [1, 2, 3]
export(Array, int) var ints = [1,2,3]
export(Array, int, "Red", "Green", "Blue") var enums = [2, 1, 0]
export(Array, Array, float) var two_dimensional = [[1.0, 2.0], [3.0, 4.0]]
GDScript 格式字符串
var format_string = "We're waiting for %s."
var actual_string = format_string % "Godot"
var format_string = "We're waiting for {str}"
var actual_string = format_string.format({"str":"Godot"})
var format_string = "%s was reluctant to learn %s, but now he enjoys it."
var actual_string = format_string % ["Estragon", "GDScript"]
占位符类型
s 通过隐式 String 转换的相同方法 简单 转换为 String。
c 单个 Unicode 字符。对于代码点或单个字符, 需要一个无符号的8位整数(0-255)。
d 一个 十进制 整数。需要一个整数或实数(向下取整)。
o 一个 八进制 整数。需要一个整数或实数(向下取整)。
x 一个 小写 字母的 十六进制 整数。需要一个整数或实数(向下取整)。
X 一个 大写 字母的 十六进制 整数。需要一个整数或实数(向下取整)。
f 一个 十进制 实数。需要一个整数或实数。
占位符的修饰符
+ 用在数字说明符中,如果为正 显示 + 号 。
Integer 设置 填充 。用空格填充,或在一个整数占位符中如果整数以 0 开头,则用0填充。当使用在 . 后时,参见 . 。
. 在 f 之前,设置 精度 到0小数位。可以跟数字来改变。用零填充。
- 向右填充 而不是向左。
* 动态填充,在 . 后期望额外的整数参数来设置填充或精度。参见 动态填充。
填充
print("%10d" % 12345)
print("%010d" % 12345)
print("%10.3f" % 10000.5555)
print("%-10d" % 12345678)
#动态填充
var format_string = "%*.*f"
print(format_string % [7, 3, 8.8888])
- 通过函数名调用函数,即通过字符串调用
my_node.call(“my_function”, args)
- 将函数保存到变量
var my_func = funcref(my_node, “my_function”) #保存
my_func.call_func(args) #调用
继承
extends SomeClass #继承/扩展于一个全局类
extends “some file.gd.” #继承/扩展于一个类文件
extends “comefile.gd.“SomeInnerClass #继承文件的内部类
#判断是否继承
const Enemy = preload("enemy.gd")
if (entity is Enemy):
entity.apply_damage()
var my_class = load(“myclass.gd”)
const MyClass = preload(“myclass.gd”) #仅在编译时加载
可导出的类成员
export var number = 5 #可以导出的类成员
export(int) var number #数据类型
export(int, “Warrior”, “Magician”, “Thief”) var character_class #编辑器将枚举三个字符串为0.1.2
export(String, “Rebeca”, “Mary”, “Leah”) var character_name #枚举为字符串
以下也为枚举:
enum NamedEnum {THING_1, THING_2, ANOTHER_THING = -1}
export (NamedEnum) var x
export(String, FILE) var f #字符串是文件路径
export(String, DIR) var f #字符串是目录
export(String, FILE, “*.txt”) var f #指定文件类型
export(String, FILE, GLOBAL, “*.png”) var tool_image #字符串是全局文件系统中PNG文件的路径
export(String, DIR, GLOBAL) var tool_dir
export(String, MULTILINE) var text #多行字符串
export(int, 20) var i #值范围0-20
export(int, -10, 20) var j #值范围-10-20
export(float, -10, 20, 0.2) var k #值范围-10-20,步长0.2
export(float, EXP, 100, 1000, 20) var l #值在100-1000间按步长20变化,将显示滚动条
export(float, EASE) var transition_speed #显示ease()函数的可视化表示形式
export(Color, RGB) var col #RGB值
export(Color, RGBA) var col #RGBA值
export(NodePath) var node #场景中节点
Export(int,FLAGS) var spell/elements=ELEMENT/WIND|ELEMENT/WATER #单独编辑一个整数位
export(int, FLAGS, “Fire”, “Water”, “Earth”, “Wind”) var spell_elements = 0
export var a = [1, 2, 3] #数组
export(Array, int) var ints = [1,2,3]
set/get
var my_var setget my_var_set,my_var_get
func my_var_set(new_value):
my_var = new_value
func my_var_get():
return my_var
工具模式
tool
extends Button
func _ready():
print("Hello")
信号
signal your_signal_name
signal your_signal_name_with_args(a,b)
func _callback_no_args():
print("Got callback")
func _callback_args(a,b):
print("a:",a,"b:",b)
func _at_some_func():
instance.connect("your_signal_name",self,"_callback_no_args")
instance.connect("your_signal_name_with_args",self,"_callback_args")
instance.connect("your_signal_name", self, "_callback_args", [22, "hello"])
识别调用对象
func _button_pressed(which):
print("Button was pressed: ", which.get_name())
func _ready():
for b in get_node("buttons").get_children():
b.connect("pressed", self, "_button_pressed",[b])
协程
func my_func():
print("Hello")
yield()
print("world")
func _ready():
var y = my_func()
print("my dear")
y.resume()
依次输入:Hello my dear world
func my_func():
print("Hello")
print(yield())
return "cheers"
func _ready():
var y = my_func()
print(y.resume("world))
依次输出:Hello world cheers
协程和信号
yield(get_tree(),“idle_frame”) #继续执行下一帧
yield(get_node(“AnimationPlayer”),“finished”) #动画播放结束后继续执行
yield(get_tree().create_timer(5.0),“timeout”) #等待5秒后继续执行
func my_func():
yield(button_func(),"completed")
print("All buttons")
func button_func():
yield($Button0,"pressed")
yield($Button1,"pressed")
onready
onready var my_label = get_node(“MyLabel”) #延迟成员变量的初始化,直到调用ready
与此相同:
var my_label
func _ready():
my_label = get_node("MyLabel")
assert
assert关键字可用于检查调试构建中的条件。这些断言在非调试构建中被忽略。
assert(i==0)