О чем этот пример
Освоив основы загрузки ресурсов и создания спрайтов, разработчик сталкивается с ключевым элементом любого платформера: интерактивным миром. В этой части мы добавим в игру физическое взаимодействие, управление персонажем, анимации и систему сбора очков. Вы научитесь создавать статические платформы, динамические группы объектов и обрабатывать столкновения между ними — фундамент для большинства 2D-игр.
Версия Phaser: код и демо в этой статье рассчитаны на Phaser 3.90.0.
Живой запуск
Ниже встроен рабочий билд примера. Оригинальный источник: GitHub.
Исходный код
class Example extends Phaser.Scene
{
scoreText;
score = 0;
cursors;
platforms;
stars;
player;
preload ()
{
// this.load.setBaseURL('https://cdn.phaserfiles.com/v385');
this.load.image('sky', 'src/games/firstgame/assets/sky.png');
this.load.image('ground', 'src/games/firstgame/assets/platform.png');
this.load.image('star', 'src/games/firstgame/assets/star.png');
this.load.image('bomb', 'src/games/firstgame/assets/bomb.png');
this.load.spritesheet('dude', 'src/games/firstgame/assets/dude.png', { frameWidth: 32, frameHeight: 48 });
}
create ()
{
this.add.image(400, 300, 'sky');
this.platforms = this.physics.add.staticGroup();
this.platforms.create(400, 568, 'ground').setScale(2).refreshBody();
this.platforms.create(600, 400, 'ground');
this.platforms.create(50, 250, 'ground');
this.platforms.create(750, 220, 'ground');
this.player = this.physics.add.sprite(100, 450, 'dude');
this.player.setBounce(0.2);
this.player.setCollideWorldBounds(true);
this.anims.create({
key: 'left',
frames: this.anims.generateFrameNumbers('dude', { start: 0, end: 3 }),
frameRate: 10,
repeat: -1
});
this.anims.create({
key: 'turn',
frames: [ { key: 'dude', frame: 4 } ],
frameRate: 20
});
this.anims.create({
key: 'right',
frames: this.anims.generateFrameNumbers('dude', { start: 5, end: 8 }),
frameRate: 10,
repeat: -1
});
this.cursors = this.input.keyboard.createCursorKeys();
this.stars = this.physics.add.group({
key: 'star',
repeat: 11,
setXY: { x: 12, y: 0, stepX: 70 }
});
this.stars.children.forEach(child =>
{
child.setBounceY(Phaser.Math.FloatBetween(0.4, 0.8));
});
this.scoreText = this.add.text(16, 16, 'score: 0', { fontSize: '32px', fill: '#000' });
this.physics.add.collider(this.player, this.platforms);
this.physics.add.collider(this.stars, this.platforms);
this.physics.add.overlap(this.player, this.stars, this.collectStar, null, this);
}
update ()
{
if (this.cursors.left.isDown)
{
this.player.setVelocityX(-160);
this.player.anims.play('left', true);
}
else if (this.cursors.right.isDown)
{
this.player.setVelocityX(160);
this.player.anims.play('right', true);
}
else
{
this.player.setVelocityX(0);
this.player.anims.play('turn');
}
if (this.cursors.up.isDown && this.player.body.touching.down)
{
this.player.setVelocityY(-330);
}
}
collectStar (player, star)
{
star.disableBody(true, true);
this.score += 10;
this.scoreText.setText(`Score: ${this.score}`);
}
}
const config = {
type: Phaser.AUTO,
width: 800,
height: 600,
physics: {
default: 'arcade',
arcade: {
gravity: { y: 300 },
debug: false
}
},
scene: Example
};
const game = new Phaser.Game(config);
Настройка физического мира и статических платформ
Phaser Arcade Physics предоставляет простой и быстрый движок для 2D-игр. В методе create() мы настраиваем игровой мир, начиная с фона.
this.add.image(400, 300, 'sky');
Затем создаётся группа статических физических тел this.platforms. Статические тела не подвержены силам вроде гравитации и идеально подходят для земли и платформ.
this.platforms = this.physics.add.staticGroup();
this.platforms.create(400, 568, 'ground').setScale(2).refreshBody();
this.platforms.create(600, 400, 'ground');
Метод refreshBody() необходимо вызывать после изменения масштаба (setScale) или позиции статического тела, чтобы физический движок корректно пересчитал его границы.
Группы (Group) в Phaser — это мощный инструмент для управления коллекциями похожих объектов с общим поведением.
Создание и анимация игрока
Персонаж создаётся как динамическое физическое тело (physics.add.sprite), которое будет падать под действием гравитации и сталкиваться с миром.
this.player = this.physics.add.sprite(100, 450, 'dude');
this.player.setBounce(0.2);
this.player.setCollideWorldBounds(true);
setBounce(0.2) задаёт упругость, а setCollideWorldBounds(true) не позволяет спрайту вылететь за границы игрового поля.
Далее создаются три анимации на основе спрайтшита 'dude'.
this.anims.create({
key: 'left',
frames: this.anims.generateFrameNumbers('dude', { start: 0, end: 3 }),
frameRate: 10,
repeat: -1
});
Ключевые параметры: key — имя для обращения, frames — массив кадров (генерируется автоматически), frameRate — скорость проигрывания, repeat: -1 — бесконечный цикл. Анимация 'turn' использует один кадр (№4), в котором персонаж смотрит в камеру.
Управление и логика в методе Update
Логика управления обрабатывается каждый кадр в методе update(). Мы проверяем состояние клавиш-стрелок, которые были получены через this.input.keyboard.createCursorKeys().
if (this.cursors.left.isDown) {
this.player.setVelocityX(-160);
this.player.anims.play('left', true);
}
else if (this.cursors.right.isDown) {
this.player.setVelocityX(160);
this.player.anims.play('right', true);
}
else {
this.player.setVelocityX(0);
this.player.anims.play('turn');
}
Метод setVelocityX() напрямую задаёт горизонтальную скорость. Одновременно с этим проигрывается соответствующая анимация. Второй аргумент true в anims.play() означает «принудительно запустить», даже если эта анимация уже играет.
Прыжок реализован через проверку this.player.body.touching.down, что гарантирует прыжок только с поверхности.
if (this.cursors.up.isDown && this.player.body.touching.down) {
this.player.setVelocityY(-330);
}
Работа с группами объектов: звёзды и их сбор
Звёзды создаются не по одной, а целой физической группой с помощью this.physics.add.group. Это эффективно как для производительности, так и для управления.
this.stars = this.physics.add.group({
key: 'star',
repeat: 11,
setXY: { x: 12, y: 0, stepX: 70 }
});
Параметр repeat: 11 создаст 12 объектов (1 исходный + 11 повторений). setXY автоматически расставит их в линию с заданным шагом. Затем для каждого ребёнка в группе задаётся случайный отскок по оси Y.
this.stars.children.forEach(child => {
child.setBounceY(Phaser.Math.FloatBetween(0.4, 0.8));
});
Ключевые взаимодействия настраиваются через collider и overlap.
this.physics.add.collider(this.player, this.platforms);
this.physics.add.collider(this.stars, this.platforms);
this.physics.add.overlap(this.player, this.stars, this.collectStar, null, this);
Коллайдер (collider) предотвращает взаимное проникновение тел. Оверлап (overlap) регистрирует факт пересечения и вызывает callback-функцию collectStar, не влияя на физику.
Обработка сбора и обновление счёта
Функция collectStar вызывается движком физики при каждом пересечении игрока и звезды. В неё автоматически передаются ссылки на оба участвующих спрайта.
collectStar (player, star) {
star.disableBody(true, true);
this.score += 10;
this.scoreText.setText(`Score: ${this.score}`);
}
Метод disableBody(true, true) — стандартный способ «деактивации» физического тела в Arcade Physics. Первый параметр true отключает тело, второй — скрывает родительский игровой объект (звезду).
Текст счёта создаётся в create() через систему текста Phaser.
this.scoreText = this.add.text(16, 16, 'score: 0', { fontSize: '32px', fill: '#000' });
При каждом сборе переменная score увеличивается, а текст обновляется методом setText().
Что попробовать дальше
Вы создали полноценный игровой прототип с физикой, управлением и игровой механикой. Освоили ключевые концепции Phaser: группы объектов, коллайдеры, оверлапы и анимации. Для экспериментов попробуйте: изменить параметры гравитации в конфиге, добавить звук при сборе звезды, реализовать «опасные» объекты (используя созданный ресурс 'bomb'), которые отнимают очки при столкновении, или сделать звёзды появляющимися заново через некоторое время.
