Тестируем Mighty Editor и Phaser. HTML5 игра за час

в 11:58, , рубрики: game development, html5 canvas, html5 games, javascript, mightyfingers

Тестируем Mighty Editor и Phaser. HTML5 игра за час - 1

HTML5 игры растут и развиваются, также как инсрументы для их создания.Например, phaser.js стал достаточно популярным движком, подходящим для производства игр среднего размера. Но каждый раз, применяя phaser.js, разработчикам html5 игр приходится писать повторяющийся код для стандартных операций. Визуальный редактор позволяет сгенерировать типовой код автоматически.

В этой статье я опишу основные возможности MightyEditor и процесс разработки игр. Учебник покажет, как сделать HTML5 мини-игру в течение часа.

Требования

Новейший браузер Google Chrome. Можно попробовать и другие браузеры, но мы их еще не тестировали.

Что такое MightyEditor?

MightyEditor — это онлайн редактор для создания и хостинга HTML5 игры. Он с открытым исходным кодом и совместим с популярным игровым движком Phaser, но вы также можете использовать его и с другими движками. Основные особенности редактора являются: ассет менеджмент, редактирование карт, редактор кода и экспорт данных. Подробнее о MightyEditor http://mightyfingers.com/editor-features/.

Как работает MightyEditor?

Процесс использования редактора работает следующим образом:

  1. Создание проекта
  2. Загрузка текстур
  3. Создание карты
  4. Группирование текстуры в слоях например фон, блоки...
  5. Добавление коллизий и функциональности в редакторе кода
  6. Запуск игры и экспорт данных

Зачем нужны редакторы HTML5 игр?

Онлайн-инструмент с функцией редактора карт и кода позволяет сделать прототип игры быстрее. Для работы достаточно открыть вкладку браузера, нет лишних проблем с установкой и настройкой различных программных решений, что существенно экономит драгоценное время.
Также упрощается сотрудничество с дизайнером, другим разработчиком или клиентом, для этого опять же достаточно отправить ссылку на проект. Можно работать в команде: назначить иллюстратора который добавит графику, в то время как гейм-дизайнер создает различные уровни в редакторе карт, а разработчик добавляет функциональность с JavaScript кодом.

Игра, сделанная в MightyEditor не зависит от него. Все текстуры и код можно экспортировать в любой момент, также благодаря открытому исходному коду, данные могут быть перемещены на локальный компьютер с локальной версией редактора. Исходный код можно найти на GitHub https://github.com/TheMightyFingers/mightyeditor.

Идея мини-игры

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

Создание проекта

Открой ссылку http://mightyeditor.mightyfingers.com. Введите название проекта -Digger.

Тестируем Mighty Editor и Phaser. HTML5 игра за час - 2

На нижней правой панели введите размеры игры: worldWidth, worldHeight 640 x 640. Для простоты мы установим view-port те же размеры 640 x 640.

Тестируем Mighty Editor и Phaser. HTML5 игра за час - 3

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

В следующей части туториала мы будем использовать текстуры, которые можно скачать здесь. В правом верхнем углу панели в списке опций выберите upload file и добавьте файлы. В качестве альтернативы можно перетаскивать файлы на панель и текстуры загрузятся автоматически.

Тестируем Mighty Editor и Phaser. HTML5 игра за час - 4

Создание карты

Нажмите иконку в виде штампа на левой панели, затем выберите из панели текстур background_sky.png, затем нажмите на карту, таким образом создавая фон (удерживая кнопку Ctrl для привязки к сетке). Для перемещения фона, выбери стрелку в левой панели инструментов. Для более точного положения можно изменить X, Y позицию в панели настроек.

Затем повторите этот шаг со следующими текстурами: background_city.png, background_hill1.png, background_hill2.png, background_grass.png. В конце все должно выглядеть как показано на следующем изображении

Тестируем Mighty Editor и Phaser. HTML5 игра за час - 5

Создание группы

А теперь создадим группу для созданных фоновых объектов. На правой панели Objects в списке выбора нажмите Add Group. Переименуйте группу на bg и перетащите объекты в эту группу. Чтобы выбрать несколько объектов, удерживайте клавишу Shift.

Тестируем Mighty Editor и Phaser. HTML5 игра за час - 6

Добавление остальных текстур

Создадим 5 рядов блоков под группы фона. Есть четыре различных типа блоков: камень, земля, трава, золото (rock, ground, grass, gold). При этом нужно собирать золотые блоки, но нельзя разбить камни. Объекты должны быть добавлены в группу blocks.

Тестируем Mighty Editor и Phaser. HTML5 игра за час - 7

Наконец, мы должны добавить объекты героя и магазин. Что касается текстуры героя — изображение содержит несколько кадров. Нам необходимо определить ширину кадра и высоту в нижней правой панели Settings. Размер 90 x 90. Отдельные кадры можно посмотреть на нижней assetPreview панели. Также нужно отредактировать anchorX и anchorY. Установите их на 0.5

Тестируем Mighty Editor и Phaser. HTML5 игра за час - 8

В дополнение, можно установить свои собственные переменные для объекта. Откройте закладку userData на той же панели и добавьте параметр золото со значением 0.

Тестируем Mighty Editor и Phaser. HTML5 игра за час - 9

Выберите иконку текст на панели слева и напишите «0» в правом верхнем углу карты, таким образом будут отображаться очки. Переименуйте текстовый объект в панели Objects на points.

Тестируем Mighty Editor и Phaser. HTML5 игра за час - 10

Теперь мы готовы добавить объекты и завершить редактирование карты. Объекты героя и магазина добавляются следующем образом

Тестируем Mighty Editor и Phaser. HTML5 игра за час - 11

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

Редактор кода

Перейдем к редактору кода на верхней левой стороне.

Тестируем Mighty Editor и Phaser. HTML5 игра за час - 12

На левой стороне отображается список с игровыми файлами. В основном, программировать нужно будет в файле state/play.js . Вы можете найти ключевые шорткаты для редактора здесь.

Стейты игры

Для программирования мы собираемся использовать популярный игровой движок Phaser. По умолчанию, редактор дает шаблон с четырьмя стейтами boot, load, menu, play. Для каждого стейта есть некоторые предопределенные методы: preload, create, update, render. Для этого проекта мы должны знать два метода: метод create вызывается сразу после того, когда все текстуры загрузились и метод update вызывается в игровом цикле 60 раз в секунду. Больше можно узнать в документации.

По умолчанию шаблон вызывает menu стейт. Для простоты мы не будем создавать меню на этот раз и начнем с геймплея. Для этого нужно вызвать play стейт в файле menu.js, в методе create.

window.Digger.state.menu = {
    create: function() {
        this.game.state.start("play");
    }
}

Откройте игру

Нажмите на кнопку Open game, на верхней панели появится черный экран. Для отображения только что созданных объектов мы должны инициализировать их в play.js в методе create.

create: function() {
    this.bg = mt.create("bg");
    this.blocks = mt.create("blocks");
    this.shop = mt.create("shop");
    this.character = mt.create("character");
    this.points = mt.create("points");
}

Инициализация делается с помощью функции mt.create. «bg» и «blocks» представляют имена групп в панели объектов в правой стороне. «shop» и «character» представляют имена спрайтов, в той же панели и, наконец, «points» представляют текст. Как видите, все объекты (группы, спрайты и текст) инициализируются тем же методом.

Откройте игру сейчас и объекты будут видны на экране.

Добавление физики

По умолчанию физика в Phaser отключена, для лучшей производительности. Мы включим самую легкую и быструю физику из всех доступных — Arcade physics. Откройте вкладку физики в нижнем правом углу. Измените параметр enable на 1 и остальные параметры появятся ниже. Установите размер тела героя width: 60 и height 60. Включите гравитацию и установите y на 1000. И в конце установите collideWorldBounds параметр на 1.

Тестируем Mighty Editor и Phaser. HTML5 игра за час - 13

При открытии игры Вы увидите героя, падающего в нижнюю часть экрана.

Управления героем

Инициализируем клавиши управления в методе create.

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

Эта функция дает нам объект с четырьмя клавишами со стрелками: up, down, left and right. В методе update отследим, когда будет нажата клавиша left/right/up и установим скорость для героя или остановим его если клавиши не активны.

update: function() {
    if (this.cursors.left.isDown) {
        this.character.body.velocity.x = -200;
    } else if (this.cursors.right.isDown) {
        this.character.body.velocity.x = 200;
    } else {
        this.character.body.velocity.x = 0;
    } if (this.cursors.up.isDown) {
        this.character.body.velocity.y = -300;
    }
}

Откройте игру и нажмите клавиши со стрелками для управления героя.

Коллизии между героями и блоками

Включим физику для блоков и установим их неподвижными в Map editor в правом нижнем панели физики.

Тестируем Mighty Editor и Phaser. HTML5 игра за час - 14

Добавим коллизии в методе update. Первые два аргумента означают, что мы будем проверять коллизии объекта спрайта и группы. Третий аргумент является функцией, которая вызывается при столкновении.

this.game.physics.arcade.collide(this.character, this.blocks.self, function(character, block) {
    console.log('Collision between', character, block);
}, null, this);

Открывая игру, герой не будет падать до конца экрана, но приземлится на блоки.

Анимация героя

Мы должны определить кадры в спрайт-листе для различных типов анимации. Добавьте эти строки в метод create.

this.character.animations.add('stand', [0, 1, 2, 3], 10, true);
this.character.animations.add('fly', [4, 5, 6, 7], 10, true);
this.character.animations.add('run', [8, 9, 10], 10, true);
this.character.animations.add('fall', [12, 13, 14, 15], 10, true);
this.character.animations.add('dig', [16, 17, 18], 10, false);
this.character.animations.add('dig_down', [20, 21, 22], 10, false);
this.character.animations.play('stand');

Последняя строка кода начинает анимацию stand.

Для остальной части анимации мы должны изменить метод update и добавить анимацию для каждого нажатия клавиши. Обратите внимание, что мы имеем ту же анимацию run для левой и правой стрелки клавиши. Для того, чтобы переворачивать по горизонтали спрайт мы используем scale -1.

if (this.cursors.left.isDown) {
    this.character.body.velocity.x = -200;
    this.character.animations.play('run');
    this.character.scale.x = -1;
} else if (this.cursors.right.isDown) {
    this.character.body.velocity.x = 200;
    this.character.animations.play('run');
    this.character.scale.x = 1;
} else {
    this.character.body.velocity.x = 0;
    this.character.animations.play('stand');
} if (this.cursors.up.isDown) {
    this.character.body.velocity.y = -300;
    this.character.animations.play('fly');
}

Уничтожение блоков

Для того, чтобы копать блоки нужно добавить функциональность для нашей функции коллизии:

this.game.physics.arcade.collide(this.character, this.blocks.self, function(character, block) {
    if (this.cursors.left.isDown) {
        if (block.body.touching.right) {
            this.destroyBlock(block);
        }
    }
    if (this.cursors.right.isDown) {
        if (block.body.touching.left) {
            this.destroyBlock(block);
        }
    }
    if (this.cursors.down.isDown) {
        if (block.body.touching.up) {
            this.destroyBlock(block);
        }
    }
}, null, this);

Различные блоки должны быть обработаны по-разному. Блоки травы и земли просто уничтожаются, камни нерушимы и мы можем собирать золото и сохранять его в параметре у героя. Добавим метод destroyBlock в стейт play.

destroyBlock: function(block) {
    switch (block.key) {
        case '/rock.png':
            break;
        case '/grass.png':
        case '/ground.png':
            block.destroy();
            break;
        case '/gold.png':
            this.character.getData().userData.gold++;
            block.destroy();
            break;
    }
},

Перекрытие и продажа золота

На следующем шаге мы должны реализовать продажу собранного золота в магазине. В конце метода update добавьте следующие строки:

if (this.checkOverlap(this.character, this.shop)) {
    if (this.character.getData().userData.gold > 0) {
        var newPoints = parseInt(this.points._text) + this.character.getData().userData.gold;
        this.points.setText(newPoints);
        this.character.getData().userData.gold = 0;
    }
}

И создайте новый метод в play state, который проверяет границы героя и магазина:

checkOverlap: function(spriteA, spriteB) {
    var boundsA = spriteA.getBounds();
    var boundsB = spriteB.getBounds();
    return Phaser.Rectangle.intersects(boundsA, boundsB);
}

Рефакторинг и добавление окончательных анимаций

Добавим анимации dig и произведем рефакторинг в методе update для того, чтобы код игры оставался достаточно красивым. Учитывая предыдущие образцы, код ниже должны быть понятен.

"use strict";
window.Digger.state.play = {        
        create: function(){
                this.cursors = this.game.input.keyboard.createCursorKeys();


                this.bg = mt.create("bg");
                this.blocks = mt.create("blocks");
                this.shop = mt.create("shop");
                this.character = mt.create("character");
                this.points = mt.create("points");


                this.character.animations.add('stand', [0, 1, 2, 3], 10, true);
                this.character.animations.add('fly', [4, 5, 6, 7], 10, true);
                this.character.animations.add('run', [8, 9, 10], 10, true);
                this.character.animations.add('fall', [12, 13, 14, 15], 10, true);
                this.character.animations.add('dig', [16, 17, 18], 10, false);
                this.character.animations.add('dig_down', [20, 21, 22], 10, false);
                this.character.animations.play('stand');
        },


        update: function(){
                var collideDown = false;
                this.game.physics.arcade.collide(this.character, this.blocks.self,
                  function(character, block){


                        if(this.dig) return;        


                        if(this.cursors.left.isDown){                
                                if(block.body.touching.right){
                                        this.dig = this.character.animations.play('dig');
                                        this.dig.onComplete.addOnce(function(){
                                              this.destroyBlock(block);
                                        }, this);                                                        
                                } else {
                                        this.character.animations.play('run');
                                }                                        
                        }


                        if(this.cursors.right.isDown){                                        
                                if(block.body.touching.left){
                                        this.dig = this.character.animations.play('dig');
                                        this.dig.onComplete.addOnce(function(){
                                              this.destroyBlock(block);
                                        }, this);                                        
                                } else {
                                        this.character.animations.play('run');
                                }


                        }


                        if(this.cursors.down.isDown){                                        
                                if(block.body.touching.up){
                                        this.dig = this.character.animations.play('dig_down');
                                        this.dig.onComplete.addOnce(function(){
                                              this.destroyBlock(block);
                                        }, this);


                                } else {
                                        this.character.animations.play('stand');
                                }
                        }                


                        if(block.body.touching.up){
                                collideDown = true;
                        }


                }, null, this);


                if(this.dig){
                        return;        
                }


                if(this.cursors.left.isDown){
                        this.character.scale.x = -1;
                        this.character.body.velocity.x = -200;
                }
                else if(this.cursors.right.isDown){
                        this.character.scale.x = 1;
                        this.character.body.velocity.x = 200;
                }
                else {
                        this.character.body.velocity.x = 0;        
                }


                if(this.cursors.up.isDown){
                        this.character.body.velocity.y = -300;
                        this.character.animations.play('fly');
                } else {
                        if(!collideDown){
                                this.character.animations.play('fall');
                        }
                        else if(this.character.body.velocity.x === 0){
                                this.character.animations.play('stand');
                        }
                }


                if(this.checkOverlap(this.character, this.shop)){
                        if(this.character.getData().userData.gold > 0){
                                var newPoints = parseInt(this.points._text) + this.character.getData().userData.gold;
                                this.points.setText(newPoints);
                                this.character.getData().userData.gold = 0;
                        }
                }
        },


        destroyBlock: function(block){
                this.dig = false;
                switch(block.key){
                        case '/rock.png':
                                break;
                        case '/grass.png':
                        case '/ground.png':
                                block.destroy();
                                break;
                        case '/gold.png':
                                this.character.getData().userData.gold++;
                                block.destroy();
                                break;
                }
        },


        checkOverlap: function (spriteA, spriteB) {
            var boundsA = spriteA.getBounds();
            var boundsB = spriteB.getBounds();
            return Phaser.Rectangle.intersects(boundsA, boundsB);
        },


        stopDig: function(){
                this.dig = false;        
        }
};

Полный проект игры доступен здесь: http://mightyeditor.mightyfingers.com/#pde5-copy
Финальная игра: http://mightyeditor.mightyfingers.com/data/projects/pde5/phaser/index.html
Facebook: https://www.facebook.com/mightyfingers
Twitter: https://twitter.com/Mighty_Fingers

Автор: Jedi_Knight

Источник

* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js