godot-best-practices

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Godot 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
undefined

Classes: 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
undefined
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
undefined

2. Type Hints (Static Typing)

2. 类型提示(静态类型)

Use explicit type hints everywhere for autocomplete and error detection:
gdscript
undefined
在所有地方使用显式类型提示以获得自动补全和错误检测:
gdscript
undefined

Variable 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
undefined

3. Node References

3. 节点引用

Use modern patterns for stable, refactor-friendly references:
gdscript
undefined
使用现代模式获取稳定、便于重构的引用:
gdscript
undefined

PREFER: @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
undefined

4. Signal-Driven Architecture

4. 信号驱动架构

Use signals for decoupled communication. Follow "signal up, call down":
gdscript
undefined
使用信号实现解耦通信。遵循“信号向上,调用向下”原则:
gdscript
undefined

Child 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()

```gdscript
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()

```gdscript

Parent 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()
undefined
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()
undefined

5. Resource Loading

5. 资源加载

Choose the right loading strategy:
gdscript
undefined
选择合适的加载策略:
gdscript
undefined

preload(): 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)
undefined
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)
undefined

Quick Reference

快速参考

CategoryPreferAvoid
Node references
@onready var x: Type = $Path
get_node()
in
_ready()
Unique nodes
%UniqueName
Deep paths
$A/B/C/D
Resource loading
preload()
for small/critical
load()
everywhere
SignalsTyped:
signal x(val: int)
String:
emit_signal("x")
Type safetyExplicit type hintsUntyped variables
Constants
const
or
@export
Magic numbers/strings
Null checks
is_instance_valid(node)
node != null
for freed nodes
Coroutines
await
yield
(deprecated)
GroupsScene-specific groupsGlobal groups for everything
AutoloadsServices/managers onlyGame logic in autoloads
PropertiesSetters/gettersDirect mutation
CommunicationSignal up, call downChild calling parent methods
分类推荐做法避免做法
节点引用
@onready var x: Type = $Path
_ready()
中使用
get_node()
唯一节点
%UniqueName
深层路径
$A/B/C/D
资源加载对小型/关键资源使用
preload()
处处使用
load()
信号带类型的信号:
signal x(val: int)
字符串形式:
emit_signal("x")
类型安全显式类型提示无类型变量
常量
const
@export
魔法数字/字符串
空值检查
is_instance_valid(node)
对已释放节点使用
node != null
协程
await
yield
(已废弃)
场景特定的组所有内容都使用全局组
自动加载(Autoloads)仅用于服务/管理器在自动加载中放置游戏逻辑
属性Setter/Getter直接修改
通信信号向上,调用向下子节点调用父节点方法

Code Generation Guidelines

代码生成指南

Script Structure

脚本结构

Order sections consistently:
gdscript
class_name MyClass
extends Node2D
始终按以下顺序组织代码块:
gdscript
class_name MyClass
extends Node2D

Brief 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
undefined
func _calculate_knockback() -> Vector2: return Vector2.ZERO
undefined

Export Annotations

导出注解

Use exports for editor-configurable values:
gdscript
undefined
使用导出(exports)配置编辑器可修改的值:
gdscript
undefined

Basic 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
undefined

Common 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
references/patterns/state-machine.md
for advanced implementations.
对于简单场景,使用基于枚举的状态机:
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.md

Object 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
references/patterns/object-pooling.md
for complete implementation.
复用对象以避免实例化开销:
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.md

Save/Load (Overview)

存档/读档(概述)

Use Resources or JSON for save data:
gdscript
undefined
使用资源(Resources)或JSON存储存档数据:
gdscript
undefined

Custom 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-PatternProblemSolution
Polling in
_process
Wastes CPU on unchanged stateUse signals for state changes
get_parent().get_parent()
Tight coupling, fragileSignal up, or use groups
Deep node paths
$A/B/C/D
Breaks on refactorUse
%UniqueName
load()
in
_process
Stuttering, memory churn
preload()
or cache reference
String signals
emit_signal("x")
Typos, no autocompleteTyped:
signal_name.emit()
Untyped
@onready var x = $Node
Loses autocompleteAlways add type hint
Logic in autoloadsTesting difficulty, couplingKeep autoloads thin
Magic numbersUnclear meaningUse
const
or
@export
node != null
for freed nodes
Returns true for freedUse
is_instance_valid()
Circular dependenciesLoad errors, unclear flowDependency injection or signals
反模式问题解决方案
_process
中轮询
对未变化的状态浪费CPU使用信号监听状态变化
get_parent().get_parent()
紧耦合,脆弱向上发送信号,或使用组
深层节点路径
$A/B/C/D
重构时容易失效使用
%UniqueName
_process
中使用
load()
卡顿,内存波动使用
preload()
或缓存引用
字符串形式信号
emit_signal("x")
易打错,无自动补全带类型的信号:
signal_name.emit()
无类型的
@onready var x = $Node
失去自动补全功能始终添加类型提示
在自动加载中放置逻辑测试困难,耦合度高保持自动加载代码精简
魔法数字含义不明确使用
const
@export
对已释放节点使用
node != null
已释放节点会返回true使用
is_instance_valid()
循环依赖加载错误,流程不清晰依赖注入或使用信号

Additional Resources

额外资源

Pattern Guides

模式指南

  • references/patterns/state-machine.md
    - Full state machine implementations
  • references/patterns/object-pooling.md
    - Complete pooling system
  • references/patterns/save-load-system.md
    - Comprehensive save/load guide
  • references/patterns/input-handling.md
    - Input buffering and rebinding
  • references/patterns/state-machine.md
    - 完整状态机实现
  • references/patterns/object-pooling.md
    - 完整对象池系统
  • references/patterns/save-load-system.md
    - 全面的存档/读档指南
  • references/patterns/input-handling.md
    - 输入缓冲与重绑定

Architecture

架构

  • references/architecture/project-structure.md
    - Directory organization
  • references/architecture/scene-composition.md
    - Scene design patterns
  • references/architecture/node-communication.md
    - Signals vs direct calls
  • references/architecture/project-structure.md
    - 目录组织
  • references/architecture/scene-composition.md
    - 场景设计模式
  • references/architecture/node-communication.md
    - 信号vs直接调用

GDScript Deep Dives

GDScript深入解析

  • references/gdscript/type-system.md
    - Static typing in depth
  • references/gdscript/coroutines-await.md
    - Async patterns with await
  • references/gdscript/type-system.md
    - 静态类型深入讲解
  • references/gdscript/coroutines-await.md
    - 基于await的异步模式

Templates

模板

  • assets/templates/base-script.gd.md
    - Standard script template
  • assets/templates/state-machine.gd.md
    - State machine template
  • assets/templates/autoload-manager.gd.md
    - Autoload singleton template
  • 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运行时)",