A comprehensive 2D game engine built entirely from scratch using only Python's standard library. No external dependencies like pygame, OpenGL, or any third-party graphics libraries. This engine demonstrates how to create a complete game development framework using pure Python and tkinter for cross-platform windowing.
This project proves that you can build sophisticated game engines without relying on external libraries. Every component - from vector mathematics to input handling to graphics rendering - is implemented from the ground up using only Python's built-in modules.
- Pure Python Implementation: Zero external dependencies beyond Python standard library
- Cross-Platform: Uses tkinter for universal compatibility across Windows, macOS, and Linux
- Game Engine Architecture: Professional game engine design patterns and structure
- Bounded Variable-Timestep Loop: Responsive updates with smoothed, stall-safe delta time
- Built-in Debugging Tools: Stats overlay, collider visualization, log control, and scene inspection on every engine
- 2D/3D Hybrid Support: Optional 3D mathematics with 2D rendering capabilities
- Scene Management: Organize game objects into scenes with deterministic lifecycle callbacks
- Named Scene Registry & Stacking: Register scenes by name and stack overlays for pause menus and dialogs
- Persistent Objects: Carry selected objects safely across scene replacements
- GameObject Architecture: Component-based game objects with transform hierarchy
- Component System: Modular components for extending game object functionality
- Vector2: Comprehensive 2D vector implementation with all standard operations
- Vector3: Full 3D vector mathematics with cross product, magnitude, and transformations
- Transform System: 2D/3D position, rotation, and scale with parent-child relationships
- Quaternion Support: 3D rotation support with quaternion mathematics (optional 3D mode)
- Advanced Math: Dot product, cross product, interpolation, and coordinate transformations
- Collision Geometry: Point, circle, and axis-aligned rectangle intersection queries
- Custom 2D Renderer: Built on tkinter Canvas with advanced drawing capabilities
- Shape Rendering: Rectangles, circles, triangles, and custom polygons
- Transform Support: Full rotation, scaling, and translation for all shapes
- Color Management: RGB color support with outline and fill options
- Z-Ordering: Proper layering system for depth sorting
- Keyboard Input: Complete keyboard state management with key press detection
- Mouse Input: Mouse position, button states, and click detection
- Input Utilities: Convenience methods for common input patterns (WASD, arrows)
- Event-Driven: Proper event handling with frame-accurate input detection
- Procedural Sound Generation: Real waveform sample generation — sine, square, sawtooth, triangle, and noise, using only the standard library
- Sound Effects: Built-in generators for bullets, explosions, and engine sounds
- Real Playback (Optional): Install the
audioextra (pip install pure-python-game-engine[audio]) for actual real-time mixed audio output; the core engine still requires nothing beyond it - Terminal-Bell Fallback: Without the extra, or without a usable audio device, distinct sounds are approximated by rhythm and timing instead of real playback — see
docs/AUDIO.mdfor why and what that means in practice
- Central Image Cache: Canonical path resolution and identity reuse
- Tkinter Image Rendering: PNG, GIF, PGM, and PPM support without dependencies
- Sprite Atlases: Named regions with lazy frame extraction and caching
- Reusable Animation Clips: Shared clips with independent playback state
- Playback Controls: Play, pause, resume, stop, loop, and completion callbacks
.
├── engine/ # Game engine package
│ ├── assets/ # Image caching, atlases, and animation clips
│ ├── audio/ # Procedural sound generation
│ ├── collision/ # Collider components and overlap detection
│ ├── core/ # Main loop, window, and logging
│ ├── debug/ # Stats overlay, collider visualization, scene inspection
│ ├── ecs/ # Experimental Entity Component System
│ ├── graphics/ # Canvas renderer and sprites
│ ├── input/ # Keyboard, mouse, and input profiles
│ ├── math/ # Vectors, transforms, and quaternions
│ └── scene/ # Scenes, GameObjects, components, and the SceneManager
├── examples/
│ ├── games/ # Complete playable games
│ └── demos/ # Focused engine feature demonstrations
├── tests/ # Headless standard-library test suite
├── docs/ # Developer guide, subsystem references, and project roadmap
├── README.md
└── LICENSE
No installation required! This engine uses only Python's standard library.
Requirements:
- Python 3.10 or higher (currently tested with Python 3.14)
- tkinter (included with most Python installations)
Optional: pip install pure-python-game-engine[audio] adds real audio playback (see Audio System below). Nothing else in the engine ever requires it.
The engine uses a bounded variable timestep. target_fps controls frame-rate limiting, elapsed time is measured with time.perf_counter(), and unusually long frames are capped at 0.1 seconds by default before delta smoothing. Games can customize the cap with GameEngine(..., max_delta_time=...).
The headless test suite uses Python's standard-library unittest framework and does not open a game window:
python -m unittest discover -s tests -vNew here? Start with docs/GAME_DEVELOPER_GUIDE.md. It's a single, self-contained walkthrough that builds a complete small game step by step, in the order you'd actually build one — from opening a window through movement, collisions, sound, and multiple scenes. The reference games under examples/games/ are excellent once you know the basics, but they're advanced reference material, not a tutorial; the developer guide is the intended starting point.
from engine import GameEngine, GameObject, Vector2, Sprite, SoundGenerator
class MyGame(GameEngine):
def initialize(self):
# Initialize sound system
self.sound_generator = SoundGenerator()
self.sound_generator.initialize_default_sounds()
# Create a game object
player = GameObject("Player")
player.transform.position = Vector2(400, 300)
# Add a sprite component
sprite = Sprite(color='#0096FF', size=Vector2(50, 50))
player.add_component(sprite)
# Add to scene
self.current_scene.add_object(player)
def update(self, delta_time):
# Game logic here
if self.input_manager.is_key_pressed('space'):
self.sound_generator.play_sound("bullet")
print("Space pressed!")
# Run the game
game = MyGame("My 2D Game", (800, 600))
game.run()Run examples as modules from the repository root so Python can locate the sibling engine package.
python -m examples.games.asteroids_game
python -m examples.games.breakout_game
python -m examples.games.centipede_game
python -m examples.games.lane_crosser
python -m examples.games.space_shooter
python -m examples.games.ui_gamepython -m examples.demos.basic_game
python -m examples.demos.atlas
python -m examples.demos.ecs
python -m examples.demos.input_profiles
python -m examples.demos.logging
python -m examples.demos.scene_management
python -m examples.demos.debug_tools- Left/Right or A/D: Rotate ship
- Up or W: Thrust
- Space or Ctrl: Shoot
- ESC: Quit game
The examples demonstrate:
- Player movement with keyboard input
- Rotating enemies and physics simulation
- Real-time FPS display
- Component-based architecture
- Transform hierarchies
- Scene-managed collision events in Breakout
- Procedural audio generation
- Complete game state management
Once your game is ready to share, package it into a standalone executable that runs on a machine with no Python installed — see docs/PACKAGING.md for a step-by-step guide covering PyInstaller, bundling real image/audio assets, and the platform-specific gotchas that break silently if skipped.
A frogger-style lane-dodging game built entirely on the engine's Scene/GameObject/Component system.
python -m examples.games.lane_crosserThe Scene → GameObject → Component model is the primary supported architecture and should be used for new games. The separate ECS under engine.ecs remains available for experimentation and its focused demo, but it is not re-exported from the top-level package or automatically synchronized with GameObjects.
See docs/ARCHITECTURE.md for the full decision, boundaries, consequences, and criteria for reconsidering it.
The engine uses a component-based architecture where game objects are containers for components that define behavior:
from engine import Component, GameObject, Sprite, Vector2
# Create a game object
player = GameObject("Player")
# Add components
player.add_component(Sprite(color='#FF0000'))
player.add_component(CustomBehavior())
# Components can access the game object
class CustomBehavior(Component):
def update(self, delta_time):
# Move the game object
self.game_object.transform.translate(Vector2(100 * delta_time, 0))Supports parent-child relationships with automatic world space calculations:
parent = GameObject("Parent")
child = GameObject("Child")
# Set up hierarchy
child.transform.parent = parent.transform
# Child position is relative to parent
child.transform.position = Vector2(50, 0) # 50 units to the right of parent
# Optional 3D support
child.transform.enable_3d()
child.transform.quaternion_rotation = Quaternion.from_axis_angle(Vector3.up(), math.pi/4)Each game owns an AssetManager for loading and caching Tk-compatible images. Sprites can render images directly or select lazily extracted atlas frames driven by reusable animation clips:
from engine import AnimationClip, SpriteAtlas, Vector2
sheet = self.asset_manager.load_image('assets/characters.png')
atlas = SpriteAtlas(Vector2(320, 64), sheet)
frames = atlas.create_animation_frames(
'walk', 5, Vector2(64, 64), Vector2.zero()
)
clip = AnimationClip.from_frames('walk', frames, 0.12)See docs/ASSETS_AND_ANIMATION.md and run python -m examples.demos.atlas for the real PNG demo.
Scenes automatically detect contacts between CircleCollider and AABBCollider components. Layers and masks filter pairs, while enter, stay, and exit callbacks let games define their own responses:
from engine import CircleCollider
collider = player.add_component(CircleCollider(radius=12))
collider.on_enter(lambda other: print(f"Hit {other.game_object.name}"))See docs/COLLISION.md for geometry queries, layer configuration, lifecycle behavior, and current limitations. Breakout is the first complete reference game using the system.
A SceneManager gives every GameEngine a named scene registry and a scene stack, on top of the existing Scene lifecycle (deferred object mutation, pause/resume, persistent objects across replacements):
self.register_scene('menu', MenuScene)
self.register_scene('game', GameScene)
self.load_scene('menu') # replace the current scene
self.push_scene('pause') # stack a paused overlay on top
self.pop_scene() # remove the overlay, resume what's beneathcurrent_scene and load_scene(scene) still work exactly as before, so no existing game needed changes. See docs/SCENE_MANAGEMENT.md for lifecycle ordering, stack semantics, and persistent-object rules. examples/games/ui_game.py is the reference implementation, including a pushed PauseScene.
Every GameEngine owns a DebugOverlay automatically — no setup, and no existing game needed to change anything:
# F3 stats overlay | F4 collider outlines | F5 cycle log level | F6 inspect sceneSlow frames are logged automatically too, with no key required:
[Debug] [WARNING] Slow frame: 41.2ms (target 16.7ms)
See docs/DEBUGGING.md for the full key reference and current scope, and run python -m examples.demos.debug_tools for bouncing colliders plus a deliberate periodic hitch to see the slow-frame warning fire on cue.
SoundGenerator computes real PCM sample data — sine, square, sawtooth, triangle, and filtered-noise waveforms — with no external dependencies:
from engine.audio.sound_generator import Sound
laser = Sound('laser')
laser.generate_sweep(800, 200, 0.1, 'square', 0.3)By default those samples are never actually sent to an audio output device — there's no cross-platform way to do that from the standard library alone. Install the optional audio extra (pip install pure-python-game-engine[audio]) and SoundGenerator automatically opens a real, mixed-audio playback device instead, with zero code changes to any existing game. Without it, playback is approximated with the terminal bell (\a), timed and repeated based on each sound's generation metadata (noise, continuous, or average frequency) rather than truly reproducing its pitch. Run python -m examples.games.asteroids_game with the extra installed to hear the difference — its engine hum, bullet laser, and explosion sounds all use this system. See docs/AUDIO.md for exactly what that means and why.
Custom 2D renderer built on tkinter Canvas:
# The renderer can draw various shapes
renderer.draw_rectangle(position, size, color='#FF0000', rotation=math.pi/4)
renderer.draw_circle(position, radius, color='#00FF00')
renderer.draw_polygon(points, color='#0000FF')Extend the Component class to create custom behaviors:
class HealthComponent(Component):
def __init__(self, max_health=100):
super().__init__()
self.max_health = max_health
self.current_health = max_health
def take_damage(self, damage):
self.current_health = max(0, self.current_health - damage)
if self.current_health == 0:
self.game_object.destroy()Comprehensive input system with multiple access patterns:
# In your game update loop
if input_manager.is_key_just_pressed('space'):
player.jump()
# Get normalized movement vector
movement = input_manager.get_movement_vector()
player.transform.translate(movement * speed * delta_time)Rich 2D and 3D vector systems with all standard operations:
# 2D Vector operations
velocity = Vector2(100, 50)
acceleration = Vector2(0, -9.8)
velocity += acceleration * delta_time
# 3D Vector operations
position_3d = Vector3(10, 20, 30)
direction_3d = Vector3.forward()
cross_product = position_3d.cross(direction_3d)
# Advanced operations
distance = player_pos.distance_to(enemy_pos)
direction = (target_pos - current_pos).normalize()
rotated = velocity.rotate(math.pi / 4)Generate sound effects using mathematical waveforms:
from engine import SoundGenerator
# Initialize sound system
sound_gen = SoundGenerator()
# Create custom sounds
bullet_sound = sound_gen.create_bullet_sound()
explosion_sound = sound_gen.create_explosion_sound()
engine_sound = sound_gen.create_engine_sound()
# Register and play sounds
sound_gen.register_sound(bullet_sound)
sound_gen.play_sound("bullet")
# Or use built-in sounds
sound_gen.initialize_default_sounds()
sound_gen.play_sound("explosion")Organize your game into named scenes, with stacked overlays for menus and pause screens:
self.register_scene("menu", MenuScene)
self.register_scene("game", GameScene)
# Switch between scenes by name
self.load_scene("game")
# Or stack an overlay without tearing down what's underneath
self.push_scene("pause")
self.pop_scene()See docs/SCENE_MANAGEMENT.md for the full lifecycle, stack semantics, and persistent-object rules.
This project demonstrates several important concepts:
- Understanding Fundamentals: Building from scratch teaches you how game engines actually work
- No Dependencies: Eliminates external library conflicts and licensing concerns
- Educational Value: Perfect for learning game development concepts
- Portability: Runs anywhere Python runs, no additional installations
- Customization: Complete control over every aspect of the engine
This engine prioritizes education and simplicity over speculative optimization. Current safeguards include:
- Bounded Frame Timing: Prevents large simulation jumps after stalls
- Squared-Distance Geometry: Avoids square roots where only overlap is needed
- Collision Layer Filtering: Rejects disallowed pairs before geometry tests
- Snapshot-Safe Updates: Allows callbacks to add or destroy objects safely
- Asset Caching: Reuses decoded images and extracted atlas frames
Collision broad-phase detection currently checks collider pairs directly. Object pooling and spatial partitioning are intentionally deferred until profiling demonstrates a need.
By studying this engine, you'll learn:
- Game engine architecture and design patterns
- 2D and 3D mathematics and coordinate systems
- Vector mathematics and quaternion rotations
- Component-based entity systems
- Input handling and event processing
- 2D graphics rendering techniques
- Transform hierarchies and world/local space conversions
- Scene management and state machines
- Image caching, sprite atlases, and frame animation
- Procedural audio generation and waveform synthesis
- Mathematical sound effect creation
- Performance optimization techniques
This project is designed for educational purposes. Contributions that improve the learning experience or add well-documented features are welcome!
This project is open source and available under the MIT License.
Built with ❤️ using only Python's standard library
