Skip to main content
Coding Quests
The Scroll Library
Reference

GDScript Cheat Sheet: Everything You Need in One Place

February 24, 2026Updated July 19, 202611 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.

New to the language? Start with the GDScript tutorial for beginners for the "why" behind each form, read the signals guide when events come up, then write it for real in the free Code From Zero quest. Keep this sheet open in another tab. For the full learning order, follow the project-based Godot roadmap; to browse guides by topic, see the Godot tutorials library.

On this page

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

Common Godot 4 Syntax Mistakes

The syntax errors that bite Godot 4 beginners most often, and the fix for each:

GDScript
# Integer division truncates. 5 / 2 is 2, not 2.5.
var half := 5 / 2 # 2 (both operands are ints)
var half := 5.0 / 2 # 2.5 (make one a float)
# Frame-rate-dependent movement: forgetting delta.
position.x += 100 # moves faster on a fast PC
position.x += 100 * delta # same speed everywhere
# Comparing floats with ==. Rounding makes this fail.
if speed == 10.0: # fragile
if is_equal_approx(speed, 10.0): # correct
# Typed arrays need the element type on BOTH sides.
var enemies: Array[Node] = [] # ok
var enemies := [] # untyped; loses autocomplete + checks
# Type check: use `is`, not `==`, on classes.
if node is CharacterBody2D: # correct

A few more that are not syntax errors but behave surprisingly: connecting the same signal twice (guard with is_connected()), reading a $Node that is not in the tree yet (use @onready), and get_node("Wrong/Path") crashing instead of returning null (use get_node_or_null()). The five most common GDScript mistakes covers the habits worth forming early.

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. If you are still learning the language, the GDScript tutorial for beginners walks through these concepts in order, the signals guide goes deep on Godot's event system, and the free Code From Zero quest puts most of this sheet to work in your browser.

gdscriptreferencebeginner

Stop reading. Start building.

Write real Godot code in your browser, then build this for real in the Code From Zero 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 Omar

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.