Проект: платформер
Собираем платформер
В этой теме всё из курса складывается в игру: уровень, который строится из текстовой карты, большой мир с камерой, монеты и переход на следующий уровень. Новых больших идей немного — в основном это правильная организация кода.
Уровень из карты
Рисовать уровни координатами вручную утомительно. Удобнее описать уровень картой из строк, где каждый символ — клетка:
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 уничтожит все объекты сцены и вызовет её методы заново, по порядку:
init(data)— получает переданные данные, здесь их запоминают;preload()— загрузка (уже загруженное не грузится повторно);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("ключ").