Гузариш ба мундариҷа
Мавзӯи 5 аз 5
Назария

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

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

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

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

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

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("ключ").

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

Осон+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)

Барои ҳал кардани масъалаҳо коннекторро пайваст кун

Коди масъалаҳо дар компютери худат иҷро мешавад — ин аз браузер тезтар ва боэътимодтар аст. Барои ин барномаи ройгони коннектори CodeWithMe лозим аст. Онро як бор пайваст кардан кифоя аст.

Коннекторро дар ин компютер ҷустуҷӯ мекунем…

  1. 1
    Коннекторро барои системаи худ (Windows, macOS ё Linux) зеркашӣ кун.Зеркашии коннекторДар саҳифаи зеркашӣ файлеро интихоб кун, ки дар номаш системаи ту ҳаст, масалан windows-amd64.
  2. 2Онро иҷро кун ва ҷузвдонеро интихоб кун, ки ҳалҳоят дар он ҷо меистанд. Python ва Node.js-ро ӯ худаш ҳангоми зарурат ба он ҷо мегузорад.
  3. 3
    Дар поён рамзи 6-рақамаро ворид кун — он дар равзанаи коннектор навишта шудааст («Рамз барои сайти CodeWithMe»). Агар браузер дар бораи дастрасӣ ба шабакаи маҳаллӣ пурсад — иҷозат деҳ.

Коннектор кушода аст (рамзи манбаъ дар GitHub), танҳо дар компютери ту кор мекунад ва кодро танҳо пас аз ворид кардани рамзи пайвастшавӣ иҷро мекунад.