Coding Quests
The Scroll Library
Reference

GDScript Cheat Sheet: Everything You Need in One Place

February 24, 2026Updated June 11, 20269 min read

GDScript is Godot's built-in language, and it has exactly one job: making games in Godot. If you're coming from Python it'll feel familiar within an hour. Coming from C# or JavaScript, the first thing you'll notice is how much boilerplate just isn't there.

This is a reference, not a tutorial. Everything below is Godot 4.x syntax. Bookmark it and Ctrl+F your way around when something slips your mind.

Variables and Types

GDScript
# Basic types
var health: int = 100
var speed: float = 5.5
var name: String = "Player"
var alive: bool = true
var direction: Vector2 = Vector2(1, 0)
var position: Vector3 = Vector3.ZERO
# Type inference
var score := 0 # int
var damage := 10.5 # float
var label := "Hello" # String
# Constants
const MAX_SPEED: float = 10.0
const GRAVITY: float = 9.8
# Enums
enum State { IDLE, RUN, JUMP, ATTACK }
var current_state: State = State.IDLE
# Exports (editable in Inspector)
@export var max_health: int = 100
@export var walk_speed: float = 3.0
@export var player_name: String = "Hero"
@export var item_scene: PackedScene
@export_range(0, 100, 1) var volume: int = 80
@export_enum("Sword", "Axe", "Bow") var weapon: String
@export_group("Movement")
@export var run_speed: float = 6.0
@export var jump_force: float = 8.0

Functions

GDScript
# Basic function
func take_damage(amount: int) -> void:
health -= amount
# Return value
func get_speed() -> float:
return speed * speed_multiplier
# Default parameters
func heal(amount: int = 10) -> void:
health = mini(health + amount, max_health)
# Optional return type
func is_alive() -> bool:
return health > 0
# Static function (callable without instance)
static func calculate_damage(attack: int, defense: int) -> int:
return maxi(attack - defense, 1)
# Lambda / anonymous function
var double := func(x: int) -> int: return x * 2

Control Flow

GDScript
# If / elif / else
if health <= 0:
die()
elif health < 20:
show_warning()
else:
regenerate()
# Match (like switch)
match current_state:
State.IDLE:
play_idle()
State.RUN:
play_run()
State.JUMP, State.ATTACK:
# Multiple values
play_action()
_:
# Default
pass
# Ternary
var label := "Dead" if health <= 0 else "Alive"
# For loops
for i in range(10): # 0 to 9
print(i)
for item in inventory: # Iterate array
print(item.name)
for key in stats: # Iterate dictionary keys
print(key, stats[key])
# While loop
while not is_on_floor():
velocity.y -= gravity * delta

Collections

GDScript
# Arrays
var items: Array[String] = ["Sword", "Shield", "Potion"]
items.append("Bow")
items.remove_at(0)
items.has("Shield") # true
items.size() # 3
items.find("Potion") # index or -1
items.sort()
items.shuffle()
items.filter(func(i): return i != "Bow")
items.map(func(i): return i.to_upper())
# Typed arrays
var enemies: Array[Enemy] = []
var numbers: Array[int] = [1, 2, 3]
# Dictionary
var stats: Dictionary = {
"health": 100,
"strength": 15,
"defense": 10,
}
stats["health"] # 100
stats.get("mana", 0) # 0 (default)
stats.has("strength") # true
stats.keys() # Array of keys
stats.values() # Array of values
stats.erase("defense")
stats.merge({"speed": 5})

Strings

GDScript
var name := "Adventurer"
# String operations
name.length() # 10
name.to_upper() # "ADVENTURER"
name.to_lower() # "adventurer"
name.begins_with("Adv") # true
name.contains("vent") # true
name.replace("er", "or") # "Adventuror"
name.split("e") # ["Adv", "ntur", "r"]
# String formatting
var msg := "HP: %d / %d" % [health, max_health]
var pos := "Position: %.2f, %.2f" % [x, y]
# String interpolation (Godot 4.x - no built-in f-strings, use % or +)
var greeting := "Hello, " + name + "!"

Node Lifecycle

GDScript
extends CharacterBody3D
# Called when node enters the scene tree
func _ready() -> void:
pass
# Called every frame (variable delta)
func _process(delta: float) -> void:
pass
# Called at fixed rate (default 60/sec) - use for physics
func _physics_process(delta: float) -> void:
pass
# Called for unhandled input events
func _unhandled_input(event: InputEvent) -> void:
pass
# Called for all input events
func _input(event: InputEvent) -> void:
pass
# Called when node exits the scene tree
func _exit_tree() -> void:
pass

Input

GDScript
# Polling (check state right now)
if Input.is_action_pressed("move_forward"): # Held down
move()
if Input.is_action_just_pressed("jump"): # Just pressed this frame
jump()
if Input.is_action_just_released("attack"): # Just released this frame
stop_charging()
# Movement vector (combines 4 directional inputs into Vector2)
var input := Input.get_vector("left", "right", "forward", "back")
# Mouse
Input.set_mouse_mode(Input.MOUSE_MODE_CAPTURED)
Input.set_mouse_mode(Input.MOUSE_MODE_VISIBLE)
# Event-based (in _unhandled_input or _input)
func _unhandled_input(event: InputEvent) -> void:
if event is InputEventMouseMotion:
rotate_y(-event.relative.x * sensitivity)
if event is InputEventKey:
if event.pressed and event.keycode == KEY_ESCAPE:
get_tree().quit()

Signals

GDScript
# Define a signal
signal health_changed(new_health: int)
signal died
# Emit a signal
func take_damage(amount: int) -> void:
health -= amount
health_changed.emit(health)
if health <= 0:
died.emit()
# Connect a signal (in code)
func _ready() -> void:
$Enemy.died.connect(_on_enemy_died)
func _on_enemy_died() -> void:
score += 100
# Connect with lambda
$Button.pressed.connect(func(): print("clicked"))
# One-shot connection (auto-disconnects after firing once)
$Timer.timeout.connect(_on_timeout, CONNECT_ONE_SHOT)
# Disconnect
$Enemy.died.disconnect(_on_enemy_died)
# Check if connected
$Enemy.died.is_connected(_on_enemy_died)

Node References

GDScript
# Get child node (same scene)
@onready var sprite: Sprite2D = $Sprite2D
@onready var health_bar: ProgressBar = $UI/HealthBar
@onready var anim: AnimationPlayer = $AnimationPlayer
# Export (set in Inspector - survives restructuring)
@export var target: Node3D
@export var weapon_scene: PackedScene
# Get node by path
var player := get_node("/root/Main/Player")
# Get parent
var parent := get_parent()
# Get children
for child in get_children():
if child is Enemy:
child.take_damage(10)
# Groups
add_to_group("enemies")
if is_in_group("enemies"):
pass
var all_enemies := get_tree().get_nodes_in_group("enemies")
# Find nodes
var camera := get_viewport().get_camera_3d()

Scenes and Instantiation

GDScript
# Load a scene
var enemy_scene: PackedScene = preload("res://scenes/enemy.tscn")
# Instantiate
var enemy := enemy_scene.instantiate()
add_child(enemy)
enemy.global_position = Vector3(10, 0, 5)
# Remove from scene
enemy.queue_free()
# Change scene
get_tree().change_scene_to_file("res://scenes/game_over.tscn")
get_tree().change_scene_to_packed(preload("res://scenes/menu.tscn"))
# Reload current scene
get_tree().reload_current_scene()

Physics and Movement (CharacterBody3D)

GDScript
extends CharacterBody3D
@export var speed: float = 5.0
@export var jump_force: float = 8.0
@export var gravity: float = 20.0
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_force
# Movement
var input := Input.get_vector("left", "right", "forward", "back")
velocity.x = input.x * speed
velocity.z = input.y * speed
move_and_slide()
# Useful checks after move_and_slide()
is_on_floor()
is_on_wall()
is_on_ceiling()
get_last_slide_collision()

Timers

GDScript
# Create timer in code
var timer := Timer.new()
timer.wait_time = 2.0
timer.one_shot = true
timer.timeout.connect(_on_timer_done)
add_child(timer)
timer.start()
# Quick one-shot timer
await get_tree().create_timer(1.5).timeout
print("1.5 seconds later")
# Scene tree timer (no node needed)
get_tree().create_timer(0.5).timeout.connect(func(): print("done"))

Tweens

GDScript
# Create tween
var tween := create_tween()
# Move to position over 0.5 seconds
tween.tween_property(self, "position", Vector3(10, 0, 0), 0.5)
# Chain animations
tween.tween_property(self, "modulate:a", 0.0, 0.3) # Fade out
tween.tween_callback(queue_free) # Then remove
# Parallel tweens
tween.set_parallel(true)
tween.tween_property(self, "position:y", 5.0, 0.3)
tween.tween_property(self, "rotation:y", PI, 0.3)
# Easing
tween.tween_property(self, "position", target, 0.5)\
.set_ease(Tween.EASE_OUT)\
.set_trans(Tween.TRANS_CUBIC)
# Loop
tween.set_loops(3) # Repeat 3 times
tween.set_loops(0) # Loop forever

Resources

GDScript
# Define a custom Resource
class_name ItemData
extends Resource
@export var id: String
@export var name: String
@export var icon: Texture2D
@export var value: int
# Load a resource
var sword: ItemData = load("res://data/items/sword.tres")
var sword2: ItemData = preload("res://data/items/sword.tres") # Compile-time
# Create at runtime
var item := ItemData.new()
item.name = "Potion"
item.value = 50
# Save a resource
ResourceSaver.save(item, "user://custom_item.tres")

File I/O

GDScript
# Write JSON
func save_data(data: Dictionary) -> void:
var file := FileAccess.open("user://save.json", FileAccess.WRITE)
file.store_string(JSON.stringify(data, "\t"))
# Read JSON
func load_data() -> Dictionary:
if not FileAccess.file_exists("user://save.json"):
return {}
var file := FileAccess.open("user://save.json", FileAccess.READ)
var json := JSON.new()
if json.parse(file.get_as_text()) == OK:
return json.data
return {}
# ConfigFile (INI-style)
var config := ConfigFile.new()
config.set_value("audio", "volume", 0.8)
config.save("user://settings.cfg")
config.load("user://settings.cfg")
var vol: float = config.get_value("audio", "volume", 1.0)
# Check paths
FileAccess.file_exists("user://save.json")
DirAccess.dir_exists_absolute("user://saves/")

Math Helpers

GDScript
# Common math
abs(-5) # 5
sign(-3.2) # -1.0
clamp(value, 0, 100) # Constrain to range
clampf(0.5, 0.0, 1.0) # Float version
clampi(50, 0, 100) # Int version
mini(a, b) # Smaller int
maxi(a, b) # Larger int
minf(a, b) # Smaller float
maxf(a, b) # Larger float
# Interpolation
lerp(0.0, 100.0, 0.5) # 50.0
lerpf(a, b, t) # Float lerp
lerp_angle(a, b, t) # Angle lerp (handles wrapping)
# Random
randf() # 0.0 to 1.0
randi() # Random int
randf_range(1.0, 10.0) # Float in range
randi_range(1, 6) # Int in range (inclusive)
# Vectors
var v := Vector3(1, 2, 3)
v.normalized() # Unit vector
v.length() # Magnitude
v.distance_to(other) # Distance between two vectors
v.direction_to(other) # Normalized direction
v.lerp(other, 0.5) # Halfway between
v.dot(other) # Dot product
v.cross(other) # Cross product (3D)
# Angles
deg_to_rad(90.0) # 1.5708
rad_to_deg(PI) # 180.0
atan2(y, x) # Angle from components

Autoloads (Singletons)

GDScript
# Set up: Project → Project Settings → Autoload → Add script
# GameManager.gd (autoloaded as "GameManager")
extends Node
var score: int = 0
var current_level: int = 1
signal score_changed(new_score: int)
func add_score(amount: int) -> void:
score += amount
score_changed.emit(score)
# Use from anywhere:
GameManager.add_score(100)
GameManager.score_changed.connect(_on_score_changed)

Coroutines (Await)

GDScript
# Wait for a signal
await $AnimationPlayer.animation_finished
# Wait for a timer
await get_tree().create_timer(2.0).timeout
# Wait for a tween
var tween := create_tween()
tween.tween_property(self, "position:y", 10.0, 1.0)
await tween.finished
# Async function
func fade_out() -> void:
var tween := create_tween()
tween.tween_property(self, "modulate:a", 0.0, 0.5)
await tween.finished
queue_free()

Class Inheritance

GDScript
# Base class
# weapon.gd
class_name Weapon
extends Node3D
@export var damage: int = 10
@export var attack_speed: float = 1.0
func attack() -> void:
print("Base attack")
# Derived class
# sword.gd
class_name Sword
extends Weapon
func _ready() -> void:
damage = 25
func attack() -> void:
super.attack() # Call parent method
print("Sword slash!")
# Type checking
if weapon is Sword:
weapon.special_ability()

Common Patterns

GDScript
# Null-safe access
var health_bar := get_node_or_null("UI/HealthBar")
if health_bar:
health_bar.value = health
# Deferred calls (safe for physics/tree changes)
call_deferred("add_child", new_node)
set_deferred("monitoring", true)
queue_free() # Already deferred
# Process mode (pause behavior)
process_mode = Node.PROCESS_MODE_ALWAYS # Runs during pause
process_mode = Node.PROCESS_MODE_PAUSABLE # Stops during pause (default)
process_mode = Node.PROCESS_MODE_DISABLED # Never runs
# Pause the game
get_tree().paused = true
get_tree().paused = false

That's the GDScript I reach for daily. The official Godot docs cover the rest of the API when you need it.

Syntax only sticks when you build with it, though. The inventory system quest is free and puts most of this sheet to work, and these five beginner mistakes are worth dodging while your habits are still forming.

gdscriptreferencebeginner

Stop reading. Start building.

Beat the demo boss by writing real Godot code, then build this for real in the Inventory System Quest.

Free, and no card needed. Built by a real person, with new quests every month.

Get the next Godot build in your inbox

New quests, project breakdowns, and game-dev tips. Free, no spam, unsubscribe anytime.

Written by Coding Quests

We teach Godot 4 by making you build complete systems: inventories, save systems, action roguelike controllers, enemy AI. The scrolls are free. The quests are where it sticks.