---
title: "Тайловые сетки и устройство 2D-миров: от классики до современных редакторов уровней"
description: "Как устроены тайловые сетки в 2D-играх: расчёт экономии памяти, слои сцены, битовые маски автотайлинга на 16 и 47 клеток, отсечение невидимых клеток камеры и проверенный код поиска клетки и автотайлинга на JavaScript."
slug: tajlovye-setki-i-ustrojstvo-2d-mirov
date: 2026-10-29
updated: 2026-11-02
scheduled_at: "2026-10-29T09:00:00+03:00"
rubric: ustrojstvo-2d-igr
tags: ["tajly", "redaktor-urovnej", "novichkam", "profi", "2d-dvizhok"]
reading_time: 9
draft: false
summary: "Тайловая сетка собирает 2D-уровень из многократно переиспользуемых квадратных клеток фиксированного размера (16×16, 32×32 или 64×64 пикселя) по номерам из единого набора-атласа. Такой подход сокращает объём данных карты в сотни раз по сравнению с цельной картинкой, разделяет сцену на независимые слои (фон, твёрдые платформы, триггеры опасности, интерфейс) и позволяет отрисовывать в каждом кадре только клетки внутри видимого окна камеры."
---

## Коротко

Тайловая сетка (Tilemap) — это способ построения 2D-уровня из повторяющихся квадратных или прямоугольных блоков фиксированного размера (например, `16×16` или `32×32` пикселя), где карта хранится как таблица числовых индексов, а сама графика загружается один раз в общей картинке — наборе тайлов (Tileset). Это экономит оперативную и видеопамять, ускоряет расчёт столкновений до одного обращения по индексу ячейки и позволяет собирать протяжённые уровни из десятков тысяч клеток за минуты. В статье разобраны исторические ограничения видеопамяти, послойная структура 2D-сцены, формулы битовых масок автотайлинга на 16 и 47 вариантов и проверенный в Node.js код поиска клетки, автотайлинга и отсечения камеры.

## Содержание

1. Почему 2D-миры собирают из клеток, а не рисуют одной картинкой
2. Как экономили память в классических 8-битных играх
3. Послойная архитектура 2D-уровня: от фона до физических коллайдеров
4. Автотайлинг по битовым маскам: формулы на 16 и 47 клеток
5. Одномерный массив карты и отсечение невидимых клеток камеры
6. Практический сценарий: участок уровня из тайлов в коде на JavaScript
7. Когда вам это не подойдёт
8. Чек-лист проверки 2D-уровня перед запуском

## Почему 2D-миры собирают из клеток, а не рисуют одной картинкой

Цельная растровая картинка длинного 2D-уровня занимает сотни мегабайт видеопамяти и не содержит информации о том, где находятся твёрдые стены, а где — пустой воздух. Если уровень платформера имеет ширину `10 000` пикселей и высоту `2 000` пикселей, то один несжатый буфер изображения в формате RGBA (4 байта на пиксель) потребует ровно:

```text
Объём RGBA = 10 000 × 2 000 × 4 байта = 80 000 000 байт ≈ 76,29 МБ
```

При разбиении того же поля `10 000 × 2 000` пикселей на квадратные клетки (тайлы) размером `32×32` пикселя получается сетка из `313` столбцов и `63` строк — всего `19 719` ячеек. Если хранить номер клетки в 2-байтовом целом числе (`Uint16`, до `65 535` видов клеток), вся геометрия уровня займёт `39 438` байт (около `38,5 КБ`), то есть **в 2 029 раз меньше**, чем одна полноразмерная текстура.

Для школьника или начинающего автора тайловая сетка работает как лист тетради в клетку и коробка с деталями конструктора. Вместо того чтобы заново рисовать каждый кирпич стены или каждый кусок травы, автор один раз берёт готовый блок «платформа», «монета» или «шипы» и расставляет его по клеткам с точными координатами `X` и `Y`.

## Как экономили память в классических 8-битных играх

Тайловая графика появилась в конце 1970-х и начале 1980-х годов как аппаратное решение для игровых автоматов и домашних приставок с жёстким лимитом оперативной памяти. В аркаде *Galaxian* (Namco, 1979 год) и консоли Nintendo Entertainment System / Famicom (выход в Японии — 15 июля 1983 года) видеопроцессор (PPU Ricoh 2C02) вообще не имел кадрового буфера на весь экран и собирал изображение на лету из фоновой сетки `32×30` тайлов размером `8×8` пикселей (источник: техническая документация архитектуры NES PPU на портале *NESdev Wiki*, `nesdev.org/wiki/PPU_nametables`, проверено: октябрь 2026).

Вся таблица имен экрана (Nametable) на NES занимала ровно `960` байт (по 1 байту на каждую из `960` клеток `8×8` при разрешении `256×240` пикселей) плюс `64` байта таблицы цветовых атрибутов — итого `1 024` байта (`1 КБ`) видеопамяти на целый экран. В игре *Super Mario Bros.* (Nintendo, 1985 год; картридж NROM объёмом `32 КБ` PRG-ROM для кода и данных уровней и `8 КБ` CHR-ROM для графики спрайтов и тайлов, проверено: октябрь 2026) облака и кусты даже использовали одни и те же графические клетки `8×8` из CHR-ROM, отличаясь только номером палитры (бело-голубая для неба, зелёная для земли).

В современных браузерных 2D-движках, работающих поверх WebGPU и WebGL2, гигабайты памяти давно доступны, но принцип атласа клеток по-прежнему даёт два инженерных преимущества:
1. **Один вызов отрисовки (Draw Call) на слой:** видеокарта берёт координаты всех видимых клеток из одного текстурного атласа и рисует их за один проход шейдера без переключения текстур.
2. **Мгновенный поиск препятствий:** чтобы узнать, стоит ли персонаж на твёрдой платформе в точке `(x, y)`, движку не нужно перебирать тысячи объектов — достаточно разделить координаты на размер клетки:

```text
col = floor(x / tileSize)
row = floor(y / tileSize)
```

## Послойная архитектура 2D-уровня: от фона до физических коллайдеров

Профессиональный 2D-уровень никогда не сваливает декорации, стены и ловушки в одну плоскую сетку, а разделяет их на 4–5 независимых слоёв по порядку отрисовки (Z-order) и физическому поведению. В таблице ниже приведена стандартная схема слоёв для платформера или приключенческой игры с видом сверху.

Таблица: Стандартная структура слоёв 2D-сцены

| Порядок (от дальнего к ближнему) | Название слоя | Физика и столкновения | Скорость движения относительно камеры | Что размещается на слое |
|---|---|---|---|---|
| 1 | Дальний фон (Background) | Нет | `0.2x – 0.5x` (параллакс) | Небо, далёкие горы, облака, силуэты башен |
| 2 | Фоновые декорации (Back Props) | Нет | `1.0x` (вместе со сценой) | Задние стены подземелья, окна, факелы, деревья позади героя |
| 3 | Основной игровой слой (Solids & Gameplay) | Да (коллайдеры) | `1.0x` | Твёрдые платформы, стены, шипы, монеты, ключи, враги и сам персонаж |
| 4 | Передний план (Foreground) | Нет | `1.0x – 1.2x` | Трава и арки, частично перекрывающие ноги персонажа для ощущения глубины |
| 5 | Экранный интерфейс (UI / HUD) | Нет | `0.0x` (закреплён на экране) | Текстовые счётчики очков и монет, таймер, подсказки управления |

Разделение фоновых декораций и твёрдых платформ избавляет игрока от главной проблемы любительских платформеров — визуальной путаницы, когда фоновый кирпич выглядит точно так же, как уступ, на который можно запрыгнуть. Практическое правило читаемости сцены: твёрдые платформы на игровом слое всегда имеют более высокий контраст контура и насыщенность, чем элементы фонового слоя.

## Автотайлинг по битовым маскам: формулы на 16 и 47 клеток

Автотайлинг (Autotiling) автоматически выбирает нужную картинку угла, края или середины стены в зависимости от того, какие соседние клетки уже заполнены тем же материалом. Этот алгоритм избавляет автора от ручного подбора сотен уголков при рисовании уровня.

### Базовая 4-соседняя битовая маска (16 вариантов)

При проверке четырёх ортогональных соседей (С — север/верх, В — восток/право, Ю — юг/низ, З — запад/лево) каждому направлению присваивается вес степени двойки:
- Север `(x, y - 1)`: бит `2⁰ = 1`;
- Восток `(x + 1, y)`: бит `2¹ = 2`;
- Юг `(x, y + 1)`: бит `2² = 4`;
- Запад `(x - 1, y)`: бит `2³ = 8`.

Итоговый индекс тайла `mask` вычисляется побитовым сложением заполненных соседей и принимает значения от `0` (одиночный блок в воздухе) до `15` (блок, окружённый землёй со всех четырёх сторон):

```text
mask_4 = 1 × N + 2 × E + 4 × S + 8 × W   (диапазон 0..15)
```

Например, для верхнего левого угла платформы заполнены соседи справа (`E = 1`) и снизу (`S = 1`), а сверху и слева — пусто (`N = 0, W = 0`). Формула даёт индекс `mask_4 = 2 + 4 = 6`, и редактор подставляет спрайт №6 (верхний левый угол с травой сверху и обрывом слева).

### Расширенная 8-соседняя маска с внутренними углами (47 вариантов)

Схема из 16 клеток не умеет оформлять внутренние углы (когда все 4 прямых соседа заполнены, но одна из диагоналей пуста — например, поворот пещеры буквой «Г»). Полный перебор 8 соседей даёт `2⁸ = 256` комбинаций, однако диагональный сосед влияет на форму угла только тогда, когда оба прилегающих к нему прямых соседа тоже заполнены:
- Северо-Восток (`NE`) учитывается только при `N = 1` и `E = 1`;
- Юго-Восток (`SE`) учитывается только при `S = 1` и `E = 1`;
- Юго-Запад (`SW`) учитывается только при `S = 1` и `W = 1`;
- Северо-Запад (`NW`) учитывается только при `N = 1` и `W = 1`.

Отбрасывание избыточных диагональных состояний сокращает 256 комбинаций ровно до **47 различных тайлов** (или 48 с учётом пустой клетки; стандарт «Blob tileset», популяризированный редакторами Tiled Map Editor и LDTK — документация Tiled 1.11, раздел «Using Terrains», `doc.mapeditor.org/en/stable/manual/terrain/`, проверено: октябрь 2026).

## Одномерный массив карты и отсечение невидимых клеток камеры

Хранение двумерной сетки шириной `W` и высотой `H` в одномерном массиве длиной `W × H` исключает вложенные массивы в памяти и повышает попадание в кэш процессора при обходе строк. Перевод координат клетки `(col, row)` в линейный индекс `index` и обратно выполняется по формулам:

```text
index = row * W + col
col   = index % W
row   = floor(index / W)
```

При отрисовке кадра движок не проверяет все клетки уровня, а вычисляет прямоугольное окно видимости камеры (Viewport Culling). Если камера с верхним левым углом `(C_x, C_y)` показывает область экрана `V_w × V_h` пикселей, а размер клетки равен `T` пикселей, то цикл отрисовки перебирает только диапазон с запасом в 1 клетку по краям:

```text
col_min = max(0, floor(C_x / T) - 1)
col_max = min(W - 1, ceil((C_x + V_w) / T) + 1)
row_min = max(0, floor(C_y / T) - 1)
row_max = min(H - 1, ceil((C_y + V_h) / T) + 1)
```

При разрешении игрового окна `1280×720` пикселей и размере клетки `32×32` пикселя в кадре находится всего `(40 + 2) × (23 + 2) = 1 050` клеток, независимо от того, составляет ли общая площадь карты десять тысяч или миллион ячеек.

## Практический сценарий: участок уровня из тайлов в коде на JavaScript

Участок платформера с пропастью в 4 клетки (`128` пикселей), шипами на дне, монетой и флагом описывается 15 строками текста, а поиск клетки, автотайлинг и отсечение невидимых клеток укладываются в четыре функции на JavaScript. Код ниже проверен в Node.js 24; комментарии справа показывают его фактический вывод.

```js
// Участок уровня: '#' — земля, '^' — шипы, 'C' — монета, 'F' — флаг, '.' — воздух. Клетка 32 пикс.
const T = 32;
const level = [
  "....................",
  "....................",
  "....................",
  "....................",
  "....................",
  "....................",
  "....................",
  "....................",
  "....................",
  "....................",
  "....................",
  ".........C........F.",
  "....................",
  "########....########",
  "########^^^^########",
];
const W = level[0].length, H = level.length;

function tileAt(col, row) {
  if (col < 0 || row < 0 || col >= W || row >= H) return ".";
  return level[row][col];
}
// Клетка по координатам в пикселях: col = floor(x / T), row = floor(y / T)
function tileAtPoint(x, y) { return tileAt(Math.floor(x / T), Math.floor(y / T)); }
function isSolid(x, y) { return tileAtPoint(x, y) === "#"; }

// Автотайлинг по 4 соседям: N = 1, E = 2, S = 4, W = 8
function mask4(col, row) {
  const s = function (c, r) { return tileAt(c, r) === "#" ? 1 : 0; };
  return 1 * s(col, row - 1) + 2 * s(col + 1, row) + 4 * s(col, row + 1) + 8 * s(col - 1, row);
}

// Диапазон клеток, которые попадают в камеру, с запасом в 1 клетку
function visibleRange(camX, camY, viewW, viewH, mapW, mapH) {
  return {
    colMin: Math.max(0, Math.floor(camX / T) - 1),
    colMax: Math.min(mapW - 1, Math.ceil((camX + viewW) / T) + 1),
    rowMin: Math.max(0, Math.floor(camY / T) - 1),
    rowMax: Math.min(mapH - 1, Math.ceil((camY + viewH) / T) + 1),
  };
}
console.log("стоит на земле в (100, 420):", isSolid(100, 420));       // true
console.log("над пропастью (300, 420):", isSolid(300, 420));           // false
console.log("шипы на дне (300, 460):", tileAtPoint(300, 460));          // ^
console.log("маска угла (0,13):", mask4(0, 13), " края (7,13):", mask4(7, 13), " середины (3,13):", mask4(3, 13));
const r = visibleRange(0, 0, 1280, 720, 313, 63);
console.log("клеток в кадре:", (r.colMax - r.colMin + 1) * (r.rowMax - r.rowMin + 1)); // 1050
```

Строковая карта удобна для прототипа: уровень видно прямо в коде. Когда карта вырастет до десятков тысяч клеток, те же функции переводят на одномерный массив `Uint16Array` с индексом `row * W + col` — формулы из раздела выше не меняются.

## Когда вам это не подойдёт

Строгая прямоугольная сетка подходит не всем жанрам, и это стоит учесть до начала работы над уровнями.

- **Свободная органическая геометрия и плавные склоны:** если геймплей строится вокруг катания шара по произвольным кривым или разрушаемого попиксельно грунта (как в *Worms*, Team17, 1995 год), жёсткая сетка из квадратных блоков создаст «ступеньки». Для таких задач используют полигональные сплайны или битовые маски разрушаемости.
- **Изометрические и шестиугольные карты:** формулы `floor(x / T)` и маски на 4 соседа рассчитаны на квадратную сетку; для ромбической или гексагональной сетки перевод координат и набор соседей другие.
- **Уровень на один экран:** если вся игра помещается в окно `1280×720` и на сцене 10–20 объектов, отсечение невидимых клеток не даёт выигрыша, а сетку можно заменить списком прямоугольников.
- **Рисование сложного тайлсета с нуля:** сетка задаёт только расположение клеток; саму графику удобнее рисовать в специализированных пакетах вроде Aseprite или Krita.

## Чек-лист проверки 2D-уровня перед запуском

Уровень готов к проверке игроками, когда выполнены пять пунктов ниже.

- [ ] Все опорные платформы и преграды выровнены по единому шагу сетки (например, кратно `32` пикселям).
- [ ] Фоновые декорации вынесены на отдельные слои и визуально контрастируют с твёрдыми платформами игрового слоя.
- [ ] Самая широкая пропасть на уровне проходится при тестовом запуске с запасом минимум в 1 клетку (`32` пикселя).
- [ ] Все опасные зоны (шипы, падение ниже `Y = 640`) и флаг финиша подключены к правилам перезапуска и победы.
- [ ] Уровень проверен в окне целевого размера (например, `1280×720`) при масштабе браузера `100%`, чтобы между клетками не появлялись щели.

---

Мы разрабатываем **J2DRU** — браузерный 2D игровой движок; он в разработке и публично пока не открыт, заявку можно оставить на [странице раннего доступа](/early-access?utm_source=blog&utm_medium=article&utm_campaign=tajlovye-setki-i-ustrojstvo-2d-mirov) (подача заявки не гарантирует приглашение).

**Читайте также:**
- [Идеальный прыжок и секрет Coyote Time: почему одни платформеры ощущаются отзывчивыми, а другие нет](/blog/idealnyj-pryzhok-i-sekret-coyote-time)
- [Игровой цикл (Game Loop) и физика столкновений (AABB, хитбоксы и триггеры) простыми словами и на уровне профи](/blog/igrovoj-cikl-i-fizika-stolknovenij-aabb)
- [Камера, параллакс и «джус» (Game Feel): как оживить плоскую сцену в 2D-игре](/blog/kamera-parallaks-i-game-feel-v-2d-igre)
