Бесконечные карты и прокрутка

Библиотека Phaser предоставляет мощные инструменты для создания 2D-игр с прокруткой, в том числе бесконечных карт. Основной подход основан на сочетании тайловых карт и динамического управления объектами, что позволяет эффективно использовать память и ресурсы браузера.

Для бесконечной карты важно понимать два ключевых компонента: tilemap и camera. Tilemap отвечает за сетку тайлов, формирующих игровое пространство, а Camera обеспечивает видимую область, которая следует за игроком или другим объектом.

const map = this.make.tilemap({ width: 1000, height: 1000, tileWidth: 32, tileHeight: 32 });
const tileset = map.addTilesetImage('tileset');
const layer = map.createLayer(0, tileset, 0, 0);
this.cameras.main.startFollow(player);

Создание эффекта бесконечной прокрутки

Бесконечная прокрутка реализуется через повторное заполнение карты тайлами по мере движения камеры. Существует два подхода:

  1. Циклическое повторение (wrap-around) — карта визуально «замыкается» на себя, создавая иллюзию бесконечности.
  2. Динамическая генерация тайлов — тайлы создаются и уничтожаются по мере движения игрока, что позволяет строить действительно бесконечный мир без ограничения по размерам.

Для wrap-around можно использовать свойства tilePosition слоя:

layer.setTileIndexCallback(tileIndex, callback, this);
layer.setScrollFactor(1, 1);
layer.tilePositionX = this.cameras.main.scrollX % map.widthInPixels;
layer.tilePositionY = this.cameras.main.scrollY % map.heightInPixels;

Для динамической генерации тайлов применяют пул объектов:

  • Игровое поле разделяется на секции.
  • Когда игрок входит в новую секцию, старые тайлы удаляются, а новые добавляются.
  • Используется Group для эффективного управления большим количеством тайлов.
const tilesGroup = this.add.group();
function generateTiles(sectionX, sectionY) {
    for (let y = 0; y < sectionSize; y++) {
        for (let x = 0; x < sectionSize; x++) {
            const tile = this.add.sprite(x * tileSize, y * tileSize, 'tileTexture');
            tilesGroup.add(tile);
        }
    }
}

Оптимизация производительности

Для бесконечных карт критически важна оптимизация. Основные методы:

  • Использование тайловых карт вместо отдельных спрайтов. Tilemap работает быстрее и потребляет меньше памяти.
  • Пул объектов для повторного использования тайлов и врагов.
  • Ограничение области рендеринга через Camera и cullBounds, чтобы Phaser не рендерил невидимые тайлы.
  • Пакетная отрисовка тайлов с использованием RenderTexture для статических слоев.
this.cameras.main.setBounds(0, 0, map.widthInPixels, map.heightInPixels);
this.cameras.main.startFollow(player, true, 0.08, 0.08);

Слои и глубина

Phaser позволяет создавать несколько слоев тайлов и объектов для сложных эффектов:

  • Фоновый слой — статичные элементы, создающие визуальную глубину.
  • Основной игровой слой — тайлы, с которыми взаимодействует игрок.
  • Слой объектов — враги, бонусы, препятствия.
  • UI и эффекты — поверх всех слоев, фиксированные относительно камеры.

Использование setDepth обеспечивает правильное наложение:

backgroundLayer.setDepth(0);
mainLayer.setDepth(1);
objectsLayer.setDepth(2);
uiLayer.setScrollFactor(0).setDepth(3);

Плавная прокрутка и параллакс

Для реалистичной прокрутки используют параллакс-эффект, когда фоны движутся медленнее переднего плана:

backgroundLayer.tilePositionX = this.cameras.main.scrollX * 0.5;
backgroundLayer.tilePositionY = this.cameras.main.scrollY * 0.5;

0.5 — коэффициент скорости. Меняя его, можно создавать многослойные параллаксы с разной скоростью движения, усиливая ощущение глубины.


Генерация случайного мира

Для настоящей бесконечной карты тайлы создаются процедурно. Основные подходы:

  • Шум Перлина (Perlin Noise) для генерации ландшафта.
  • Случайные распределения объектов с учетом правил (например, деревья не ближе 2 тайлов друг к другу).
  • Частичная генерация — создаются только ближайшие секции вокруг игрока.

Пример генерации случайного тайла:

function getRandomTile(x, y) {
    const noiseValue = perlin.noise(x / 10, y / 10, 0);
    if (noiseValue < 0.3) return 1; // вода
    else if (noiseValue < 0.6) return 2; // земля
    else return 3; // гора
}

Взаимодействие с игроком

Бесконечные карты требуют корректного обновления коллизий и физики. Phaser позволяет:

  • Использовать Arcade Physics для динамических объектов.
  • Обновлять коллизии только для тайлов в пределах видимой камеры.
  • Сохранять состояние объектов в карте секций, чтобы при возвращении игрока объекты оставались на своих местах.
this.physics.add.collider(player, tilesGroup);
tilesGroup.children.iterate(tile => {
    if (!this.cameras.main.worldView.contains(tile.x, tile.y)) {
        tile.setActive(false).setVisible(false);
    }
});

Примеры сложных эффектов

  1. Появление врагов по мере движения — враги создаются только в ближайших секциях и удаляются, когда уходят за границы камеры.
  2. Динамическая смена тайлов — для имитации смены биома (лес → пустыня → снежный ландшафт) можно менять текстуры слоев при входе в новую секцию.
  3. Плавающий фон с эффектом движения — задний слой с более медленной скоростью движения создаёт глубину, как в платформерах типа старых 16-битных игр.

Итоговая архитектура

Для бесконечной карты создаётся система секций, каждая из которых содержит тайлы и объекты. Камера отслеживает игрока, и при достижении границы секции происходит:

  • Генерация новых тайлов и объектов.
  • Деактивация старых тайлов для экономии памяти.
  • Обновление коллизий и взаимодействий.

Такой подход обеспечивает бесконечное ощущение мира, плавную прокрутку и оптимизированное использование ресурсов.