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

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

Требования

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

Что такое MightyEditor?

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Инициализируи клавиши управления в методе 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 в правом нижнем панели физики.

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

Leave a Reply

Your email address will not be published. Required fields are marked *