A Flutter package for integrating LDtk levels into Flame Engine games.
- 🌍 World Management - Simplified API with
LdtkWorldfor managing levels and assets - 🎮 Super Simple Export Support - Optimized loading of LDtk levels using Super Simple Export format
- 🗺️ Level Rendering - Individual layer rendering with transparency support
- 🖼️ Background Images - Automatic background loading with positioning modes
- 🎯 Entity Parsing - Extract entities with positions, sizes, custom fields, colors, and sprites
- 🖼️ Entity Tiles - Automatic sprite loading from entity tiles defined in LDtk
- 🧱 IntGrid Support - CSV-based IntGrid for collisions and game logic
- 🔄 Level Switching - Change levels dynamically without recreating components
- 🎨 Flexible Architecture - Override hooks to customize entity rendering
- 📦 Generic Design - No built-in collision logic, adapt to your game type
- ⚡ Optimized Performance - LRU cache system and fast CSV parsing
📖 Looking for JSON format support? See JSON_FORMAT.md (experimental, not fully implemented)
flutter pub add flame_ldtk
- Create your level in LDtk
- Go to Project Settings → Super Simple Export
- Enable Super Simple Export
- Set your export path (e.g.,
assets/world/simplified/) - Save your project to generate the export files
Each exported level will contain:
_composite.png- Complete level visual (optional, use individual layers instead)[LayerName].png- Individual layer images (e.g.,Tiles.png)data.json- Level metadata and entities (lightweight, ~500B for simple levels)[LayerName].csv- IntGrid layers (for collisions, etc.)
For background images: Keep the .ldtkl file to read background configuration.
flutter:
assets:
- assets/world/simplified/Level_0/ # Simplified export folder
- assets/world/Level_0.ldtkl # For background and entity tiles
- assets/world.ldtk # LDtk project file
- assets/background.png # Background image
- assets/tilemap.png # Tileset for entity spritesSimple usage with LdtkWorld:
import 'package:flame/game.dart';
import 'package:flame_ldtk/flame_ldtk.dart';
class MyGame extends FlameGame {
@override
Future<void> onLoad() async {
await super.onLoad();
// Load the world once
final world = await LdtkWorld.load('assets/world.ldtk');
// Create and load a level
final level = LdtkLevelComponent(world);
await level.loadLevel('Level_0');
await add(level);
// Optional: Center camera on the level
camera.viewfinder.position = Vector2(
level.levelData!.width / 2,
level.levelData!.height / 2,
);
}
}With collisions and background:
import 'package:flame/game.dart';
import 'package:flame_ldtk/flame_ldtk.dart';
class MyGame extends FlameGame {
@override
Future<void> onLoad() async {
await super.onLoad();
// Load the world - it handles all paths automatically
final world = await LdtkWorld.load('assets/world.ldtk');
// Create and load a level with collision layer
// Background and entity sprites are loaded automatically
final level = LdtkLevelComponent(world);
await level.loadLevel('Level_0', intGridLayers: ['Collisions']);
await add(level);
}
}Switching levels dynamically:
class MyGame extends FlameGame {
late LdtkLevelComponent level;
@override
Future<void> onLoad() async {
await super.onLoad();
final world = await LdtkWorld.load('assets/world.ldtk');
level = LdtkLevelComponent(world);
await level.loadLevel('Level_0', intGridLayers: ['Collisions']);
await add(level);
}
Future<void> goToNextLevel() async {
// Change level without recreating the component
await level.loadLevel('Level_1', intGridLayers: ['Collisions']);
}
}Override onEntitiesLoaded() to handle your entities:
class MyLevelComponent extends LdtkLevelComponent {
Player? player;
MyLevelComponent(super.world);
@override
Future<void> onEntitiesLoaded(List<LdtkEntity> entities) async {
for (final entity in entities) {
switch (entity.identifier) {
case 'Player':
player = Player(entity, levelData!);
await add(player!);
break;
case 'Enemy':
final enemy = Enemy(entity, levelData!);
await add(enemy);
break;
case 'Coin':
final coin = Coin(entity);
await add(coin);
break;
}
}
}
}class Player extends PositionComponent {
final LdtkEntity entity;
final LdtkLevel level;
Player(this.entity, this.level) {
position = entity.position; // Position from LDtk
size = entity.size; // Size from LDtk
}
@override
Future<void> onLoad() async {
// entity.sprite - Sprite from LDtk tile (if assigned)
// entity.color - Color from LDtk
// Add your rendering logic here
...
}
}class Chest extends PositionComponent {
Chest(LdtkEntity entity) {
position = entity.position;
size = entity.size;
// Access custom fields defined in LDtk
final loot = entity.fields['loot'] as String? ?? 'gold';
final amount = entity.fields['amount'] as int? ?? 10;
// Use the custom fields in your game logic
...
}
}// When loading a level, specify which IntGrid layers to load
await level.loadLevel(
'Level_0',
intGridLayers: ['Collisions', 'Water', 'Hazards'], // Load IntGrid layers
);class Player extends PositionComponent {
final LdtkLevel level;
@override
void update(double dt) {
// Access IntGrid layers loaded from LDtk
final collisions = level.intGrids['Collisions'];
if (collisions == null) return;
// Use IntGrid methods to check collisions
final canMove = !collisions.isSolidAtPixel(newX, newY);
// Your game physics logic here
...
}
}final grid = level.intGrids['Collisions']!;
// Check by pixel position
bool solid = grid.isSolidAtPixel(128.5, 64.0);
// Check by grid cell
bool solid = grid.isSolid(16, 8); // Cell coordinates
// Get cell value
int value = grid.getValue(16, 8); // Returns 0 for empty, 1+ for solid
// Grid properties
int cellSize = grid.cellSize; // Size of each cell in pixels
int width = grid.width; // Grid width in cells
int height = grid.height; // Grid height in cellsimport 'package:flame/game.dart';
import 'package:flame_ldtk/flame_ldtk.dart';
// 1. Load the world and level
class MyGame extends FlameGame {
@override
Future<void> onLoad() async {
await super.onLoad();
// Load the LDtk world
final world = await LdtkWorld.load('assets/world.ldtk');
// Create custom level component
final level = MyLevelComponent(world);
await level.loadLevel('Level_0', intGridLayers: ['Collisions']);
await add(level);
// Center camera on the level
camera.viewfinder.position = Vector2(
level.levelData!.width / 2,
level.levelData!.height / 2,
);
}
}
// 2. Override onEntitiesLoaded to handle entities from LDtk
class MyLevelComponent extends LdtkLevelComponent {
Player? player;
MyLevelComponent(super.world);
@override
Future<void> onEntitiesLoaded(List<LdtkEntity> entities) async {
for (final entity in entities) {
switch (entity.identifier) {
case 'Player':
player = Player(entity, levelData!);
await add(player!);
break;
case 'Enemy':
await add(Enemy(entity, levelData!));
break;
}
}
}
}
// 3. Use entity data and IntGrid for collision detection
class Player extends PositionComponent {
final LdtkEntity entity;
final LdtkLevel level;
Player(this.entity, this.level) {
// Get position and size from LDtk entity
position = entity.position;
size = entity.size;
}
@override
Future<void> onLoad() async {
// Use sprite from LDtk if available
if (entity.sprite != null) {
await add(SpriteComponent(sprite: entity.sprite, size: size));
}
// Your rendering logic here
...
}
@override
void update(double dt) {
// Access IntGrid for collision detection
final collisions = level.intGrids['Collisions'];
if (collisions == null) return;
// Check collisions using IntGrid methods
if (!collisions.isSolidAtPixel(newX, newY)) {
position.x = newX;
}
// Your game physics here
...
}
}Manages LDtk project configuration and level loading.
// Load a world
final world = await LdtkWorld.load('assets/world.ldtk');
// Access world properties
bool isSimplified = world.isSimplified; // Super Simple Export?
bool hasExternalLevels = world.hasExternalLevels; // External .ldtkl files?
String assetBasePath = world.assetBasePath; // Base path for assets
List<LdtkJsonLevel> levels = world.levels; // All available levels
// Get paths for a level
String? ldtklPath = world.getLdtklPath('Level_0');
String? bgPath = world.getBackgroundPath('Level_0');
String? levelPath = world.getSimplifiedLevelPath('Level_0');Main component for loading and displaying LDtk levels.
// Create component with world
final level = LdtkLevelComponent(world);
// Load a level by identifier
await level.loadLevel(
'Level_0',
intGridLayers: ['Collisions', 'Water'], // Optional: load collision layers
useComposite: false, // Optional: use individual layers (default)
loadBackground: true, // Optional: load background image (default)
);
// Access level data
LdtkLevel? data = level.levelData;
// Change level dynamically
await level.loadLevel('Level_1', intGridLayers: ['Collisions']);
// Override to customize entity creation
@override
Future<void> onEntitiesLoaded(List<LdtkEntity> entities) async {
// Your custom entity creation logic
}Parameters:
levelIdentifier- Name of the level in LDtk (e.g., 'Level_0', 'Dungeon_1')intGridLayers- List of IntGrid layer names to load for collisionsuseComposite- Use composite image or individual layers (default: false for transparency)loadBackground- Automatically load background image if defined (default: true)
Background images and entity sprites:
- Background images are loaded automatically from
.ldtklfiles - Entity sprites are loaded automatically if you assign a tile to an entity in LDtk
- All paths are managed by
LdtkWorld, no manual configuration needed
Supported background positioning modes:
Cover- Background covers the entire levelContain- Background scaled to fit while maintaining aspect ratioUnscaled- Background uses original size
Contains all level data.
String name; // Level identifier
int width, height; // Level dimensions in pixels
Color? bgColor; // Background color
List<LdtkEntity> entities; // All entities
Map<String, LdtkIntGrid> intGrids; // IntGrid layers by name
Map<String, dynamic> customData; // Custom fieldsRepresents an entity from LDtk.
String identifier; // Entity type (e.g., "Player")
Vector2 position; // Top-left position in pixels
Vector2 size; // Size in pixels
Map<String, dynamic> fields; // Custom fields
Color? color; // Color from LDtk
Sprite? sprite; // Sprite from entity tile (if assigned in LDtk)Grid-based collision/logic layer.
int cellSize; // Cell size in pixels
int width, height; // Grid dimensions in cells
bool isSolid(int x, int y); // Check cell by grid coords
bool isSolidAtPixel(double x, double y); // Check by pixel coords
int getValue(int x, int y); // Get cell value (0 = empty)class Player extends PositionComponent { ... }
class Enemy extends PositionComponent { ... }
class Item extends PositionComponent { ... }class GameEntity extends PositionComponent {
final LdtkLevel level;
GameEntity(LdtkEntity entity, this.level) {
position = entity.position;
size = entity.size;
}
}In LDtk, add custom fields to entities:
speed: Intfor movement speedhealth: Intfor HPloot: Stringfor item type
Access them in your components:
final speed = entity.fields['speed'] as int? ?? 100;
final health = entity.fields['health'] as int? ?? 3;final collisions = level.intGrids['Collisions'];
final water = level.intGrids['Water'];
final spikes = level.intGrids['Hazards'];
if (collisions?.isSolidAtPixel(x, y) ?? false) {
// Hit solid wall
}
if (water?.isSolidAtPixel(x, y) ?? false) {
// In water, apply different physics
}Note: I created this project for a game I'm currently developing. The roadmap may evolve based on my needs. The Super Simple Export mode is the most tested and stable format.
- Super Simple Export support
- World Management -
LdtkWorldclass for simplified project and level management - Entity Tiles/Sprites - Automatic sprite loading from entity tiles defined in LDtk
- Level Switching - Change levels dynamically without recreating components
- Custom fields extraction
- LRU cache system with memory limits
- Improved error handling with detailed messages
- Individual Layer Rendering - Load and render tile layers separately (via
useComposite: falseby default) - Background Images - Basic positioning modes (Cover, Contain, Unscaled) supported. Advanced options (custom scale, crop rectangles) not yet implemented.
- JSON Export support (experimental) - See JSON_FORMAT.md
- AutoLayers Support - Render auto-generated tile layers
- Level Transitions - Fade, slide, and custom transition effects between levels
- Parallax Backgrounds - Support for parallax effects with background images
- Advanced Background Options - Custom scale and crop rectangle support
- Tile Animations - Animated tileset support with metadata parsing
- Entity Registry/Factory - Automatic entity-to-component mapping system
- Collision Generation from IntGrid - Automatic hitbox generation (polygons/rectangles)
- Hot Reload Support - Watch LDtk files and reload in development
- Debug Renderer - Visualize grids, entity bounds, collisions, and IntGrid values
- Platformer Behavior Mixin - Reusable gravity and collision behaviors
- Typed Field Values - Strong typing for Point, Color, Enum, EntityRef, Array fields
- Enum Support - Parse and use LDtk enum definitions
- Render Optimization - Tile batching, atlases, and off-screen culling
- Level Streaming - Progressive loading for large levels
- PNG-based IntGrid parsing - Alternative to CSV format
Contributions are welcome! Please feel free to submit a Pull Request.
MIT License - see LICENSE file for details.