An example of building a game with ECS + Event Bus + Responsive UI in Pygame.
This project serves as an educational reference showing how to structure a small game professionally using Python and Pygame. It demonstrates clean architecture, responsive UI, scenes, ECS, animations, event-driven logic, and asset pipelines.
๐ฌ Gameplay demo:
Open the dist/ folder and choose the appropriate executable for your system. Choose it if available, otherwise download the source code and follow the instructions down below. For Windows extract the .zip file and put the .exe anywhere you like. Then run it. For linux users - you know the drill. For macOS users - sorry, I don't have a Mac to test on. (# TODO: add macOS instructions).
- Lightweight ECS (EntityโComponentโSystem)
- Responsive UI with virtual resolution & letterboxing
- Scene management with transitions and lifecycle hooks
- Event Bus for decoupled communication between systems
- Asset Loader with progress display
- Polished UI animations (button press, shadows, highlight effects)
- Config system using pydantic / pydantic-settings
- Logging, error handling, and a clean, scalable file structure
It is a blueprint for beginners and intermediate developers who want to learn solid architecture on a small, understandable game.
GuessNumberPygame/
โโโ main.py # Entry point: creates GameApp and starts the game loop
โ
โโโ build.py # Python helper script for building executables
โโโ build.spec # PyInstaller spec for cross-platform builds
โ
โโโ app/
โ โโโ __init__.py # Exports application module
โ โโโ application.py # GameApp class: main loop, window, responsive scaling
โ
โโโ engine/ # Core ECS / engine layer (framework, not game-specific)
โ โโโ __init__.py # Engine exports
โ โโโ ecs.py # GameObject (entity) and component storage
โ โโโ base_scene.py # BaseScene class with lifecycle and fade handling
โ โโโ scene_manager.py # SceneManager: switching scenes safely
โ โโโ service_locator.py # ServiceLocator for global services (app, sound, etc.)
โ โโโ event_bus.py # EventBus: pub/sub for decoupled communication
โ โโโ asset_loader.py # AssetLoader: staged loading with progress
โ โโโ ui_builder.py # UIBuilder: factories for buttons, labels, image buttons
โ โโโ components/ # ECS components
โ โ โโโ __init__.py
โ โ โโโ alpha.py # AlphaComponent: fade in/out & transparency animation
โ โ โโโ button.py # ButtonComponent: text, pressed state, hover, shortcuts
โ โ โโโ headers.py # H1/H2/H3 components for semantic text styling
โ โ โโโ image.py # ImageComponent: displays images (icons, sprites)
โ โ โโโ input.py # InputFieldComponent: text input fields
โ โ โโโ label.py # LabelComponent: simple text labels
โ โ โโโ position.py # Position: x/y coordinates in virtual space
โ โ โโโ progress_bar.py # ProgressBarComponent: loading bar for boot scene
โ โ โโโ sound.py # SoundComponent: describes sounds to be played
โ โโโ systems/ # Systems: logic that operates on components
โ โโโ __init__.py
โ โโโ render.py # RenderSystem: draws UI, buttons, text, images, animations
โ โโโ input.py # InputSystem: mouse, keyboard, focus, button press logic
โ โโโ sound.py # SoundSystem: loads & plays sound effects
โ
โโโ game/ # Game-specific logic / scenes using the engine
โ โโโ __init__.py
โ โโโ logic.py # GameLogic: number generation, guess checking, statuses
โ โโโ scenes/
โ โโโ __init__.py
โ โโโ boot.py # BootScene: staged asset loading with progress bar
โ โโโ menu.py # MenuScene: start game, toggle sound, etc.
โ โโโ game.py # GameScene: main gameplay (input, hints, attempts)
โ โโโ win.py # WinScene: win screen & attempts summary
โ โโโ dialog.py # DialogScene: modal overlays / messages
โ โโโ results_modal.py # Results modal: highscore summary
โ
โโโ assets/ # All runtime assets used by the game
โ โโโ fonts/ # Custom fonts
โ โ โโโ *.ttf
โ โโโ images/ # Icons & UI images
โ โ โโโ mute.png
โ โ โโโ volume.png
โ โโโ sounds/ # Sound effects
โ โ โโโ button-click.mp3
โ โ โโโ keyboard-click.mp3
โ โ โโโ soft-treble-win-fade-out.mp3
โ โโโ icon.png # Window / application icon
โ
โโโ config/ # Configuration system (pydantic-based)
โ โโโ __init__.py
โ โโโ base.py # Base models and shared config pieces
โ โโโ game_config.py # GameConfig: sizes, colors, FPS, UI constants
โ โโโ logging.py # Logging setup used by logger/
โ โโโ settings.py # Settings: env integration (.env, GAME_* vars)
โ โโโ ui.py # UI-specific config (padding, radii, colors)
โ
โโโ stats/ # Simple game statistics / persistence layer
โ โโโ __init__.py
โ โโโ models.py # Stats data models
โ โโโ manager.py # Stats management logic
โ โโโ storage.py # Stats persistence layer
โ
โโโ utils/ # Small helpers not tied to ECS
โ โโโ __init__.py
โ โโโ responsive.py # ResponsiveScaleManager: virtual surface + scaling
โ
โโโ logger/ # Logging convenience wrapper
โ โโโ __init__.py # get_logger(), setup_logging()
โ โโโ colored_formatter.py # Colored log formatting
โ
โโโ tests/ # Tests
โ
โโโ docs/ # Additional documentation & guides
โ โโโ UV.md # UV usage & command cheatsheet
โ โโโ ADVANCED_ARCHITECTURE_GUIDE.md # Deep-dive engine/architecture internals
โ โโโ TESTS.md # Testing guide and test structure
โ โโโ demo.mp4 # Gameplay demo video
โ
โโโ .env.example # Example environment configuration
โโโ pyproject.toml # Project dependencies and metadata (for uv/pip)
โโโ uv.lock # Resolved dependency lockfile
โโโ README.md # **THIS FILE**
The project is intentionally structured like a mini real-world game codebase:
engine/โ reusable framework: ECS, scenes, event bus, asset loader, UI buildergame/โ game rules: number guessing, scenes for menu/game/winapp/โ application shell: Pygame window, virtual surface, main loopconfig/โ configuration via pydantic, environment overridesassets/โ all runtime assets in one place
For a deeper, maintainer-level description of internals (ECS details, scaling model, animation behavior, performance notes), see:
๐ docs/ADVANCED_ARCHITECTURE_GUIDE.md
Also see the testing guide for details on testing.
Entities โ lightweight containers (GameObject)
Components โ pure data (Position, Label, Button, Input, Image, Alpha, etc.)
Systems โ pure logic operating on entities that have the required components.
Example: creating a UI button entity via UIBuilder:
def button_entity(self, text: str, x: int, y: int, onclick, keyboard_shortcut: Optional[str] = None):
e = GameObject()
btn = ButtonComponent(text, keyboard_shortcut)
btn.on_click = onclick
e.add(Position(x, y)).add(btn)
return eThe EventBus provides a simple pub/sub mechanism so systems and scenes can react to events without direct references to each other.
from engine.event_bus import event_bus
def on_play(name: str):
sound_system.play(name)
event_bus.subscribe("sound:play", on_play)
event_bus.emit("sound:play", "click")Used for:
- UI button events
- Scene transitions
- Sound triggering
- Input notifications
The game renders into a fixed 640ร400 virtual surface, then:
- Scales uniformly to fit the window
- Adds letterboxing (black borders) if the aspect ratio doesnโt match
- Converts mouse coordinates from screen โ virtual space, so the logic works in a consistent coordinate system
Implemented in utils/responsive.py.
Buttons and UI elements are designed to feel tactile:
- Shadows and depth
- Press animation (button โsinksโ a few pixels on click)
- Hover / active states
- Gradient highlights
Assets are loaded gradually in the Boot Scene with progress feedback:
- Fonts
- Images
- Sounds
- Services
AssetLoader executes tasks over multiple frames to avoid freezing the UI:
def execute_next_task(self, dt: float):
if self.current_task_index < len(self.tasks):
self.current_task_frame_counter += 1
if self.current_task_frame_counter >= self.frames_per_task:
task = self.tasks[self.current_task_index]
self.description = task.description
task.execute()
self.current_task_index += 1
self.progress = self.current_task_index / len(self.tasks)
self.current_task_frame_counter = 0
else:
self.completed = TrueThe boot scene displays a loading bar driven by progress.
A simple but polished number-guessing game:
- Guess a random number in a configurable range (e.g.
1โNor-N to +Ndepending on difficulty choice) - Type your guess in an input field and submit
- The game tells you if your guess is too low / too high
- Counts attempts
- Shows a win screen and basic stats
- Maintains high scores / stats via the
stats/module - Navigation flow: Menu โ Game โ Win (with reset and back buttons)
The game supports multiple difficulty levels with different number ranges:
- Easy: 1-10
- Medium: 1-100
- Hard: 1-1000
- Very Hard: 1-10000
- Extreme: 1-100000
- Impossible: -100000 to 100000 (supports negative numbers!)
The game fully supports negative numbers in difficulty ranges:
- Games with negative ranges allow users to enter negative guesses (e.g.,
-50) - When typing negative numbers, there's no error shown for the minus sign (
-) during input - If a game doesn't allow negative numbers, typing
-will show "Number should be positive" - The UI validation is context-aware based on the current game's range
Controls:
- Mouse for buttons and input focus
- Keyboard for typing numbers
- Optional hotkeys (e.g. ESC โ back to menu)
- Python 3.13+
- uv (optional, recommended)
Copy .env.example to .env and fill in the required values.
uv syncTo also install build / test / dev extras, see:
๐ docs/UV.md
pip install -r requirements.txtuv run main.pyor using venv:
python main.pyVia UV:
uv sync --extra build
uv run build.pyVia pip and venv:
pip install -r requirements.txt
python build.pyOr build with the spec file using PyInstaller:
python -m PyInstaller build.specThe executable bundle will be generated into the dist/ directory.
More details:
๐ docs/UV.md
๐ docs/TESTS.md
๐ docs/ADVANCED_ARCHITECTURE_GUIDE.md
