Newer
Older
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
//! `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:
//!
//! ```text
//! Flat array data
//! [1, 2, 3, 4, 5, 6, 7, 8, 9]
//! ```
//!
//! ```text
//! 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.
//! ```rust
//! # use micro_autotile::{TileLayout, TileMatcher};
//! use micro_autotile::{AutoTileRule, AutoRuleSet};
//!
//! const WALL_TILE: i32 = 0;
//! const GROUND_TILE: i32 = 1;
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
//!
//! // 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
//!
//! ```rust
//! # use micro_autotile::{TileLayout, TileMatcher};
//! use micro_autotile::{AutoTileRule, AutoRuleSet};
//!
//! # fn main() {
//! use micro_autotile::{TileMatcher, TileStatus};
//! const WALL_TILE: i32 = 0;
//! const GROUND_TILE: i32 = 1;
//! const OTHER_TILE: i32 = 342;
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
//!
//! 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));
//! # }
//! ```
mod layout;
mod autotile;
mod output;
mod utils;
pub use layout::*;
pub use autotile::*;
pub use output::*;