Эта статья была написана Элвином Уррадом и Ричардом Дэви

Свежо обновлена для Phaser 2.0!

Введение

Добро пожаловать на наш первый урок по созданию игры с использованием Phaser. Здесь мы научимся создавать небольшую игру, в которой игрок будет бегать и прыгать по платформам, собирая звезды. В ходе этого процесса мы объясним некоторые ключевые особенности фреймворка.

Что такое Phaser?

Phaser — это HTML5 игровой фреймворк, который помогает разработчикам быстро создавать мощные кросс-браузерные HTML5 игры. В отличие от некоторых других фреймворков, Phaser специально разработан для работы с мобильными браузерами. Единственное требование для браузера — поддержка элемента. Также он многое заимствует от Flixel.

Требования

  • Скачайте исходные файлы и ресурсы, которые используются в этом уроке.
  • Вам нужно иметь очень базовые знания JavaScript.
  • Также убедитесь, что вы прошли руководство по началу работы, которое покажет, как скачать фреймворк, настроить локальную среду разработки и даст представление о структуре проекта на Phaser и его основных функциях.

Если вы прошли руководство, то вы уже скачали Phaser, настроили окружение и готовы писать код. Скачайте ресурсы для этого урока отсюда и распакуйте их в корневую папку вашего веб-сервера.

Откройте файл part1.html в редакторе на ваш выбор, и давайте подробнее рассмотрим код. После небольшого шаблона HTML, который включает Phaser, структура кода выглядит следующим образом:

    var game = new Phaser.Game(800, 600, Phaser.AUTO, '', { preload: preload, create: create, update: update });

    function preload() {
    }

    function create() {
    }

    function update() {
    }

Строка 1 — здесь вы запускаете Phaser, создавая экземпляр объекта Phaser.Game и присваивая его локальной переменной под названием game. Называть её 'game' — это распространённая практика, но не обязательная, и вы увидите это в примерах Phaser.

Первые два параметра — это ширина и высота элемента, который создаст Phaser. В данном случае — 800 x 600 пикселей. Ваш игровой мир может быть любого размера, но именно в таком разрешении игра будет отображаться. Третий параметр может быть либо Phaser.CANVAS, Phaser.WEBGL или Phaser.AUTO. Это контекст рендеринга, который вы хотите использовать. Рекомендуемый параметр — Phaser.AUTO, который автоматически пытается использовать WebGL, но если браузер или устройство его не поддерживает, он переключится на Canvas.

Четвёртый параметр — это пустая строка, которая является идентификатором DOM-элемента, в который вы хотите вставить элемент, созданный Phaser. Так как мы оставили его пустым, он будет просто добавлен в тело документа. Последний параметр — это объект, содержащий четыре ссылки на важные функции Phaser. Их использование подробно объясняется здесь. Обратите внимание, что этот объект не обязателен — Phaser поддерживает полную систему состояний (State), которая позволяет разбивать код на более чистые отдельные объекты. Но для простого руководства, такого как это, мы будем использовать этот подход, так как он позволяет быстрее создавать прототипы.

Загрузка ассетов

Загрузим ассеты, которые нам понадобятся для нашей игры. Это делается с помощью вызова game.load внутри функции preload. Phaser автоматически вызовет эту функцию при запуске и загрузит всё, что в ней определено.

На данный момент функция preload пуста. Измените её на следующую:

function preload() {
    game.load.image('sky', 'assets/sky.png');
    game.load.image('ground', 'assets/platform.png');
    game.load.image('star', 'assets/star.png');
    game.load.spritesheet('dude', 'assets/dude.png', 32, 48);
}

Это загрузит 4 ресурса: 3 изображения и спрайт-лист. Возможно, для некоторых это очевидно, но я хочу обратить внимание на первый параметр, также известный как ключ ресурса. Эта строка является ссылкой на загруженный ресурс и будет использоваться в вашем коде при создании спрайтов. Вы можете использовать любую допустимую строку JavaScript в качестве ключа.

Создание спрайта

Чтобы добавить спрайт в нашу игру, добавьте следующий код в функцию create:

game.add.sprite(0, 0, 'star');
Создание спрайта phaser js

Порядок, в котором элементы отображаются на экране, соответствует порядку их создания. Таким образом, если вы хотите разместить фон за спрайтом звезды, нужно убедиться, что фон был добавлен в качестве спрайта первым, перед добавлением звезды.

Пример создания игрового окна в PhaserJS

Внутри game.add.sprite создаёт новый объект Phaser.Sprite и добавляет sprite в "игровой мир". Этот мир — место, где существуют все ваши объекты, его можно сравнить со сценой в ActionScript3.

Примечание: игровой мир не имеет фиксированного размера и простирается бесконечно во всех направлениях, с координатами 0,0 в центре. Для удобства Phaser размещает точку 0,0 в верхнем левом углу вашей игры, но с помощью встроенной камеры можно перемещаться по миру по мере необходимости.

Класс мира доступен через game.world и содержит множество удобных методов и свойств, которые помогают размещать объекты в мире. Он включает простые свойства, такие как game.world.height, а также более сложные, которые мы рассмотрим в другом уроке.

А пока давайте построим сцену, добавив фон и платформы. Вот обновлённая функция create:

    var platforms;

    function create() {
    
        // Мы будем использовать физику, поэтому включите систему Arcade Physics
        game.physics.startSystem(Phaser.Physics.ARCADE);
    
        // Простой фон для нашей игры
        game.add.sprite(0, 0, 'sky');
    
        // Группа платформ состоит из земли и 2-х уступов, на которые мы можем запрыгнуть
        platforms = game.add.group();
    
        // Мы включим физику для любого объекта, созданного в этой группе
        platforms.enableBody = true;
    
        // Здесь мы создаем землю.
        var ground = platforms.create(0, game.world.height - 64, 'ground');
    
        // Масштабируем его под ширину игры (оригинальный спрайт имеет размер 400x32)
        ground.scale.setTo(2, 2);
    
        // Это предотвратит его падение, когда вы прыгаете на него.
        ground.body.immovable = true;
    
        // Теперь давайте создадим два выступа
        var ledge = platforms.create(400, 400, 'ground');
    
        ledge.body.immovable = true;
    
        ledge = platforms.create(-150, 250, 'ground');
    
        ledge.body.immovable = true;
        
    }

Если вы запустите это (файл part4.html, который находится в архиве с учебником), то увидите сцену, которая больше похожа на игру.

Первая часть такая же, как и у спрайта звезды, который у нас был ранее, только на этот раз мы изменили ключ на 'sky', и теперь он отображает фон неба. Это PNG размером 800×600, который заполняет экран игры.

Группы

Группы действительно мощные. Как следует из их названия, они позволяют вам объединить схожие объекты и управлять ими как единым целым. Вы также можете проверять столкновения между группами, и для этой игры мы будем использовать две разные группы, одна из которых создана в коде выше для платформ.

platforms = game.add.group();

Как и со sprites, game.add создает наш объект группы. Мы присваиваем его новой локальной переменной, называемой platforms. Теперь, когда группа создана, мы можем добавлять в нее объекты. Первая из них - это земля. Она располагается внизу игры и использует изображение 'ground', загруженное ранее. Земля масштабируется, чтобы заполнить ширину игры. Наконец, мы устанавливаем для нее свойство 'immovable' в true. Если бы мы этого не сделали, земля двигалась бы при столкновении с игроком (об этом мы поговорим в разделе "Физика").

С землей на месте мы создаем две меньшие платформы для прыжков, используя ту же технику, что и для земли.

Готов игрок один

Создайте новую локальную переменную, названную 'player', и добавьте следующий код в функцию создания. Вы можете увидеть это в файле part5.html:

// Игрок и его настройки
player = game.add.sprite(32, game.world.height - 150, 'dude');

// Нам нужно включить физику для игрока
game.physics.arcade.enable(player);

// Свойства физики для игрока. Даем персонажу небольшую упругость.
player.body.bounce.y = 0.2;
player.body.gravity.y = 300;
player.body.collideWorldBounds = true;

// Две анимации, ходьба влево и вправо.
player.animations.add('left', [0, 1, 2, 3], 10, true);
player.animations.add('right', [5, 6, 7, 8], 10, true);

Это создает новый спрайт, называемый 'player', расположенный на 32 пикселя по горизонтали и 150 пикселей от низа игры по вертикали. Мы указываем использовать актив 'dude', загруженный ранее. Если вы вернетесь к функции preload, вы увидите, что 'dude' был загружен как спрайтовый лист, а не как изображение. Это потому, что он содержит кадры анимации. Вот как выглядит весь спрайтовый лист:

Вы можете увидеть 9 кадров в целом: 4 для бега влево, 1 для лицом к камере и 4 для бега вправо. Заметьте, что Phaser поддерживает переворачивание спрайтов, чтобы сэкономить на кадрах анимации, но для этого урока мы будем использовать старый метод. Мы определяем две анимации: 'left' и 'right'. Анимация 'left' использует кадры 0, 1, 2 и 3 и воспроизводится с частотой 10 кадров в секунду. Параметр 'true' указывает, что анимация будет зациклена. Это наш стандартный цикл бега, и мы повторяем его для бега в противоположном направлении. С установленными анимациями мы добавляем несколько физических свойств.

Тело и скорость: мир физики

Phaser поддерживает различные системы физики. Он включает Arcade Physics, Ninja Physics и P2.JS Full-Body Physics. Для этого урока мы будем использовать систему Arcade Physics, которая проста и легка, идеально подходит для мобильных браузеров. Вы заметите, что в коде мы должны запустить систему физики, а затем для каждого спрайта или группы, которые мы хотим использовать в физике, мы включаем их.

Как только это сделано, спрайты получают новое свойство `body`, которое является экземпляром `ArcadePhysics.Body`. Оно представляет спрайт как физическое тело в физическом движке Phaser's Arcade Physics. Объект `body` имеет множество свойств, с которыми можно взаимодействовать. Чтобы симулировать действие гравитации на спрайт, достаточно написать следующее:

player.body.gravity.y = 300;

Это произвольное значение, но логично: чем выше значение, тем тяжелее объект ощущается и быстрее падает. Если вы добавите это в код или запустите part5.html, вы увидите, что игрок падает вниз, не останавливаясь, полностью игнорируя созданную ранее землю:

Причина этого в том, что мы еще не проверяем столкновение между землей и игроком. Мы уже указали Phaser, что наши земля и платформы должны быть неподвижными. Если бы мы этого не сделали, когда игрок сталкивался бы с ними, он бы остановился на мгновение, а затем все бы рухнуло. Это потому, что, если не указать иное, спрайт земли является движущимся физическим объектом (также известным как динамическое тело), и когда игрок сталкивается с ним, результирующая сила столкновения применяется к земле, поэтому оба тела обмениваются своими скоростями, и земля тоже начинает падать.

Чтобы позволить игроку сталкиваться и использовать свойства физики, нам нужно ввести проверку столкновений в функцию обновления:

function update() {
    // Столкновение игрока и звезд с платформами
    game.physics.arcade.collide(player, platforms);
}

Функция обновления вызывается основным игровым циклом каждый кадр. Функция Physics.collide выполняет волшебство. Она принимает два объекта, проверяет их на столкновение и выполняет их разделение. В данном случае мы даем ей спрайт игрока и группу платформ. Она достаточно умна, чтобы обрабатывать столкновения со всеми членами группы, так что этот один вызов обеспечит столкновение как с землей, так и с двумя платформами. Результат - твердая платформа:

Управление игроком с клавиатуры

cursors = game.input.keyboard.createCursorKeys();

Это заполняет объект cursors четырьмя свойствами: up, down, left, right, которые все являются экземплярами объектов Phaser.Key. Затем все, что нам нужно сделать, это опрашивать их в нашем цикле обновления:

player.body.velocity.x = 0;

if (cursors.left.isDown)
{
    player.body.velocity.x = -150;
    player.animations.play('left');
}
else if (cursors.right.isDown)
{
    player.body.velocity.x = 150;
    player.animations.play('right');
}
else
{
    player.animations.stop();
    player.frame = 4;
}

if (cursors.up.isDown && player.body.touching.down)
{
    player.body.velocity.y = -350;
}

Хотя мы добавили много кода, он должен быть довольно читабельным. Первое, что мы делаем, это сбрасываем горизонтальную скорость спрайта. Затем проверяем, нажата ли клавиша курсора влево. Если да, то применяем отрицательную горизонтальную скорость и запускаем анимацию «бег влево». Если нажата клавиша «вправо», то делаем буквально противоположное. Очищая скорость и устанавливая её таким образом в каждом кадре, мы создаем стиль движения «стоп-старт».

Спрайт игрока будет двигаться только при нажатой клавише и остановится, как только клавиша будет отпущена. Phaser также позволяет создавать более сложные движения, с учетом инерции и ускорения, но нам требуется именно такой эффект для этой игры. Последняя часть проверки клавиш устанавливает кадр 4, если ни одна клавиша не нажата. Кадр 4 в спрайт-листе — это кадр, где игрок смотрит на вас, находясь в состоянии покоя.

Прыжок

Последняя часть кода добавляет возможность прыжка. Курсор вверх — это наша клавиша прыжка, и мы проверяем, нажата ли она. Однако мы также проверяем, касается ли игрок земли, иначе он мог бы прыгать в воздухе. Если оба условия выполнены, мы применяем вертикальную скорость в 350 пикселей/сек. Игрок автоматически упадет на землю из-за значения гравитации, которое мы к нему применили. С управлением у нас теперь есть игровой мир, который можно исследовать. Загрузите part7.html и поиграйте. Попробуйте изменить значение 350 для прыжка на меньшее или большее, чтобы увидеть, как это повлияет на игру.

Звезды

Пора придать нашей маленькой игре цель. Добавим немного звезд в сцену, и позволим игроку их собирать. Для этого мы создадим новую группу «звезды» и заполним её. В нашей функции создания добавляем следующий код (его можно увидеть в part8.html):

    stars = game.add.group();

    stars.enableBody = true;

    //  Here we'll create 12 of them evenly spaced apart
    for (var i = 0; i < 12; i++)
    {
        //  Create a star inside of the 'stars' group
        var star = stars.create(i * 70, 0, 'star');

        //  Let gravity do its thing
        star.body.gravity.y = 6;

        //  This just gives each star a slightly random bounce value
        star.body.bounce.y = 0.7 + Math.random() * 0.2;
    }

Процесс похож на тот, который мы использовали при создании группы платформ. С помощью цикла for мы указываем создание 12 звезд в нашей игре. Они имеют координату x, равную i * 70, что означает, что они будут расположены на одинаковом расстоянии в 70 пикселей друг от друга. Как и у игрока, мы задаем им значение гравитации, чтобы они падали вниз, и значение отскока, чтобы они немного подскакивали при ударе о платформы. Отскок — это значение между 0 (отскока нет) и 1 (полный отскок). Наши звезды будут отскакивать на величину от 0.7 до 0.9. Если запустить код в таком виде, звезды будут падать сквозь дно игры. Чтобы этого не происходило, мы должны проверить их столкновение с платформами в цикле обновления:

    game.physics.arcade.collide(stars, platforms);

Также мы проверим, пересекается ли игрок со звездой или нет:

    game.physics.arcade.overlap(player, stars, collectStar, null, this);

Это указывает Phaser проверять пересечение между игроком и любой звездой в группе звезд. Если оно найдено, вызывается функция collectStar:

    function collectStar (player, star) {

        // Removes the star from the screen
        star.kill();
    
    }

Звезда просто «убивается», что удаляет её с отображения. Запуск игры сейчас даст нам игрока, который может бегать, прыгать, отскакивать от платформ и собирать падающие звезды. Неплохо для нескольких строк, надеюсь, достаточно читабельного кода 🙂

Завершающие штрихи

Последнее изменение, которое мы сделаем, — это добавление счета. Для этого мы воспользуемся объектом Phaser.Text. Здесь мы создаем две новые переменные: одну для хранения фактического счета, а другую для самого текстового объекта:

    var score = 0;
    var scoreText;

scoreText настраивается в функции создания:

    scoreText = game.add.text(16, 16, 'score: 0', { fontSize: '32px', fill: '#000' });

Координаты 16×16 указывают, где отображать текст. ‘score: 0’ — это строка по умолчанию для отображения, а объект, который следует за ней, содержит размер шрифта и цвет заливки. Не указывая, какой шрифт использовать, браузер выберет шрифт по умолчанию, в Windows это будет Arial. Далее мы модифицируем функцию collectStar, чтобы при подборе звезды игроком его счет увеличивался, и текст обновлялся:

    function collectStar(player, star) {
        // Убирает звезду с экрана
        star.kill();
        // Добавляем и обновляем счет
        score += 10;
        scoreText.text = 'Score: ' + score;
    }        

Каждая звезда добавляет 10 очков, и scoreText обновляется для отображения нового значения. Если запустить part9.html, вы увидите финальную игру.

Заключение

Теперь вы научились создавать спрайт с физическими свойствами, управлять его движением и заставлять его взаимодействовать с другими объектами в маленьком игровом мире. Существует множество способов улучшить это. Например, пока нет ощущения завершения или опасности. Почему бы не добавить шипы, которые нужно избегать? Вы можете создать новую группу 'шипы' и проверять их столкновение с игроком. Вместо того чтобы убивать спрайт шипа, можно убить игрока. Или, для ненасильственной игры, вы могли бы сделать игру на время, где задача — собрать звезды как можно быстрее. Мы добавили несколько дополнительных графических элементов в архив, чтобы вдохновить вас.

С помощью того, что вы узнали в этом уроке и более чем 250 примеров, доступных вам, вы теперь обладаете прочной основой для будущего проекта. Но, как всегда, если у вас есть вопросы, нужна консультация или вы хотите поделиться тем, над чем работали, не стесняйтесь просить о помощи на форуме Phaser.