Crate micro_autotile

source ·
Expand description

micro_autotile provides an implementation of the LDTK auto-tiling algorithm, for use in programs at runtime. The representation is compatible with that saved by LDTK, meaning that definitions can be loaded directly from LDTK JSON exports.

Creating a single rule works like this:

  1. Create a TileMatcher out of TileStatus entries.
    • Tile Matchers are squares represented as fixed size flat arrays
    • Currently only 1x1 and 3x3 matchers are supported, 5x5 matchers are incompatible
    • Since a Tile Matcher is a rule, they are usually created statically or loaded as an asset that will not change much / at all
  2. Create a TileOutput that represents the value produces by this rule when it matches
    • An output value of Skip will cause the rule to be a noop. This has utility when combined with a rule’s chance value, as part of a set of rules
    • A Single output will always produce the same value
    • A Random output will produce one of the provided values at random
  3. Combine these into an AutoTileRule
    • There are a number of convenience methods for doing this process without mistakes in a single function call

To utilise your matcher, you’ll need to provide a specifically formatted slice of your input data (typically a sub-grid of a tile map). If you’re matching a single tile, you can use the convenience method TileLayout::single, otherwise you will need to provide a 9 element array that represents 3 rows and 3 columns of data, in the following format:

Flat array data
[1, 2, 3, 4, 5, 6, 7, 8, 9]
Formatted in the way it would appear if laid out in a tile map
  1, 2, 3, # First row, all three columns
  4, 5, 6, # Second row, all three columns
  7, 8, 9, # Third row, all three columns

As we can see, the fifth element of the array is the centre tile of our matching grid. In fact, TileLayout::single constructs a 9 element array where the fifth element is Some(your_value), and the rest are simply None. This is possible because the actual data represents each element as an Option (not as the simple numbers above), which allows matching up against edges of data arrays, or against non-regular shapes. Putting this together to match against data from our tile map, we have the following:


use micro_autotile::TileMatcher;
// Tile maps often use unsigned integers to represent different types of tiles
const WALL_TILE: usize = 0;
const GROUND_TILE: usize = 1;

// Match a 1x1 ground tile, output the index within the spritesheet that we'll use for rendering
let match_1_x_1 = AutoTileRule::exact(GROUND_TILE, 57);


// More realistically, we might have some tile data for a ground tile with other data arround it.
// When we match against a rule, we're always trying to produce a value for the _central_ value in
// our `TileLayout` (the fifth element)
let enclosed_ground = TileLayout([


// There may also be situations in which you just want to know that a given layout matches a rule, without
// concern for producing a value for that layout. You can directly use a `TileMatcher` for this

There’s already a lot of utility to these structures, but we still need to manually run a set of rules against our maps and do some work with the AutoTileRule::chance property to figure out what the final output should be for a given layout.

Introducing the AutoRuleSet struct, that represents a sequence of rules that should be evaluated to produce an output. It provides similar methods to the individual AutoTileRule, but will execute against a set of rules at once. There are also convenience methods for combining AutoRuleSet instances

use micro_autotile::{TileMatcher, TileStatus};
const WALL_TILE: usize = 0;
const GROUND_TILE: usize = 1;
const OTHER_TILE: usize = 342;

let wall_rules = AutoRuleSet(vec![
	AutoTileRule::single_when(TileMatcher([ // Top Left Corner
		TileStatus::IsNot(WALL_TILE), TileStatus::IsNot(WALL_TILE), TileStatus::IsNot(WALL_TILE),
		TileStatus::IsNot(WALL_TILE), TileStatus::Is(WALL_TILE), TileStatus::Is(WALL_TILE),
		TileStatus::IsNot(WALL_TILE), TileStatus::Is(WALL_TILE), TileStatus::Is(WALL_TILE),
	]), 54),
	AutoTileRule::single_when(TileMatcher([ // Top Right Corner
		TileStatus::IsNot(WALL_TILE), TileStatus::IsNot(WALL_TILE), TileStatus::IsNot(WALL_TILE),
		TileStatus::Is(WALL_TILE), TileStatus::Is(WALL_TILE), TileStatus::IsNot(WALL_TILE),
		TileStatus::Is(WALL_TILE), TileStatus::Is(WALL_TILE), TileStatus::IsNot(WALL_TILE),
	]), 55),
	// ... Etc

let ground_rules = AutoRuleSet(vec![
	// Use decorated tiles in 10% of cases
	AutoTileRule::single_any_chance(GROUND_TILE, vec![45, 46, 47], 0.1),
	// Fall back to the basic tile if we don't match previously
	AutoTileRule::exact(GROUND_TILE, 44),

// Easily merge rule sets in an ordered way
let combined_rules = wall_rules + ground_rules;

let sublayout = TileLayout([

// We've got a layout that represents the top right corner of a wall, the second rule in our
// set - the value of the tiles that match "IsNot(WALL_TILE)" are irrelevant, as long as they
// exist (Option::Some)
let output = combined_rules.resolve_match(&sublayout);
assert_eq!(output, Some(55));


  • Holds a list of rules, for efficiently evaluating a tile layout against multiple exclusive rules. Rules will be evaluated in the order they are added to the set, and will stop evaluating when a match is found
  • Checks tile layouts against a matcher instance, and uses the output to produce a value
  • Represents a grid of input data. What this data means is dependant on your application, and could realistically correlate to anything. It is assumed to be a 3x3 slice of tile data from a tile map
  • Holds the evaluation rules for a 3x3 grid of tiles. A 1x1 grid of tile matchers can be created by providing an array of TileStatus structs that are all TileStatus::Ignore, except for the value in the fifth position


  • Represents the value produced when a rule is matched. Will need to be inspected to find out the raw data value. This value will typically correspond to an index in a spritesheet, but there is no proscribed meaning - it will be application dependant and could represent some other index or meaning
  • Represents how a single tile location should be matched when evaluating a rule