Войти
ПрограммированиеСтатьиОбщее

Сделай быстро игру используя Phaser и MightyEditor

Внимание! Этот документ ещё не опубликован.

Автор:

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

Требования


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

Что такое MightyEditor?


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

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


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

Почему я должен использовать редактор игр?


Имея инструмент в онлайн с функцией редактора карт и кода, позволяет сделать прототип игр быстрее. Открой ссылку в браузере, и ты готов работать. Нет лишних проблем с установкой и настройкой различных программных решений, что существенно экономит Твое драгоценное время.

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

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

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


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

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


Открой ссылку http://mightyeditor.mightyfingers.com. Введи название проекта - Digger.
01_project_name | Сделай быстро игру используя Phaser и MightyEditor

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

02_game_dimensions | Сделай быстро игру используя Phaser и MightyEditor

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


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

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


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

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

04_bg_screenshot | Сделай быстро игру используя Phaser и MightyEditor

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


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

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


Создадим 5 рядов блоков под группы фона. Есть четыре различных типа блоков: камень, земля, трава, золото (rock, ground, grass, gold). Где Ты должен собирать золотые блоки, но не можешь разбить камни. Объекты должны быть добавлены в группу blocks.
06_blocks | Сделай быстро игру используя Phaser и MightyEditor

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

07_spritesheet | Сделай быстро игру используя Phaser и MightyEditor

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

0711_userData | Сделай быстро игру используя Phaser и MightyEditor

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

071_text | Сделай быстро игру используя Phaser и MightyEditor

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

08_final_map | Сделай быстро игру используя Phaser и MightyEditor

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

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


Перейдем к редактору кода на верхней левой стороне.
09_source_editor_tab | Сделай быстро игру используя Phaser и MightyEditor

На левой стороне отображается список с игровыми файлами. В основном программировать нужно будет в файле 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.
10_character_physics | Сделай быстро игру используя Phaser и MightyEditor

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

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


Инициализируи клавиши управления в методе 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 в правом нижнем панели физики.
11_group_physics | Сделай быстро игру используя Phaser и MightyEditor

Добавим коллизии в методе 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

#game editor, #HTML5, #JavaScript, #игровой редактор, #редактор карт, #редактор уровней

26 ноября 2014