diff --git a/skills/create-tile-palette-or-rule-tiles/SKILL.md b/skills/create-tile-palette-or-rule-tiles/SKILL.md new file mode 100644 index 0000000..e3210e0 --- /dev/null +++ b/skills/create-tile-palette-or-rule-tiles/SKILL.md @@ -0,0 +1,12 @@ +--- +name: create-tile-palette-or-rule-tiles +description: Create a 2D tile palette from sprites or textures, or change which tile Unity paints based on surrounding tiles. Make sure to use this skill when the user wants to create tile palettes or self-connecting tiles, even if they don't explicitly ask about tile palettes or tile assets. +--- + +# Create a tile palette + +Pick the matching reference doc based on what the user wants, then follow it: + +- **Tile Palette** — [references/palette-create.md](references/palette-create.md) — when the user wants to organize tiles for 2D level design or create a new rectangular, hexagonal, isometric, or isometric Z as Y grid layout. +- **Blank/empty RuleTile** (no sprites provided) — [references/ruletile-createempty.md](references/ruletile-createempty.md) — when the user wants a blank RuleTile (rectangular grid), HexagonalRuleTile, or IsometricRuleTile for custom rule configuration and has not provided or referenced any sprites. +- **RuleTile built from existing sprites** (sprites, terrain art, edge tiles provided) — [references/ruletile-createfromsegment.md](references/ruletile-createfromsegment.md) — when the user wants tiles that autotile as they paint, or a RuleTile built from existing terrain or edge sprites so tiles connect correctly. diff --git a/skills/create-tile-palette-or-rule-tiles/references/hexagonalruletile.md b/skills/create-tile-palette-or-rule-tiles/references/hexagonalruletile.md new file mode 100644 index 0000000..7cc492b --- /dev/null +++ b/skills/create-tile-palette-or-rule-tiles/references/hexagonalruletile.md @@ -0,0 +1,71 @@ +# Skill: Advanced Hexagonal RuleTile Creation + +This skill provides a template and logic for creating complex `HexagonalRuleTiles`. Use this to programmatically generate or manually configure new Hexagonal RuleTiles for pointy-top hex grids. + +## Hexagonal Tile Rule Pattern (Pointy Top) + +A Hexagonal RuleTile uses 6 possible neighbor directions. These neighbors are defined as follows: + +### Neighbor Directions and Coordinate Mappings + +For a tile at `(x, y, 0)`, the 6 neighbors are at: +1. **North-West (NW)**: `(-1, 1, 0)` +2. **North (N)**: `(0, 1, 0)` +3. **East (E)**: `(1, 0, 0)` +4. **South-West (SW)**: `(-1, -1, 0)` +5. **South (S)**: `(0, -1, 0)` +6. **West (W)**: `(-1, 0, 0)` + +### Legend: +- `.`: **This** (Type 1) +- `X`: **Any** (Type 0) +- **Fixed**: Rule is applied exactly as defined. +- **MirrorX**: Rule is applied as defined and mirrored across the X-axis. + +## Full Tiling Rules Template + +The hexagonal tile set uses **MirrorX** and **Fixed** transforms. **Rotated** is not used in this specific set. + +| Rule ID | Neighbor Configurations (Pos: Type) | Transform | Description | +|:--------|:------------------------------------| :--- | :--- | +| 0 | NW:., N:., E:., SW:., S:., W:. | Fixed | Full 6-neighbor surrounded | +| 1 | NW:., W:., SW:., S:., E:. | MirrorX | 5-neighbor surrounded | +| 2 | NW:., W:., E:., SW:., N:. | MirrorX | 5-neighbor surrounded variant | +| 3 | N:., NW:., W:., SW:., S:. | MirrorX | 5-neighbor surrounded variant | +| 4 | W:., SW:., S:., E:. | Fixed | 4-neighbor connectivity | +| 5 | W:., NW:., N:., E:. | Fixed | 4-neighbor connectivity | +| 6 | NW:., W:., SW:., S:. | MirrorX | 4-neighbor corner/edge | +| 7 | NW:., W:., SW:., N:. | MirrorX | 4-neighbor corner/edge | +| 8 | NW:., SW:., W:., E:. | MirrorX | 4-neighbor bridge | +| 9 | NW:., W:., E:., S:. | MirrorX | 4-neighbor connectivity | +| 10 | NW:., SW:., S:., E:. | MirrorX | 4-neighbor connectivity | +| 11 | NW:., N:., S:., SW:. | Fixed | 4-neighbor vertical-ish | +| 12 | N:., NW:., W:., S:. | Fixed | 4-neighbor horizontal-ish | +| 13 | W:., SW:., S:. | MirrorX | 3-neighbor corner | +| 14 | NW:., W:., SW:. | MirrorX | 3-neighbor flat edge | +| 15 | W:., NW:., N:. | MirrorX | 3-neighbor corner | +| 16 | NW:., N:., S:. | MirrorX | 3-neighbor T-junction | +| 17 | W:., SW:., E:. | MirrorX | 3-neighbor T-junction | +| 18 | W:., N:., SW:. | MirrorX | 3-neighbor wide corner | +| 19 | E:., NW:., SW:. | MirrorX | 3-neighbor wide corner | +| 20 | NW:., W:., E:. | MirrorX | 3-neighbor wide corner | +| 21 | SW:., S:., NW:. | MirrorX | 3-neighbor variant | +| 22 | W:., NW:., S:. | MirrorX | 3-neighbor variant | +| 23 | NW:., W:. | Fixed | 2-neighbor edge | +| 24 | W:., SW:. | Fixed | 2-neighbor edge | +| 25 | SW:., S:. | Fixed | 2-neighbor edge | +| 26 | E:., S:. | Fixed | 2-neighbor edge | +| 27 | N:., E:. | Fixed | 2-neighbor edge | +| 28 | NW:., N:. | Fixed | 2-neighbor edge | +| 29 | W:., E:. | MirrorX | 2-neighbor opposite bridge | +| 30 | N:., SW:. | MirrorX | 2-neighbor long bridge | +| 31 | W:., S:. | MirrorX | 2-neighbor acute corner | +| 32 | E:., NW:. | MirrorX | 2-neighbor long bridge variant | +| 33 | N:., S:. | MirrorX | 2-neighbor vertical bridge | +| 34 | N:. | MirrorX | Single neighbor North | +| 35 | E:. | MirrorX | Single neighbor East | +| 36 | S:. | MirrorX | Single neighbor South | +| 37 | all 6 surrounding positions: X | Fixed | Default / No-neighbor | + +#### Usage +When applying this skill to a new texture set, first ensure the texture is sliced into sprites matching these 38 logical configurations. Then, run a script using the description to map the sprites to each rule. Ensure that HexagonalRuleTile has all 38 TilingRules listed in the template. \ No newline at end of file diff --git a/skills/create-tile-palette-or-rule-tiles/references/palette-create.md b/skills/create-tile-palette-or-rule-tiles/references/palette-create.md new file mode 100644 index 0000000..d89e0bf --- /dev/null +++ b/skills/create-tile-palette-or-rule-tiles/references/palette-create.md @@ -0,0 +1,38 @@ +# Tilemap Palette Creation + +Requires package: `com.unity.2d.tilemap` >=1.0.0 + +## Workflow + +### Step 1: Parameter Gathering +WAIT for the user to specify the following parameters if not already provided: +- **Palette Name**: The name of the asset. +- **Grid Type**: Rectangular, Hexagonal, or Isometric. +- **Cell Size**: Optional (defaults based on Grid Type). +- **Sort Axis**: Required for Isometric palettes. + +### Step 2: Asset Creation +Utilise `UnityEditor.Tilemaps.GridPaletteUtility.CreateNewPalette` to create the Tile Palette asset. + +## Branching Logic (Grid Types) + +### Path A: Rectangular +- Use **Automatic** cell sizing. Use cell size: `(1, 1, 0)` as a default. + +### Path B: Hexagonal +- Use cell size: `(0.8659766, 1, 1)`. + +### Path C: Isometric +- Use cell size: `(1, 0.5, 1)`. +- Use a **Custom Sort Axis** with value `(0, 0, 1)`. + +### Path D: Isometric Z As Y +- Use cell size: `(1, 0.5, 1)`. +- Use a **Custom Sort Axis** with value `(0, 0, 1)`. + +## Post-Creation +Ensure that there is a `GridPalette` as a sub-asset of the Tile Palette asset. + +## References + +Code Template: "../scripts/CreatePaletteTemplate.cs" \ No newline at end of file diff --git a/skills/create-tile-palette-or-rule-tiles/references/ruletile-createempty.md b/skills/create-tile-palette-or-rule-tiles/references/ruletile-createempty.md new file mode 100644 index 0000000..7d9ad39 --- /dev/null +++ b/skills/create-tile-palette-or-rule-tiles/references/ruletile-createempty.md @@ -0,0 +1,35 @@ +# Tilemap RuleTile Create Empty + +Use ONLY when the user wants a blank RuleTile, HexagonalRuleTile, or IsometricRuleTile for custom rule configuration AND has not provided or referenced any sprites. If the user mentions existing sprites, terrain art, edge tiles, or a tiles folder, use [ruletile-createfromsegment.md](ruletile-createfromsegment.md) instead, never this. + +Requires packages: `com.unity.2d.tilemap` >=1.0.0, `com.unity.2d.tilemap.extras` >=4.0.0 + +## Workflow + +### Step 1: Verify No Sprite Inputs +**WAIT** - Confirm that no Sprites or Spritesheets were specified by the user. This skill is only for empty RuleTiles. If there are Sprites or Spritesheets specified by the user, use [ruletile-createfromsegment.md](ruletile-createfromsegment.md) instead. + +### Step 2: Determine RuleTile Type +Identify which RuleTile type to create based on user request: +- **RuleTile**: Standard rectangular grid +- **HexagonalRuleTile**: Hexagonal grid layout +- **IsometricRuleTile**: Isometric grid layout + +### Step 3: Create Empty TilingRules +For each TilingRule, ensure the Sprite array has one `null` entry. + +## Branching Logic (RuleTile Types) + +### Path A: RuleTile +- Use template from [ruletile.md](ruletile.md). + +### Path B: HexagonalRuleTile +- Use template from [hexagonalruletile.md](hexagonalruletile.md). + +### Path C: IsometricRuleTile +- Create empty rules with appropriate neighbor positions for isometric layout. + +## Important Notes + +- **TilingRuleOutput.Neighbor.This**: Use to identify RuleTiles that are the same (matching neighbors). +- **TilingRuleOutput.Neighbor.NotThis**: Do NOT use unless explicitly specified by the user to ignore a Tile at a certain position. \ No newline at end of file diff --git a/skills/create-tile-palette-or-rule-tiles/references/ruletile-createfromsegment.md b/skills/create-tile-palette-or-rule-tiles/references/ruletile-createfromsegment.md new file mode 100644 index 0000000..8dba0b8 --- /dev/null +++ b/skills/create-tile-palette-or-rule-tiles/references/ruletile-createfromsegment.md @@ -0,0 +1,212 @@ +# Tilemap RuleTile Create From Segment + +Use when the user wants tiles that auto-tile (autotile) as they paint, wants a RuleTile built from existing terrain or edge sprites, or asks to make sprites "tile correctly" or "connect properly". Also converts sprite-segment-3x3grid output patterns into Unity RuleTile TilingRules: 3x3 grid text patterns (X, ., *) become TilingRule neighbor configurations, mapping '.' to 'This' rules and 'X' to 'DontCare', sorted by specificity (more 'This' rules first). Sprites must be provided as input. + +Requires packages: `com.unity.2d.tilemap` >=1.0.0, `com.unity.2d.tilemap.extras` >=4.0.0 + +## Purpose + +This skill creates Unity RuleTile TilingRules by first analyzing sprites using the `sprite-segment-3x3grid` skill, then converting the resulting text patterns into neighbor rule configurations. The user must specify Sprites or a Spritesheet input. + +## Workflow + +``` +Step 1: sprite-segment-3x3grid Step 2: Create TilingRules ++--------------------------+ +--------------------------+ +| Analyze each Sprite | | Parse text patterns | +| using color matching | -> | Map to neighbor rules | +| Output: text pattern | | Sort by specificity | ++--------------------------+ +--------------------------+ +``` + +**CRITICAL**: Always run the `sprite-segment-3x3grid` skill first on each Sprite to generate the text pattern. + +## Step 1: Apply sprite-segment-3x3grid (Required) + +For each Sprite in your texture, analyze it with `sprite-segment-3x3grid`: + +```csharp +string pattern = SpriteSegment3x3Grid.AnalyzeSpriteGrid(sprite, matchThreshold: 0.75f); +``` + +### Pattern Output Format + +Single-line text pattern: `[TopRow] / [MiddleRow] / [BottomRow]` + +Each row contains 3 characters separated by spaces: +- `X` - Cell does NOT meet match threshold +- `.` - Cell MEETS match threshold (matches center color) +- `*` - Center cell (the tile itself) + +Example: `X X X / X * X / . . .` + +## Step 2: Create TilingRules from Patterns + +### Symbol to Rule Mapping + +| Symbol | RuleTile Neighbor | Value | Behavior | +|--------|-------------------|-------|-----------------------------------------------| +| `.` | `This` | 1 | Neighbor must be an instance of this RuleTile | +| `X` | `DontCare` | 0 | Position not added to neighbors list | +| `*` | (center) | N/A | Ignored - represents the tile itself | + +**Note**: "DontCare" is not an explicit constant. A position is "don't care" when it is simply not included in the `m_Neighbors` and `m_NeighborPositions` lists. + +### Neighbor Position Mapping + +``` +Grid Layout: Unity Vector3Int positions: +[0] [1] [2] (Top) (-1,1,0) (0,1,0) (1,1,0) +[3] [*] [5] (Middle) (-1,0,0) --- (1,0,0) +[6] [7] [8] (Bottom) (-1,-1,0) (0,-1,0) (1,-1,0) +``` + +| Index | Position | Vector3Int | +|-------|--------------|----------------| +| 0 | Top-Left | `(-1, 1, 0)` | +| 1 | Top | `(0, 1, 0)` | +| 2 | Top-Right | `(1, 1, 0)` | +| 3 | Left | `(-1, 0, 0)` | +| 4 | Center | (skipped) | +| 5 | Right | `(1, 0, 0)` | +| 6 | Bottom-Left | `(-1, -1, 0)` | +| 7 | Bottom | `(0, -1, 0)` | +| 8 | Bottom-Right | `(1, -1, 0)` | + +## Pattern Filtering + +By default, detected patterns that do not match any entry in the Common Tile Patterns table are discarded. Only the 47 known patterns are kept. This prevents unexpected or noisy rules from appearing in the final RuleTile. + +If the user explicitly asks to keep non-standard patterns, set `filterToKnownPatterns: false` to bypass this filtering. + +## Deduplication + +Duplicate TilingRules with identical neighbor configurations are automatically removed. When multiple sprites produce the same pattern, only the first sprite encountered is kept. + +## Rule Sorting + +Rules are sorted by specificity (number of `This` rules) in descending order. More specific rules are evaluated first. + +| This Count | Example Pattern | Priority | +|------------|---------------------------------|----------| +| 8 | `. . . / . * . / . . .` | First | +| 5 | `. . . / . * . / X X X` | ... | +| 3 | `X X X / X * X / . . .` | ... | +| 0 | `X X X / X * X / X X X` | Last | + +## Sprite Assignment + +Each TilingRule is assigned at most 1 Sprite: +- Set `m_Output` to `TilingRuleOutput.OutputSprite.Single` +- Assign the sprite to `m_Sprites[0]` + +The RuleTile's `m_DefaultSprite` is set to the sprite from the bottom-most (least specific) rule. Since rules are sorted by specificity descending, this is the last rule in the list. This sprite is used when no TilingRule matches. + +## Algorithm Summary + +1. **For each Sprite**: Run `sprite-segment-3x3grid` and store the pattern +2. **Filter**: Discard patterns not in Common Tile Patterns (unless user opts out) +3. **Deduplicate**: Remove duplicate patterns (first sprite wins) +4. **Parse**: Split by ` / ` for rows, then by space for cells +5. **Extract Neighbors**: `.` -> add position with `This` (1); `X` -> skip +6. **Create TilingRule**: Populate `m_Neighbors` and `m_NeighborPositions` +7. **Assign Sprite**: Set single sprite output +8. **Sort**: Order by specificity descending +9. **Apply**: Add sorted rules to RuleTile asset +10. **Default Sprite**: Set `m_DefaultSprite` to the last (least specific) rule's sprite + +## Scripts + +Implementation and usage examples are in the `scripts/` folder: + +| File | Description | +|------|-------------| +| [`TilemapRuleTileCreateFromSegment.cs`](../scripts/TilemapRuleTileCreateFromSegment.cs) | Core implementation with `CreateRuleTileFromSprites`, `CreateTilingRuleFromPattern`, `ParsePattern`, and `ApplyRulesToTile` | +| [`RuleTileGenerator.cs`](../scripts/RuleTileGenerator.cs) | Editor window example (`Tools > Generate RuleTile from Sprites`) | +| [`ManualWorkflowExample.cs`](../scripts/ManualWorkflowExample.cs) | Manual two-step workflow for finer control over analysis | + +### Quick Usage + +```csharp +// Get sprites and create rules in one call +var rules = TilemapRuleTileCreateFromSegment.CreateRuleTileFromSprites( + sprites, matchThreshold: 0.75f, colorTolerance: 0.04f); + +// Apply to RuleTile +var ruleTile = ScriptableObject.CreateInstance(); +TilemapRuleTileCreateFromSegment.ApplyRulesToTile(ruleTile, rules); +``` + +## Common Tile Patterns + +| Tile Type | Pattern | This Count | +|------------------------------------------------------|----------------------------------|------------| +| Fully surrounded (inner tile) | `. . . / . * . / . . .` | 8 | +| Top left corner missing | `X . . / . * . / . . .` | 7 | +| Top right corner missing | `. . X / . * . / . . .` | 7 | +| Bottom left corner missing | `. . . / . * . / X . .` | 7 | +| Bottom right corner missing | `. . . / . * . / . . X` | 7 | +| Top corners missing | `X . X / . * . / . . .` | 6 | +| Bottom corners missing | `. . . / . * . / X . X` | 6 | +| Left corners missing | `X . . / . * . / X . .` | 6 | +| Right corners missing | `. . X / . * . / . . X` | 6 | +| Top Left and Bottom Right diagonal corners missing | `X . . / . * . / . . X` | 6 | +| Top Right and Bottom Left diagonal corners missing | `. . X / . * . / X . .` | 6 | +| Top Left, Top Right and Bottom Left corners missing | `X . X / . * . / X . .` | 5 | +| Top Left, Top Right and Bottom Right corners missing | `X . X / . * . / . . X` | 5 | +| Top Left, Bottom Left and Bottom Right corners missing | `X . . / . * . / X . X` | 5 | +| Top Right, Bottom Left and Bottom Right corners missing | `. . X / . * . / X . X` | 5 | +| Cross Section | `X . X / . * . / X . X` | 4 | +| Flat edge (Bottom) | `X X X / . * . / . . .` | 5 | +| Flat edge (Top) | `. . . / . * . / X X X` | 5 | +| Flat edge (Left) | `. . X / . * X / . . X` | 5 | +| Flat edge (Right) | `X . . / X * . / X . .` | 5 | +| L-Shape, Point Right | `X X X / . * . / . . X` | 4 | +| L-Shape, Point Bottom | `. . X / . * X / X . X` | 3 | +| L-Shape, Point Left | `X . . / . * . / X X X` | 4 | +| L-Shape, Point Top | `X . X / X * . / X . .` | 3 | +| L-Shape Inverse, Point Left | `X X X / . * . / X . .` | 4 | +| L-Shape Inverse, Point Bottom | `X . . / X * . / X . X` | 3 | +| L-Shape Inverse, Point Right | `. . X / . * . / X X X` | 4 | +| L-Shape Inverse, Point Top | `X . X / . * . / . . X` | 5 | +| T-Shape, face down | `X X X / . * . / X . X` | 3 | +| T-Shape, face top | `X . X / . * . / X X X` | 3 | +| T-Shape, face left | `X . X / . * X / X . X` | 3 | +| T-Shape, face right | `X . X / X * . / X . X` | 3 | +| Horizontal bridge | `X X X / . * . / X X X` | 2 | +| Vertical bridge | `X . X / X * X / X . X` | 2 | +| Corner piece, Bottom Right | `X X X / X * . / X . .` | 3 | +| Corner piece, Bottom Left | `X X X / . * X / . . X` | 3 | +| Corner piece, Top Left | `. . X / . * X / X X X` | 3 | +| Corner piece, Top Right | `X . . / X * . / X X X` | 3 | +| Edge end, Bottom Right | `X X X / X * . / X . X` | 2 | +| Edge end, Bottom Left | `X X X / . * X / X . X` | 2 | +| Edge end, Top Left | `X . X / . * X / X X X` | 2 | +| Edge end, Top Right | `X . X / X * . / X X X` | 2 | +| Single isolated tile, Left | `X X X / . * X / X X X` | 1 | +| Single isolated tile, Right | `X X X / X * . / X X X` | 1 | +| Single isolated tile, Top | `X . X / X * X / X X X` | 1 | +| Single isolated tile, Bottom | `X X X / X * X / X . X` | 1 | +| Center | `X X X / X * X / X X X` | 0 | + +## Notes + +- Always run `sprite-segment-3x3grid` first -- this skill depends on its output +- Rules are evaluated in order; first matching rule wins +- A rule with 0 `This` conditions matches any configuration (use as fallback) +- The center cell (`*`) is always ignored in neighbor calculations +- Unity's RuleTile supports up to 8 neighbors in a standard 3x3 grid +- For hexagonal or isometric grids, use `HexagonalRuleTile` or `IsometricRuleTile` + +## Prerequisites + +- **`sprite-segment-3x3grid` skill**: generates the input patterns. It ships in this + same plugin as `sprite-segment-3x3grid`, so it is always available — invoke + it rather than treating it as an unmet dependency. +- **com.unity.2d.tilemap.extras package**: Required for RuleTile class + +## See Also + +- `sprite-segment-3x3grid` - required prerequisite; generates the input patterns +- `Packages/com.unity.2d.tilemap.extras/Runtime/Tiles/RuleTile/RuleTile.cs` for RuleTile implementation +- Unity Manual: [Rule Tile](https://docs.unity3d.com/Packages/com.unity.2d.tilemap.extras@latest) diff --git a/skills/create-tile-palette-or-rule-tiles/references/ruletile.md b/skills/create-tile-palette-or-rule-tiles/references/ruletile.md new file mode 100644 index 0000000..7c0621a --- /dev/null +++ b/skills/create-tile-palette-or-rule-tiles/references/ruletile.md @@ -0,0 +1,90 @@ +# Skill: Advanced RuleTile Creation + +This skill provides a template and logic for creating complex `RuleTiles`. Use this guide to programmatically generate or manually configure new RuleTiles that follow consistent connectivity patterns. + +## Rule Tile Rule Pattern + +The following rules define the connectivity for a standard 2D top-down layout. Each rule checks for the presence (.) or absence (X) of a tile in its 3x3 neighborhood. Depending on the Spritesheets used, the Rule Tile can use Fixed Rule Transforms or Rotated Rule Transforms. Use Rotated Rule Transforms to reduce the number of Tiling Rules used. + +## Legend +- `.`: **This** (Tile must be present) +- `X`: **Any** (Does not matter) +- `*`: The tile itself (Center) + +### Tiling Rules Template for Fixed Rule Tiles + +| Rule ID | Neighbors (Top to Bottom, Left to Right) | Rule Transform | Description | +|:--------|:-----------------------------------------|:---------------|:--------------------------------------------------------| +| 0 | `. . .` / `. * .` / `. . .` | Fixed | Fully surrounded (inner tile) | +| 1 | `X . .` / `. * .` / `. . .` | Fixed | Top left corner missing | +| 2 | `. . X` / `. * .` / `. . .` | Fixed | Top right corner missing | +| 3 | `. . .` / `. * .` / `X . .` | Fixed | Bottom left corner missing | +| 4 | `. . .` / `. * .` / `. . X` | Fixed | Bottom right corner missing | +| 5 | `X . X` / `. * .` / `. . .` | Fixed | Top corners missing | +| 6 | `. . .` / `. * .` / `X . X` | Fixed | Bottom corners missing | +| 7 | `X . .` / `. * .` / `X . .` | Fixed | Left corners missing | +| 8 | `. . X` / `. * .` / `. . X` | Fixed | Right corners missing | +| 9 | `X . .` / `. * .` / `. . X` | Fixed | Top Left and Bottom Right diagonal corners missing | +| 10 | `. . X` / `. * .` / `X . .` | Fixed | Top Right and Bottom Left diagonal corners missing | +| 11 | `X . X` / `. * .` / `X . .` | Fixed | Top Left, Top Right and Bottom Left corners missing | +| 12 | `X . X` / `. * .` / `. . X` | Fixed | Top Left, Top Right and Bottom Right corners missing | +| 13 | `X . .` / `. * .` / `X . X` | Fixed | Top Left, Bottom Left and Bottom Right corners missing | +| 14 | `. . X` / `. * .` / `X . X` | Fixed | Top Right, Bottom Left and Bottom Right corners missing | +| 15 | `X . X` / `. * .` / `X . X` | Fixed | Cross Section | +| 16 | `X X X` / `. * .` / `. . .` | Fixed | Flat edge (Bottom) | +| 17 | `. . .` / `. * .` / `X X X` | Fixed | Flat edge (Top) | +| 18 | `. . X` / `. * X` / `. . X` | Fixed | Flat edge (Left) | +| 19 | `X . .` / `X * .` / `X . .` | Fixed | Flat edge (Right) | +| 20 | `X X X` / `. * .` / `. . X` | Fixed | L-Shape, Point Right | +| 21 | `. . X` / `. * X` / `X . X` | Fixed | L-Shape, Point Bottom | +| 22 | `X . .` / `. * .` / `X X X` | Fixed | L-Shape, Point Left | +| 23 | `X . X` / `X * .` / `X . .` | Fixed | L-Shape, Point Top | +| 24 | `X X X` / `. * .` / `X . .` | Fixed | L-Shape Inverse, Point Left | +| 25 | `X . .` / `X * .` / `X . X` | Fixed | L-Shape Inverse, Point Bottom | +| 26 | `. . X` / `. * .` / `X X X` | Fixed | L-Shape Inverse, Point Right | +| 27 | `X . X` / `. * .` / `. . X` | Fixed | L-Shape Inverse, Point Top | +| 28 | `X X X` / `. * .` / `X . X` | Fixed | T-Shape, face down | +| 29 | `X . X` / `. * .` / `X X X` | Fixed | T-Shape, face top | +| 30 | `X . X` / `. * X` / `X . X` | Fixed | T-Shape, face left | +| 31 | `X . X` / `X * .` / `X . X` | Fixed | T-Shape, face right | +| 32 | `X X X` / `. * .` / `X X X` | Fixed | Horizontal bridge | +| 33 | `X . X` / `X * X` / `X . X` | Fixed | Vertical bridge | +| 34 | `X X X` / `X * .` / `X . .` | Fixed | Corner piece, Bottom Right | +| 35 | `X X X` / `. * X` / `. . X` | Fixed | Corner piece, Bottom Left | +| 36 | `. . X` / `. * X` / `X X X` | Fixed | Corner piece, Top Left | +| 37 | `X . .` / `X * .` / `X X X` | Fixed | Corner piece, Top Right | +| 38 | `X X X` / `X * .` / `X . X` | Fixed | Edge end, Bottom Right | +| 39 | `X X X` / `. * X` / `X . X` | Fixed | Edge end, Bottom Left | +| 40 | `X . X` / `. * X` / `X X X` | Fixed | Edge end, Top Left | +| 41 | `X . X` / `X * .` / `X X X` | Fixed | Edge end, Top Right | +| 42 | `X X X` / `. * X` / `X X X` | Fixed | Single isolated tile, Left | +| 43 | `X X X` / `X * .` / `X X X` | Fixed | Single isolated tile, Right | +| 44 | `X . X` / `X * X` / `X X X` | Fixed | Single isolated tile, Top | +| 45 | `X X X` / `X * X` / `X . X` | Fixed | Single isolated tile, Bottom | +| 46 | `X X X` / `X * X` / `X X X` | Fixed | Center | + +#### Usage +When applying this skill to a new texture set, first ensure the texture is sliced into sprites matching these 47 logical configurations. Then, run a script using the description to map the sprites to each rule. Ensure that RuleTile has all 47 TilingRules listed in the template. + +### Tiling Rules Template for Rotated Rule Tiles + +| Rule ID | Neighbors (Top to Bottom, Left to Right) | Rule Transform | Description | +|:--------|:-----------------------------------------|:---------------|:------------------------------| +| 0 | `. . .` / `. * .` / `. . .` | Fixed | Fully surrounded (inner tile) | +| 1 | `X . .` / `. * .` / `. . .` | Rotated | One corner missing | +| 2 | `X . X` / `. * .` / `. . .` | Rotated | Two corners missing | +| 3 | `X . .` / `. * .` / `. . X` | Rotated | Two diagonal corners missing | +| 4 | `X . X` / `. * .` / `. . X` | Rotated | Three corners missing | +| 5 | `X . X` / `. * .` / `X . X` | Fixed | Cross Section | +| 6 | `X X X` / `. * .` / `. . .` | Rotated | Flat edge | +| 7 | `X X X` / `. * .` / `. . X` | Rotated | L-Shape | +| 8 | `X X X` / `. * .` / `X . .` | Rotated | L-Shape Inverse | +| 9 | `X X X` / `. * .` / `X . X` | Rotated | Isolated edge | +| 10 | `X X X` / `. * .` / `X X X` | Rotated | Vertical/Horizontal bridge | +| 11 | `X X X` / `X * .` / `X . .` | Rotated | Corner piece | +| 12 | `X X X` / `X * .` / `X . X` | Rotated | Edge end | +| 13 | `X X X` / `. * X` / `X X X` | Rotated | Single isolated tile | +| 14 | `X X X` / `X * X` / `X X X` | Fixed | Center | + +#### Usage +When applying this skill to a new texture set, first ensure the texture is sliced into sprites matching these 15 logical configurations. Then, run a script using the description to map the sprites to each rule. Ensure that RuleTile has all 15 TilingRules listed in the template. diff --git a/skills/create-tile-palette-or-rule-tiles/scripts/CreatePaletteTemplate.cs b/skills/create-tile-palette-or-rule-tiles/scripts/CreatePaletteTemplate.cs new file mode 100644 index 0000000..461f3ec --- /dev/null +++ b/skills/create-tile-palette-or-rule-tiles/scripts/CreatePaletteTemplate.cs @@ -0,0 +1,148 @@ +// Code template for creating Tile Palettes using GridPaletteUtility.CreateNewPalette +// Reference: Packages/com.unity.2d.tilemap/Editor/GridPaletteUtility.cs + +using UnityEngine; +using UnityEditor.Tilemaps; + +namespace TilePaletteCreation +{ + public static class CreatePaletteTemplate + { + // CreateNewPalette signature: + // public static GameObject CreateNewPalette( + // string folderPath, + // string name, + // GridLayout.CellLayout layout, + // GridPalette.CellSizing cellSizing, + // Vector3 cellSize, + // GridLayout.CellSwizzle swizzle, + // TransparencySortMode sortMode, + // Vector3 sortAxis) + + /// + /// Creates a rectangular tile palette with automatic cell sizing. + /// + public static GameObject CreateRectangularPalette(string folderPath, string paletteName) + { + return GridPaletteUtility.CreateNewPalette( + folderPath, + paletteName, + GridLayout.CellLayout.Rectangle, + GridPalette.CellSizing.Automatic, + new Vector3(1f, 1f, 0f), + GridLayout.CellSwizzle.XYZ, + TransparencySortMode.Default, + new Vector3(0f, 0f, 1f)); + } + + /// + /// Creates a hexagonal tile palette (point-top orientation). + /// Uses the standard hexagonal cell size. + /// + public static GameObject CreateHexagonalPalette(string folderPath, string paletteName) + { + return GridPaletteUtility.CreateNewPalette( + folderPath, + paletteName, + GridLayout.CellLayout.Hexagon, + GridPalette.CellSizing.Manual, + new Vector3(0.8659766f, 1f, 1f), + GridLayout.CellSwizzle.XYZ, + TransparencySortMode.Default, + new Vector3(0f, 0f, 1f)); + } + + /// + /// Creates an isometric tile palette with custom sort axis. + /// + public static GameObject CreateIsometricPalette(string folderPath, string paletteName) + { + return GridPaletteUtility.CreateNewPalette( + folderPath, + paletteName, + GridLayout.CellLayout.Isometric, + GridPalette.CellSizing.Manual, + new Vector3(1f, 0.5f, 1f), + GridLayout.CellSwizzle.XYZ, + TransparencySortMode.CustomAxis, + new Vector3(0f, 0f, 1f)); + } + + /// + /// Creates an isometric Z as Y tile palette. + /// Used when Z position should affect Y sorting. + /// + public static GameObject CreateIsometricZAsYPalette(string folderPath, string paletteName) + { + return GridPaletteUtility.CreateNewPalette( + folderPath, + paletteName, + GridLayout.CellLayout.IsometricZAsY, + GridPalette.CellSizing.Manual, + new Vector3(1f, 0.5f, 1f), + GridLayout.CellSwizzle.XYZ, + TransparencySortMode.CustomAxis, + new Vector3(0f, 0f, 1f)); + } + + /// + /// Creates a tile palette with fully customizable parameters. + /// + /// Project-relative folder path (e.g., "Assets/Palettes") + /// Name of the palette asset + /// Grid cell layout type + /// Automatic or Manual cell sizing + /// Size of each cell in the grid + /// Cell coordinate swizzle + /// Transparency sort mode for rendering + /// Custom sort axis (used when sortMode is CustomAxis) + /// The created palette GameObject, or null if creation failed + public static GameObject CreateCustomPalette( + string folderPath, + string paletteName, + GridLayout.CellLayout layout, + GridPalette.CellSizing cellSizing, + Vector3 cellSize, + GridLayout.CellSwizzle swizzle, + TransparencySortMode sortMode, + Vector3 sortAxis) + { + return GridPaletteUtility.CreateNewPalette( + folderPath, + paletteName, + layout, + cellSizing, + cellSize, + swizzle, + sortMode, + sortAxis); + } + } +} + +// Parameter Reference: +// +// GridLayout.CellLayout: +// - Rectangle: Standard rectangular grid +// - Hexagon: Hexagonal grid +// - Isometric: Isometric grid +// - IsometricZAsY: Isometric grid where Z position affects Y sorting +// +// GridPalette.CellSizing: +// - Automatic: Cell size determined automatically from sprites +// - Manual: Cell size specified explicitly +// +// GridLayout.CellSwizzle: +// - XYZ: Standard coordinate system +// - XZY, YXZ, YZX, ZXY, ZYX: Various coordinate swizzles +// +// TransparencySortMode: +// - Default: Use default sorting +// - Perspective: Sort by perspective +// - Orthographic: Sort orthographically +// - CustomAxis: Sort along a custom axis +// +// Recommended Cell Sizes: +// - Rectangular: (1, 1, 0) with Automatic sizing +// - Hexagonal: (0.8659766, 1, 1) +// - Isometric: (1, 0.5, 1) diff --git a/skills/create-tile-palette-or-rule-tiles/scripts/ManualWorkflowExample.cs b/skills/create-tile-palette-or-rule-tiles/scripts/ManualWorkflowExample.cs new file mode 100644 index 0000000..3420c8c --- /dev/null +++ b/skills/create-tile-palette-or-rule-tiles/scripts/ManualWorkflowExample.cs @@ -0,0 +1,63 @@ +using System.Collections.Generic; +using System.Linq; +using UnityEngine; + +/// +/// Example: Manual two-step workflow for more control over the analysis step. +/// +public static class ManualWorkflowExample +{ + public static void RunManualWorkflow(List sprites, RuleTile myRuleTile, + bool filterToKnownPatterns = true) + { + // Step 1: Run sprite-segment-3x3grid on each sprite, filter, and deduplicate + var seenPatterns = new HashSet(); + var uniquePatterns = new List<(string pattern, Sprite sprite)>(); + + foreach (var sprite in sprites) + { + // Apply sprite-segment-3x3grid analysis + string pattern = SpriteSegment3x3Grid.AnalyzeSpriteGrid( + sprite, + matchThreshold: 0.75f, + colorTolerance: 0.04f + ); + + // Discard patterns not in the known set + if (filterToKnownPatterns && + !TilemapRuleTileCreateFromSegment.IsKnownPattern(pattern)) + { + Debug.Log($"Sprite '{sprite.name}' skipped (unknown pattern: {pattern})"); + continue; + } + + // Skip duplicates + if (seenPatterns.Contains(pattern)) + { + Debug.Log($"Sprite '{sprite.name}' skipped (duplicate pattern: {pattern})"); + continue; + } + + seenPatterns.Add(pattern); + Debug.Log($"Sprite '{sprite.name}' pattern: {pattern}"); + uniquePatterns.Add((pattern, sprite)); + } + + // Step 2: Create TilingRules from unique patterns + var rules = new List<(RuleTile.TilingRule rule, int specificity)>(); + foreach (var (pattern, sprite) in uniquePatterns) + { + var result = TilemapRuleTileCreateFromSegment.CreateTilingRuleFromPattern(pattern, sprite); + rules.Add(result); + } + + // Sort by specificity + rules.Sort((a, b) => b.specificity.CompareTo(a.specificity)); + + // Apply to RuleTile + var sortedRules = rules.Select(r => r.rule).ToList(); + TilemapRuleTileCreateFromSegment.ApplyRulesToTile(myRuleTile, sortedRules); + + Debug.Log($"Created {sortedRules.Count} unique rules from {sprites.Count} sprites"); + } +} diff --git a/skills/create-tile-palette-or-rule-tiles/scripts/RuleTileGenerator.cs b/skills/create-tile-palette-or-rule-tiles/scripts/RuleTileGenerator.cs new file mode 100644 index 0000000..d5069a5 --- /dev/null +++ b/skills/create-tile-palette-or-rule-tiles/scripts/RuleTileGenerator.cs @@ -0,0 +1,61 @@ +using System.Linq; +using UnityEditor; +using UnityEngine; + +public class RuleTileGenerator : EditorWindow +{ + float m_MatchThreshold = 0.75f; + + [MenuItem("Tools/Generate RuleTile from Sprites")] + static void ShowWindow() + { + GetWindow("RuleTile Generator"); + } + + void OnGUI() + { + GUILayout.Label("Match Threshold", EditorStyles.boldLabel); + m_MatchThreshold = EditorGUILayout.Slider(m_MatchThreshold, 0.5f, 0.9f); + + if (GUILayout.Button("Generate from Selected Texture")) + GenerateRuleTile(); + } + + void GenerateRuleTile() + { + var texture = Selection.activeObject as Texture2D; + if (texture == null) + { + Debug.LogError("Please select a Texture2D asset"); + return; + } + + // Get sprites from texture + string path = AssetDatabase.GetAssetPath(texture); + var sprites = AssetDatabase.LoadAllAssetsAtPath(path).OfType().ToList(); + + if (sprites.Count == 0) + { + Debug.LogError("No sprites found in texture. Ensure texture is sliced."); + return; + } + + // Complete workflow: sprite-segment-3x3grid -> TilingRules + var rules = TilemapRuleTileCreateFromSegment.CreateRuleTileFromSprites( + sprites, + matchThreshold: m_MatchThreshold + ); + + // Create and save RuleTile asset + var ruleTile = ScriptableObject.CreateInstance(); + TilemapRuleTileCreateFromSegment.ApplyRulesToTile(ruleTile, rules); + + string savePath = path.Replace(".png", "_RuleTile.asset") + .Replace(".jpg", "_RuleTile.asset"); + AssetDatabase.CreateAsset(ruleTile, savePath); + AssetDatabase.SaveAssets(); + + Debug.Log($"Created RuleTile with {rules.Count} rules at {savePath}"); + Selection.activeObject = ruleTile; + } +} diff --git a/skills/create-tile-palette-or-rule-tiles/scripts/TilemapRuleTileCreateFromSegment.cs b/skills/create-tile-palette-or-rule-tiles/scripts/TilemapRuleTileCreateFromSegment.cs new file mode 100644 index 0000000..daadc0e --- /dev/null +++ b/skills/create-tile-palette-or-rule-tiles/scripts/TilemapRuleTileCreateFromSegment.cs @@ -0,0 +1,225 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using UnityEngine; + +/// +/// Creates RuleTile TilingRules by first applying sprite-segment-3x3grid analysis, +/// then converting the text patterns to neighbor configurations. +/// +public static class TilemapRuleTileCreateFromSegment +{ + /// + /// Grid position to Unity neighbor Vector3Int mapping. + /// Index 4 (center) is not included as it's not a neighbor. + /// + static readonly Dictionary k_GridIndexToPosition = new() + { + { 0, new Vector3Int(-1, 1, 0) }, // Top-Left + { 1, new Vector3Int(0, 1, 0) }, // Top + { 2, new Vector3Int(1, 1, 0) }, // Top-Right + { 3, new Vector3Int(-1, 0, 0) }, // Left + // 4 is center, skipped + { 5, new Vector3Int(1, 0, 0) }, // Right + { 6, new Vector3Int(-1, -1, 0) }, // Bottom-Left + { 7, new Vector3Int(0, -1, 0) }, // Bottom + { 8, new Vector3Int(1, -1, 0) }, // Bottom-Right + }; + + /// + /// The 47 known tile patterns from the Common Tile Patterns table. + /// Patterns not in this set are discarded by default. + /// + static readonly HashSet k_KnownPatterns = new() + { + ". . . / . * . / . . .", // Fully surrounded + "X . . / . * . / . . .", // Top left corner missing + ". . X / . * . / . . .", // Top right corner missing + ". . . / . * . / X . .", // Bottom left corner missing + ". . . / . * . / . . X", // Bottom right corner missing + "X . X / . * . / . . .", // Top corners missing + ". . . / . * . / X . X", // Bottom corners missing + "X . . / . * . / X . .", // Left corners missing + ". . X / . * . / . . X", // Right corners missing + "X . . / . * . / . . X", // Top Left and Bottom Right diagonal + ". . X / . * . / X . .", // Top Right and Bottom Left diagonal + "X . X / . * . / X . .", // Top Left, Top Right, Bottom Left + "X . X / . * . / . . X", // Top Left, Top Right, Bottom Right + "X . . / . * . / X . X", // Top Left, Bottom Left, Bottom Right + ". . X / . * . / X . X", // Top Right, Bottom Left, Bottom Right + "X . X / . * . / X . X", // Cross Section + "X X X / . * . / . . .", // Flat edge (Bottom) + ". . . / . * . / X X X", // Flat edge (Top) + ". . X / . * X / . . X", // Flat edge (Left) + "X . . / X * . / X . .", // Flat edge (Right) + "X X X / . * . / . . X", // L-Shape, Point Right + ". . X / . * X / X . X", // L-Shape, Point Bottom + "X . . / . * . / X X X", // L-Shape, Point Left + "X . X / X * . / X . .", // L-Shape, Point Top + "X X X / . * . / X . .", // L-Shape Inverse, Point Left + "X . . / X * . / X . X", // L-Shape Inverse, Point Bottom + ". . X / . * . / X X X", // L-Shape Inverse, Point Right + "X . X / . * . / . . X", // L-Shape Inverse, Point Top + "X X X / . * . / X . X", // T-Shape, face down + "X . X / . * . / X X X", // T-Shape, face top + "X . X / . * X / X . X", // T-Shape, face left + "X . X / X * . / X . X", // T-Shape, face right + "X X X / . * . / X X X", // Horizontal bridge + "X . X / X * X / X . X", // Vertical bridge + "X X X / X * . / X . .", // Corner piece, Bottom Right + "X X X / . * X / . . X", // Corner piece, Bottom Left + ". . X / . * X / X X X", // Corner piece, Top Left + "X . . / X * . / X X X", // Corner piece, Top Right + "X X X / X * . / X . X", // Edge end, Bottom Right + "X X X / . * X / X . X", // Edge end, Bottom Left + "X . X / . * X / X X X", // Edge end, Top Left + "X . X / X * . / X X X", // Edge end, Top Right + "X X X / . * X / X X X", // Single isolated tile, Left + "X X X / X * . / X X X", // Single isolated tile, Right + "X . X / X * X / X X X", // Single isolated tile, Top + "X X X / X * X / X . X", // Single isolated tile, Bottom + "X X X / X * X / X X X", // Center + }; + + /// + /// Creates a RuleTile from sprites by first running sprite-segment-3x3grid analysis. + /// This is the main entry point that implements the complete workflow. + /// Duplicate patterns are automatically removed (first sprite wins). + /// Patterns not in the known 47 Common Tile Patterns are discarded by default. + /// + /// Sprites to analyze and create rules for + /// Match threshold for sprite-segment-3x3grid (0.5, 0.75, or 0.9) + /// Color tolerance for sprite-segment-3x3grid + /// If true (default), discard patterns not in Common Tile Patterns + /// Sorted list of unique TilingRules (most specific first) + public static List CreateRuleTileFromSprites( + IEnumerable sprites, + float matchThreshold = 0.75f, + float colorTolerance = 0.04f, + bool filterToKnownPatterns = true) + { + var rules = new List<(RuleTile.TilingRule rule, int specificity, string pattern)>(); + var seenPatterns = new HashSet(); + + foreach (var sprite in sprites) + { + // Step 1: Always run sprite-segment-3x3grid first + string pattern = SpriteSegment3x3Grid.AnalyzeSpriteGrid(sprite, matchThreshold, colorTolerance); + + // Step 2: Discard patterns not in the known set + if (filterToKnownPatterns && !k_KnownPatterns.Contains(pattern)) + { + Debug.Log($"Sprite '{sprite.name}' skipped (unknown pattern: {pattern})"); + continue; + } + + // Step 3: Skip duplicate patterns (keep first sprite only) + if (seenPatterns.Contains(pattern)) + continue; + + seenPatterns.Add(pattern); + + // Step 4: Create TilingRule from the pattern + var (rule, specificity) = CreateTilingRuleFromPattern(pattern, sprite); + rules.Add((rule, specificity, pattern)); + } + + // Sort by specificity (descending) - more 'This' rules first + rules.Sort((a, b) => b.specificity.CompareTo(a.specificity)); + + return rules.Select(r => r.rule).ToList(); + } + + /// + /// Returns true if the pattern is one of the 47 known Common Tile Patterns. + /// + public static bool IsKnownPattern(string pattern) => k_KnownPatterns.Contains(pattern); + + /// + /// Creates a single TilingRule from a sprite-segment-3x3grid output pattern. + /// + /// Pattern string from sprite-segment-3x3grid, e.g., "X X X / X * X / . . ." + /// Sprite to assign to this rule + /// Tuple of (TilingRule, specificity count) + public static (RuleTile.TilingRule rule, int specificity) CreateTilingRuleFromPattern(string pattern, Sprite sprite) + { + var cells = ParsePattern(pattern); + var neighbors = new List(); + var neighborPositions = new List(); + int specificity = 0; + + for (int i = 0; i < 9; i++) + { + // Skip center cell + if (i == 4) + continue; + + string cell = cells[i]; + + // Map '.' to 'This' rule + // Map 'X' to DontCare (don't add position) + if (cell == ".") + { + neighbors.Add(RuleTile.TilingRuleOutput.Neighbor.This); + neighborPositions.Add(k_GridIndexToPosition[i]); + specificity++; + } + // 'X' is implicitly DontCare - position not added + } + + var rule = new RuleTile.TilingRule + { + m_Neighbors = neighbors, + m_NeighborPositions = neighborPositions, + m_Sprites = new[] { sprite }, + m_Output = RuleTile.TilingRuleOutput.OutputSprite.Single, + m_ColliderType = Tile.ColliderType.Sprite, + m_RuleTransform = RuleTile.TilingRuleOutput.Transform.Fixed, + }; + + return (rule, specificity); + } + + /// + /// Parses a sprite-segment-3x3grid output pattern into an array of 9 cell values. + /// + /// Pattern string, e.g., "X X X / X * X / . . ." + /// Array of 9 cell strings in order: top-left to bottom-right + public static string[] ParsePattern(string pattern) + { + var rows = pattern.Split(new[] { " / " }, StringSplitOptions.None); + if (rows.Length != 3) + throw new ArgumentException($"Pattern must have 3 rows, got {rows.Length}: {pattern}"); + + var cells = new string[9]; + for (int row = 0; row < 3; row++) + { + var rowCells = rows[row].Split(' '); + if (rowCells.Length != 3) + throw new ArgumentException($"Row {row} must have 3 cells, got {rowCells.Length}: {rows[row]}"); + + for (int col = 0; col < 3; col++) + { + cells[row * 3 + col] = rowCells[col]; + } + } + + return cells; + } + + /// + /// Applies generated rules to a RuleTile asset. + /// Sets the default sprite to the last rule's sprite (least specific / bottom-most match). + /// + /// Target RuleTile to configure + /// Sorted list of TilingRules to apply + public static void ApplyRulesToTile(RuleTile ruleTile, List rules) + { + ruleTile.m_TilingRules.Clear(); + ruleTile.m_TilingRules.AddRange(rules); + + // Use the bottom-most (least specific) rule's sprite as the default + if (rules.Count > 0 && rules[rules.Count - 1].m_Sprites.Length > 0) + ruleTile.m_DefaultSprite = rules[rules.Count - 1].m_Sprites[0]; + } +}