How: Developed by Juan Linietsky and Ariel Manzur, first released publicly in 2014 as open-source.
Who: Maintained by the Godot Engine community and the Godot Foundation.
Why: To provide a fully free, open-source game engine with no royalties, no vendor lock-in, and a clean scene-based architecture.
Introduction
Godot is a feature-rich, cross-platform game engine for 2D and 3D games. It uses a unique scene/node system, supports GDScript (Python-like), C#, and C++ (via GDExtension), and exports to Windows, Linux, macOS, Android, iOS, and Web.
Advantages
Fully open-source and free — no royalties, no subscription.
Lightweight editor (~100MB), fast iteration.
Unified 2D and 3D workflows in one engine.
GDScript is beginner-friendly and tightly integrated.
Active community, frequent releases (Godot 4.x is a major leap).
Disadvantages
Smaller ecosystem than Unity/Unreal (fewer ready-made assets).
C# support in Godot 4 is still maturing (.NET 6+).
3D rendering less mature than Unreal Engine for AAA-level visuals.
Editor & Project Setup
Editor Layout
Scene Panel (left) — tree of all nodes in the current scene.
Viewport (center) — visual editor for 2D/3D.
Inspector (right) — properties of the selected node.
.tscn Text Scene — human-readable, version-control friendly (default for scenes)
.scn Binary Scene — compiled binary, faster to load, used in exported builds
.tres Text Resource — human-readable resource (materials, custom data, etc.)
.res Binary Resource — compiled binary version of .tres
.escn Exported Scene — identical to .tscn but marks the file as externally exported
(e.g. from Blender); auto-compiled to .scn on import
When you export your game, Godot automatically converts .tscn → .scn and .tres → .res for performance.
Use .tscn / .tres during development (readable diffs in git), and let the exporter handle the rest.
# Loading works the same regardless of format:var scene = preload("res://scenes/Player.tscn") # text scenevar scene = preload("res://scenes/Player.scn") # binary scene (exported)var data = preload("res://data/item.tres") # text resourcevar data = preload("res://data/item.res") # binary resource
Common Node Types
Node2D/3D Base for all 2D/3D nodes (has position, rotation, scale)
Sprite2D/3D Displays a texture in 2D/3D
AnimatedSprite2D/3D Sprite with frame animation
CollisionShape2D/3D Defines collision area shape
Area2D/3D Detects overlaps (no physics response)
CharacterBody2D/3D Kinematic body for player/enemies
RigidBody2D/3D Physics-simulated body
StaticBody2D/3D Immovable physics body (walls, floors)
Camera2D/3D 2D/3D camera with follow/zoom
Label UI text
Button UI button
CanvasLayer UI layer (always on top of game world)
3D Node Types
Node3D Base for all 3D nodes
MeshInstance3D Renders a 3D mesh
CollisionShape3D 3D collision shape
CharacterBody3D 3D kinematic body
RigidBody3D 3D physics body
Camera3D 3D camera
DirectionalLight3D Sun-like light
OmniLight3D Point light (all directions)
SpotLight3D Cone-shaped spotlight
WorldEnvironment Sky, ambient light, fog, tone mapping
Instancing Scenes
# In editor: drag .tscn into scene tree# In code:var bullet_scene = preload("res://scenes/Bullet.tscn")func shoot(): var bullet = bullet_scene.instantiate() bullet.position = $Muzzle.global_position get_parent().add_child(bullet)
GDScript
GDScript is Godot’s built-in scripting language — Python-like syntax, tightly integrated with the engine, supports static typing, signals, coroutines, lambdas, and full OOP.
For the complete in-depth GDScript language reference (variables, types, OOP, signals, annotations, coroutines, patterns, and more), see the dedicated GDScript note.
Quick reference:
extends CharacterBody2D # inherit a Godot classclass_name Player # register as global class@export var speed: float = 200.0 # editable in Inspector@onready var anim := $AnimationPlayer # assigned at _readysignal health_changed(new_hp: int)var health: int = 100: set(v): health = clamp(v, 0, 100) health_changed.emit(health)func _physics_process(delta: float) -> void: var dir = Input.get_axis("move_left", "move_right") velocity.x = dir * speed move_and_slide()
Core Lifecycle Methods
Built-in Callbacks
extends Nodefunc _init() -> void: # Called when object is created (before added to scene) passfunc _ready() -> void: # Called once when node enters the scene tree # All children are ready at this point passfunc _process(delta: float) -> void: # Called every frame (tied to FPS) # delta = time since last frame in seconds position.x += speed * deltafunc _physics_process(delta: float) -> void: # Called every physics tick (default 60/s, fixed timestep) # Use for movement, physics, collision move_and_slide()func _input(event: InputEvent) -> void: # Called for every input event if event is InputEventKey: print(event.keycode)func _unhandled_input(event: InputEvent) -> void: # Called if no other node consumed the input passfunc _notification(what: int) -> void: # Low-level lifecycle notifications if what == NOTIFICATION_WM_CLOSE_REQUEST: get_tree().quit()func _exit_tree() -> void: # Called when node is removed from scene tree pass
delta Time Pattern
# Always multiply movement by delta for frame-rate independencefunc _process(delta: float) -> void: position += velocity * delta # Without delta: moves 200px per frame (FPS-dependent) # With delta: moves 200px per second (FPS-independent)
Input System
Input Actions (Project Settings → Input Map)
# Check action statefunc _process(delta: float) -> void: if Input.is_action_pressed("move_right"): velocity.x = speed elif Input.is_action_pressed("move_left"): velocity.x = -speed else: velocity.x = 0 if Input.is_action_just_pressed("jump"): jump() if Input.is_action_just_released("attack"): end_attack()# Get axis (returns -1, 0, or 1)var h = Input.get_axis("move_left", "move_right")var v = Input.get_axis("move_up", "move_down")var dir = Vector2(h, v).normalized()
Mouse Input
func _input(event: InputEvent) -> void: if event is InputEventMouseButton: if event.button_index == MOUSE_BUTTON_LEFT and event.pressed: print("Left click at: ", event.position) if event is InputEventMouseMotion: print("Mouse moved: ", event.relative)# Get mouse positionvar mouse_pos = get_global_mouse_position()
Gamepad Input
# Axis (joystick)var h = Input.get_joy_axis(0, JOY_AXIS_LEFT_X)var v = Input.get_joy_axis(0, JOY_AXIS_LEFT_Y)# Buttonif Input.is_joy_button_pressed(0, JOY_BUTTON_A): jump()
2D Game Development
CharacterBody2D — Player Movement
extends CharacterBody2Dconst SPEED = 200.0const JUMP_VELOCITY = -400.0const GRAVITY = 980.0func _physics_process(delta: float) -> void: # Apply gravity if not is_on_floor(): velocity.y += GRAVITY * delta # Horizontal movement var direction = Input.get_axis("move_left", "move_right") velocity.x = direction * SPEED # Jump if Input.is_action_just_pressed("jump") and is_on_floor(): velocity.y = JUMP_VELOCITY move_and_slide() # handles collision automatically
# Sprite2D — single texture$Sprite2D.texture = preload("res://assets/player.png")$Sprite2D.flip_h = true # mirror horizontally# AnimatedSprite2D — frame-based animation# Set up SpriteFrames resource in editor, then:$AnimatedSprite2D.play("run")$AnimatedSprite2D.stop()$AnimatedSprite2D.animation = "idle"$AnimatedSprite2D.frame = 0# Connect animation_finished signal$AnimatedSprite2D.animation_finished.connect(_on_anim_done)
TileMap — Level Design
# TileMap node with TileSet resource# In editor: paint tiles, set collision layers# In code: read/write tilesvar tilemap = $TileMapvar cell = tilemap.get_cell_source_id(0, Vector2i(5, 3))tilemap.set_cell(0, Vector2i(5, 3), 1, Vector2i(0, 0))# Convert world position to tile coordsvar tile_pos = tilemap.local_to_map(player.position)
3D Game Development
CharacterBody3D — 3D Player Movement
extends CharacterBody3Dconst SPEED = 5.0const JUMP_VELOCITY = 4.5var gravity = ProjectSettings.get_setting("physics/3d/default_gravity")func _physics_process(delta: float) -> void: if not is_on_floor(): velocity.y -= gravity * delta if Input.is_action_just_pressed("jump") and is_on_floor(): velocity.y = JUMP_VELOCITY 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() if direction: velocity.x = direction.x * SPEED velocity.z = direction.z * SPEED else: velocity.x = move_toward(velocity.x, 0, SPEED) velocity.z = move_toward(velocity.z, 0, SPEED) move_and_slide()
Camera3D — First Person Look
extends Node3D@export var sensitivity: float = 0.003@onready var camera = $Camera3Dfunc _ready() -> void: Input.set_mouse_mode(Input.MOUSE_MODE_CAPTURED)func _unhandled_input(event: InputEvent) -> void: if event is InputEventMouseMotion: rotate_y(-event.relative.x * sensitivity) camera.rotate_x(-event.relative.y * sensitivity) camera.rotation.x = clamp(camera.rotation.x, -PI/2, PI/2)
MeshInstance3D & Materials
# Assign mesh in editor or codevar mesh_instance = $MeshInstance3D# Create material in codevar mat = StandardMaterial3D.new()mat.albedo_color = Color.REDmat.metallic = 0.5mat.roughness = 0.3mesh_instance.material_override = mat# Load texturemat.albedo_texture = preload("res://assets/texture.png")
Raycasting 3D
func shoot_ray() -> void: var space_state = get_world_3d().direct_space_state var cam = $Camera3D var from = cam.global_position var to = from + (-cam.global_transform.basis.z * 100.0) var query = PhysicsRayQueryParameters3D.create(from, to) query.exclude = [self] var result = space_state.intersect_ray(query) if result: print("Hit: ", result.collider.name) print("Point: ", result.position) print("Normal: ", result.normal)
Physics System
Collision Layers & Masks
Layer: What this object IS (what layer it occupies)
Mask: What this object DETECTS (what layers it scans)
Example:
Player → Layer 1, Mask 2 (detects enemies)
Enemy → Layer 2, Mask 1 (detects player)
Bullet → Layer 3, Mask 2 (detects enemies only)
# Set in editor (Physics → Layers) or in code:collision_layer = 1 # bit 1collision_mask = 2 # bit 2# Check group membershipif body.is_in_group("enemy"): body.take_damage(10)
RigidBody2D / RigidBody3D
extends RigidBody2Dfunc _ready() -> void: # Apply impulse (instant force) apply_impulse(Vector2(0, -500)) # Apply continuous force apply_force(Vector2(100, 0)) # Set linear velocity directly linear_velocity = Vector2(200, 0) # Connect body_entered signal body_entered.connect(_on_collision)func _on_collision(body: Node) -> void: print("Collided with: ", body.name)
Groups
# Add to group in editor (Node → Groups) or code:add_to_group("enemies")add_to_group("collectibles")# Checkif is_in_group("enemies"): take_damage(10)# Call method on all in groupget_tree().call_group("enemies", "freeze")# Get all nodes in groupvar enemies = get_tree().get_nodes_in_group("enemies")
Control Base UI node
Label Display text
Button Clickable button
TextEdit Multi-line text input
LineEdit Single-line text input
ProgressBar Health/loading bar
Slider Value slider
CheckBox Toggle checkbox
OptionButton Dropdown menu
Panel Background panel
VBoxContainer Vertical layout
HBoxContainer Horizontal layout
GridContainer Grid layout
ScrollContainer Scrollable area
Anchor presets (Inspector → Layout):
Top Left / Top Right / Bottom Left / Bottom Right
Center / Full Rect (stretches to fill parent)
# In code:
$Label.anchor_left = 0.0
$Label.anchor_right = 1.0 # stretch full width
$Label.offset_left = 10
$Label.offset_right = -10
Audio System
AudioStreamPlayer
# AudioStreamPlayer — non-positional (music, UI sounds)# AudioStreamPlayer2D — positional 2D audio# AudioStreamPlayer3D — positional 3D audio@onready var music = $AudioStreamPlayer@onready var sfx = $SFXfunc _ready() -> void: music.stream = preload("res://audio/theme.ogg") music.play()func play_jump_sound() -> void: sfx.stream = preload("res://audio/jump.wav") sfx.play()# Volume (in dB)music.volume_db = -10.0# Pitchsfx.pitch_scale = randf_range(0.9, 1.1) # random pitch variation# Stopmusic.stop()print(music.playing) # false
Audio Buses
Audio buses (Project → Audio):
Master → Music → SFX → Ambient
# Assign bus in code:
$AudioStreamPlayer.bus = "Music"
# Control bus volume:
AudioServer.set_bus_volume_db(AudioServer.get_bus_index("Music"), -5.0)
AudioServer.set_bus_mute(AudioServer.get_bus_index("SFX"), true)
Resource System & Data Management
Resources
# Resources are data containers saved as .tres or .res files# Examples: Texture2D, AudioStream, Material, TileSet, Font# Load resourcevar texture = load("res://assets/player.png")var texture = preload("res://assets/player.png") # compile-time load# Custom Resourceclass_name ItemDataextends Resource@export var item_name: String = ""@export var damage: int = 0@export var icon: Texture2D# Use it:var sword = ItemData.new()sword.item_name = "Iron Sword"sword.damage = 25ResourceSaver.save(sword, "res://data/iron_sword.tres")# Load back:var loaded = load("res://data/iron_sword.tres") as ItemData
@export — Inspector Variables
extends Node@export var speed: float = 200.0@export var health: int = 100@export var sprite: Texture2D@export var enemy_scene: PackedScene@export_range(0, 100) var volume: float = 50.0@export_enum("Idle", "Run", "Attack") var state: int = 0@export_color_no_alpha var tint: Color = Color.WHITE
Saving & Loading Game Data
# Save with ConfigFile (simple key-value)func save_game() -> void: var config = ConfigFile.new() config.set_value("player", "health", health) config.set_value("player", "position", position) config.save("user://save.cfg")func load_game() -> void: var config = ConfigFile.new() if config.load("user://save.cfg") == OK: health = config.get_value("player", "health", 100) position = config.get_value("player", "position", Vector2.ZERO)# Save with JSONfunc save_json() -> void: var data = {"health": health, "score": score} var file = FileAccess.open("user://save.json", FileAccess.WRITE) file.store_string(JSON.stringify(data))func load_json() -> void: var file = FileAccess.open("user://save.json", FileAccess.READ) if file: var data = JSON.parse_string(file.get_as_text()) health = data["health"]
Scene Management
Changing Scenes
# Change scene (unloads current, loads new)get_tree().change_scene_to_file("res://scenes/Level2.tscn")# Change scene from PackedScenevar next_level = preload("res://scenes/Level2.tscn")get_tree().change_scene_to_packed(next_level)# Reload current sceneget_tree().reload_current_scene()# Quit gameget_tree().quit()
# Annotate functions with @rpc to call them on remote peers@rpc("any_peer") # any peer can call thisfunc take_damage(amount: int) -> void: health -= amount@rpc("authority", "call_local") # only server calls, runs locally toofunc sync_position(pos: Vector2) -> void: position = pos@rpc("any_peer", "reliable") # guaranteed delivery (like TCP)func chat_message(msg: String) -> void: print(msg)# Call on specific peertake_damage.rpc_id(peer_id, 25)# Call on all peerssync_position.rpc(global_position)
MultiplayerSpawner & MultiplayerSynchronizer
# MultiplayerSpawner — auto-spawns nodes on all clients# Add MultiplayerSpawner node, set spawn_path and auto_spawn_list in editor# MultiplayerSynchronizer — syncs properties automatically# Add MultiplayerSynchronizer node, configure replication in editor# Or in code:var sync = $MultiplayerSynchronizersync.root_path = NodePath("..")# Then add properties to replicate via editor or:# ReplicationConfig resource → add properties like "position", "health"
print("value: ", health) # basic printprint_rich("[color=red]Error![/color]") # colored outputpush_warning("Low health!") # shows in debugger as warningpush_error("Critical failure!") # shows as error, doesn't stop executionassert(health > 0, "Health must be positive") # crashes in debug builds# Print only in debug buildsif OS.is_debug_build(): print("Debug info: ", position)
Debugger Panel
Bottom panel → Debugger tab:
Errors — runtime errors and warnings
Stack Trace — call stack when paused/crashed
Inspector — live node property inspection while running
Profiler — CPU time per function (enable before running)
Visual Profiler — GPU frame time breakdown
Network — RPC and sync traffic monitor
Monitors — FPS, memory, physics objects, draw calls
Breakpoints
Click the line number gutter in the script editor to set a breakpoint.
Run the game → execution pauses at the breakpoint.
Use Step Over (F10), Step Into (F11), Continue (F12) to navigate.
Inspect local variables in the Debugger → Stack Locals panel.
Performance Tips
# Use _physics_process for physics, _process for visuals only# Cache node references with @onready instead of $Node in loops@onready var player = $Player # cached once# Avoid get_node() in hot loops# Use object pooling for bullets/particles instead of queue_free + instantiate# Check draw calls: Debug → Visible Collision Shapes / Navigation# Use VisibleOnScreenNotifier2D/3D to disable off-screen processing# Profile with:var time = Time.get_ticks_usec()# ... code ...print("Took: ", Time.get_ticks_usec() - time, " µs")
# .pck = packed resource file containing all game assets# Can be distributed separately from the executable# Useful for DLC or updates# Load external .pck at runtime:ProjectSettings.load_resource_pack("res://dlc_pack.pck")
Android Export
Requirements:
- Android SDK (API 28+)
- JDK 17+
- Godot Android export templates
Editor → Export → Android:
- Set package name (e.g. com.yourname.yourgame)
- Set keystore for release builds
- Enable permissions (INTERNET, VIBRATE, etc.)
- Min SDK: 21 (Android 5.0)
C# in Godot 4
Setup
Use the Godot .NET version (not the standard build).
Download: godotengine.org → .NET version
Requires: .NET SDK 6.0 or later
IDE: VS Code (with C# extension) or JetBrains Rider
C# Script Basics
using Godot;public partial class Player : CharacterBody2D{ [Export] public float Speed = 200.0f; [Export] public int Health = 100; public override void _Ready() { GD.Print("Player ready!"); } public override void _PhysicsProcess(double delta) { var velocity = Velocity; var direction = Input.GetAxis("move_left", "move_right"); velocity.X = direction * Speed; Velocity = velocity; MoveAndSlide(); } public void TakeDamage(int amount) { Health -= amount; if (Health <= 0) QueueFree(); }}
GDScript:
+ Faster iteration, no compile step
+ Tightly integrated with Godot API
+ Best for game logic, UI, scripting
- Slower than C# for heavy computation
C#:
+ Faster execution (JIT compiled)
+ Strong typing, better IDE support
+ Familiar for Unity developers
- Requires .NET build, slower hot-reload
- Some Godot 4 features lag behind GDScript support
GDExtension (C++ / Rust / Other Languages)
What is GDExtension
GDExtension lets you write native code (C++, Rust, Swift, etc.)
that integrates with Godot as if it were built-in.
Use it for: performance-critical systems, existing C++ libraries,
custom physics, or platform-specific features.
Replaces GDNative from Godot 3.
Uses godot-cpp bindings (official C++ library).
Build with SCons, output a .gdextension file pointing to your .dll/.so.
Useful Patterns & Tips
State Machine Pattern
enum State { IDLE, RUN, JUMP, ATTACK }var current_state: State = State.IDLEfunc _physics_process(delta: float) -> void: match current_state: State.IDLE: _state_idle(delta) State.RUN: _state_run(delta) State.JUMP: _state_jump(delta) State.ATTACK: _state_attack(delta)func _state_idle(_delta: float) -> void: if Input.get_axis("move_left", "move_right") != 0: current_state = State.RUN if Input.is_action_just_pressed("jump"): current_state = State.JUMP
Object Pooling
# Reuse nodes instead of instantiate/queue_free every framevar pool: Array[Node] = []func get_from_pool() -> Node: for node in pool: if not node.visible: node.visible = true return node var new_node = bullet_scene.instantiate() add_child(new_node) pool.append(new_node) return new_nodefunc return_to_pool(node: Node) -> void: node.visible = false
Event Bus (Decoupled Signals)
# EventBus.gd — autoload singletonextends Nodesignal enemy_died(enemy_id: int)signal score_updated(new_score: int)signal level_completed# Any script can emit:EventBus.enemy_died.emit(id)# Any script can listen:EventBus.enemy_died.connect(_on_enemy_died)
@tool Scripts (Editor Scripts)
@toolextends Node2D@export var tile_count: int = 10 : set = _set_tile_countfunc _set_tile_count(value: int) -> void: tile_count = value _rebuild() # runs in editor when you change the valuefunc _rebuild() -> void: # Generate tiles procedurally in the editor for child in get_children(): child.queue_free() for i in tile_count: var tile = preload("res://scenes/Tile.tscn").instantiate() tile.position.x = i * 64 add_child(tile)
Coroutines with await
# await pauses execution until a signal fires or a time passesfunc show_message(text: String) -> void: $Label.text = text $Label.visible = true await get_tree().create_timer(2.0).timeout $Label.visible = false# await animation finish$AnimationPlayer.play("attack")await $AnimationPlayer.animation_finished$AnimationPlayer.play("idle")# await signalawait player_diedget_tree().reload_current_scene()
Navigation & Pathfinding
# 2D: NavigationRegion2D + NavigationAgent2Dextends CharacterBody2D@onready var nav_agent = $NavigationAgent2Dfunc move_to(target_pos: Vector2) -> void: nav_agent.target_position = target_posfunc _physics_process(delta: float) -> void: if nav_agent.is_navigation_finished(): return var next = nav_agent.get_next_path_position() var dir = (next - global_position).normalized() velocity = dir * speed move_and_slide()# 3D: NavigationRegion3D + NavigationAgent3D (same pattern)
More Learn
Explore the following links for valuable resources, communities, and tools to enhance your skills : -