sxdy4591

godot-engine-4-7

Use when writing GDScript or C# code for Godot Engine 4.x projects, creating game scenes, configuring physics/navigation/shaders/UI/audio/animation/networking/XR, debugging Godot projects, or answering any Godot Engine development question. Covers complete GDScript and C# syntax, all 1083+ API classes, node system, 2D/3D rendering, physics, shaders, UI, animation, audio, navigation, input, networking, file I/O, exporting, performance optimization, XR, and engine architecture.

sxdy4591 0 Updated 1w ago

Resources

14
GitHub

Install

npx skillscat add sxdy4591/godot-4-7-skills

Install via the SkillsCat registry.

SKILL.md

Godot Engine 4.7 Complete Reference

Engine Overview

Godot Engine 4.7 is an open-source, cross-platform game engine supporting 2D and 3D game development. It uses a node-based architecture where every game element is a node organized in a tree hierarchy.

Core Architecture

Node Tree -- Every Godot application is a tree of nodes. The root of the tree is a SceneTree containing a Window (the main viewport). Every node has a name, can have children, and processes per-frame callbacks (_process, _physics_process). Nodes are added to the tree with add_child() and removed with remove_child() or queue_free(). The tree processes nodes top-down for _process and bottom-up for _ready.

Scene System -- A scene is a reusable branch of the node tree saved as a .tscn (text) or .scn (binary) file. Scenes can be instantiated at runtime via PackedScene.instantiate(). Scenes are Godot's primary composition unit: a character scene contains its sprite, collision shape, and scripts; a level scene contains terrain, enemies (instances of other scenes), and triggers. This "scene = prefab + inheritance" model replaces traditional entity-component systems.

Signals -- Signals are Godot's observer pattern implementation. Any node can define custom signals and emit them; other nodes connect to these signals to react. Built-in signals include Node.ready, Node.tree_entered, Timer.timeout, Area2D.body_entered, Button.pressed, and hundreds more. Signals decouple communication between nodes without requiring direct references.

# Defining and using signals
signal health_changed(new_health: int)

func take_damage(amount: int) -> void:
    health -= amount
    health_changed.emit(health)

# Connecting signals
func _ready() -> void:
    health_changed.connect(_on_health_changed)
    # Or connect to another node's signal:
    $Button.pressed.connect(_on_button_pressed)

Resources -- Resources are data containers that can be saved to disk and shared between nodes. Examples: Texture2D, AudioStream, Material, PackedScene, Script, Font, AnimationLibrary. Custom resources extend Resource and export properties for editor-editable data. Resources are reference-counted and deduplicated by the engine when loaded from the same path.

Three Rendering Backends:

Renderer API Target Features
Forward+ Vulkan Desktop / Console Full 3D feature set: GI, volumetric fog, SDFGI, SSR, SSAO, clustered lighting
Mobile Vulkan Mobile / Mid-range Reduced feature set optimized for tile-based GPUs, single-pass rendering
Compatibility OpenGL 3.3 / ES 3.0 / WebGL 2.0 Low-end / Web Broadest hardware support, limited advanced effects

All renderers share the same scene API. Switching renderer only affects visual fidelity and performance, not game logic.

Key Engine Concepts

  • GDScript -- Python-like language designed for Godot. Tightly integrated, fast iteration, optional static typing.
  • C#/.NET -- Full .NET 8+ support. Write game logic in C# with access to the entire .NET ecosystem.
  • GDExtension -- Native C/C++/Rust extensions without recompiling the engine.
  • Autoloads (Singletons) -- Nodes/scripts loaded at startup, accessible globally via name. Used for game managers, audio, data persistence.
  • Groups -- Lightweight tags for nodes. Call methods on all nodes in a group simultaneously.
  • Viewports -- Render targets. The main window is a viewport. Sub-viewports enable render-to-texture, split-screen, minimaps.
  • Servers -- Low-level singletons (RenderingServer, PhysicsServer2D, PhysicsServer3D, AudioServer, NavigationServer2D/3D) for direct high-performance API access bypassing the node layer.

Support File Index

Language Reference

  • gdscript-reference.md -- Complete GDScript language reference: syntax, types, annotations, lambdas, await, pattern matching, typed arrays, static typing
  • csharp-reference.md -- C#/.NET integration reference: signals in C#, exports, Godot collections, async, source generators, cross-language communication
  • gdextension-cpp.md -- GDExtension and C++ bindings: building native modules, godot-cpp, binding methods/properties/signals

Core Concepts

  • core-concepts.md -- Design philosophy, node lifecycle, scene composition, signals in depth, resources, groups, Autoloads
  • best-practices.md -- Scene organization, OOP principles, project structure, code style, data-driven design, when to use nodes vs resources
  • editor-guide.md -- Editor usage, project settings, editor settings, CLI flags, debugging tools, remote scene inspector
  • migration-guide.md -- Version upgrade guide (Godot 3 to 4, 4.0 to 4.7): API renames, breaking changes, new features per release

Subsystem Reference

  • 2d-development.md -- 2D graphics: Sprite2D, AnimatedSprite2D, particles, TileMapLayer, Camera2D, CanvasLayer, Line2D, 2D lighting
  • 3d-development.md -- 3D rendering: MeshInstance3D, materials (StandardMaterial3D, ShaderMaterial), lighting (OmniLight3D, SpotLight3D, DirectionalLight3D), GI (LightmapGI, VoxelGI, SDFGI), environment, sky, fog, post-processing
  • physics.md -- Physics bodies (RigidBody, CharacterBody, StaticBody, Area), collision shapes, layers/masks, raycasting, ShapeCast, Jolt physics backend, joints, soft bodies
  • animation.md -- AnimationPlayer, AnimationTree (state machines, blend trees, blend spaces), Tween API, AnimationMixer, procedural animation
  • shaders-reference.md -- Shader types (spatial, canvas_item, particles, fog, sky, composite), built-in variables, functions, render modes, visual shaders
  • ui-system.md -- Control nodes, containers (HBox, VBox, Grid, Margin), anchors, themes, StyleBox, fonts, RichTextLabel with BBCode, drag-and-drop, focus
  • audio.md -- AudioStreamPlayer (2D/3D), audio buses, effects (reverb, EQ, compressor, chorus), AudioStreamPolyphonic, interactive music
  • navigation.md -- NavigationRegion2D/3D, NavigationAgent2D/3D, NavigationObstacle, avoidance, navigation meshes, A* grid/graph
  • input-system.md -- InputEvent hierarchy, InputMap, actions, deadzone, input buffering, mouse/keyboard/gamepad/touch, gesture recognition
  • networking.md -- MultiplayerAPI, ENet/WebSocket/WebRTC peers, RPCs, authority, spawning, synchronizers, HTTPRequest, HTTPClient
  • rendering.md -- Rendering pipeline, viewports, SubViewport, resolution scaling (FSR2, MetaFSR), compositor effects, post-processing, environment
  • xr-development.md -- XR/VR/AR development: OpenXR, XRCamera3D, XRController3D, hand tracking, passthrough, XRToolsKit

Tools and Deployment

  • file-io.md -- FileAccess, DirAccess, user:// vs res://, config files, save/load patterns, encryption, ResourceSaver/Loader
  • asset-pipeline.md -- Asset import system: images, textures, audio, 3D scenes (glTF, FBX), import presets, reimport, streaming
  • export-platform.md -- Export templates, feature tags, platform configs (Windows, Linux, macOS, Android, iOS, Web, consoles), PCK files, code signing
  • performance.md -- CPU/GPU profiling, optimization techniques, servers API, MultiMesh, LOD, occlusion culling, multithreading, object pooling
  • internationalization.md -- TranslationServer, .po/.csv translations, locales, BiDi text, RTL layouts, pseudolocalization
  • plugins.md -- EditorPlugin, @tool scripts, inspector plugins, import plugins, custom docks, EditorScript
  • math-reference.md -- Vector2/3/4, Basis, Transform2D/3D, Quaternion, AABB, Plane, Projection, interpolation, random, noise, curves, geometry utilities
  • engine-architecture.md -- Engine internals: main loop, servers, Object system, Variant, StringName, RID, ClassDB, memory model, build system (SCons)

API Class Reference (All 1083+ Classes)

  • api-reference/node-classes-a-c.md -- Node subclasses A-C (AnimatedSprite2D/3D, AnimationPlayer, Area2D/3D, AudioStreamPlayer, Button, Camera2D/3D, CanvasLayer, CharacterBody2D/3D, CollisionShape2D/3D, ColorRect, Control, CSGBox3D...)
  • api-reference/node-classes-d-h.md -- Node subclasses D-H (DirectionalLight3D, FileDialog, GPUParticles2D/3D, GraphEdit, GridContainer, HBoxContainer, HSlider, HTTPRequest...)
  • api-reference/node-classes-i-n.md -- Node subclasses I-N (ItemList, Label, Light2D, Line2D, LineEdit, LinkButton, MarginContainer, MeshInstance2D/3D, MultiplayerSpawner, NavigationAgent2D/3D, NinePatchRect, Node2D/3D...)
  • api-reference/node-classes-o-s.md -- Node subclasses O-S (OmniLight3D, OptionButton, Panel, ParallaxBackground, Path2D/3D, ProgressBar, RayCast2D/3D, RichTextLabel, RigidBody2D/3D, Skeleton3D, SpotLight3D, Sprite2D/3D, StaticBody2D/3D, SubViewport...)
  • api-reference/node-classes-t-z.md -- Node subclasses T-Z (TabContainer, TextEdit, TextureRect, TileMapLayer, Timer, Tree, VBoxContainer, VideoStreamPlayer, VSlider, Window, WorldEnvironment, XRCamera3D, XRController3D...)
  • api-reference/resource-classes-a-c.md -- Resource subclasses A-C (Animation, AnimationLibrary, ArrayMesh, AtlasTexture, AudioBusLayout, AudioEffect*, AudioStream*, BitMap, BoxMesh, BoxShape3D, ButtonGroup, CapsuleMesh, CircleShape2D, CompressedTexture2D, ConcavePolygonShape3D, ConvexPolygonShape3D, Curve, CurveTexture...)
  • api-reference/resource-classes-d-i.md -- Resource subclasses D-I (DirAccess, Environment, FileAccess, Font, FontFile, FontVariation, GDScript, Gradient, GradientTexture*, HeightMapShape3D, Image, ImageTexture, ImmediateMesh, InputEvent*...)
  • api-reference/resource-classes-j-p.md -- Resource subclasses J-P (JSON, LabelSettings, Material, Mesh, MeshLibrary, MultiMesh, NavigationMesh, NavigationPolygon, NoiseTexture2D, OccluderInstance3D, OccluderPolygon2D, OpenXRAction*, PackedScene, ParticleProcessMaterial, PhysicsMaterial, PlaceholderTexture2D, PlaneMesh, PointMesh, PolygonPathFinder, PortableCompressedTexture2D, PrimitiveMesh...)
  • api-reference/resource-classes-q-s.md -- Resource subclasses Q-S (QuadMesh, RectangleShape2D, RenderSceneBuffersRD, Resource, RichTextEffect, Shader, ShaderMaterial, Shape2D/3D, Shortcut, Skin, Sky, SphereShape3D, SphereMesh, SpriteFrames, StandardMaterial3D, StyleBox*, SurfaceTool...)
  • api-reference/resource-classes-t-z.md -- Resource subclasses T-Z (TextMesh, Texture2D, Texture3D, TextureLayered, Theme, TileMapPattern, TileSet, TileSetSource, TorusMesh, Translation, TubeTrailMesh, ViewportTexture, VisualShader*, World2D, World3D, WorldBoundaryShape2D/3D...)
  • api-reference/object-classes-a-e.md -- Object subclasses A-E (AudioServer, CameraServer, ClassDB, DisplayServer, EditorInterface, EditorPlugin, EditorSettings, Engine, EngineDebugger...)
  • api-reference/object-classes-f-n.md -- Object subclasses F-N (Geometry2D, Geometry3D, GodotSharp, Input, InputMap, IP, JavaScriptBridge, Marshalls, MultiplayerAPI, NavigationServer2D/3D, OS...)
  • api-reference/object-classes-o-p.md -- Object subclasses O-P (Object, Performance, PhysicsDirectBodyState2D/3D, PhysicsDirectSpaceState2D/3D, PhysicsServer2D/3D, ProjectSettings...)
  • api-reference/object-classes-q-s.md -- Object subclasses Q-S (RenderingDevice, RenderingServer, ResourceLoader, ResourceSaver, SceneTree, TextServerManager, ThemeDB, Time, TranslationServer...)
  • api-reference/object-classes-t-z.md -- Object subclasses T-Z (TreeItem, Tween, UndoRedo, WorkerThreadPool, XMLParser, XRServer...)
  • api-reference/variant-types.md -- Variant built-in types: bool, int, float, String, StringName, NodePath, Vector2/3/4, Rect2, Color, Transform2D/3D, Basis, Quaternion, AABB, Plane, Projection, RID, Callable, Signal, Dictionary, Array, Packed*Array
  • api-reference/gdscript-globals.md -- @GDScript built-in functions (preload, load, assert, range, print...) and @GlobalScope (math, type checking, utility functions, enums, constants)

GDScript Quick Reference

Variables and Types

# Untyped
var x = 10

# Typed (inferred)
var y := 10           # int
var name := "Godot"   # String
var vel := Vector2.ZERO  # Vector2

# Typed (explicit)
var health: int = 100
var speed: float = 200.0
var items: Array[String] = []
var scores: Dictionary[String, int] = {}

# Constants
const MAX_SPEED: float = 400.0
const TILE_SIZE := 16

# Enums
enum State { IDLE, RUN, JUMP, FALL }
var current_state: State = State.IDLE

# Exports (editable in Inspector)
@export var move_speed: float = 200.0
@export var max_health: int = 100
@export_range(0, 100, 1) var volume: int = 80
@export var scene: PackedScene
@export var texture: Texture2D
@export_file("*.json") var data_path: String
@export_enum("Sword", "Bow", "Staff") var weapon: int
@export_group("Movement")
@export var acceleration: float = 10.0
@export_subgroup("Jump")
@export var jump_force: float = 300.0
@export_category("Stats")
@export var strength: int = 5

Functions

# Basic function
func greet(name: String) -> String:
    return "Hello, " + name

# Virtual callbacks (override these)
func _ready() -> void:             # Called when node enters tree (children ready)
    pass
func _process(delta: float) -> void:       # Called every frame
    pass
func _physics_process(delta: float) -> void:  # Called every physics tick (default 60/s)
    pass
func _input(event: InputEvent) -> void:        # Called on any unhandled input
    pass
func _unhandled_input(event: InputEvent) -> void:  # Called after _input if not consumed
    pass

# Static functions
static func clamp_health(val: int) -> int:
    return clampi(val, 0, 100)

# Lambda
var square := func(x: float) -> float: return x * x

Control Flow

# If/elif/else
if health <= 0:
    die()
elif health < 30:
    show_warning()
else:
    hide_warning()

# Match (pattern matching)
match state:
    State.IDLE:
        play_idle()
    State.RUN:
        play_run()
    State.JUMP, State.FALL:
        play_air()
    _:
        pass

# For loops
for i in range(10):            # 0..9
    print(i)
for item in inventory:          # iterate array
    print(item.name)
for key in dict:                # iterate dictionary keys
    print(key, dict[key])
for child in get_children():    # iterate child nodes
    child.queue_free()

# While
while not is_on_floor():
    velocity.y += gravity * delta
    move_and_slide()

Common Annotations

@onready var sprite: Sprite2D = $Sprite2D       # Initialized when _ready() runs
@onready var anim: AnimationPlayer = $AnimationPlayer
@onready var label: Label = %StatusLabel         # Unique name access (scene-unique)

@export var speed: float = 100.0                 # Visible in Inspector

@tool                                            # Script runs in editor
class_name PlayerCharacter                       # Registers as global class
extends CharacterBody2D                          # Inheritance

Signals

# Define signal
signal died
signal health_changed(new_value: int)
signal item_collected(item_name: String, count: int)

# Emit signal
died.emit()
health_changed.emit(health)

# Connect signal (code)
$Button.pressed.connect(_on_button_pressed)
$Area2D.body_entered.connect(_on_body_entered)

# Connect with bind
$Timer.timeout.connect(_on_timeout.bind("special"))

# Disconnect
$Button.pressed.disconnect(_on_button_pressed)

# One-shot connection
$Timer.timeout.connect(_on_timeout, CONNECT_ONE_SHOT)

Await and Coroutines

# Wait for a signal
await get_tree().create_timer(1.5).timeout

# Wait for animation to finish
$AnimationPlayer.play("attack")
await $AnimationPlayer.animation_finished

# Wait for a custom signal
await target.died

# Wait for next frame
await get_tree().process_frame

# Wait for physics frame
await get_tree().physics_frame

Node Access

# Child node access
$Sprite2D                      # Direct child
$"Sprite2D"                    # Same, quoted form
$Body/CollisionShape2D         # Nested path
%UniqueNode                    # Scene-unique node (set in editor)

# Typed access
var sprite: Sprite2D = $Sprite2D as Sprite2D

# Find nodes
get_node("Path/To/Node")
get_node_or_null("MaybeExists")
find_child("Enemy*")           # Wildcard search in subtree
get_parent()
get_children()
get_tree().get_nodes_in_group("enemies")

# Add/remove
add_child(node)
remove_child(node)
node.queue_free()              # Safe deferred deletion

# Reparent
node.reparent(new_parent)

Type Checking and Casting

if node is CharacterBody2D:
    var body := node as CharacterBody2D
    body.move_and_slide()

# Class name check
if node.is_class("RigidBody2D"):
    pass

# Get class
print(node.get_class())  # "CharacterBody2D"

Useful Built-in Functions

# Math
lerp(a, b, t)                  # Linear interpolation
lerpf(0.0, 100.0, 0.5)         # 50.0
clampf(x, 0.0, 1.0)            # Clamp float
clampi(x, 0, 100)              # Clamp int
move_toward(current, target, delta)  # Move value toward target
snapped(value, step)            # Snap to step
remap(value, 0, 100, 0.0, 1.0) # Remap range

# Random
randf()                         # 0.0 to 1.0
randi() % 10                   # 0 to 9
randf_range(-1.0, 1.0)
randi_range(1, 6)              # Dice roll
var rng = RandomNumberGenerator.new()
rng.randomize()

# String
"Hello %s, you have %d coins" % [name, coins]
"  hello  ".strip_edges()
"path/to/file.txt".get_file()   # "file.txt"
"path/to/file.txt".get_extension()  # "txt"

# Print
print("Debug output")
print_rich("[color=red]Error![/color]")
push_warning("Something odd")
push_error("Critical failure")
printerr("Goes to stderr")

20 Essential Code Patterns

1. Platformer Character Movement

extends CharacterBody2D

@export var speed: float = 200.0
@export var jump_velocity: float = -350.0
@export var gravity_scale: float = 1.0
@export var coyote_time: float = 0.1
@export var jump_buffer_time: float = 0.1

var _coyote_timer: float = 0.0
var _jump_buffer_timer: float = 0.0
var _was_on_floor: bool = false

func _physics_process(delta: float) -> void:
    var gravity: float = ProjectSettings.get_setting("physics/2d/default_gravity")

    # Gravity
    if not is_on_floor():
        velocity.y += gravity * gravity_scale * delta

    # Coyote time: allow jumping shortly after leaving a platform
    if is_on_floor():
        _coyote_timer = coyote_time
    elif _was_on_floor:
        _coyote_timer = coyote_time
    _coyote_timer -= delta
    _was_on_floor = is_on_floor()

    # Jump buffering: register jump input slightly before landing
    if Input.is_action_just_pressed("jump"):
        _jump_buffer_timer = jump_buffer_time
    _jump_buffer_timer -= delta

    # Jump execution
    if _jump_buffer_timer > 0.0 and _coyote_timer > 0.0:
        velocity.y = jump_velocity
        _jump_buffer_timer = 0.0
        _coyote_timer = 0.0

    # Variable jump height: release early for shorter jump
    if Input.is_action_just_released("jump") and velocity.y < 0:
        velocity.y *= 0.5

    # Horizontal movement
    var direction := Input.get_axis("move_left", "move_right")
    if direction != 0.0:
        velocity.x = direction * speed
    else:
        velocity.x = move_toward(velocity.x, 0.0, speed)

    move_and_slide()

2. Top-Down 8-Direction Movement

extends CharacterBody2D

@export var speed: float = 150.0
@export var acceleration: float = 800.0
@export var friction: float = 1000.0

@onready var sprite: AnimatedSprite2D = $AnimatedSprite2D

func _physics_process(delta: float) -> void:
    var input_dir := Input.get_vector("move_left", "move_right", "move_up", "move_down")

    if input_dir != Vector2.ZERO:
        # Normalize to prevent faster diagonal movement
        input_dir = input_dir.normalized()
        velocity = velocity.move_toward(input_dir * speed, acceleration * delta)
        _update_animation(input_dir)
    else:
        velocity = velocity.move_toward(Vector2.ZERO, friction * delta)
        sprite.play("idle")

    move_and_slide()

func _update_animation(direction: Vector2) -> void:
    if abs(direction.x) > abs(direction.y):
        sprite.play("walk_side")
        sprite.flip_h = direction.x < 0
    elif direction.y < 0:
        sprite.play("walk_up")
    else:
        sprite.play("walk_down")

3. Bullet / Projectile Spawning

extends Node2D

@export var bullet_scene: PackedScene
@export var fire_rate: float = 0.15
@export var bullet_speed: float = 600.0

var _can_fire: bool = true

func _process(_delta: float) -> void:
    if Input.is_action_pressed("fire") and _can_fire:
        fire()

func fire() -> void:
    _can_fire = false
    var bullet := bullet_scene.instantiate() as Node2D
    bullet.global_position = $Muzzle.global_position
    bullet.global_rotation = $Muzzle.global_rotation
    # Add to scene tree at a level that won't move with the player
    get_tree().current_scene.add_child(bullet)
    bullet.set_meta("speed", bullet_speed)

    # Cooldown
    await get_tree().create_timer(fire_rate).timeout
    _can_fire = true

# --- Bullet script (separate) ---
# extends Area2D
#
# var speed: float = 600.0
#
# func _ready() -> void:
#     if has_meta("speed"):
#         speed = get_meta("speed")
#     body_entered.connect(_on_body_entered)
#     # Auto-destroy after lifetime
#     await get_tree().create_timer(3.0).timeout
#     queue_free()
#
# func _physics_process(delta: float) -> void:
#     position += transform.x * speed * delta
#
# func _on_body_entered(body: Node2D) -> void:
#     if body.has_method("take_damage"):
#         body.take_damage(1)
#     queue_free()

4. Scene Transition

# Autoload: SceneManager.gd (add to Project > Autoload)
extends Node

signal scene_changed

var _transition_in_progress: bool = false

func change_scene(scene_path: String, transition: String = "fade") -> void:
    if _transition_in_progress:
        return
    _transition_in_progress = true

    # Optional: play transition animation
    if transition == "fade":
        var tween := create_tween()
        # Assumes a ColorRect named "FadeRect" covering the screen in a CanvasLayer
        var fade_rect := _get_or_create_fade_rect()
        tween.tween_property(fade_rect, "color:a", 1.0, 0.3)
        await tween.finished

    get_tree().change_scene_to_file(scene_path)
    await get_tree().tree_changed

    if transition == "fade":
        var fade_rect := _get_or_create_fade_rect()
        var tween := create_tween()
        tween.tween_property(fade_rect, "color:a", 0.0, 0.3)
        await tween.finished

    _transition_in_progress = false
    scene_changed.emit()

func _get_or_create_fade_rect() -> ColorRect:
    if has_node("TransitionLayer"):
        return $TransitionLayer/FadeRect
    var layer := CanvasLayer.new()
    layer.name = "TransitionLayer"
    layer.layer = 100
    add_child(layer)
    var rect := ColorRect.new()
    rect.name = "FadeRect"
    rect.color = Color(0, 0, 0, 0)
    rect.set_anchors_preset(Control.PRESET_FULL_RECT)
    rect.mouse_filter = Control.MOUSE_FILTER_IGNORE
    layer.add_child(rect)
    return rect

# Usage from any script:
# SceneManager.change_scene("res://scenes/level_2.tscn")

5. Save / Load Game Data

# Autoload: SaveManager.gd
extends Node

const SAVE_PATH := "user://savegame.json"

var game_data: Dictionary = {
    "player_position": Vector2.ZERO,
    "player_health": 100,
    "coins": 0,
    "level": "res://scenes/level_1.tscn",
    "inventory": [],
    "flags": {},
}

func save_game() -> Error:
    var file := FileAccess.open(SAVE_PATH, FileAccess.WRITE)
    if file == null:
        push_error("Failed to open save file: %s" % error_string(FileAccess.get_open_error()))
        return FileAccess.get_open_error()

    # Collect save data from all "saveable" nodes
    _collect_save_data()

    # Convert to JSON with pretty print
    var json_string := JSON.stringify(game_data, "\t")
    file.store_string(json_string)
    file.close()
    print("Game saved.")
    return OK

func load_game() -> Error:
    if not FileAccess.file_exists(SAVE_PATH):
        push_warning("No save file found.")
        return ERR_FILE_NOT_FOUND

    var file := FileAccess.open(SAVE_PATH, FileAccess.READ)
    if file == null:
        return FileAccess.get_open_error()

    var json := JSON.new()
    var error := json.parse(file.get_as_text())
    file.close()

    if error != OK:
        push_error("JSON parse error: %s at line %d" % [json.get_error_message(), json.get_error_line()])
        return error

    game_data = json.data
    _apply_save_data()
    print("Game loaded.")
    return OK

func _collect_save_data() -> void:
    for node in get_tree().get_nodes_in_group("saveable"):
        if node.has_method("get_save_data"):
            game_data[node.scene_file_path + ":" + str(node.get_path())] = node.get_save_data()

func _apply_save_data() -> void:
    for node in get_tree().get_nodes_in_group("saveable"):
        if node.has_method("load_save_data"):
            var key := node.scene_file_path + ":" + str(node.get_path())
            if game_data.has(key):
                node.load_save_data(game_data[key])

func has_save() -> bool:
    return FileAccess.file_exists(SAVE_PATH)

func delete_save() -> void:
    if FileAccess.file_exists(SAVE_PATH):
        DirAccess.remove_absolute(SAVE_PATH)

6. Tween Animations

extends Node2D

func tween_examples() -> void:
    # Simple move
    var tween := create_tween()
    tween.tween_property(self, "position", Vector2(500, 300), 0.5)

    # Chained tweens (sequential)
    tween = create_tween()
    tween.tween_property($Sprite2D, "modulate", Color.RED, 0.2)
    tween.tween_property($Sprite2D, "modulate", Color.WHITE, 0.2)

    # Parallel tweens
    tween = create_tween().set_parallel(true)
    tween.tween_property(self, "position:x", 500.0, 0.5)
    tween.tween_property(self, "rotation", TAU, 0.5)

    # Easing and transitions
    tween = create_tween()
    tween.tween_property(self, "position", Vector2(400, 200), 0.8) \
        .set_ease(Tween.EASE_OUT).set_trans(Tween.TRANS_BACK)  # Overshoot

    # Looping
    tween = create_tween().set_loops()  # Infinite loops
    tween.tween_property($Sprite2D, "modulate:a", 0.0, 0.5)
    tween.tween_property($Sprite2D, "modulate:a", 1.0, 0.5)

    # Callback after tween
    tween = create_tween()
    tween.tween_property(self, "scale", Vector2(1.5, 1.5), 0.1)
    tween.tween_property(self, "scale", Vector2.ONE, 0.1)
    tween.tween_callback(func(): print("Squash and stretch done!"))

    # Tween method (call a method with interpolated value)
    tween = create_tween()
    tween.tween_method(_set_health_bar, 100.0, 0.0, 2.0)

    # Wait before next step
    tween = create_tween()
    tween.tween_property(self, "modulate:a", 0.0, 0.3)
    tween.tween_interval(1.0)  # Wait 1 second
    tween.tween_callback(queue_free)

func _set_health_bar(value: float) -> void:
    $HealthBar.value = value

7. Timer Usage

extends Node

# Method 1: Timer node (add as child in editor or code)
@onready var attack_timer: Timer = $AttackTimer

func _ready() -> void:
    # Configure Timer node
    attack_timer.wait_time = 2.0
    attack_timer.one_shot = true
    attack_timer.timeout.connect(_on_attack_timer_timeout)

func attack() -> void:
    if attack_timer.is_stopped():
        perform_attack()
        attack_timer.start()

func _on_attack_timer_timeout() -> void:
    print("Can attack again")

# Method 2: SceneTreeTimer (one-liner, no node needed)
func spawn_with_delay() -> void:
    await get_tree().create_timer(0.5).timeout
    spawn_enemy()

# Method 3: Creating Timer in code
func start_repeating_timer() -> void:
    var timer := Timer.new()
    timer.wait_time = 1.0
    timer.autostart = true
    timer.timeout.connect(_on_tick)
    add_child(timer)

func _on_tick() -> void:
    print("Tick at: ", Time.get_ticks_msec())

8. Camera Shake / Screen Shake

extends Camera2D

@export var decay_rate: float = 5.0
@export var max_offset: Vector2 = Vector2(100, 75)
@export var max_rotation: float = 0.1

var _trauma: float = 0.0
var _noise := FastNoiseLite.new()
var _noise_y: int = 0

func _ready() -> void:
    _noise.seed = randi()
    _noise.frequency = 4.0
    _noise.noise_type = FastNoiseLite.TYPE_SIMPLEX_SMOOTH

func add_trauma(amount: float) -> void:
    _trauma = minf(_trauma + amount, 1.0)

func shake(intensity: float = 0.5) -> void:
    add_trauma(intensity)

func _process(delta: float) -> void:
    if _trauma > 0.0:
        _trauma = maxf(_trauma - decay_rate * delta, 0.0)
        var shake_intensity := _trauma * _trauma  # Quadratic falloff
        _noise_y += 1
        offset = Vector2(
            max_offset.x * shake_intensity * _noise.get_noise_2d(_noise.seed, _noise_y),
            max_offset.y * shake_intensity * _noise.get_noise_2d(_noise.seed + 100, _noise_y)
        )
        rotation = max_rotation * shake_intensity * _noise.get_noise_2d(_noise.seed + 200, _noise_y)
    else:
        offset = Vector2.ZERO
        rotation = 0.0

# Usage from any node:
# get_viewport().get_camera_2d().shake(0.6)

9. Custom Resource (Data Container)

# item_data.gd
class_name ItemData
extends Resource

@export var name: String = ""
@export var description: String = ""
@export var icon: Texture2D
@export var stack_size: int = 1
@export var value: int = 0
@export_enum("Weapon", "Armor", "Consumable", "Key") var type: int = 0
@export var stats: Dictionary = {}

func get_display_name() -> String:
    return name if name != "" else "Unknown Item"

# Create in editor: right-click FileSystem > New Resource > ItemData
# Or in code:
# var sword := ItemData.new()
# sword.name = "Iron Sword"
# sword.value = 50
# ResourceSaver.save(sword, "res://items/iron_sword.tres")
#
# Load:
# var sword: ItemData = load("res://items/iron_sword.tres")

# --- Usage in inventory system ---
# @export var items: Array[ItemData] = []
# for item in items:
#     print(item.get_display_name())

10. Singleton / Autoload Pattern

# GameState.gd -- Add to Project > Project Settings > Autoload
extends Node

signal score_changed(new_score: int)
signal game_over

# Persistent state across scenes
var score: int = 0 :
    set(value):
        score = value
        score_changed.emit(score)

var high_score: int = 0
var current_level: int = 1
var is_paused: bool = false

func _ready() -> void:
    process_mode = Node.PROCESS_MODE_ALWAYS  # Keep processing even when paused

func add_score(points: int) -> void:
    score += points
    if score > high_score:
        high_score = score

func reset() -> void:
    score = 0
    current_level = 1

func pause_game() -> void:
    is_paused = true
    get_tree().paused = true

func resume_game() -> void:
    is_paused = false
    get_tree().paused = false

func toggle_pause() -> void:
    if is_paused:
        resume_game()
    else:
        pause_game()

# Usage from anywhere:
# GameState.add_score(100)
# GameState.pause_game()
# GameState.score_changed.connect(func(s): $ScoreLabel.text = str(s))

11. 3D Mouse Picking / Raycasting

extends Node3D

@export var ray_length: float = 1000.0

func _unhandled_input(event: InputEvent) -> void:
    if event is InputEventMouseButton and event.pressed and event.button_index == MOUSE_BUTTON_LEFT:
        var result := _raycast_from_mouse(event.position)
        if result:
            print("Clicked on: ", result.collider.name)
            print("At position: ", result.position)
            print("Surface normal: ", result.normal)
            if result.collider.has_method("interact"):
                result.collider.interact()

func _raycast_from_mouse(mouse_pos: Vector2) -> Dictionary:
    var camera := get_viewport().get_camera_3d()
    if camera == null:
        return {}

    var from := camera.project_ray_origin(mouse_pos)
    var to := from + camera.project_ray_normal(mouse_pos) * ray_length

    var query := PhysicsRayQueryParameters3D.create(from, to)
    query.collision_mask = 1  # Layer 1 only
    query.collide_with_areas = true

    var space_state := get_world_3d().direct_space_state
    return space_state.intersect_ray(query)

# 2D equivalent:
# func _raycast_from_mouse_2d(mouse_pos: Vector2) -> Dictionary:
#     var space_state := get_world_2d().direct_space_state
#     var query := PhysicsPointQueryParameters2D.new()
#     query.position = get_global_mouse_position()
#     query.collision_mask = 1
#     var results := space_state.intersect_point(query)
#     return results[0] if results.size() > 0 else {}

12. Signal Connection Patterns

extends Node

signal player_died(player_name: String)
signal wave_completed(wave_number: int, enemies_killed: int)

func _ready() -> void:
    # 1. Direct connect
    $Button.pressed.connect(_on_button_pressed)

    # 2. Lambda connect
    $Button.pressed.connect(func(): print("Pressed!"))

    # 3. One-shot (auto-disconnects after first emission)
    $AnimationPlayer.animation_finished.connect(
        func(anim_name): print("Played: ", anim_name),
        CONNECT_ONE_SHOT
    )

    # 4. Deferred (called at end of frame)
    $Area2D.body_entered.connect(_on_body_entered, CONNECT_DEFERRED)

    # 5. Connect with extra arguments via bind
    for i in range(5):
        var btn := $Buttons.get_child(i) as Button
        btn.pressed.connect(_on_numbered_button.bind(i))

    # 6. Connect to Autoload signal
    GameState.game_over.connect(_on_game_over)

    # 7. Check if connected
    if not $Timer.timeout.is_connected(_on_timeout):
        $Timer.timeout.connect(_on_timeout)

func _on_button_pressed() -> void:
    print("Button pressed")

func _on_numbered_button(index: int) -> void:
    print("Button %d pressed" % index)

func _on_body_entered(body: Node2D) -> void:
    if body.is_in_group("player"):
        body.take_damage(10)

func _on_timeout() -> void:
    pass

func _on_game_over() -> void:
    get_tree().change_scene_to_file("res://scenes/game_over.tscn")

13. Finite State Machine

# state_machine.gd
class_name StateMachine
extends Node

@export var initial_state: State

var current_state: State
var states: Dictionary[StringName, State] = {}

func _ready() -> void:
    for child in get_children():
        if child is State:
            states[child.name.to_lower()] = child
            child.state_machine = self

    if initial_state:
        initial_state.enter({})
        current_state = initial_state

func _process(delta: float) -> void:
    if current_state:
        current_state.update(delta)

func _physics_process(delta: float) -> void:
    if current_state:
        current_state.physics_update(delta)

func _unhandled_input(event: InputEvent) -> void:
    if current_state:
        current_state.handle_input(event)

func transition_to(target_state_name: StringName, data: Dictionary = {}) -> void:
    if not states.has(target_state_name):
        push_warning("State '%s' not found." % target_state_name)
        return

    current_state.exit()
    current_state = states[target_state_name]
    current_state.enter(data)

# state.gd
class_name State
extends Node

var state_machine: StateMachine

func enter(_data: Dictionary) -> void:
    pass

func exit() -> void:
    pass

func update(_delta: float) -> void:
    pass

func physics_update(_delta: float) -> void:
    pass

func handle_input(_event: InputEvent) -> void:
    pass

# --- Example: idle_state.gd ---
# extends State
#
# func enter(_data: Dictionary) -> void:
#     owner.get_node("AnimationPlayer").play("idle")
#
# func physics_update(_delta: float) -> void:
#     if Input.get_axis("move_left", "move_right") != 0.0:
#         state_machine.transition_to("run")
#     if Input.is_action_just_pressed("jump") and owner.is_on_floor():
#         state_machine.transition_to("jump", {"force": -400.0})

14. Simple Dialogue System

# dialogue_system.gd -- Autoload or attach to a Control node
class_name DialogueSystem
extends CanvasLayer

signal dialogue_started
signal dialogue_finished

@onready var panel: PanelContainer = $Panel
@onready var name_label: Label = $Panel/VBox/NameLabel
@onready var text_label: RichTextLabel = $Panel/VBox/TextLabel
@onready var indicator: TextureRect = $Panel/VBox/ContinueIndicator

var _dialogue_lines: Array[Dictionary] = []
var _current_line: int = -1
var _is_active: bool = false
var _is_typing: bool = false

const TYPE_SPEED: float = 0.03  # Seconds per character

func start_dialogue(lines: Array[Dictionary]) -> void:
    # lines format: [{"name": "NPC", "text": "Hello!"}, ...]
    _dialogue_lines = lines
    _current_line = -1
    _is_active = true
    panel.visible = true
    indicator.visible = false
    dialogue_started.emit()
    advance()

func advance() -> void:
    if _is_typing:
        # Skip typewriter effect
        text_label.visible_ratio = 1.0
        _is_typing = false
        indicator.visible = true
        return

    _current_line += 1
    if _current_line >= _dialogue_lines.size():
        end_dialogue()
        return

    var line: Dictionary = _dialogue_lines[_current_line]
    name_label.text = line.get("name", "")
    text_label.text = line.get("text", "")
    indicator.visible = false
    _type_text()

func _type_text() -> void:
    _is_typing = true
    text_label.visible_ratio = 0.0
    var tween := create_tween()
    var duration: float = text_label.text.length() * TYPE_SPEED
    tween.tween_property(text_label, "visible_ratio", 1.0, duration)
    tween.tween_callback(func():
        _is_typing = false
        indicator.visible = true
    )

func end_dialogue() -> void:
    _is_active = false
    panel.visible = false
    dialogue_finished.emit()

func _unhandled_input(event: InputEvent) -> void:
    if not _is_active:
        return
    if event.is_action_pressed("ui_accept") or event.is_action_pressed("interact"):
        advance()
        get_viewport().set_input_as_handled()

# Usage:
# var lines := [
#     {"name": "Elder", "text": "Welcome to our village, traveler."},
#     {"name": "Elder", "text": "The forest to the east has become dangerous."},
#     {"name": "Player", "text": "I will investigate."},
# ]
# DialogueSystem.start_dialogue(lines)
# await DialogueSystem.dialogue_finished

15. Health / Damage System Component

# health_component.gd -- Attach to any Node with a health bar
class_name HealthComponent
extends Node

signal health_changed(current: int, maximum: int)
signal damaged(amount: int, source: Node)
signal healed(amount: int)
signal died

@export var max_health: int = 100
@export var invincibility_time: float = 0.0  # 0 = no invincibility frames

var health: int :
    get:
        return health
    set(value):
        var old := health
        health = clampi(value, 0, max_health)
        if health != old:
            health_changed.emit(health, max_health)
        if health <= 0 and old > 0:
            died.emit()

var _is_invincible: bool = false

func _ready() -> void:
    health = max_health

func take_damage(amount: int, source: Node = null) -> void:
    if _is_invincible or health <= 0:
        return
    health -= amount
    damaged.emit(amount, source)
    if invincibility_time > 0.0:
        _start_invincibility()

func heal(amount: int) -> void:
    if health <= 0:
        return  # Can't heal if dead
    health += amount
    healed.emit(amount)

func _start_invincibility() -> void:
    _is_invincible = true
    # Flash effect on parent
    var tween := create_tween().set_loops(int(invincibility_time / 0.1))
    tween.tween_property(get_parent(), "modulate:a", 0.3, 0.05)
    tween.tween_property(get_parent(), "modulate:a", 1.0, 0.05)
    await get_tree().create_timer(invincibility_time).timeout
    _is_invincible = false
    get_parent().modulate.a = 1.0

func is_alive() -> bool:
    return health > 0

func get_health_ratio() -> float:
    return float(health) / float(max_health)

16. Object Pooling

# object_pool.gd
class_name ObjectPool
extends Node

@export var scene: PackedScene
@export var pool_size: int = 20
@export var auto_expand: bool = true

var _pool: Array[Node] = []

func _ready() -> void:
    for i in pool_size:
        _create_instance()

func _create_instance() -> Node:
    var instance := scene.instantiate()
    instance.set_meta("pooled", true)
    add_child(instance)
    _deactivate(instance)
    _pool.append(instance)
    return instance

func acquire() -> Node:
    for instance in _pool:
        if not instance.visible:
            _activate(instance)
            return instance

    if auto_expand:
        var instance := _create_instance()
        _activate(instance)
        return instance

    push_warning("ObjectPool: No available instances and auto_expand is off.")
    return null

func release(instance: Node) -> void:
    if instance.get_meta("pooled", false):
        _deactivate(instance)

func _activate(instance: Node) -> void:
    instance.visible = true
    instance.process_mode = Node.PROCESS_MODE_INHERIT
    if instance.has_method("on_pool_acquire"):
        instance.on_pool_acquire()

func _deactivate(instance: Node) -> void:
    instance.visible = false
    instance.process_mode = Node.PROCESS_MODE_DISABLED
    if instance.has_method("on_pool_release"):
        instance.on_pool_release()

# Usage:
# @onready var bullet_pool: ObjectPool = $BulletPool
# func fire():
#     var bullet = bullet_pool.acquire()
#     if bullet:
#         bullet.global_position = $Muzzle.global_position
#         bullet.direction = transform.x

17. 3D First-Person Controller

extends CharacterBody3D

@export_group("Movement")
@export var walk_speed: float = 5.0
@export var sprint_speed: float = 8.0
@export var jump_velocity: float = 4.5
@export var acceleration: float = 10.0
@export var air_control: float = 0.3

@export_group("Mouse Look")
@export var mouse_sensitivity: float = 0.002
@export var max_pitch: float = 89.0

@onready var head: Node3D = $Head
@onready var camera: Camera3D = $Head/Camera3D

var _gravity: float = ProjectSettings.get_setting("physics/3d/default_gravity")

func _ready() -> void:
    Input.mouse_mode = Input.MOUSE_MODE_CAPTURED

func _unhandled_input(event: InputEvent) -> void:
    if event is InputEventMouseMotion:
        rotate_y(-event.relative.x * mouse_sensitivity)
        head.rotate_x(-event.relative.y * mouse_sensitivity)
        head.rotation.x = clampf(head.rotation.x, deg_to_rad(-max_pitch), deg_to_rad(max_pitch))

    if event.is_action_pressed("ui_cancel"):
        Input.mouse_mode = Input.MOUSE_MODE_VISIBLE

func _physics_process(delta: float) -> void:
    # Gravity
    if not is_on_floor():
        velocity.y -= _gravity * delta

    # Jump
    if Input.is_action_just_pressed("jump") and is_on_floor():
        velocity.y = jump_velocity

    # Movement
    var input_dir := Input.get_vector("move_left", "move_right", "move_forward", "move_back")
    var direction := (transform.basis * Vector3(input_dir.x, 0, input_dir.y)).normalized()

    var speed := sprint_speed if Input.is_action_pressed("sprint") else walk_speed
    var accel := acceleration if is_on_floor() else acceleration * air_control

    if direction:
        velocity.x = move_toward(velocity.x, direction.x * speed, accel * delta)
        velocity.z = move_toward(velocity.z, direction.z * speed, accel * delta)
    else:
        velocity.x = move_toward(velocity.x, 0.0, accel * delta)
        velocity.z = move_toward(velocity.z, 0.0, accel * delta)

    move_and_slide()

18. Inventory System

# inventory.gd
class_name Inventory
extends Resource

signal item_added(item: ItemData, slot: int)
signal item_removed(item: ItemData, slot: int)
signal inventory_changed

@export var slots: Array[ItemData] = []
@export var max_slots: int = 20

func _init() -> void:
    slots.resize(max_slots)

func add_item(item: ItemData) -> bool:
    # First try to stack with existing item
    for i in slots.size():
        if slots[i] != null and slots[i].name == item.name:
            if slots[i].stack_size > 1:  # Stackable
                item_added.emit(item, i)
                inventory_changed.emit()
                return true

    # Find empty slot
    for i in slots.size():
        if slots[i] == null:
            slots[i] = item
            item_added.emit(item, i)
            inventory_changed.emit()
            return true

    push_warning("Inventory full!")
    return false

func remove_item(slot: int) -> ItemData:
    if slot < 0 or slot >= slots.size() or slots[slot] == null:
        return null
    var item := slots[slot]
    slots[slot] = null
    item_removed.emit(item, slot)
    inventory_changed.emit()
    return item

func swap_items(slot_a: int, slot_b: int) -> void:
    var temp := slots[slot_a]
    slots[slot_a] = slots[slot_b]
    slots[slot_b] = temp
    inventory_changed.emit()

func get_item(slot: int) -> ItemData:
    if slot < 0 or slot >= slots.size():
        return null
    return slots[slot]

func has_item(item_name: String) -> bool:
    return slots.any(func(item): return item != null and item.name == item_name)

func get_item_count() -> int:
    return slots.reduce(func(acc, item): return acc + (1 if item != null else 0), 0)

19. Spawner with Weighted Random

extends Node2D

@export var spawn_interval: float = 2.0
@export var max_spawns: int = 10
@export var spawn_radius: float = 300.0

## Array of {"scene": PackedScene, "weight": float}
@export var spawn_table: Array[Dictionary] = []

var _spawn_count: int = 0
var _total_weight: float = 0.0

func _ready() -> void:
    # Calculate total weight
    for entry in spawn_table:
        _total_weight += entry.get("weight", 1.0)

    # Start spawning
    var timer := Timer.new()
    timer.wait_time = spawn_interval
    timer.timeout.connect(_on_spawn_timer)
    timer.autostart = true
    add_child(timer)

func _on_spawn_timer() -> void:
    if _spawn_count >= max_spawns:
        return

    var scene := _pick_random_scene()
    if scene == null:
        return

    var instance := scene.instantiate() as Node2D
    instance.global_position = _get_random_spawn_position()
    get_parent().add_child(instance)
    _spawn_count += 1

    # Track when spawned entity is freed
    instance.tree_exited.connect(func(): _spawn_count -= 1)

func _pick_random_scene() -> PackedScene:
    var roll := randf() * _total_weight
    var cumulative := 0.0
    for entry in spawn_table:
        cumulative += entry.get("weight", 1.0)
        if roll <= cumulative:
            return entry.get("scene") as PackedScene
    return spawn_table.back().get("scene") as PackedScene

func _get_random_spawn_position() -> Vector2:
    var angle := randf() * TAU
    var distance := randf_range(50.0, spawn_radius)
    return global_position + Vector2(cos(angle), sin(angle)) * distance

20. Pause Menu with Settings

extends Control

@onready var master_slider: HSlider = %MasterSlider
@onready var sfx_slider: HSlider = %SFXSlider
@onready var music_slider: HSlider = %MusicSlider
@onready var fullscreen_check: CheckButton = %FullscreenCheck
@onready var vsync_check: CheckButton = %VsyncCheck

func _ready() -> void:
    visible = false
    process_mode = Node.PROCESS_MODE_ALWAYS

    # Initialize UI from current settings
    master_slider.value = db_to_linear(AudioServer.get_bus_volume_db(
        AudioServer.get_bus_index("Master")))
    sfx_slider.value = db_to_linear(AudioServer.get_bus_volume_db(
        AudioServer.get_bus_index("SFX")))
    music_slider.value = db_to_linear(AudioServer.get_bus_volume_db(
        AudioServer.get_bus_index("Music")))
    fullscreen_check.button_pressed = (
        DisplayServer.window_get_mode() == DisplayServer.WINDOW_MODE_FULLSCREEN)
    vsync_check.button_pressed = (
        DisplayServer.window_get_vsync_mode() != DisplayServer.VSYNC_DISABLED)

    # Connect signals
    master_slider.value_changed.connect(_on_master_changed)
    sfx_slider.value_changed.connect(_on_sfx_changed)
    music_slider.value_changed.connect(_on_music_changed)
    fullscreen_check.toggled.connect(_on_fullscreen_toggled)
    vsync_check.toggled.connect(_on_vsync_toggled)
    %ResumeButton.pressed.connect(_on_resume)
    %QuitButton.pressed.connect(_on_quit)

func _unhandled_input(event: InputEvent) -> void:
    if event.is_action_pressed("pause"):
        _toggle_pause()
        get_viewport().set_input_as_handled()

func _toggle_pause() -> void:
    visible = !visible
    get_tree().paused = visible

func _on_resume() -> void:
    _toggle_pause()

func _on_quit() -> void:
    get_tree().paused = false
    get_tree().change_scene_to_file("res://scenes/main_menu.tscn")

func _on_master_changed(value: float) -> void:
    _set_bus_volume("Master", value)

func _on_sfx_changed(value: float) -> void:
    _set_bus_volume("SFX", value)

func _on_music_changed(value: float) -> void:
    _set_bus_volume("Music", value)

func _set_bus_volume(bus_name: String, linear_value: float) -> void:
    var bus_index := AudioServer.get_bus_index(bus_name)
    AudioServer.set_bus_volume_db(bus_index, linear_to_db(linear_value))
    AudioServer.set_bus_mute(bus_index, linear_value < 0.01)

func _on_fullscreen_toggled(enabled: bool) -> void:
    if enabled:
        DisplayServer.window_set_mode(DisplayServer.WINDOW_MODE_FULLSCREEN)
    else:
        DisplayServer.window_set_mode(DisplayServer.WINDOW_MODE_WINDOWED)

func _on_vsync_toggled(enabled: bool) -> void:
    if enabled:
        DisplayServer.window_set_vsync_mode(DisplayServer.VSYNC_ENABLED)
    else:
        DisplayServer.window_set_vsync_mode(DisplayServer.VSYNC_DISABLED)

Common Node Hierarchy Recipes

2D Platformer Player

CharacterBody2D (player.gd)
  +-- Sprite2D (or AnimatedSprite2D)
  +-- CollisionShape2D (CapsuleShape2D)
  +-- Camera2D
  +-- AnimationPlayer
  +-- StateMachine
  |     +-- Idle (idle_state.gd)
  |     +-- Run (run_state.gd)
  |     +-- Jump (jump_state.gd)
  +-- Hitbox (Area2D)
  |     +-- CollisionShape2D
  +-- Hurtbox (Area2D)
  |     +-- CollisionShape2D
  +-- CoyoteTimer (Timer, one_shot=true)
  +-- JumpBufferTimer (Timer, one_shot=true)

3D Character

CharacterBody3D (character.gd)
  +-- CollisionShape3D (CapsuleShape3D)
  +-- Head (Node3D)
  |     +-- Camera3D
  +-- MeshInstance3D (or imported scene)
  +-- AnimationTree
  +-- NavigationAgent3D
  +-- RayCast3D (ground detect)

UI Layout

Control (root)
  +-- MarginContainer
  |     +-- VBoxContainer
  |           +-- Label (title)
  |           +-- HBoxContainer
  |           |     +-- TextureRect (icon)
  |           |     +-- VBoxContainer
  |           |           +-- Label (name)
  |           |           +-- ProgressBar (health)
  |           +-- GridContainer (inventory grid)
  +-- CanvasLayer (layer=10, for overlays)
        +-- PauseMenu (Control)
        +-- DialogueBox (Control)

Key Project Settings

Setting Path Default Notes
Window size display/window/size/viewport_width/height 1152x648 Base resolution
Stretch mode display/window/stretch/mode disabled canvas_items for pixel art, viewport for 3D
Stretch aspect display/window/stretch/aspect ignore keep or expand recommended
Physics FPS physics/common/physics_ticks_per_second 60
2D gravity physics/2d/default_gravity 980.0 pixels/sec^2
3D gravity physics/3d/default_gravity 9.8 m/sec^2
Rendering method rendering/renderer/rendering_method forward_plus mobile, gl_compatibility
MSAA 3D rendering/anti_aliasing/quality/msaa_3d disabled 2x, 4x, 8x
Shadow atlas size rendering/lights_and_shadows/directional_shadow/size 4096
Audio driver audio/driver/driver platform default

File System Conventions

res://                           # Project root (read-only in exported builds)
  +-- project.godot              # Project configuration
  +-- scenes/                    # .tscn scene files
  +-- scripts/                   # .gd script files
  +-- assets/
  |     +-- sprites/             # 2D textures
  |     +-- models/              # 3D models (.glb, .gltf)
  |     +-- audio/               # Sound effects and music
  |     +-- fonts/               # .ttf, .otf font files
  |     +-- shaders/             # .gdshader files
  +-- resources/                 # .tres custom resources
  +-- addons/                    # Editor plugins
  +-- export_presets.cfg         # Export configurations

user://                          # Writable directory (persistent)
  # Windows: %APPDATA%/Godot/app_userdata/<project_name>/
  # Linux:   ~/.local/share/godot/app_userdata/<project_name>/
  # macOS:   ~/Library/Application Support/Godot/app_userdata/<project_name>/

Debugging Cheat Sheet

Task Method
Print to output print(), print_rich(), printerr()
Breakpoints Click line number gutter in script editor
Remote scene tree Debugger > Scene panel while running
Draw debug shapes draw_line(), draw_circle() in _draw() + queue_redraw()
Physics debug Project Settings > Debug > Visible Collision Shapes
Navigation debug Project Settings > Debug > Visible Navigation
Performance monitor Debugger > Monitors tab
Profile frame Debugger > Profiler tab
GDScript warnings Project Settings > Debug > GDScript > Enable Warnings
Assert assert(condition, "message")
Push warnings push_warning("msg"), push_error("msg")
Group calls debug print(get_tree().get_nodes_in_group("enemies"))

This skill covers Godot Engine 4.7. For API details on specific classes, methods, properties, signals, and enums, refer to the API class reference files listed above.

Categories