A simple, cross-platform 2D game engine written in D with an Entity-Component System architecture. Quark provides a complete game development framework including physics simulation, sprite rendering, animation, audio, scripting, and a tilemap editor. See the project website for a video demo and API documentation.
- Entity-Component System: Modular game object architecture with components for transform, sprites, animation, physics, colliders, and scripting
- Physics Engine: Rigidbody dynamics with gravity, velocity integration, and collision response
- Spatial grid broadphase for efficient collision detection
- Box and circle collider support with AABB and circle-circle/circle-rectangle narrowphase
- Collision events (enter, stay, exit) and trigger support
- Rendering System: SDL3-based sprite rendering with camera support and z-ordering
- Animation System: Grid-based sprite sheet animation with configurable speed and looping
- Audio System: Sound playback and looping using SDL3 audio streams
- Scene Management: Hierarchical scene graph with entity trees and scene transitions
- QScript Scripting Language: Custom scripting language for game logic with lexer, parser, and interpreter
- Tilemap Editor: GTK-based visual editor for creating and editing tilemaps (JSON and binary export)
- Multi-threaded Architecture: Separate threads for resource loading, audio, and physics
The project uses DUB, the D package manager and build system.
- DMD or LDC2 (D compiler)
- DUB (D package manager)
- Linux x86-64 or Windows x86-64/ARM64 (macOS not currently supported)
SDL3 and SDL3_image libraries are bundled in the lib/ directory for supported platforms.
Since Quark is a game engine library, you build projects that use it rather than the engine itself. Choose a project to build:
| Project | Path | Description |
|---|---|---|
| Platformer | game/platformer/ |
Full game demo showcasing engine features |
| Bouncy Balls | game/bouncyballs/ |
Physics engine demo |
| Tilemap Editor | editors/tilemap/ |
Visual tilemap creation tool |
# Build and run the platformer demo
cd game/platformer
dub
# Build and run the physics demo
cd game/bouncyballs
dub
# Build and run the tilemap editor (requires GTK)
cd editors/tilemap
dub
# Run unit tests from the root directory
dub testExecutables are output to the bin/ directory.
Pre-built demo games for x86-64 users are available in EngineBuild.zip on the project website. Extract the archive and run the appropriate executable:
- Windows:
.exefiles - Linux:
.binfiles
ARM users must compile from source.
To create a new game using Quark:
- Create a new directory under
game/(e.g.,game/mygame/) - Add a
dub.jsonwith a dependency on quark:{ "dependencies": { "quark": {"path": "../.."} }, "targetType": "executable", "targetPath": "../../bin" } - Create your source files in
game/mygame/source/ - Place art, sounds, and other assets in
assets/mygame/ - Run
dubfrom your game directory
quark/
├── dub.json # Engine library configuration
├── assets/ # Game assets (images, sounds, tilemaps)
│ ├── bouncyballs/ # Bouncy balls demo assets
│ └── platformer/ # Platformer demo assets
│ ├── images/ # Sprite sheets and backgrounds
│ └── sounds/ # Audio files
├── bin/ # Built executables
├── docs/ # Documentation
├── editors/
│ └── tilemap/ # GTK-based tilemap editor
│ └── source/
│ ├── editor.d # Main editor window
│ └── components/ # Canvas and palette widgets
├── game/
│ ├── bouncyballs/ # Physics demo
│ └── platformer/ # Full game demo
│ └── source/
│ ├── platformer.d # Game host implementation
│ ├── scenes/ # Level definitions
│ └── scripts/ # QScript game logic (.qscr)
├── lib/ # Pre-built SDL3 libraries
│ ├── linux/x64/ # libSDL3.so, libSDL3_image.so
│ └── windows/
│ ├── x64/ # SDL3.dll, SDL3_image.dll
│ └── arm/ # SDL3.dll, SDL3_image.dll
└── source/quark/ # Engine source code
├── common/ # Math utilities
│ ├── vector.d # Vec2 types (Vec2f, Vec2i, Vec2d)
│ ├── matrix.d # Matrix operations
│ ├── grid.d # Spatial grid data structure
│ └── tree.d # Tree/scene graph structure
├── core/ # ECS foundation
│ ├── component.d # Base Component class
│ ├── entity.d # Entity container
│ ├── scene.d # Scene/entity tree management
│ ├── host.d # Game loop and threading
│ └── manager.d # Base Manager class
├── components/ # Built-in components
│ ├── transform.d # Position, rotation, scale
│ ├── spriterenderer.d # Sprite drawing
│ ├── animator.d # Sprite animation
│ ├── camera.d # Viewport camera
│ ├── rigidbody.d # Physics body
│ ├── script.d # QScript attachment
│ └── colliders/ # Collision shapes
│ ├── boxcollider.d # AABB collider
│ └── circlecollider.d # Circle collider
├── managers/ # Singleton managers
│ ├── resourcemanager.d # Texture and sound loading
│ ├── audiomanager.d # Sound playback
│ ├── scenemanager.d # Scene transitions
│ ├── physicsmanager.d # Physics world interface
│ └── debugmanager.d # Debug visualization
├── physics/ # Physics subsystem
│ ├── world.d # Physics world coordination
│ ├── broadphase.d # Spatial grid collision culling
│ ├── collision.d # Narrowphase detection
│ └── solver.d # Collision response
├── scripting/ # QScript language
│ ├── lexer.d # Tokenizer
│ ├── parser.d # AST generation
│ ├── interpreter.d # Runtime execution
│ └── grammar.txt # Language specification
└── events/ # Event system
├── types.d # Event definitions
├── inbox.d # Thread-safe message queue
└── wiring.d # Event routing
Entities are containers for components. Components provide specific functionality and are updated each frame:
// Create an entity with components
auto player = new Entity("Player");
auto transform = new TransformComponent();
transform.position = Vec2f(100, 200);
player.addComponent(transform);
auto sprite = new SpriteRendererComponent("assets/player.png");
player.addComponent(sprite);
auto rigidbody = new RigidbodyComponent();
rigidbody.useGravity = true;
player.addComponent(rigidbody);
auto collider = new BoxColliderComponent(Vec2f(32, 64));
player.addComponent(collider);
scene.addEntity(player);Components have lifecycle methods called at appropriate times:
| Method | Description |
|---|---|
onInit() |
Called when component is added to an entity |
onStart() |
Called on first frame the component is active |
preUpdate(dt) |
Called every frame before physics |
postUpdate(dt) |
Called every frame after physics |
preRender() |
Called before rendering |
postRender() |
Called after rendering |
onExit() |
Called when component is removed |
QScript is a custom scripting language for game logic. Scripts can access engine APIs through built-in functions:
// player.qscr
var speed = 200;
var jumpForce = 400;
func onCollisionEnter(other) {
if (getName(other) == "Coin") {
playSound("assets/coin.wav");
destroy(other);
}
}
func update(dt) {
var vx = 0;
if (isKeyDown("left")) { vx = -speed; }
if (isKeyDown("right")) { vx = speed; }
setVelocityX(self, vx);
if (isKeyDown("space") && isGrounded(self)) {
setVelocityY(self, -jumpForce);
}
}The physics engine uses a broadphase-narrowphase approach:
- Broadphase: Spatial grid partitioning culls distant objects
- Narrowphase: Precise collision detection (AABB, circle-circle, circle-AABB)
- Solver: Collision response with impulse-based resolution
// Configure rigidbody physics
rigidbody.mass = 2.0f;
rigidbody.bounciness = 0.8f;
rigidbody.linearDrag = 0.1f;
rigidbody.useGravity = true;
rigidbody.isKinematic = false; // Set true for static/animated objects
// Apply forces
rigidbody.addForce(Vec2f(100, 0));
rigidbody.velocity = Vec2f(50, -200);If the window appears but nothing is visible, try using the software renderer:
export SDL_RENDER_DRIVER=software && ./bin/platformerAudio is currently very buggy on WSL and will likely not be fixed.
The platformer demo includes a hidden debug mode. Type the sequence [0w0] during gameplay to toggle collider visualization and editing.
MIT License — Copyright © 2026 Nathan Abebe