Lesson 2: GDScript Fundamentals and Project Architecture
In Lesson 1 you defined your core game concept and created a basic Godot 4 project with a main scene and a placeholder player. In this lesson you will learn the GDScript fundamentals you actually need for this course and shape your project into a clear, modular architecture.
The goal is not to memorize the entire language; it is to become comfortable enough with GDScript and scene structure that you can add new systems without rewriting everything later.
Step 1 β GDScript Basics You Will Use Constantly
GDScript is a lightweight, Python-like language tightly integrated with Godot. For most 2D action games, you will repeatedly use a small set of features:
- Variables and types
- Functions and return values
- Signals and callbacks
- Nodes and the scene tree
Variables and types
GDScript supports both dynamic and typed variables. For clarity and safety in a full project, use typed variables where it helps:
var health: int = 100
var speed: float = 200.0
var is_dead := false # type inferred as bool
You can declare constants for values that should not change:
const MAX_HEALTH := 100
const GRAVITY := 980.0
Functions and lifecycle methods
Every script can define custom functions and use Godotβs lifecycle callbacks:
func _ready() -> void:
# Called when the node enters the scene tree
initialize_player()
func _process(delta: float) -> void:
# Called every frame
update_ui(delta)
func _physics_process(delta: float) -> void:
# Called at fixed intervals, ideal for movement and physics
apply_movement(delta)
You will use _ready for one-time setup and _physics_process for movement and physics-heavy logic.
Step 2 β Understanding the Scene Tree and Node Composition
Godotβs power comes from the scene tree: every object in your game is a Node, and complex objects are made by composing smaller nodes.
Typical patterns:
- A
Playerscene might contain:CharacterBody2DrootSprite2Dfor visualsCollisionShape2Dfor physics- Optional
AudioStreamPlayer2Dfor sounds
Instead of one script doing everything in a giant file, you can attach small scripts to specific nodes where it makes sense.
Decide what lives where
- Character logic (movement, health, attacks) β Script on the root
CharacterBody2D - UI logic (updating health bars, showing prompts) β Scripts on UI scenes
- Game flow (starting levels, pausing, loading scenes) β Scripts on a
GameorGameManagernode
Write down a quick mapping in your notes:
- Which node controls game flow?
- Which node owns player state?
- Which nodes handle level transitions?
This mental map prevents you from sprinkling logic randomly in many scripts.
Step 3 β Create a GameManager Autoload
To coordinate your game across scenes, create a simple GameManager script and add it as an autoload (singleton).
- Create
scripts/autoload/game_manager.gd:
extends Node
var current_level_name: String = ""
var player_max_health: int = 100
var player_current_health: int = 100
func _ready() -> void:
print("GameManager ready")
func reset_player_state() -> void:
player_current_health = player_max_health
- Add it as an autoload:
- Go to Project β Project Settings β Autoload
- Add
game_manager.gdwith the nameGameManager - Click Add
Now GameManager is available globally:
GameManager.player_current_health -= 10
Use this for cross-scene state (current level, game difficulty, global flags), not for every tiny detail.
Step 4 β Organize Scripts by Role
To keep the project understandable, mirror your scenes with your scripts:
scripts/player/β Movement, combat, animation controllersscripts/enemies/β AI, behavior, state machinesscripts/ui/β Menus, HUD, overlaysscripts/levels/β Level-specific triggers and logicscripts/autoload/β Singletons likeGameManager
Adjust your folder structure if needed:
scripts/
player/
player_controller.gd
enemies/
ui/
levels/
autoload/
game_manager.gd
This way, when you come back after a break, you know exactly where to look for logic.
Step 5 β Refine the Player Script with Clear Responsibilities
In Lesson 1 you added a basic movement script. Now rewrite it slightly to keep responsibilities clear and typed.
scripts/player/player_controller.gd:
extends CharacterBody2D
const SPEED := 200.0
var input_direction: Vector2 = Vector2.ZERO
func _ready() -> void:
# Any one-time initialization for the player
GameManager.reset_player_state()
func _physics_process(delta: float) -> void:
read_input()
move_player(delta)
func read_input() -> void:
input_direction = Vector2.ZERO
input_direction.x = Input.get_action_strength("ui_right") - Input.get_action_strength("ui_left")
input_direction.y = Input.get_action_strength("ui_down") - Input.get_action_strength("ui_up")
input_direction = input_direction.normalized()
func move_player(delta: float) -> void:
velocity = input_direction * SPEED
move_and_slide()
This small separation makes it easier to add things like dash, knockback, or status effects later.
Step 6 β Scene and Script Loading Patterns
You will often want to:
- Load a level scene by name
- Restart the current level
- Switch between menus and gameplay
Add simple helper methods to GameManager:
func go_to_level(level_name: String) -> void:
current_level_name = level_name
var error := get_tree().change_scene_to_file("res://levels/%s.tscn" % level_name)
if error != OK:
push_error("Failed to load level: %s" % level_name)
func restart_level() -> void:
if current_level_name == "":
return
go_to_level(current_level_name)
From UI or game flow scripts you can now do:
GameManager.go_to_level("level_01")
This centralizes level loading instead of scattering change_scene_to_file calls everywhere.
Step 7 β Mini Architecture Checklist
Before you move on to art and assets in Lesson 3, check that your architecture feels clear:
- You have a GameManager autoload that stores cross-scene state.
- Player logic lives in a dedicated
player_controller.gdscript. - Scripts are stored in logical folders (
player,ui,levels,autoload). - You know which node is responsible for:
- Game flow and scene changes
- Player state
- UI updates
If you cannot answer βwhere should this code live?β for a feature, note that down and refine your mental model before adding more systems.
Common Mistakes to Avoid in Lesson 2
- Putting all game logic in one giant script. This makes refactoring painful and limits reuse.
- Using autoloads for everything. Global singletons are helpful, but overuse leads to hidden dependencies.
- Mixing UI and gameplay logic. Keep UI scripts focused on displaying state, not simulating the game.
Mini Challenge
By the end of this lesson:
GameManageris configured as an autoload and can track the current level.- Your player script is refactored into a clear
player_controller.gdwith typed fields and separate functions for input and movement. - Your scripts are reorganized into role-based folders under
scripts/.
If all of this is in place, you will be ready for Lesson 3, where you will define an art pipeline and organize assets so they flow smoothly into your Godot 4 scenes.