A 2D platformer / parkour game built from scratch with Python and Pygame, featuring responsive movement, double jumping, dashing, hazards, collectibles, animated environments, persistent progression, and a modular game architecture.
The objective is simple:
Navigate each level, survive the obstacles, collect items, and reach the exit.
Behind the gameplay, EscapeGame is also an exploration of practical game-engine concepts such as collision broad-phase optimization, entity management, camera systems, state-driven menus, persistent configuration, animation, and executable packaging.
EscapeGame is a custom 2D platformer implemented without a full game engine.
The project handles its own:
- Player physics and movement
- Collision detection
- Spatial partitioning
- Level parsing
- Entity lifecycle
- Camera tracking
- Animation states
- Audio management
- Menu navigation
- Player progression
- Runtime configuration
- HUD rendering
- Game-over / victory states
- Standalone application builds
The game currently contains 2 playable levels stored as text-based map definitions.
Your goal is to make your way through each level and reach the exit while dealing with environmental hazards and enemy abilities.
Movement
Move horizontally through the level while the camera follows the player.
Jump
Perform a standard jump from the ground.
Double Jump
Jump once more while airborne to recover from mistakes or reach higher platforms.
Dash
Perform a high-speed horizontal dash.
Dash uses a cooldown system and is displayed directly on the HUD.
Health
The player has a health system and can take damage from hazards.
Falling outside the playable world or losing all HP results in death.
| Key | Action |
|---|---|
← |
Move left |
→ |
Move right |
↑ |
Jump / Double Jump |
G |
Dash |
Esc |
Return to previous menu |
Development builds also contain internal debug controls for testing status effects.
Coin
Collectible currency scattered throughout the levels.
Diamond
A separate collectible tracked by the gameplay HUD.
Heart
Restores a randomized amount of HP, up to the player's maximum health.
GrassCutter
A moving/spinning environmental hazard that damages the player on contact.
SnowEater
A passive enemy with an area-based ability. Getting too close allows it to temporarily freeze the player, preventing movement.
Moving Platforms
Dynamic platforms that participate in the game's collision system and level traversal.
The player controller implements:
- Horizontal movement
- Gravity
- Ground detection
- Jumping
- Air jumping
- Dash movement
- Dash cooldowns
- Direction tracking
- Character animation states
- Collision resolution
- HP and death state
- Collectible interaction
- Enemy interaction
- Win-condition detection
Movement parameters are centralized in the runtime configuration, making gameplay behavior easy to tune.
Instead of testing the player against every object in the level every frame, EscapeGame implements a spatial hash grid.
The world is divided into spatial cells, allowing the collision system to retrieve only nearby collision candidates.
Conceptually:
World
├── Cell (0, 0)
│ ├── Wall
│ └── Platform
│
├── Cell (1, 0)
│ └── Wall
│
└── Cell (2, 0)
├── Hazard
└── Platform
The collision layer supports both:
Static geometry
+
Dynamic obstacles
↓
CollisionSpace
↓
Nearby collision candidates
↓
Player collision resolution
This keeps the architecture scalable as map complexity increases.
Levels are stored under:
assets/levels/
Current maps:
level_1.txt
level_2.txt
MapManager parses these text maps and converts symbols into game objects such as:
Tiles
Walls
Terrain
Player spawn
Exit
Coins
Diamonds
Hearts
Enemies
Moving platforms
This separates level design from gameplay logic, allowing maps to be modified without rewriting the game loop.
Game objects are separated into dedicated entity modules.
src/entities/
├── entity.py
├── player.py
├── coin.py
├── diamond.py
├── heart.py
├── grass_cutter.py
├── snow_eater.py
└── moving_platform.py
Each entity is responsible for its own behavior while shared functionality is handled through the base entity architecture.
The game contains a reusable effect system.
Currently implemented:
FreezeEffect
BlindEffect
For example, SnowEater can trigger FreezeEffect, temporarily preventing player input.
Effect logic is isolated under:
src/effect/
which makes additional status effects easier to introduce.
The game uses a dedicated camera module rather than coupling world coordinates directly to screen coordinates.
The camera tracks the player's world position and converts game-world coordinates into screen-space coordinates during rendering.
This allows maps to extend beyond the visible 1280 × 720 viewport.
Game screens are managed by a reusable MenuManager.
Current states include:
MENU
├── PLAYING
│ ├── DIE
│ └── WIN
│
├── SETTINGS
│
└── HELP
├── ITEMS
└── MOBS
This keeps state transitions separate from the main game loop.
The gameplay HUD displays important runtime information including:
- Player HP
- Dash cooldown
- Coin count
- Diamond count
- FPS
The dash meter visually communicates when the player's dash becomes available again.
EscapeGame includes a configuration manager for saving runtime settings and player progress.
Configuration values include areas such as:
Display
World
Player
Dash
Entities
Audio
Progress
The default game configuration is defined centrally in:
src/config.py
while persistent player data is stored under:
assets/player/
The game contains separate audio handling for music and sound effects.
Examples include:
Menu music
Level music
Jump sound
Dash sound
Coin collection sound
Audio volume can be controlled through the game configuration/settings system.
EscapeGame/
│
├── assets/
│ ├── entities/
│ ├── images/
│ ├── levels/
│ │ ├── level_1.txt
│ │ └── level_2.txt
│ ├── player/
│ ├── skills/
│ ├── sound/
│ └── texture/
│
├── src/
│ ├── effect/
│ │ ├── blind_effect.py
│ │ └── freeze_effect.py
│ │
│ ├── entities/
│ │ ├── coin.py
│ │ ├── diamond.py
│ │ ├── entity.py
│ │ ├── grass_cutter.py
│ │ ├── heart.py
│ │ ├── moving_platform.py
│ │ ├── player.py
│ │ └── snow_eater.py
│ │
│ ├── menu/
│ │ ├── encyclopedia_menu.py
│ │ ├── game_over_menu.py
│ │ ├── help_menu.py
│ │ ├── main_menu.py
│ │ ├── menu_manager.py
│ │ ├── play_menu.py
│ │ ├── settings_menu.py
│ │ └── win_menu.py
│ │
│ ├── world/
│ │ ├── camera.py
│ │ ├── map_manager.py
│ │ └── spatial_hash_grid.py
│ │
│ ├── audio.py
│ ├── config.py
│ ├── game_types.py
│ ├── hash_table.py
│ ├── texture_loader.py
│ └── utils.py
│
├── main.py
├── Makefile
├── build_windows.bat
├── requirements.txt
└── README.md
| Technology | Purpose |
|---|---|
| Python | Core game logic |
| Pygame | Rendering, input, audio and game loop |
| NumPy | Numerical utilities |
| PyTMX | TMX-related map tooling dependency |
| PyInstaller | Standalone executable packaging |
| Pytest | Testing infrastructure |
| Make | Development/build automation |
git clone https://github.com/RyouLegacy/EscapeGame.git
cd EscapeGameThe Makefile automatically creates an isolated virtual environment and installs the required dependencies.
make runThat will:
Create ./venv if necessary
↓
Install requirements.txt
↓
Validate the environment
↓
Launch main.py
If you prefer not to use make:
python -m venv venvWindows:
venv\Scripts\activateLinux/macOS:
source venv/bin/activatepip install -r requirements.txtpython main.pyThe project currently uses:
pygame >= 2.5.2
numpy >= 1.26.0
pytmx >= 3.32
pytest >= 8.0.0
pyinstaller >= 6.4.0
make buildThe Makefile uses PyInstaller and packages the game into:
dist/
Windows users can also run:
build_windows.batThe script:
Checks Python
↓
Uses the virtual environment when available
↓
Updates PyInstaller
↓
Removes previous build output
↓
Builds EscapeGame.exe
↓
Copies game assets
↓
Outputs the distributable build
Result:
dist/
├── EscapeGame.exe
└── assets/
make helpShow available development commands.
make runCreate the environment if necessary, install dependencies, and start the game.
make buildPackage the game using PyInstaller.
make cleanRemove generated environments, build artifacts, caches, compiled Python files, and PyInstaller output.
At a high level:
main.py
│
▼
MenuManager
│
├───────────────┐
▼ ▼
Main Menu PlayMenu
│
┌────────┼────────┐
▼ ▼ ▼
Player MapManager Camera
│ │
│ ├── Level Parser
│ ├── Tiles
│ ├── Entities
│ └── Collision Grid
│
├── Movement
├── Physics
├── Dash
├── Effects
├── Items
└── Hazards
The main loop remains relatively small because gameplay responsibilities are delegated to dedicated components.
Text-based maps make level layouts:
- Easy to inspect
- Easy to modify
- Version-control friendly
- Independent from Python gameplay code
Naive collision detection can approach:
Player × every collider
for every update.
Spatial hashing reduces the candidate set to objects occupying nearby world cells before precise rectangle collision checks are performed.
Keeping menu screens isolated avoids turning main.py into a large conditional state machine and makes additional screens easier to add.
Physics values such as:
PLAYER_SPEED
PLAYER_JUMP_POWER
DASH_SPEED
DASH_COOLDOWN_MS
GRAVITY
can be tuned without modifying the underlying movement algorithms.
The second jump is treated as an air jump and becomes available again after the player lands.
Dash behavior depends on:
DASH_SPEED
DASH_DURATION_MS
DASH_COOLDOWN_MS
The HUD shows dash recharge progress so the player can see when the ability becomes available.
Potential extensions to the project include:
More handcrafted levels
Level-selection screen
Checkpoint system
Additional enemies
Additional player abilities
Boss encounters
Controller support
Save slots
Improved automated testing
External level editor
Particle system
Performance profiling
CI build pipeline
Release packages
EscapeGame is not only a gameplay project. It demonstrates several software-engineering concepts applied to game development:
Object-oriented game architecture
State management
Real-time update loops
Collision broad-phase optimization
Custom spatial data structures
2D physics
Camera transforms
Persistent configuration
Asset management
Entity lifecycle management
Modular UI architecture
Executable packaging
Contributions and experimentation are welcome.
A typical workflow:
git clone https://github.com/RyouLegacy/EscapeGame.git
cd EscapeGame
git checkout -b feature/my-feature
# Make your changes
git add .
git commit -m "Add my feature"
git push origin feature/my-featureThen open a pull request describing:
What changed
Why it changed
How it was tested
Any gameplay/configuration impact
RyouLegacy
GitHub: @RyouLegacy
No license is currently specified in this repository.
Unless a license is added, the source code should not be assumed to grant permission for redistribution, modification, or commercial use.
Built with Python, Pygame, and a lot of platforming experiments.
If you find the project interesting, consider leaving a ⭐ on the repository.