godot-best-practices
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseGodot 4.x GDScript Best Practices
Godot 4.x GDScript 编码最佳实践
Guide AI agents in writing high-quality GDScript code for Godot 4.x. This skill provides coding standards, architecture patterns, and templates for game development.
指导AI Agent为Godot 4.x编写高质量的GDScript代码。本技能提供游戏开发的编码标准、架构模式和模板。
When to Use This Skill
何时使用此技能
Use this skill when:
- Generating new GDScript code
- Creating or organizing Godot scenes
- Designing game architecture and node hierarchies
- Implementing state machines, object pools, or save systems
- Answering questions about GDScript patterns or Godot conventions
- Reviewing GDScript code for quality issues
Do NOT use this skill when:
- Working with C# in Godot (use C# patterns)
- Working with Godot 3.x (syntax differs significantly)
- Using GDExtension/C++ (different paradigm)
- Working with Godot's visual scripting
在以下场景使用此技能:
- 生成新的GDScript代码
- 创建或整理Godot场景
- 设计游戏架构和节点层级
- 实现状态机、对象池或存档系统
- 解答关于GDScript模式或Godot约定的问题
- 审查GDScript代码的质量问题
请勿在以下场景使用此技能:
- 在Godot中使用C#(请使用C#模式)
- 使用Godot 3.x(语法差异显著)
- 使用GDExtension/C++(不同的范式)
- 使用Godot的可视化脚本
Core Principles
核心原则
1. Naming Conventions
1. 命名规范
Follow GDScript naming standards consistently:
gdscript
undefined始终遵循GDScript命名标准:
gdscript
undefinedClasses: PascalCase
Classes: PascalCase
class_name PlayerController
extends CharacterBody2D
class_name PlayerController
extends CharacterBody2D
Signals: past_tense_snake_case (describe what happened)
Signals: past_tense_snake_case (describe what happened)
signal health_changed(new_health: int)
signal player_died
signal item_collected(item: Item)
signal health_changed(new_health: int)
signal player_died
signal item_collected(item: Item)
Constants: SCREAMING_SNAKE_CASE
Constants: SCREAMING_SNAKE_CASE
const MAX_SPEED: float = 200.0
const JUMP_FORCE: int = -400
const MAX_SPEED: float = 200.0
const JUMP_FORCE: int = -400
Variables and functions: snake_case
Variables and functions: snake_case
var current_health: int = 100
var _private_variable: float = 0.0 # Leading underscore for private
func calculate_damage(base: int, multiplier: float) -> int:
return int(base * multiplier)
func _private_helper() -> void: # Leading underscore for private
pass
undefinedvar current_health: int = 100
var _private_variable: float = 0.0 # Leading underscore for private
func calculate_damage(base: int, multiplier: float) -> int:
return int(base * multiplier)
func _private_helper() -> void: # Leading underscore for private
pass
undefined2. Type Hints (Static Typing)
2. 类型提示(静态类型)
Use explicit type hints everywhere for autocomplete and error detection:
gdscript
undefined在所有地方使用显式类型提示以获得自动补全和错误检测:
gdscript
undefinedVariable declarations
Variable declarations
var speed: float = 100.0
var player: CharacterBody2D
var items: Array[Item] = []
var stats: Dictionary = {}
var speed: float = 100.0
var player: CharacterBody2D
var items: Array[Item] = []
var stats: Dictionary = {}
Function signatures with return types
Function signatures with return types
func get_damage() -> int:
return _base_damage * _multiplier
func find_nearest_enemy(position: Vector2) -> Enemy:
# Implementation
return null
func get_damage() -> int:
return _base_damage * _multiplier
func find_nearest_enemy(position: Vector2) -> Enemy:
# Implementation
return null
Typed signals (Godot 4.x)
Typed signals (Godot 4.x)
signal score_updated(new_score: int, old_score: int)
signal target_acquired(target: Node2D, distance: float)
signal score_updated(new_score: int, old_score: int)
signal target_acquired(target: Node2D, distance: float)
Node references with types
Node references with types
@onready var sprite: Sprite2D = $Sprite2D
@onready var collision: CollisionShape2D = $CollisionShape2D
@onready var animation_player: AnimationPlayer = %AnimationPlayer
undefined@onready var sprite: Sprite2D = $Sprite2D
@onready var collision: CollisionShape2D = $CollisionShape2D
@onready var animation_player: AnimationPlayer = %AnimationPlayer
undefined3. Node References
3. 节点引用
Use modern patterns for stable, refactor-friendly references:
gdscript
undefined使用现代模式获取稳定、便于重构的引用:
gdscript
undefinedPREFER: @onready with type hints
PREFER: @onready with type hints
@onready var health_bar: ProgressBar = $UI/HealthBar
@onready var weapon: Weapon = $WeaponMount/Weapon
@onready var health_bar: ProgressBar = $UI/HealthBar
@onready var weapon: Weapon = $WeaponMount/Weapon
PREFER: Unique names with % for critical nodes
PREFER: Unique names with % for critical nodes
@onready var player: Player = %Player
@onready var game_manager: GameManager = %GameManager
@onready var player: Player = %Player
@onready var game_manager: GameManager = %GameManager
AVOID: get_node() in _ready()
AVOID: get_node() in _ready()
func _ready() -> void:
# Don't do this
var sprite = get_node("Sprite2D")
func _ready() -> void:
# Don't do this
var sprite = get_node("Sprite2D")
AVOID: Deep fragile paths
AVOID: Deep fragile paths
@onready var thing = $Parent/Child/GrandChild/GreatGrandChild # Fragile
undefined@onready var thing = $Parent/Child/GrandChild/GreatGrandChild # Fragile
undefined4. Signal-Driven Architecture
4. 信号驱动架构
Use signals for decoupled communication. Follow "signal up, call down":
gdscript
undefined使用信号实现解耦通信。遵循“信号向上,调用向下”原则:
gdscript
undefinedChild node emits signals (doesn't know about parent)
Child node emits signals (doesn't know about parent)
class_name HealthComponent
extends Node
signal health_changed(current: int, maximum: int)
signal died
var _health: int = 100
var _max_health: int = 100
func take_damage(amount: int) -> void:
_health = max(0, _health - amount)
health_changed.emit(_health, _max_health)
if _health <= 0:
died.emit()
```gdscriptclass_name HealthComponent
extends Node
signal health_changed(current: int, maximum: int)
signal died
var _health: int = 100
var _max_health: int = 100
func take_damage(amount: int) -> void:
_health = max(0, _health - amount)
health_changed.emit(_health, _max_health)
if _health <= 0:
died.emit()
```gdscriptParent connects to child signals (knows about children)
Parent connects to child signals (knows about children)
class_name Player
extends CharacterBody2D
@onready var health: HealthComponent = $HealthComponent
@onready var sprite: Sprite2D = $Sprite2D
func _ready() -> void:
health.health_changed.connect(_on_health_changed)
health.died.connect(_on_died)
func _on_health_changed(current: int, maximum: int) -> void:
# Update UI, play effects, etc.
pass
func _on_died() -> void:
sprite.modulate = Color.RED
queue_free()
undefinedclass_name Player
extends CharacterBody2D
@onready var health: HealthComponent = $HealthComponent
@onready var sprite: Sprite2D = $Sprite2D
func _ready() -> void:
health.health_changed.connect(_on_health_changed)
health.died.connect(_on_died)
func _on_health_changed(current: int, maximum: int) -> void:
# Update UI, play effects, etc.
pass
func _on_died() -> void:
sprite.modulate = Color.RED
queue_free()
undefined5. Resource Loading
5. 资源加载
Choose the right loading strategy:
gdscript
undefined选择合适的加载策略:
gdscript
undefinedpreload(): Compile-time loading for critical/small assets
preload(): Compile-time loading for critical/small assets
const BULLET_SCENE: PackedScene = preload("res://scenes/bullet.tscn")
const PLAYER_SPRITE: Texture2D = preload("res://sprites/player.png")
const DAMAGE_SOUND: AudioStream = preload("res://audio/damage.wav")
const BULLET_SCENE: PackedScene = preload("res://scenes/bullet.tscn")
const PLAYER_SPRITE: Texture2D = preload("res://sprites/player.png")
const DAMAGE_SOUND: AudioStream = preload("res://audio/damage.wav")
load(): Runtime loading for optional/large assets
load(): Runtime loading for optional/large assets
func load_level(level_name: String) -> void:
var path := "res://levels/%s.tscn" % level_name
var level_scene: PackedScene = load(path)
var level := level_scene.instantiate()
add_child(level)
func load_level(level_name: String) -> void:
var path := "res://levels/%s.tscn" % level_name
var level_scene: PackedScene = load(path)
var level := level_scene.instantiate()
add_child(level)
ResourceLoader for async loading (prevents stuttering)
ResourceLoader for async loading (prevents stuttering)
func _load_level_async(path: String) -> void:
ResourceLoader.load_threaded_request(path)
# Check with: ResourceLoader.load_threaded_get_status(path)
# Get with: ResourceLoader.load_threaded_get(path)
undefinedfunc _load_level_async(path: String) -> void:
ResourceLoader.load_threaded_request(path)
# Check with: ResourceLoader.load_threaded_get_status(path)
# Get with: ResourceLoader.load_threaded_get(path)
undefinedQuick Reference
快速参考
| Category | Prefer | Avoid |
|---|---|---|
| Node references | | |
| Unique nodes | | Deep paths |
| Resource loading | | |
| Signals | Typed: | String: |
| Type safety | Explicit type hints | Untyped variables |
| Constants | | Magic numbers/strings |
| Null checks | | |
| Coroutines | | |
| Groups | Scene-specific groups | Global groups for everything |
| Autoloads | Services/managers only | Game logic in autoloads |
| Properties | Setters/getters | Direct mutation |
| Communication | Signal up, call down | Child calling parent methods |
| 分类 | 推荐做法 | 避免做法 |
|---|---|---|
| 节点引用 | | 在 |
| 唯一节点 | | 深层路径 |
| 资源加载 | 对小型/关键资源使用 | 处处使用 |
| 信号 | 带类型的信号: | 字符串形式: |
| 类型安全 | 显式类型提示 | 无类型变量 |
| 常量 | | 魔法数字/字符串 |
| 空值检查 | | 对已释放节点使用 |
| 协程 | | |
| 组 | 场景特定的组 | 所有内容都使用全局组 |
| 自动加载(Autoloads) | 仅用于服务/管理器 | 在自动加载中放置游戏逻辑 |
| 属性 | Setter/Getter | 直接修改 |
| 通信 | 信号向上,调用向下 | 子节点调用父节点方法 |
Code Generation Guidelines
代码生成指南
Script Structure
脚本结构
Order sections consistently:
gdscript
class_name MyClass
extends Node2D始终按以下顺序组织代码块:
gdscript
class_name MyClass
extends Node2DBrief description of this class.
此类的简要描述。
Longer description if needed, explaining purpose and usage.
如有需要,可添加更长的描述,说明用途和用法。
=== Signals ===
=== Signals ===
signal state_changed(new_state: State)
signal state_changed(new_state: State)
=== Enums ===
=== Enums ===
enum State { IDLE, RUNNING, JUMPING }
enum State { IDLE, RUNNING, JUMPING }
=== Exports ===
=== Exports ===
@export var speed: float = 100.0
@export_group("Combat")
@export var damage: int = 10
@export var attack_range: float = 50.0
@export var speed: float = 100.0
@export_group("Combat")
@export var damage: int = 10
@export var attack_range: float = 50.0
=== Constants ===
=== Constants ===
const MAX_HEALTH: int = 100
const MAX_HEALTH: int = 100
=== Public Variables ===
=== 公共变量 ===
var current_state: State = State.IDLE
var current_state: State = State.IDLE
=== Private Variables ===
=== 私有变量 ===
var _internal_counter: int = 0
var _internal_counter: int = 0
=== Onready ===
=== Onready ===
@onready var sprite: Sprite2D = $Sprite2D
@onready var collision: CollisionShape2D = $CollisionShape2D
@onready var sprite: Sprite2D = $Sprite2D
@onready var collision: CollisionShape2D = $CollisionShape2D
=== Lifecycle Methods ===
=== 生命周期方法 ===
func _ready() -> void:
pass
func _process(delta: float) -> void:
pass
func _physics_process(delta: float) -> void:
pass
func _ready() -> void:
pass
func _process(delta: float) -> void:
pass
func _physics_process(delta: float) -> void:
pass
=== Public Methods ===
=== 公共方法 ===
func take_damage(amount: int) -> void:
pass
func take_damage(amount: int) -> void:
pass
=== Private Methods ===
=== 私有方法 ===
func _calculate_knockback() -> Vector2:
return Vector2.ZERO
undefinedfunc _calculate_knockback() -> Vector2:
return Vector2.ZERO
undefinedExport Annotations
导出注解
Use exports for editor-configurable values:
gdscript
undefined使用导出(exports)配置编辑器可修改的值:
gdscript
undefinedBasic exports
Basic exports
@export var health: int = 100
@export var speed: float = 200.0
@export var player_name: String = "Player"
@export var health: int = 100
@export var speed: float = 200.0
@export var player_name: String = "Player"
Range constraints
Range constraints
@export_range(0, 100) var percentage: int = 50
@export_range(0.0, 1.0, 0.1) var volume: float = 0.8
@export_range(0, 100) var percentage: int = 50
@export_range(0.0, 1.0, 0.1) var volume: float = 0.8
Resource exports
Resource exports
@export var texture: Texture2D
@export var scene: PackedScene
@export var audio: AudioStream
@export var texture: Texture2D
@export var scene: PackedScene
@export var audio: AudioStream
Grouped exports
Grouped exports
@export_group("Movement")
@export var walk_speed: float = 100.0
@export var run_speed: float = 200.0
@export_group("Combat")
@export var attack_damage: int = 10
@export_group("Movement")
@export var walk_speed: float = 100.0
@export var run_speed: float = 200.0
@export_group("Combat")
@export var attack_damage: int = 10
Enum exports
Enum exports
@export var difficulty: Difficulty = Difficulty.NORMAL
enum Difficulty { EASY, NORMAL, HARD }
@export var difficulty: Difficulty = Difficulty.NORMAL
enum Difficulty { EASY, NORMAL, HARD }
Flags (multiselect)
Flags (multiselect)
@export_flags("Fire", "Water", "Earth", "Air") var elements: int = 0
undefined@export_flags("Fire", "Water", "Earth", "Air") var elements: int = 0
undefinedCommon Game Patterns
常见游戏模式
State Machine (Overview)
状态机(概述)
Use enum-based state machines for simple cases:
gdscript
enum State { IDLE, WALK, JUMP, ATTACK }
var current_state: State = State.IDLE
func _physics_process(delta: float) -> void:
match current_state:
State.IDLE:
_process_idle(delta)
State.WALK:
_process_walk(delta)
State.JUMP:
_process_jump(delta)
State.ATTACK:
_process_attack(delta)
func change_state(new_state: State) -> void:
if current_state == new_state:
return
_exit_state(current_state)
current_state = new_state
_enter_state(new_state)See for advanced implementations.
references/patterns/state-machine.md对于简单场景,使用基于枚举的状态机:
gdscript
enum State { IDLE, WALK, JUMP, ATTACK }
var current_state: State = State.IDLE
func _physics_process(delta: float) -> void:
match current_state:
State.IDLE:
_process_idle(delta)
State.WALK:
_process_walk(delta)
State.JUMP:
_process_jump(delta)
State.ATTACK:
_process_attack(delta)
func change_state(new_state: State) -> void:
if current_state == new_state:
return
_exit_state(current_state)
current_state = new_state
_enter_state(new_state)高级实现请参阅。
references/patterns/state-machine.mdObject Pooling (Overview)
对象池(概述)
Reuse objects to avoid instantiation cost:
gdscript
class_name ObjectPool
extends Node
var _pool: Array[Node] = []
var _scene: PackedScene
func _init(scene: PackedScene, initial_size: int = 10) -> void:
_scene = scene
for i in initial_size:
var obj := _scene.instantiate()
obj.set_process(false)
_pool.append(obj)
func acquire() -> Node:
if _pool.is_empty():
return _scene.instantiate()
var obj := _pool.pop_back()
obj.set_process(true)
return obj
func release(obj: Node) -> void:
obj.set_process(false)
_pool.append(obj)See for complete implementation.
references/patterns/object-pooling.md复用对象以避免实例化开销:
gdscript
class_name ObjectPool
extends Node
var _pool: Array[Node] = []
var _scene: PackedScene
func _init(scene: PackedScene, initial_size: int = 10) -> void:
_scene = scene
for i in initial_size:
var obj := _scene.instantiate()
obj.set_process(false)
_pool.append(obj)
func acquire() -> Node:
if _pool.is_empty():
return _scene.instantiate()
var obj := _pool.pop_back()
obj.set_process(true)
return obj
func release(obj: Node) -> void:
obj.set_process(false)
_pool.append(obj)完整实现请参阅。
references/patterns/object-pooling.mdSave/Load (Overview)
存档/读档(概述)
Use Resources or JSON for save data:
gdscript
undefined使用资源(Resources)或JSON存储存档数据:
gdscript
undefinedCustom Resource for save data
Custom Resource for save data
class_name SaveData
extends Resource
@export var player_position: Vector2
@export var player_health: int
@export var inventory: Array[String]
@export var level_name: String
class_name SaveData
extends Resource
@export var player_position: Vector2
@export var player_health: int
@export var inventory: Array[String]
@export var level_name: String
Save
Save
func save_game(data: SaveData) -> void:
ResourceSaver.save(data, "user://save.tres")
func save_game(data: SaveData) -> void:
ResourceSaver.save(data, "user://save.tres")
Load
Load
func load_game() -> SaveData:
if ResourceLoader.exists("user://save.tres"):
return load("user://save.tres") as SaveData
return SaveData.new()
See `references/patterns/save-load-system.md` for comprehensive guide.func load_game() -> SaveData:
if ResourceLoader.exists("user://save.tres"):
return load("user://save.tres") as SaveData
return SaveData.new()
完整指南请参阅`references/patterns/save-load-system.md`。Common Anti-Patterns
常见反模式
| Anti-Pattern | Problem | Solution |
|---|---|---|
Polling in | Wastes CPU on unchanged state | Use signals for state changes |
| Tight coupling, fragile | Signal up, or use groups |
Deep node paths | Breaks on refactor | Use |
| Stuttering, memory churn | |
String signals | Typos, no autocomplete | Typed: |
Untyped | Loses autocomplete | Always add type hint |
| Logic in autoloads | Testing difficulty, coupling | Keep autoloads thin |
| Magic numbers | Unclear meaning | Use |
| Returns true for freed | Use |
| Circular dependencies | Load errors, unclear flow | Dependency injection or signals |
| 反模式 | 问题 | 解决方案 |
|---|---|---|
在 | 对未变化的状态浪费CPU | 使用信号监听状态变化 |
| 紧耦合,脆弱 | 向上发送信号,或使用组 |
深层节点路径 | 重构时容易失效 | 使用 |
在 | 卡顿,内存波动 | 使用 |
字符串形式信号 | 易打错,无自动补全 | 带类型的信号: |
无类型的 | 失去自动补全功能 | 始终添加类型提示 |
| 在自动加载中放置逻辑 | 测试困难,耦合度高 | 保持自动加载代码精简 |
| 魔法数字 | 含义不明确 | 使用 |
对已释放节点使用 | 已释放节点会返回true | 使用 |
| 循环依赖 | 加载错误,流程不清晰 | 依赖注入或使用信号 |
Additional Resources
额外资源
Pattern Guides
模式指南
- - Full state machine implementations
references/patterns/state-machine.md - - Complete pooling system
references/patterns/object-pooling.md - - Comprehensive save/load guide
references/patterns/save-load-system.md - - Input buffering and rebinding
references/patterns/input-handling.md
- - 完整状态机实现
references/patterns/state-machine.md - - 完整对象池系统
references/patterns/object-pooling.md - - 全面的存档/读档指南
references/patterns/save-load-system.md - - 输入缓冲与重绑定
references/patterns/input-handling.md
Architecture
架构
- - Directory organization
references/architecture/project-structure.md - - Scene design patterns
references/architecture/scene-composition.md - - Signals vs direct calls
references/architecture/node-communication.md
- - 目录组织
references/architecture/project-structure.md - - 场景设计模式
references/architecture/scene-composition.md - - 信号vs直接调用
references/architecture/node-communication.md
GDScript Deep Dives
GDScript深入解析
- - Static typing in depth
references/gdscript/type-system.md - - Async patterns with await
references/gdscript/coroutines-await.md
- - 静态类型深入讲解
references/gdscript/type-system.md - - 基于await的异步模式
references/gdscript/coroutines-await.md
Templates
模板
- - Standard script template
assets/templates/base-script.gd.md - - State machine template
assets/templates/state-machine.gd.md - - Autoload singleton template
assets/templates/autoload-manager.gd.md
- - 标准脚本模板
assets/templates/base-script.gd.md - - 状态机模板
assets/templates/state-machine.gd.md - - 自动加载单例模板
assets/templates/autoload-manager.gd.md
Limitations
局限性
- GDScript only (not C#, GDExtension, or VisualScript)
- Godot 4.x syntax (some patterns differ from 3.x)
- Game-focused patterns (not editor plugin development)
- No runtime validation scripts (GDScript requires Godot runtime)
- 仅支持GDScript(不支持C#、GDExtension或可视化脚本)
- 仅适用于Godot 4.x语法(部分模式与3.x不同)
- 专注于游戏相关模式(不适用于编辑器插件开发)
- 无运行时验证脚本(GDScript需要Godot运行时)",