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:
- Create a
TileMatcher
out ofTileStatus
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
- 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’schance
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
- An output value of
- 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:
e.g.
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);
assert_eq!(
match_1_x_1.resolve_match(&TileLayout::single(GROUND_TILE)),
Some(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([
Some(WALL_TILE), Some(WALL_TILE), Some(WALL_TILE),
Some(WALL_TILE), Some(GROUND_TILE), Some(WALL_TILE),
Some(WALL_TILE), Some(WALL_TILE), Some(WALL_TILE),
]);
assert_eq!(
match_1_x_1.resolve_match(&enclosed_ground),
Some(57)
);
// 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
assert!(TileMatcher::single(GROUND_TILE).matches(&enclosed_ground));
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([
Some(OTHER_TILE), Some(GROUND_TILE), Some(GROUND_TILE),
Some(WALL_TILE), Some(WALL_TILE), Some(OTHER_TILE),
Some(WALL_TILE), Some(WALL_TILE), Some(GROUND_TILE),
]);
// 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));
Structs
- 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 allTileStatus::Ignore
, except for the value in the fifth position
Enums
- 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