flame_ldtk 0.2.0 copy "flame_ldtk: ^0.2.0" to clipboard
flame_ldtk: ^0.2.0 copied to clipboard

A Flutter package for integrating LDtk levels into Flame Engine games

flame_ldtk #

A Flutter package for integrating LDtk levels into Flame Engine games.

pub package

Features #

  • ๐ŸŽฎ Dual Format Support - Load LDtk levels in both Super Simple Export and standard JSON format
  • ๐Ÿ—บ๏ธ Level Rendering - Automatic composite image loading and rendering
  • ๐ŸŽฏ Entity Parsing - Extract entities with positions, sizes, custom fields, and colors
  • ๐Ÿงฑ IntGrid Support - CSV-based IntGrid for collisions and game logic
  • ๐ŸŽจ Flexible Architecture - Override hooks to customize entity rendering
  • ๐Ÿ“ฆ Generic Design - No built-in collision logic, adapt to your game type
  • โšก Optimized Performance - Shared utilities and centralized caching for both parsers

Installation #

Add flame_ldtk to your pubspec.yaml:

dependencies:
  flame: ^1.32.0
  flame_ldtk: ^0.2.0

LDtk Setup #

This package supports two LDtk export formats:

Best for: Fast loading, minimal memory usage, mobile/web games

  1. Create your level in LDtk
  2. Go to Project Settings โ†’ Super Simple Export
  3. Enable Super Simple Export
  4. Set your export path (e.g., assets/world/simplified/)
  5. Save your project to generate the export files

Each exported level will contain:

  • _composite.png - Complete level visual
  • data.json - Level metadata and entities (489B for simple levels)
  • [LayerName].csv - IntGrid layers (for collisions, etc.)

Option 2: Standard JSON Export #

Best for: Access to full project definitions, fewer files per level

  1. Create your level in LDtk
  2. In Project Settings, enable "Save levels to separate files" (optional)
  3. Save your project to generate .ldtk and .ldtkl files

Your project structure will be:

  • world.ldtk - Main project file with definitions
  • world/Level_0.ldtkl - Individual level files (if using external levels)

Basic Usage #

1. Add assets to pubspec.yaml #

flutter:
  assets:
    # For Super Simple Export
    - assets/world/simplified/Level_0/
    # For JSON format
    - assets/world.ldtk
    - assets/world/

2. Load a level in your game #

import 'package:flame/game.dart';
import 'package:flame_ldtk/flame_ldtk.dart';

class MyGame extends FlameGame {
  @override
  Future<void> onLoad() async {
    final level = LdtkLevelComponent();
    await level.loadLevel('assets/world/simplified/Level_0');
    await add(level);
  }
}

Using JSON Format

import 'package:flame/game.dart';
import 'package:flame_ldtk/flame_ldtk.dart';

class MyGame extends FlameGame {
  @override
  Future<void> onLoad() async {
    final level = LdtkJsonLevelComponent();
    await level.loadLevel('assets/world.ldtk', 'Level_0');
    await add(level);
  }
}

Note: Both components provide the same API! The only difference is the format they load.

Working with Entities #

Customize entity rendering #

Override onEntitiesLoaded() to handle your entities (works with both components):

// Works with both LdtkLevelComponent and LdtkJsonLevelComponent!
class MyLevelComponent extends LdtkLevelComponent {  // or LdtkJsonLevelComponent
  @override
  Future<void> onEntitiesLoaded(List<LdtkEntity> entities) async {
    for (final entity in entities) {
      switch (entity.identifier) {
        case 'Player':
          final player = PlayerComponent(entity, levelData!);
          await add(player);
          break;

        case 'Enemy':
          final enemy = EnemyComponent(entity, levelData!);
          await add(enemy);
          break;

        case 'Coin':
          final coin = CoinComponent(entity);
          await add(coin);
          break;
      }
    }
  }
}

Create entity components #

class PlayerComponent extends PositionComponent {
  final LdtkEntity entity;
  final LdtkLevel level;

  PlayerComponent(this.entity, this.level) {
    position = entity.position;  // LDtk position (top-left corner)
    size = entity.size;           // Entity size from LDtk
  }

  @override
  Future<void> onLoad() async {
    // Render with entity color from LDtk
    final color = entity.color ?? Colors.blue;
    final rect = RectangleComponent(
      size: size,
      paint: Paint()..color = color,
    );
    await add(rect);
  }
}

Access custom fields #

class ChestComponent extends PositionComponent {
  final LdtkEntity entity;

  ChestComponent(this.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;

    print('Chest contains $amount $loot');
  }
}

Working with IntGrid (Collisions) #

Load IntGrid layers #

class MyLevelComponent extends LdtkLevelComponent {
  @override
  Future<void> onLoad() async {
    // Load level with collision layer
    await loadLevel(
      'assets/world/simplified/Level_0',
      intGridLayers: ['Collisions'],  // Load IntGrid layers
    );
  }
}

Implement collision detection #

class PlayerComponent extends PositionComponent {
  final LdtkLevel level;
  Vector2 velocity = Vector2.zero();

  @override
  void update(double dt) {
    final collisions = level.intGrids['Collisions'];
    if (collisions == null) return;

    // Calculate new position
    final newX = position.x + velocity.x * dt;
    final newY = position.y + velocity.y * dt;

    // Check horizontal collision
    if (_canMoveTo(collisions, newX, position.y)) {
      position.x = newX;
    }

    // Check vertical collision
    if (_canMoveTo(collisions, position.x, newY)) {
      position.y = newY;
    }
  }

  bool _canMoveTo(LdtkIntGrid grid, double x, double y) {
    // Check four corners of player hitbox
    final corners = [
      Vector2(x, y),                      // Top-left
      Vector2(x + size.x, y),             // Top-right
      Vector2(x, y + size.y),             // Bottom-left
      Vector2(x + size.x, y + size.y),    // Bottom-right
    ];

    for (final corner in corners) {
      if (grid.isSolidAtPixel(corner.x, corner.y)) {
        return false; // Collision detected
      }
    }
    return true; // Can move
  }
}

IntGrid helper methods #

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 cells

Complete Platformer Example #

import 'package:flame/game.dart';
import 'package:flame/components.dart';
import 'package:flame/input.dart';
import 'package:flutter/services.dart';
import 'package:flame_ldtk/flame_ldtk.dart';

class PlatformerGame extends FlameGame with KeyboardEvents {
  PlayerComponent? player;

  @override
  Future<void> onLoad() async {
    final level = MyLevelComponent();
    await level.loadLevel(
      'assets/world/simplified/Level_0',
      intGridLayers: ['Collisions'],
    );
    await add(level);
    player = level.player;
  }

  @override
  KeyEventResult onKeyEvent(KeyEvent event, Set<LogicalKeyboardKey> keys) {
    player?.onKeyEvent(event, keys);
    return KeyEventResult.handled;
  }
}

class MyLevelComponent extends LdtkLevelComponent {
  PlayerComponent? player;

  @override
  Future<void> onEntitiesLoaded(List<LdtkEntity> entities) async {
    for (final entity in entities) {
      if (entity.identifier == 'Player') {
        player = PlayerComponent(entity, levelData!);
        await add(player!);
      }
    }
  }
}

class PlayerComponent extends PositionComponent {
  final LdtkEntity entity;
  final LdtkLevel level;

  // Physics
  static const double moveSpeed = 100.0;
  static const double jumpForce = -300.0;
  static const double gravity = 800.0;

  Vector2 velocity = Vector2.zero();
  bool isOnGround = false;
  bool isMovingLeft = false;
  bool isMovingRight = false;
  bool wantsToJump = false;

  PlayerComponent(this.entity, this.level) {
    position = entity.position;
    size = entity.size;
  }

  @override
  Future<void> onLoad() async {
    final rect = RectangleComponent(
      size: size,
      paint: Paint()..color = entity.color ?? Colors.blue,
    );
    await add(rect);
  }

  @override
  void update(double dt) {
    super.update(dt);

    final collisions = level.intGrids['Collisions'];
    if (collisions == null) return;

    // Horizontal movement
    velocity.x = (isMovingRight ? moveSpeed : 0) +
                 (isMovingLeft ? -moveSpeed : 0);

    // Jump
    if (wantsToJump && isOnGround) {
      velocity.y = jumpForce;
      isOnGround = false;
    }

    // Gravity
    velocity.y += gravity * dt;

    // Apply movement with collision detection
    final newX = position.x + velocity.x * dt;
    if (_canMoveTo(collisions, newX, position.y)) {
      position.x = newX;
    }

    final newY = position.y + velocity.y * dt;
    if (_canMoveTo(collisions, position.x, newY)) {
      position.y = newY;
      isOnGround = false;
    } else {
      if (velocity.y > 0) isOnGround = true;
      velocity.y = 0;
    }
  }

  bool _canMoveTo(LdtkIntGrid grid, double x, double y) {
    return !grid.isSolidAtPixel(x, y) &&
           !grid.isSolidAtPixel(x + size.x, y) &&
           !grid.isSolidAtPixel(x, y + size.y) &&
           !grid.isSolidAtPixel(x + size.x, y + size.y);
  }

  void onKeyEvent(KeyEvent event, Set<LogicalKeyboardKey> keys) {
    isMovingLeft = keys.contains(LogicalKeyboardKey.arrowLeft);
    isMovingRight = keys.contains(LogicalKeyboardKey.arrowRight);
    wantsToJump = keys.contains(LogicalKeyboardKey.space);
  }
}

API Reference #

LdtkLevelComponent (Super Simple Format) #

Main component for loading and displaying LDtk levels in Super Simple Export format.

// Load a level
await levelComponent.loadLevel(
  'assets/world/simplified/Level_0',
  intGridLayers: ['Collisions', 'Water'],  // Optional
);

// Access level data
LdtkLevel? data = levelComponent.levelData;

// Override to customize entity creation
@override
Future<void> onEntitiesLoaded(List<LdtkEntity> entities) async {
  // Your custom entity creation logic
}

LdtkJsonLevelComponent (JSON Format) #

Component for loading and displaying LDtk levels in standard JSON format.

// Load a level
await levelComponent.loadLevel(
  'assets/world.ldtk',      // Project file
  'Level_0',                 // Level identifier
);

// Access level data (same as LdtkLevelComponent)
LdtkLevel? data = levelComponent.levelData;

// Override to customize entity creation (same API)
@override
Future<void> onEntitiesLoaded(List<LdtkEntity> entities) async {
  // Your custom entity creation logic
}

Both components share the same API - just swap them based on your export format!

LdtkLevel #

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 fields

LdtkEntity #

Represents 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

LdtkIntGrid #

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)

Tips & Best Practices #

1. Use separate components for different entity types #

class PlayerComponent extends LdtkEntityComponent { ... }
class EnemyComponent extends LdtkEntityComponent { ... }
class ItemComponent extends LdtkEntityComponent { ... }

2. Store level reference for collision access #

class GameEntity extends PositionComponent {
  final LdtkLevel level;

  GameEntity(LdtkEntity entity, this.level) {
    position = entity.position;
    size = entity.size;
  }
}

3. Use custom fields for entity configuration #

In LDtk, add custom fields to entities:

  • speed: Int for movement speed
  • health: Int for HP
  • loot: String for item type

Access them in your components:

final speed = entity.fields['speed'] as int? ?? 100;
final health = entity.fields['health'] as int? ?? 3;

4. Handle different collision types #

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
}

Choosing the Right Parser #

Use Super Simple Parser when: #

  • ๐Ÿš€ You need fast loading times
  • ๐Ÿ“ฑ Building for mobile or web
  • ๐Ÿ’พ Memory usage is a concern
  • ๐ŸŽฎ You have many levels to load dynamically

Use JSON Parser when: #

  • ๐Ÿ“‹ You need access to entity/tileset definitions
  • ๐Ÿ“ฆ You prefer fewer files (one .ldtkl per level)
  • ๐Ÿ”ง You want to access metadata from the project file
  • ๐Ÿ–ฅ๏ธ Building for desktop with ample resources

Performance Comparison (Level_0):

Super Simple: ~500B JSON + optional assets = Fast, minimal RAM
JSON:         ~16KB project + level data = More features, higher RAM

Roadmap #

  • โœ… Super Simple Export support
  • โœ… JSON Export support
  • โœ… Custom fields extraction for both formats
  • โœ… Shared utilities and optimized performance
  • โŒ Multi-level support with transitions
  • โŒ PNG-based IntGrid parsing
  • โŒ Tile layer support (individual tiles)
  • โŒ Level background rendering
  • โŒ Hot reload support for LDtk changes

Contributing #

Contributions are welcome! Please feel free to submit a Pull Request.

License #

MIT License - see LICENSE file for details.

Credits #

  • LDtk - Level Designer Toolkit by Sรฉbastien Bรฉnard
  • Flame - Flutter game engine
0
likes
0
points
30
downloads

Publisher

unverified uploader

Weekly Downloads

A Flutter package for integrating LDtk levels into Flame Engine games

Repository (GitHub)
View/report issues

License

unknown (license)

Dependencies

csv, flame, flutter

More

Packages that depend on flame_ldtk