godot GDScript语法入门
官方文档
因为和python很像, 但又和python完全独立, 所以就说下区别, 没有说的就是和python一样:
class_name extends
因为一个文件就是一个类,跟java类似.
class_name是类名, 只有想让其他类(文件)引用的时候才要起名字.
extends就是扩展父类,也就是继承.
func
定义函数的关键字. python是def. 所以这个像是js的function的缩写
match
match相当于C的switch,但python一直没有类似switch的语句, 他们局的if/else足够了...
match param3:
3:
print("param3 is 3!")
_:
print("param3 is not 3!")
_
和default一个意思
match还支持一些更复杂的匹配方式, 这儿不罗列了, 需要的时候再去看官方文档.
is
检测变量是否继承自给定的类,或检测该变量是否为给定的内置类型。
as
尝试将值转换为给定类型的值
变量常量
跟js类似分别是var/const. python是不需要变量关键词的.
定义数组的方式没有什么不同,但定义dict的方式比较多样, 除了冒号还可以使用等号, 使用冒号的时候语法同python, key需要用双引号, 使用等号的时候类似js, key可以不用双引号(官方文档说这个事lua风格,我没用过lua):
var dict = {"key": "value", 2: 3}
var other_dict = {key = "value", other_key = 2}
访问key的方式和js一样, 比python方便: dict.key
或者dict["key"]
另外还支持严格变量类型检查:
var typed_var: int
var inferred_type := "String"
var s : String = 'hello'
enum和vector
enum类似于C
# Enums.
enum {UNIT_NEUTRAL, UNIT_ENEMY, UNIT_ALLY}
enum Named {THING_1, THING_2, ANOTHER_THING = -1}
# Built-in vector types.
var v2 = Vector2(1, 2)
var v3 = Vector3(1, 2, 3)
static
可以声明静态变量, 这个也类似C
signal
定义信号
breakpoint
用来设置脚本编辑器辅助调试断点的关键字。与在脚本编辑器每行最左侧点击红点创建断点不同, breakpoint 关键字可以储存在脚本内部。在不同机器上使用版本工具时,由 breakpoint 关键字创建的断点仍旧有效。
preload
预加载一个类或变量,参见 类作为资源 。
await
等待信号或协程完成,参见等待信号和协程。似乎类似于ES6
assert
断言条件,如果失败则记录错误。在非调试版本中忽略掉断言语法。参见 Assert 关键字。
少见的原生带断言且把断言作为关键字的语言了. 别的语言大多需要原生库或者第三方库实现.
void
用于代表函数不返回任何值. 借用的C语法.
特殊常数 PI TAU INF NAN
PI(π)常数。
TAU(τ)常数。
INF 无穷常量,用于比较和计算结果. 这个缩写我有点接受不了, 因为.inf
文件指的是information, 更多用作信息而不是无穷.
NAN(非数)常量,用作计算后不可能得到的结果。
运算符
基本的+-*/没有问题, 还支持**
乘方, %
取余.
按位运算和C,python都类似, 与或非分别是&|~
,完全一致. 异或是^
, 位移是>> <<
, 但没有循环位移>>>
逻辑比较和多数一致, 其中不等于也是!=
.
逻辑运算同时支持python和js运算符, 也就是not and or
和! && ||
同时支持.但官方更推荐not and or
风格, 比较容易理解且不容易混淆. 嗯, 这个我觉得是看个人语言习惯了. 要说符号看得头晕我觉得还得是正则表达式.
三元运算符:变量=真的值 if 条件 else 假的值
自加类运算符+= -= *= /=
都支持,但不支持++ --
可能和预期不符的运算
以下来自官方文档:
一些运算符的运算机制可能会与你的预期有所不符:
若运算符 / 两端的数值均为 int,则进行整数除法而非浮点数除法。例如: 5 /2 == 2 中 2 为该算式的结果而非 2.5 为结果。若希望进行浮点数运算,请将该运算符两端的其中一个数值的类型改为 float 。例如:直接使用浮点数( x / 2.0 )、转换类型( float(x) / y )、乘以 1.0 ( x * 1.0 / y )等。
运算符 % 仅适用于整型数值的取余运算。对于小数的取余运算,请使用 fmod() 方法。
对于负值,% 运算符和 fmod() 函数使用 截断算法 而非向负无穷大舍入,此时余数会带有符号(即余数可能为负)。如果你需要数学意义上的余数,请改用 posmod() 和 fposmod() 函数。
运算符
**
是 左结合运算符 ,也就是说,2 ** 2 ** 3
这个运算等价于(2 ** 2) ** 3
。对此,请使用括号来处理该运算的优先级,如2 ** (2 ** 3)
。
== 和 != 运算符在有些情况下允许你比较不同类型的值(例如,1 == 1.0 的结果为真),但在其他情况下可能会发生运行时错误。如果你不能确定操作数的类型,可以安全地使用 is_same() 函数(但请注意,该函数对类型和引用更加严格)。要比较浮点数,请改用 is_equal_approx() 和 is_zero_approx() 函数。
字面量
布尔值 true false
和js一样,和python不同, python是True False
十六进制和二进制还是0xabc
和0b10110
另外支持一个很有趣的下划线数字写法, 就是在很长的数字中间插入_
,和不插的意义一样, 但是方便阅读, 如:
12_345_678 # Equal to 12345678.
3.141_592_7 # Equal to 3.1415927.
0x8080_0000_ffff # Equal to 0x80800000ffff.
0b11_00_11_00 # Equal to 0b11001100.
字符串
字符串只支持双引号"hello",不支持单引号. 但支持三双引"""hello"""和三单引'''hello'''
原始字符串(不转义)r"hello"和r'hello', 也支持三引号形式.
另外还有两个特殊字符串: stringName, $"name". nodePath: ^"Node/Label"
stringName是为了提高字符串效率构造的新的类型, 我的理解是相同字符串引用的是相同地址, 这样比较起来很快速, 不需要逐字比较.
转义字符如\n \t
不在罗列,跟C js类似.
支持字符串格式化. 类似C形式的如:"We're waiting for %s." % "Godot"
, 注意这是一个字符串表达式形式的, 中间使用%
连接. 格式化占位符和C语言类似. 多个占位符怎么办呢? 后面的实际内容就得换用数组形式, 如"%s was reluctant to learn %s, but now he enjoys it." % ["Estragon", "GDScript"]
由于格式化占位符缺乏意义, 也支持更现代的名称占位符, 但这个名称却不能是变量名, 得是dict的key名, 也不能用%
简写连接, 得用String.format()
方法, 如: "Hi, {name} v{version}! ".format({ "name":"Godette", "version":"3.0" })
用%
连接格式化字符串和变量, 与用.format
连接的区别是, %
对数字格式有更好的控制, 如加入前导0, 四舍五入显示等, 后者可读性更强, 所以有时候还得结合起来用, 如: "Hi, {0} v{version} ".format({0:"Godette", "version":" %0.2f" % 3.114})
,显示为Hi, Godette v3.11
注解 @
以@
开头, 有点像C语言以#
开头. C语言中#
开头表示与编译器交互的语句.这儿@
开头表示与编辑器交互的语句. 如把有些变量值导入编辑器中.
有一个特别提出来的@onready
注解, 可以将变量的赋值推迟到ready
以后, 而不需要在_ready()
回调里面再写一次.
代码区块
这个仅仅是为了方便将超长的一段代码用编辑器折叠的. 一般编辑器可以折叠一个函数, 而代码区块可以折叠多个函数,只要是在一个区块中.
代码区块是写在注释里的一对#region
和#endregion
, 嗯, 这个跟我写C的风格有点类似, 我也喜欢将同类代码用一对注释括起来:
# This comment is outside the code region. It will be visible when collapsed.
#region Terrain generation
# This comment is inside the code region. It won't be visible when collapsed.
func generate_lakes():
pass
func generate_hills():
pass
#endregion
#region Terrain population
func place_vegetation():
pass
func place_roads():
pass
#endregion
内置类型
除了int float Array String等这些一般语言都有的内置类型以外, 还有很多特有的内置类型, 如向量Vector2 Color等, 有需要请看官文.
Array数组
var a = []
a.append(1) # 尾部添加
a.pop_front() #头部弹出
a.pop_back() #尾部弹出
a[0] # 第一个元素
a[-1] # 倒数第一个元素. 比较特别,index可以为负值,就是倒数.
a.size() # 取长度
高效运行
数组也支持类型化, 并且对于巨大的数组, 为提高效率还支持密集存储(密存), 密存类似于C的数组, 要求数组内部所有的数据类型相同, 且在内存中连续存放.
callable 可调用体
这个稍微有些奇怪. 别的语言把函数也当做变量可以赋值, 新的变量就是函数名. GDScript赋值是一样的, 但调用却一定要加call
. 如var x = f
调用需要写成x.call()
而不是x()
声明函数的返回值
函数定义时候可以声明其返回值, 以对函数进行更严格的检查, 用->
表示, 如:
func _init() -> void:
print(_data)
函数形参也可以要求类型检查
func my_function(a: int, b: String):
pass
也支持匿名函数直接赋值给变量.
还支持所谓静态函数, 其实相当于js的原型函数, 或者是class的函数, 不能访问实例(对象)的变量.
static func sum2(a, b):
return a + b
类的构造函数
func _init(arg):
super("some_default", arg) # Call the custom base constructor.
## getter和setter函数
这个显得比较高级了, 类似js中也有这样的函数. 并且vue.js里使用get/set函数保证界面刷新和数据更新同步.
```python
var milliseconds: int = 0
var seconds: int:
get:
return milliseconds / 1000
set(value):
milliseconds = value * 1000
写到这儿我稍微有点担心这个GPScript了, 因为这个语言里面用了很多高级的方法, 而且经常会更新和增加新的语法. 在其他语言里是有一个执行委员会会议确定, 又有很多大公司在写解释器, 又有很多第三方在写库文件, 整个一套复杂而有机的机制, 或者说形成了生态. GDScript虽然是游戏定制语言, 但是能否形成这样的完善程度, 只有拭目以待了.
刚查了gdscript在github上的仓库数, 有52.9k,和js的20.2M个和python的10.7M个, C的2.4M个真的相形见绌了.