GDScript 风格指南
该样式指南列出了编写优雅GDScript的约定. 目标是促进编写干净, 可读的代码, 并促进项目, 讨论和教程之间的一致性. 希望这也会促进开发自动格式化工具. PEP 8 编程风格指南. 风格指南并不是硬性的规则手册. 有时, 您可能无法应用下面的一些准则. 当这种情况发生时, 使用你最好的判断, 并询问其他开发人员的见解. 一般来说, 在项目和团队中保持代码的一致性比遵循本指南进行tee更为重要. 注解 Godot的内置脚本编辑器默认使用了很多这些约定. 让它帮助你. 下面是一个简单的示例, 说明它是如何工作的:
class_name StateMachineextends Node# Hierarchical State machine for the player.# Initializes states and delegates engine callbacks# (_physics_process, _unhandled_input) to the state.signal state_changed(previous, new)export var initial_state = NodePath()var is_active = true setget set_is_activeonready var _state = get_node(initial_state) setget set_stateonready var _state_name = _state.namefunc _init(): add_to_group("state_machine")func _ready(): connect("state_changed", self, "_on_state_changed") _state.enter()func _unhandled_input(event): _state.unhandled_input(event)func _physics_process(delta): _state.physics_process(delta)func transition_to(target_state_path, msg={}): if not has_node(target_state_path): return var target_state = get_node(target_state_path) assert(target_state.is_composite == false) _state.exit() self._state = target_state _state.enter(msg) Events.emit_signal("player_state_changed", _state.name)func set_is_active(value): is_active = value set_physics_process(value) set_process_unhandled_input(value) set_block_signals(not value)func set_state(value): _state = value _state_name = _state.namefunc _on_state_changed(previous, new): print("state changed") emit_signal("state_changed")
格式
编码和特殊字符
- LF )来换行, 而不是
CRLF或CR.(编辑默认) - 在每个文件的末尾使用一个换行符.(编辑器默认设置)
- 字节顺序标记(BOM) 的 UTF-8 编码.(编辑默认)
- Tabs 代替制表符进行缩进(称为 “软制表符”).(编辑默认)
缩进
每个缩进级别必须大于包含它的代码块. 良好的 :
糟糕的 :for i in range(10): print("hello")
使用2个缩进级别来区分续行与常规代码块. 良好的 :for i in range(10): print("hello")for i in range(10): print("hello")
糟糕的 :effect.interpolate_property(sprite, "transform/scale", sprite.get_scale(), Vector2(2.0, 2.0), 0.3, Tween.TRANS_QUAD, Tween.EASE_OUT)
此规则的例外是数组, 字典和枚举. 使用单个缩进级别来区分连续行: 良好的 :effect.interpolate_property(sprite, "transform/scale", sprite.get_scale(), Vector2(2.0, 2.0), 0.3, Tween.TRANS_QUAD, Tween.EASE_OUT)
糟糕的 :var party = [ "Godot", "Godette", "Steve",]var character_dict = { "Name": "Bob", "Age": 27, "Job": "Mechanic",}enum Tiles { TILE_BRICK, TILE_FLOOR, TILE_SPIKE, TILE_TELEPORT,}
var party = [ "Godot", "Godette", "Steve",]var character_dict = { "Name": "Bob", "Age": 27, "Job": "Mechanic",}enum Tiles { TILE_BRICK, TILE_FLOOR, TILE_SPIKE, TILE_TELEPORT,}
尾随逗号
在数组, 字典和枚举的最后一行使用逗号. 这将使版本控制中的重构更容易, 差异也更大, 因为添加新元素时不需要修改最后一行. 良好的 :
糟糕的 :enum Tiles { TILE_BRICK, TILE_FLOOR, TILE_SPIKE, TILE_TELEPORT,}
单行列表中不需要尾随逗号, 因此在这种情况下不要添加它们. 良好的 :enum Tiles { TILE_BRICK, TILE_FLOOR, TILE_SPIKE, TILE_TELEPORT}
糟糕的 :enum Tiles {TILE_BRICK, TILE_FLOOR, TILE_SPIKE, TILE_TELEPORT}
enum Tiles {TILE_BRICK, TILE_FLOOR, TILE_SPIKE, TILE_TELEPORT,}
空白行
用两个空行包围函数和类定义:
函数内部使用一个空行来分隔逻辑部分. 注解 We use a single line between classes and function definitions in the class reference and in short code snippets in this documentation.func heal(amount): health += amount health = min(health, max_health) emit_signal("health_changed", health)func take_damage(amount, effect=null): health -= amount health = max(0, health) emit_signal("health_changed", health)
行的长度
把每行代码控制在100个字符以内. 如果可以的话, 尽量把行控制在80个字符以下. 这有助于在小屏幕上阅读代码, 并在外部文本编辑器中并排打开两个脚本. 例如, 在查看差异修订时.一条语句一行
不要在一行上合并多个语句. 不要像C语言那样, 不能使用单行条件语句(三元运算符除外). 良好的 :
糟糕的 :if position.x > width: position.x = 0if flag: print("flagged")
该规则的唯一例外是三元运算符:if position.x > width: position.x = 0if flag: print("flagged")
next_state = "fall" if not is_on_floor() else "idle"
为可读性格式化多行语句
if语句特别长,或者是嵌套的三元表达式时,把它们拆分成多行可以提高可读性。因为这些连续的行仍然属于同一个表达式,应该缩进两级而不是一级。 GDScript 允许使用括号或反斜杠将语句拆成多行。本风格指南倾向于使用括号,因为重构起来更简单。使用反斜杠的话,你必须保证最后一行的末尾没有反斜杠。而如果是括号,你就不必担心最后一行的反斜杠问题。and和or关键字应当放在下一行的开头,而不是上一行的结尾。 良好的 :
糟糕的 :var angle_degrees = 135var quadrant = ( "northeast" if angle_degrees <= 90 else "southeast" if angle_degrees <= 180 else "southwest" if angle_degrees <= 270 else "northwest")var position = Vector2(250, 350)if ( position.x > 200 and position.x < 400 and position.y > 300 and position.y < 400): pass
var angle_degrees = 135var quadrant = "northeast" if angle_degrees <= 90 else "southeast" if angle_degrees <= 180 else "southwest" if angle_degrees <= 270 else "northwest"var position = Vector2(250, 350)if position.x > 200 and position.x < 400 and position.y > 300 and position.y < 400: pass
避免不必要的圆括号
避免表达式和条件语句中的括号。除非需要修改操作顺序或者是在拆分多行,否则它们只会降低可读性。 良好的 :
糟糕的 :if is_colliding(): queue_free()
if (is_colliding()): queue_free()
布尔运算
首选布尔运算符的英文版本,因为它们是最容易访问的: and代替&&。or代替||。 也可以在布尔运算符周围使用括号来清除任何歧义。这可以使长表达式更容易阅读。 良好的 :
糟糕的 :if (foo and bar) or baz: print("condition is true")
if foo && bar || baz: print("condition is true")
注释间距
普通注释开头应该留一个空格,但如果是为了停用代码而将其注释掉则不需要留。这样可以用来区分文本注释和停用的代码。 良好的 :
糟糕的 :# This is a comment.#print("This is disabled code")
注解 在脚本编辑器中,要切换已注释的选定代码,请按 Ctrl + K。此功能在选定行的开头添加一个 # 注释符号。#This is a comment.# print("This is disabled code")
空格
请始终在运算符前后和逗号后使用一个空格。同时,请避免在字典引用和函数调用中使用多余的空格。 良好的 :
糟糕的 :position.x = 5position.y = target_position.y + 10dict["key"] = 5my_array = [4, 5, 6]print("foo")
不要使用空格来垂直对齐表达式:position.x=5position.y = mpos.y+10dict ["key"] = 5myarray = [4,5,6]print ("foo")
x = 100y = 100velocity = 500
引号
尽量使用双引号,除非单引号可以让字符串中需要转义的字符变少。见如下示例:# Normal string.print("hello world")# Use double quotes as usual to avoid escapes.print("hello 'world'")# Use single quotes as an exception to the rule to avoid escapes.print('hello "world"')# Both quote styles would require 2 escapes; prefer double quotes if it's a tie.print("'hello' \"world\"")
数字
不要忽略浮点数中的前导或尾随零。否则,这会使它们的可读性降低,很难一眼与整数区分开。 良好的:
糟糕的:var float_number = 0.234var other_float_number = 13.0
对于十六进制数字,请使用小写字母,因为它们较矮,使数字更易于阅读。 良好的:var float_number = .234var other_float_number = 13.
糟糕的:var hex_number = 0xfb8c0b
利用 GDScript 的文字下划线,使大数字更易读。 良好的:var hex_number = 0xFB8C0B
糟糕的:var large_number = 1_234_567_890var large_hex_number = 0xffff_f8f8_0000var large_bin_number = 0b1101_0010_1010# Numbers lower than 1000000 generally don't need separators.var small_number = 12345
var large_number = 1234567890var large_hex_number = 0xfffff8f80000var large_bin_number = 0b110100101010# Numbers lower than 1000000 generally don't need separators.var small_number = 12_345
命名约定
这些命名约定遵循 Godot 引擎风格. 打破这些都会使你的代码与内置的命名约定冲突, 导致风格不一致的代码.文件命名
文件名用 snake_case 命名法,对于有名字的类,将其名字从 PascalCase 命名转化为 snake_case:# This file should be saved as `weapon.gd`.class_name Weaponextends Node
这种命名于 Godot 源码中的 C++ 文件命名保持了一致. 这也防止了由 Windows 导出到其他大小写敏感平台时发生的问题.# This file should be saved as `yaml_parser.gd`.class_name YAMLParserextends Object
类与节点
对类和节点名称使用帕斯卡命名法(PascalCase):
将类加载到常量或变量时同样适用:extends KinematicBody
const Weapon = preload("res://weapon.gd")
函数与变量
使用蛇形命名法(snake_case)来命名函数与变量:
在虚方法(用户覆写的函数), 私有函数和私有变量前加一个下划线(_):var particle_effectfunc load_level():
var _counter = 0func _recalculate_path():
信号
用过去时态来命名信号:signal door_openedsignal score_changed
常数和枚举
使用 CONSTANTCASE, 全部大写, 用下划线()分隔单词 :
名称 使用PascalCase, 对其成员使用CONSTANT_CASE , 因为它们是常量:const MAX_SPEED = 200
enum Element { EARTH, WATER, AIR, FIRE,}
代码顺序
格式. 有关命名约定, 请参见 命名约定. 我们建议按以下方式组织GDScript代码: ```
- tool02. class_name03. extends04. # docstring05. signals06. enums07. constants08. exported variables09. public variables10. private variables11. onready variables12. optional built-in virtual _init method13. built-in virtual _ready method14. remaining built-in virtual methods15. public methods16. private methods ``` 我们优化了顺序, 使从上到下阅读代码变得容易, 帮助第一次阅读代码的开发人员了解代码的工作原理, 并避免与变量声明顺序相关的错误. 此代码顺序遵循四个经验法则:
- 首先是属性和信号, 然后是方法.
- 公共变量优先于私有变量.
- 虚拟回调出现在类的接口之前.
_init和_ready在修改对象的函数之前运行.类声明
tool关键字放在脚本的第一行. GDScript 基础。 然后, 如果类扩展了内置类型, 则添加 extends 关键字. 然后, 您应该添加类的可选文档字符串作为注释. 您可以使用它来向您的团队解释类的角色, 工作原理, 以及其他开发人员应该如何使用它, 下面举个例子.class_name MyNodeextends Node# A brief description of the class's role and functionality.# Longer description.
信号和属性
先声明信号, 后跟属性(即成员变量), 它们都在文档注释(docstring)之后. 枚举应该在信号之后, 因为您可以将它们用作其他属性的导出提示. 然后, 按该顺序写入常量, 导出变量, 公共变量, 私有变量和 onready 变量.
注解signal spawn_player(position)enum Jobs {KNIGHT, WIZARD, ROGUE, HEALER, SHAMAN}const MAX_LIVES = 3export(Jobs) var job = Jobs.KNIGHTexport var max_health = 50export var attack = 5var health = max_health setget set_healthvar _speed = 300.0onready var sword = get_node("Sword")onready var gun = get_node("Gun")
_ready函数回调之前计算onready变量. 您可以使用它来缓存节点依赖项, 也就是说, 在您的类所依赖的场景中获取子节点. 这就是上面的例子所展示的.成员变量
如果变量只在方法中使用, 勿声明其为成员变量, 因为我们难以定位在何处使用了该变量. 相反, 你应该将它们在方法内部定义为局部变量.局部变量
声明局部变量的位置离首次使用它的位置越近越好. 这让人更容易跟上代码的思路, 而不需要上下翻找该变量的声明位置.方法和静态函数
在类的属性之后是方法._init()回调方法开始, 引擎将在创建内存对象时调用该方法. 接下来是_ready()回调, 当Godot向场景树添加一个节点时调用它. 这些函数应该声明在脚本最前面, 因为它们显示了对象是如何初始化的._unhandling_input()和_physics_process, 应该放在后面. 它们控制对象的主循环和与游戏引擎的交互. 类的其余接口, 公共和私有方法, 都是按照这个顺序出现的.func _init(): add_to_group("state_machine")func _ready(): connect("state_changed", self, "_on_state_changed") _state.enter()func _unhandled_input(event): _state.unhandled_input(event)func transition_to(target_state_path, msg={}): if not has_node(target_state_path): return var target_state = get_node(target_state_path) assert(target_state.is_composite == false) _state.exit() self._state = target_state _state.enter(msg) Events.emit_signal("player_state_changed", _state.name)func _on_state_changed(previous, new): print("state changed") emit_signal("state_changed")
静态类型
可选的静态类型.声明类型
<variable>: <type>:
要声明函数的返回类型, 使用var health: int = 0
-> <type>:func heal(amount: int) -> void:
推断类型
:=来推断类型:var health := 0 # The compiler will use the int type.
get_node()无法自动推断类型. 在这种情况下, 你应该明确地设置类型. 良好的 :onready var health_bar: ProgressBar = get_node("UI/LifeBar")
as关键字来转换返回类型,这个类型会被用于推导变量的类型。
类型安全。 糟糕的 :onready var health_bar := get_node("UI/LifeBar") as ProgressBar# health_bar will be typed as ProgressBar
# The compiler can't infer the exact type and will use Node# instead of ProgressBar.onready var health_bar := get_node("UI/LifeBar")
