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.
Node2D Base for all 2D nodes (has position, rotation, scale)
Sprite2D Displays a texture in 2D
AnimatedSprite2D Sprite with frame animation
CollisionShape2D Defines collision area shape
Area2D Detects overlaps (no physics response)
CharacterBody2D Kinematic body for player/enemies
RigidBody2D Physics-simulated body
StaticBody2D Immovable physics body (walls, floors)
Camera2D 2D 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)
# if / elif / elseif health > 50: print("Healthy")elif health > 0: print("Hurt")else: print("Dead")# match (like switch)match state: "idle": play_animation("idle") "run": play_animation("run") "jump": play_animation("jump") _: play_animation("idle") # default# for loopfor i in range(5): print(i) # 0 1 2 3 4for item in items: print(item)# while loopwhile health > 0: health -= 10
Built-in Types
# Vector2 / Vector3var pos = Vector2(100, 200)var dir = Vector3(0, 1, 0)pos += Vector2(10, 0)print(pos.length()) # magnitudeprint(pos.normalized()) # unit vectorprint(Vector2.UP) # (0, -1)# Colorvar red = Color(1, 0, 0, 1) # RGBA 0-1var blue = Color.BLUE# Rect2var rect = Rect2(Vector2(0,0), Vector2(100, 50))print(rect.has_point(Vector2(50, 25))) # true# Transform2D / Transform3Dvar t = Transform2D()t = t.translated(Vector2(10, 0))
GDScript — OOP & Classes
Classes & extends
# Every script implicitly extends a Node typeextends CharacterBody2Dclass_name Player # registers as a global classvar health: int = 100var speed: float = 200.0func _ready() -> void: print("Player ready!")func take_damage(amount: int) -> void: health -= amount if health <= 0: die()func die() -> void: queue_free() # removes node from scene
Inner Classes
class_name Inventoryclass Item: var name: String var value: int func _init(n: String, v: int) -> void: name = n value = vvar items: Array[Item] = []func add_item(n: String, v: int) -> void: items.append(Item.new(n, v))
Inheritance
# Base classclass_name Enemyextends CharacterBody2Dvar health: int = 50func take_damage(amount: int) -> void: health -= amount# Derived classclass_name Bossextends Enemyvar phase: int = 1func take_damage(amount: int) -> void: super.take_damage(amount / 2) # call parent method if health < 25: phase = 2
Signals (Observer Pattern)
# Define signalsignal health_changed(new_health: int)signal player_died# Emit signalfunc take_damage(amount: int) -> void: health -= amount health_changed.emit(health) if health <= 0: player_died.emit()# Connect in codefunc _ready() -> void: health_changed.connect(_on_health_changed) player_died.connect(_on_player_died)func _on_health_changed(new_health: int) -> void: $HealthBar.value = new_healthfunc _on_player_died() -> void: get_tree().reload_current_scene()# Connect in editor: Node panel → Signals tab → connect to method
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()