Skip to content
Topic 5 of 5
Theory

Проект: платформер

Собираем платформер

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

Уровень из карты

Рисовать уровни координатами вручную утомительно. Удобнее описать уровень картой из строк, где каждый символ — клетка:

const LEVEL = [
  "..........",
  "......o...",
  ".....###..",
  "..o.......",
  ".###......",
  "@.......o.",
  "##########",
];

# — платформа, o — монета, @ — старт игрока. Карту видно глазами, её легко править, а новый уровень — это просто новый массив строк. Превращает карту в объекты двойной перебор:

buildLevel(map) {
  map.forEach((line, row) => {
    [...line].forEach((ch, col) => {
      const x = col * TILE_W + TILE_W / 2;    // центр клетки
      const y = row * TILE_H + TILE_H / 2;
      if (ch === "#") this.platforms.add(this.add.rectangle(x, y, TILE_W, TILE_H, 0x795548));
      if (ch === "o") this.coins.add(this.add.circle(x, y, 8, 0xffd54f));
      if (ch === "@") this.start = { x, y };
    });
  });
}

forEach передаёт второй аргумент — индекс, это и есть номер строки и столбца. Центр клетки — + половина размера, потому что у фигур Phaser x, y — центр. Профессиональные игры используют для этого редактор Tiled и тайловые карты (this.make.tilemap), но идея та же.

Мир больше экрана

Уровень может быть намного шире экрана. Для этого задают размер мира и камеру:

this.physics.world.setBounds(0, 0, 1200, 300);   // где тела упираются в края
this.cameras.main.setBounds(0, 0, 1200, 300);    // куда камера может смотреть
this.cameras.main.startFollow(this.player);       // камера едет за игроком

Камера держит игрока в центре экрана, пока не упрётся в границу мира: у левого края уровня экран показывает 0…400, у правого — 800…1200. Без setBounds камера показала бы пустоту за краем.

Полезные настройки камеры:

this.cameras.main.startFollow(this.player, true, 0.1, 0.1);   // плавное следование
this.cameras.main.setZoom(1.5);                                 // приблизить
this.cameras.main.shake(200, 0.01);                             // тряска при ударе

Интерфейс (счёт, жизни) не должен уезжать с камерой — для этого у текста ставят setScrollFactor(0): он остаётся на месте экрана.

Данные между перезапусками

Переход на следующий уровень проще всего сделать перезапуском той же сцены с новыми данными:

this.scene.restart({ level: this.level + 1, score: this.score });

Phaser уничтожит все объекты сцены и вызовет её методы заново, по порядку:

  1. init(data) — получает переданные данные, здесь их запоминают;
  2. preload() — загрузка (уже загруженное не грузится повторно);
  3. create() — объекты создаются заново, уже для нового уровня.
init(data) {
  this.level = data.level ?? 1;      // при первом запуске data = {}
  this.score = data.score ?? 0;
}

create() {
  this.buildLevel(LEVELS[this.level - 1]);
  this.add.text(10, 10, `Level ${this.level}`);
}

?? даёт значение по умолчанию при первом запуске, когда данных ещё нет.

Несколько сцен

Меню, уровень и экран победы обычно делают разными сценами и переключаются между ними:

new Phaser.Game({ ..., scene: [MenuScene, GameScene, WinScene] });

// в MenuScene:
this.input.keyboard.once("keydown-SPACE", () => this.scene.start("game"));

У каждой сцены свой ключ (super("game") в конструкторе), а this.scene.start("ключ", данные) выключает текущую сцену и запускает другую.

Куда расти дальше

  • спрайты и анимации: this.load.spritesheet + this.anims.create — бегущий герой вместо квадрата;
  • звуки: this.load.audio + this.sound.play;
  • тайловые карты из Tiled для больших уровней;
  • рекорд в localStorage и экран «Game Over» отдельной сценой;
  • игра на телефоне: кнопки на экране через pointerdown.

Итог

  • Уровень — массив строк; forEach с индексами превращает символы в объекты в центрах клеток.
  • Большой мир: physics.world.setBounds + cameras.main.setBounds + startFollow(player).
  • Следующий уровень — this.scene.restart(данные); данные приходят в init(data) перед create.
  • Разные экраны — разные сцены, this.scene.start("ключ").

Уровень из карты

Easy+10 XP

Напиши метод buildLevel(map). map — массив строк, каждый символ — клетка 40 × 20 (столбец col, строка row); центр клетки — x = col * 40 + 20, y = row * 20 + 10.

  • # — платформа: прямоугольник 40 × 20 в статичной группе this.platforms;
  • o — монета: круг радиуса 8 в центре клетки, в группе this.coins;
  • @ — старт игрока: запиши this.start = { x, y };
  • остальные символы — пустота.

Группы уже созданы в create, и buildLevel(LEVEL) вызывается там же.

"..o.."
"@.###"  →  3 платформы, 1 монета, старт (20, 30)

Connect the connector to solve tasks

Task code runs on your own computer — faster and more reliable than in the browser. This needs the free CodeWithMe connector app. You only connect it once.

Looking for the connector on this computer…

  1. 1
    Download the connector for your system (Windows, macOS or Linux).Download the connectorOn the download page pick the file with your system in its name, e.g. windows-amd64.
  2. 2Run it and choose a folder for your solutions. It will install Python and Node.js there by itself when needed.
  3. 3
    Enter below the 6-digit code shown in the connector window (“Code for CodeWithMe”). If the browser asks about local network access — allow it.

The connector is open source (on GitHub), works only on your computer and runs code only after you enter the pairing code.