Phaser
В этом руководстве я собираюсь рассказать о том, как сделать игру-слайдер про животных и птиц, которые живут на ферме, используя HTML5-библиотеку Phaser. Источником вдохновения для этого проекта послужила игра для Android Animal Sounds for Baby от компании Fisher-Price.
Phaser – это бесплатный, интересный в изучении и простой в работе HTML5-фреймворк для разработки 2D-игр. Его создал Ричард Дэви (Richard Davey), вокруг Phaser сформировалось замечательное сообщество разработчиков. Обычно они общаются на форумах HTML5 Game Devs.
Игровой экран
Код и игровые ресурсы
Можете скачать код и ресурсы проекта, разработкой которого мы будем здесь заниматься, отсюда. А вот – GitHub-репозиторий.
Хочется отметить, что графика для игры создана моей компанией, Zenva. Вы можете использовать изображения для коммерческих и некоммерческих проектов, указание авторства не требуется (хотя, если вы из всего этого сделаете следующий хит вроде Candy Crash, купите мне, пожалуйста, здоровенный особняк).
Основные сведения о проекте и предварительные требования
В этом руководстве мы создадим кросс-платформенную игру для малышей, используя Phaser. Так же рассмотрим экспорт приложений для платформы Android с помощью Apache Cordova и Intel XDK.
Предполагается, что вы, приступая к чтению, обладаете основными навыками разработки на JavaScript. Если это не так, то сначала желательно разобраться с основами JS. Можете, например, пройти мой курс по JavaScript в Zenva Academy и быстро получить необходимые знания.
Для того, чтобы освоить это руководство, не требуется опыт работы с Phaser, Cordova, Android. То же самое касается и знаний в области разработки игр.
Игры, созданные на базе Phaser, нельзя запустить, просто открыв файл Index.html в браузере. Для их нормальной работы нужен веб-сервер. Phaser-игры, если не использовать в них Cordova API, можно запустить, используя обычные локальные веб-сервера. Например, WAMP для Windows, MAMP на Mac. Можно воспользоваться простым HTTP-сервером на Python или пакетом http-server для Node.js.
Кроме того, некоторые среды разработки, такие, как Intel XDK и редактор Brackets, поставляются со встроенным веб-сервером, поэтому, используя данные инструменты, устанавливать отдельный сервер не нужно.
Привет, мир!
Основа рассматриваемого Phaser-проекта представлена в виде файла Index.html, к которому подключена библиотека Phaser и скрипт, в котором и будет содержаться код игры. Вот, как выглядит файл Index.html.
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, minimum-scale=1, user-scalable=no" />
<title>Learn Game Development at ZENVA.com</title>
<script type="text/javascript" src="js/phaser.js"></script>
<style>
body {
padding: 0px;
margin: 0px;
background: black;
}
</style>
</head>
<body>
<!-- подключим главный файл игры -->
<script src="js/main.js"></script>
</body>
</html>
Вот код файла main.js. Здесь пока – только заготовки методов.
//проект будет включать в себя лишь одно игровое состояние
var GameState = {
//инициализация параметров игры
init: function() {
},
//загрузка игровых ресурсов до запуска игры
preload: function() {
},
//исполняется один раз после того, как все ресурсы будут загружены
create: function() {
},
//эта функция исполняется несколько раз в секунду
update: function() {
}
};
//инициализация Phaser
var game = new Phaser.Game(640, 360, Phaser.AUTO);
//добавляем состояние к объектам игры
game.state.add('GameState', GameState);
//запускаем состояние
game.state.start('GameState');
Если вы пользуетесь свежей версией Intel XDK, можете сгенерировать стартовый проект по шаблону Phaser. Для этого, на закладке Projects (Проекты) выполните команду Start a new project (Создать новый проект), разверните группу Templates (Шаблоны), выберите пункт Games (Игры), из списка шаблонов выберите Phaser. То, с чего мы начинаем работу, выглядит несколько иначе, чем в Intel XDK, но сути происходящего это не меняет.
Если загрузить Index.html в браузере, используя локальный веб-сервер (выше мы говорили об этом), и открыть консоль браузера, можно увидеть сведения об успешной инициализации Phaser.
Успешная инициализация Phaser
Спрайты
Изображения, или, как их принято называть в игровой индустрии, «спрайты» — это хлеб насущный 2D-игр. Phaser использует спрайты для отображения фона, персонажей игры, да практически всей игровой графики. Рассмотрим несколько примеров работы со спрайтами.
Предварительная загрузка текстур
Прежде чем вывести спрайт на экран, нужно загрузить файл изображения, который с этим спрайтом связан. Загрузка файла с диска занимает некоторое время, а вот загрузка изображения из оперативной памяти устройства происходит почти мгновенно. Предварительная загрузка игровых ресурсов – это загрузка всего, что нужно, с диска и размещение в оперативном памяти. В результате, когда изображение или что-то другое, понадобится, его можно отобразить на экране, или, в случае со звуком – воспроизвести, в мгновение ока.
Замечали, как иногда веб-страницы загружаются не полностью, а так, что можно видеть текст и лишь некоторые изображения? Это происходит из-за того, что изображения, прежде чем они станут видны, должны быть загружены с сервера. Для веб-сайтов это нормально, к этому все привыкли, но вот в играх подобное просто недопустимо! Представьте себе любимую игру с чёрным прямоугольником вместо персонажа, изображение которого не успело загрузиться.
Поэтому начнём с предварительной загрузки изображений и других игровых ресурсов, таких, как звуки, в методе preload() объекта GameState. Вот, как это выглядит.
preload: function() {
this.load.image('background', 'assets/images/background.png');
this.load.image('chicken', 'assets/images/chicken.png');
}
Команда this.load вызывает объект, который называют Загрузчиком (Loader), инициализированный движком Phaser. Этот объект оснащён различными методами для загрузки игровых ресурсов разных типов. Подробности смотрите в документации. Первый параметр – это ключ (key) ресурса. Ключ может быть любым, главное – не задавать один и тот же ключ разным ресурсам. Ключи будут использованы в коде игры для того, чтобы обращаться к конкретным ресурсам.
Вывод спрайта на экран
Для того, чтобы вывести спрайты на экран, нужно добавить соответствующий код в метод create() объекта GameState. Этот метод исполняется один раз после того, как все необходимые игровые ресурсы загружены.
//исполняется один раз после того, как все ресурсы будут загружены
create: function() {
this.background = this.game.add.sprite(0, 0, 'background');
this.chicken = this.game.add.sprite(100, 100, 'chicken');
},
Спрайты на экране расположены в заданных координатах, определяемых значениями X и Y. Координатная система начинается в верхнем левом углу игрового мира. Значения Y увеличиваются сверху вниз, X – слева направо.
Начало координат находится в левом верхнем углу
Вывод на экран спрайтов, инициализацию которых выполняет вышеприведённый код, выглядит так.
Спрайты выведены на экран эмулятора Intel XDK
Опорная точка
Точка спрайта, которая помещается в заданную ему координату (x,y), называется опорной точкой (anchor point). По умолчанию опорная точка спрайта соответствует его верхнему левому углу. Таким образом, установка позиции спрайта, например, в (10,10), означает, что в указанной позиции окажется его левый верхний угол.
Если подвергнуть спрайт вращению, то опорная точка окажется центром вращения. Кроме того, если изменить размер спрайта, масштабировать его, спрайт увеличится или уменьшится, но опорная точка останется там же, где была до начала трансформации.
Иногда опорную точку спрайта нужно изменить. Например, если возникла необходимость задавать положение спрайта с привязкой к его центру. Сделать это можно так, как показано ниже (после показа спрайта).
//установка опорной точки на центр спрайта
//можно то же самое описать короче: this.chicken.achor.setTo(0.5);
this.chicken.anchor.setTo(0.5, 0.5);
Спрайт после изменения опорной точки
Вращение спрайтов
Вращать спрайт очень просто. Для этого достаточно поменять его свойство angle (угол поворота). Вращение производится вокруг опорной точки (в нашем случае – вокруг центра спрайта). Угол поворота задаётся в градусах. Вот, например, как повернуть спрайт на 90 градусов.
this.chicken.angle = 90;
Поворот спрайта на 90 градусов
Изменение размеров спрайта
Свойство спрайта scale (масштаб) позволяет менять его размер. Ниже показаны примеры масштабирования (спрайт предварительно возвращён в состояние, в котором он был до поворота).
Растянем спрайт по вертикали.
this.chicken.scale.setTo(3,1); //двукратное увеличение по оси X с сохранением того же масштаба по оси Y
Спрайт растянут по вертикали
Уменьшим размеры спрайта в два раза:
this.chicken.scale.setTo(0.5) //изменение масштаба до 50% и по оси x, и по оси y
Спрайт, уменьшенный в два раза
Отражение спрайта
Если спрайт нужно отразить по оси X или Y, сделать это можно, задав отрицательные значения при настройке его свойства scale. Если, например, дать значение -1 по оси X, спрайт отразится по этой оси, но размер не изменит. Если же использовать отрицательные значения, отличающиеся от -1, это даст двойной эффект: отражение и изменение размера.
Отразим спрайт по оси X.
this.chicken.scale.setTo(-1,1); //отражение по оси x
Спрайт, отражённый по оси X
Размещение спрайта по центру игрового мира
Легче всего разместить спрайт по центру игрового мира, установив опорную точку спрайта в его центре и указав в качестве новых координат спрайта центр экрана. Например, сделать это можно так.
//создаём спрайт по центру игрового мира
this.chicken = this.game.add.sprite(this.game.world.centerX, this.game.world.centerY, 'chicken');
//устанавливаем опорную точку по центру спрайта
this.chicken.anchor.setTo(0.5, 0.5);
Местоположение спрайта на экране всегда можно изменить, отредактировав значение его свойств x и y.
Подробности о классе Sprite и о его методах и свойствах, часть которых мы рассмотрели выше, можно найти в документации.
Подстройка размера игрового экрана под размер экрана устройства
Неважно, для какой платформы или платформ разрабатывается игра, в любом случае придётся иметь дело с различными разрешениями и соотношениями сторон экранов. Такова реальность. В недалёком прошлом это было настоящим кошмаром для разработчиков HTML5-игр, но большая часть игровых движков берёт проблемы, связанные с разными экранами, на себя. Phaser – не исключение, он содержит встроенные возможности по поддержке всевозможных экранов и позволяет создавать игры, которые правильно работают на различных устройствах. Всем этим в Phaser занимается объект Scale Manager (Менеджер масштабирования).
Наш игровой экран будет менять размер в соответствии с размером экрана устройства, сохраняя соотношение сторон. Этот режим в менеджере масштабирования называется SHOW_ALL. Настройки производятся в методе init().
//инициализация параметров игры
init: function() {
//параметры масштабирования экрана
this.scale.scaleMode = Phaser.ScaleManager.SHOW_ALL;
//центруем игровой экран по горизонтали и вертикали
this.scale.pageAlignHorizontally = true;
this.scale.pageAlignVertically = true;
},
Игра на экране, параметры которого отличаются от параметров игрового экрана
Добавление стрелок
Добавим на игровой экран стрелки, с помощью которых можно будет переключаться между обитателями фермы. Когда пользователь касается стрелки, существо, отображаемое на экране, перемещается вправо или влево, а его место, по центру экрана, занимает новое.
Сейчас добавим спрайты стрелок и настроим некоторые их свойства. Для начала выполним предварительную загрузку текстуры в методе preload().
this.load.image('arrow', 'assets/images/arrow.png');
Теперь, в методе create(), сделаем следующее.
//левая стрелка
this.leftArrow = this.game.add.sprite(60, this.game.world.centerY, 'arrow');
this.leftArrow.anchor.setTo(0.5);
this.leftArrow.scale.x = -1;
this.leftArrow.customParams = {direction: -1};
//правая стрелка
this.rightArrow = this.game.add.sprite(580, this.game.world.centerY, 'arrow');
this.rightArrow.anchor.setTo(0.5);
this.rightArrow.customParams = {direction: 1};
Здесь мы создаём пару спрайтов – по одному на каждую стрелку. И тот, и другой, используют одну и ту же текстуру, которую мы загружали, указав «arrow» в качестве ключа. Обратите внимание на то, как спрайты размещены строго по центру оси Y игрового мира, на то, что их опорные точки установлены на центр спрайта, и на то, как с помощью свойства scale первая стрелка была перевёрнута таким образом, чтобы она указывала влево.
Дополнительные параметры спрайтов
Я хочу создать дополнительный параметр для спрайтов стрелок, который содержит сведения о направлении стрелки по оси X. Для того, чтобы задать собственное, не предусмотренное системой, свойство, можно воспользоваться конструкцией this.rightArrow.anythingHere. Однако, я предпочитаю сохранять подобные дополнительные свойства в отдельном объекте, давая ему имя «customParams». Почему я так поступаю? Например, что если некто недостаточно хорошо знаком с Phaser и решает создать дополнительное свойство объекта с именем «scale»? Как нам известно, scale – это одно из стандартных свойств спрайтов, и создав другое такое же, мы просто заменим стандартное, задав ему некое значение. Как результат – непонятные проблемы и ошибки, которые очень сложно обнаружить. Именно поэтому я предпочитаю сохранять все дополнительные данные в отдельном объекте, с полной уверенностью в том, что его имя не совпадёт ни с чем, что уже есть у объекта sprite.
Стрелки на игровом экране
Обработка ввода
Сейчас на экране имеется пара стрелок и неподвижная курица. Позже вместо неё здесь будет целая ферма. Но мы пока курицу оставим там, где она есть и разберёмся с обработкой ввода.
Для начала сделаем птицу интерактивной, реагирующей на касания и щелчки мыши.
this.chicken = this.game.add.sprite(this.game.world.centerX, this.game.world.centerY, 'chicken');
//устанавливаем опорную точку по центру спрайта
this.chicken.anchor.setTo(0.5, 0.5);
//включаем ввод, то есть – спрайт сможет реагировать на действия пользователя
this.chicken.inputEnabled = true;
//события касания вызываются только для непрозрачных частей курицы
this.chicken.input.pixelPerfectClick = true;
//функция обратного вызова для события касания
this.chicken.events.onInputDown.add(this.animateAnimal, this);
Теперь надо создать в объекте GameState метод animateAnimal. Именно в нём будет запускаться анимация и проигрываться звук.
//воспроизводим анимацию и проигрываем звук
animateAnimal: function(sprite, event) {
console.log('animate animal and play sound');
},
Включим обработку событий ввода для обеих стрелок.
//левая стрелка
this.leftArrow = this.game.add.sprite(60, this.game.world.centerY, 'arrow');
this.leftArrow.anchor.setTo(0.5);
this.leftArrow.scale.x = -1;
this.leftArrow.customParams = {direction: -1};
//обработка ввода для левой стрелки
this.leftArrow.inputEnabled = true;
this.leftArrow.input.pixelPerfectClick = true;
this.leftArrow.events.onInputDown.add(this.switchAnimal, this);
//правая стрелка
this.rightArrow = this.game.add.sprite(580, this.game.world.centerY, 'arrow');
this.rightArrow.anchor.setTo(0.5);
this.rightArrow.customParams = {direction: 1};
//обработка ввода для правой стрелки
this.rightArrow.inputEnabled = true;
this.rightArrow.input.pixelPerfectClick = true;
this.rightArrow.events.onInputDown.add(this.switchAnimal, this);
Добавим метод switchAnimal, пока пустой.
//переключиться на другое животное или птицу, параметр sprite даёт доступ к спрайту, которого коснулся пользователь
switchAnimal: function(sprite, event) {
console.log('switch animal');
}
Теперь, если щёлкать по стрелкам или по курице, в консоли будут появляться соответствующие сообщения.
Группы
Работа с наборами спрайтов – это обычная методика, применяемая при разработке игр. Вот, например, игра про космический корабль, в которой имеется множество врагов. Новые враги возникают на экране, их уничтожают, вражеские корабли одинаково взаимодействуют с игровым миром. Логично предположить, что многие операции удобнее выполнять не для каждого врага в отдельности, а сразу для некоторой их группы.
В игре имеется набор животных и птиц. Используя кнопки со стрелками, можно переключиться на следующего обитателя фермы или вернуться к предыдущему. Для того, чтобы работать с игровыми объектами, будем использовать группы.
Уникальные для каждого существа данные (имя, текстура, звук, анимация) будут представлены в игре как массив объектов. Такой массив, теоретически, можно загрузить уже в готовом виде с некоего веб-сервера или из локального хранилища. Используя эти данные, мы создаём спрайты и объединяем их в группу.
Попрощаемся с курицей, с отдельным спрайтом, и перейдём к работе с группой объектов. Начнём с предварительной загрузки изображений для них.
preload: function() {
this.load.image('background', 'assets/images/background.png');
this.load.image('arrow', 'assets/images/arrow.png');
this.load.image('chicken', 'assets/images/chicken.png');
this.load.image('horse', 'assets/images/horse.png');
this.load.image('pig', 'assets/images/pig.png');
this.load.image('sheep', 'assets/images/sheep.png');
},
В методе create нужно убрать всё, до кода, относящегося к стрелкам, и вставить туда следующее.
//создаём спрайт для фонового изображения
this.background = this.game.add.sprite(0, 0, 'background');
//данные для обитателей фермы
var animalData = [
{key: 'chicken', text: 'CHICKEN',},
{key: 'horse', text: 'HORSE'},
{key: 'pig', text: 'PIG'},
{key: 'sheep', text: 'SHEEP'}
];
//создадим группу для хранения всех объектов – животных и птиц
this.animals = this.game.add.group();
//заполним группу данными
var self = this;
var animal;
animalData.forEach(function(element){
// создаём животное или птицу, сохраняем свойства
animal = self.animals.create(-1000, self.game.world.centerY, element.key, 0);
//Я сохраняю всё, что не относится к Phaser, в отдельном объекте
animal.customParams = {text: element.text, sound: 'to be added..'};
//установим опорную точку по центру спрайта
animal.anchor.setTo(0.5);
//включим ввод, объекты будут реагировать на касания и щелчки мышью
animal.inputEnabled = true;
animal.input.pixelPerfectClick = true;
animal.events.onInputDown.add(self.animateAnimal, this);
});
//поместим первое существо по центру экрана
this.currentAnimal = this.animals.next();
this.currentAnimal.position.set(this.game.world.centerX, this.game.world.centerY);
Вот что мы сейчас сделали.
- Задали все данные обитателей фермы в виде JSON-объекта. Такой объект можно загрузить с веб-сервера или из локального хранилища, либо, для некоторых игр, сгенерировать программно
- Создали группу объектов.
- Прошлись в цикле по данным обитателей фермы, создали спрайт для каждого из них и добавили то, что получилось, в группу.
- Для каждого из спрайтов включили обработку ввода и задали функцию обратного вызова, которая должна сработать при взаимодействии пользователя с игровым объектом.
- Получили первое существо из группы, воспользовавшись методом next() и разместили его по центру экрана. Группа позволяет получать доступ к объектам, входящим в неё, с использованием команд next() и previous().
Подробности о классе Group, которым мы пользуемся, можно найти здесь.
Интерполяционная анимация
Интерполяционная анимация, которую нередко называют «твининг» (tweening), это подход к анимации, который состоит в том, что задаётся начальный и конечный кадр, а промежуточные кадры генерируются системой автоматически. Отсюда и термин «tweening», который образован от «in-between», что можно перевести как «в промежутке».
Например, мячик находится в позиции x=10. Мы решили, что в конце 10-секундного анимированного движения он должен оказаться в позиции x=100. В результате мячик за указанное время плавно переместится из начальной позиции в конечную. При этом, мы задали лишь его начальное и конечное положение, а также – длительность анимации.
Phaser содержит встроенную подсистему интерполяционной анимации (взгляните на описание класса Tween из документации). Анимировать можно практически любое свойство объекта класса Sprite, или даже несколько свойств одновременно.
Будем анимировать перемещение жителей фермы из начальной в конечную позицию. Кроме того, можно задать функцию обратного вызова, которая будет выполняться при завершении анимации.
Отключим возможность нажимать на стрелки во время движения животного или птицы по экрану. В результате пользователь не сможет нажимать на стрелки во время выполнения анимации (хотя, если этого не сделать, получается довольно забавно, можете попробовать).
Метод switchAnimal, каркас которого создан выше, теперь будет выглядеть так.
//переключение между обитателями фермы
switchAnimal: function(sprite, event) {
//если анимация происходит, не делаем ничего
if(this.isMoving) {
return false;
}
this.isMoving = true;
var newAnimal, endX;
//определяем, в зависимости от нажатой кнопки, кто должен появиться
if(sprite.customParams.direction > 0) {
newAnimal = this.animals.next();
newAnimal.x = -newAnimal.width/2;
endX = 640 + this.currentAnimal.width/2;
}
else {
newAnimal = this.animals.previous();
newAnimal.x = 640 + newAnimal.width/2;
endX = -this.currentAnimal.width/2;
}
//настраиваем и запускаем анимацию, перемещение по оси x
var newAnimalMovement = game.add.tween(newAnimal);
newAnimalMovement.to({ x: this.game.world.centerX }, 1000);
newAnimalMovement.onComplete.add(function()
{
this.isMoving = false;
}, this);
newAnimalMovement.start();
var currentAnimalMovement = game.add.tween(this.currentAnimal);
currentAnimalMovement.to({ x: endX }, 1000);
currentAnimalMovement.start();
this.currentAnimal = newAnimal;
},
Как можно заметить из кода, обычный порядок работы с интерполяционной анимацией состоит из нескольких шагов.
- Создать объект Tween, который и займётся анимацией.
- Задать свойства анимации, и, если необходимо – функцию обратного вызова.
- Запустить анимацию в нужном месте программы.
Интерполяционная анимация
Покадровая анимация
Другой тип анимации, который можно использовать в игре, называют покадровой анимацией (frame animation). При таком подходе нужны так называемые спрайт-листы (spritesheet), содержащие различные кадры анимации. Спрайт-листы, задействованные в этом проекте, имеются в материалах к нему.
Спрайт-лист
Начнём с предварительной загрузки спрайт-листов. Вместо того, чтобы загружать отдельные изображения, загружаем спрайт-листы и задаём их размеры и количество кадров.
//загрузка игровых ресурсов перед началом игры
preload: function() {
this.load.image('background', 'assets/images/background.png');
this.load.image('arrow', 'assets/images/arrow.png');
this.load.spritesheet('chicken', 'assets/images/chicken_spritesheet.png', 131, 200, 3);
this.load.spritesheet('horse', 'assets/images/horse_spritesheet.png', 212, 200, 3);
this.load.spritesheet('pig', 'assets/images/pig_spritesheet.png', 297, 200, 3);
this.load.spritesheet('sheep', 'assets/images/sheep_spritesheet.png', 244, 200, 3);
},
Не забывайте о том, что загрузка ресурсов выполняется с помощью класса Loader.
В методе create() будет создана анимация для каждого спрайта. Эту анимацию можно запустить позднее. Кроме того, обратите внимание на то, что мы, создавая спрайты, добавляем в конце новый параметр (он принимает значение 0). Данный параметр указывает на то, какой именно кадр из спрайт-листа нужно взять. Кадр с номером «0» — это первый кадр, если считать их слева направо.
animalData.forEach(function(element){
// создаём животное или птицу, сохраняем свойства
animal = self.animals.create(-1000, self.game.world.centerY, element.key, 0);
//
Я сохраняю всё, что не относится к Phaser, в отдельном объекте:
animal.customParams = {text: element.text};
// установим опорную точку по центру спрайта
animal.anchor.setTo(0.5);
//создадим анимацию
animal.animations.add('animate', [0, 1, 2, 1, 0, 1], 3, false);
// включим ввод, объекты будут реагировать на касания и щелчки мышью
animal.inputEnabled = true;
animal.input.pixelPerfectClick = true;
animal.events.onInputDown.add(self.animateAnimal, this);
});
Обратите внимание на то, как задаётся анимация:
animal.animations.add('animate', [0, 1, 2, 1, 0, 1], 3, false);
B на то, как она прикрепляется к спрайту «animal». Конкретной последовательности покадровой анимации ставится в соответствие ключ («animate» в данном случае), что позволяет обращаться к ней из кода. Второй параметр – это массив, задающий порядок, в котором нужно показывать кадры. Например, здесь всё начинается с нулевого кадра, потом идёт первый, далее – второй. Третий параметр – это частота кадров в секунду. Именно столько кадров (3 в нашем случае) будет показано за одну секунду. Последний параметр, «false», служит для указания того, следует ли автоматически начинать воспроизведение анимации сначала, когда она закончится. Если установить этот параметр в «true», то, закончившись, анимация сама начнётся снова. По умолчанию это значение – именно «false», его можно и не указывать, но для того, чтобы вам было понятнее, что мы делаем, мы задаём этот параметр явно.
Жителей фермы будем анимировать по касанию на них. Пришло время добавить код в метод animateAnimal.
//воспроизводим анимацию и проигрываем звук
animateAnimal: function(sprite, event) {
sprite.play('animate');
},
Озвучивание
Работа со звуками не особенно отличается от работы с изображениями и другими игровыми ресурсами. Сначала, как обычно, надо организовать предварительную загрузку звуковых файлов в методе preload().
this.load.audio('chickenSound', ['assets/audio/chicken.ogg', 'assets/audio/chicken.mp3']);
this.load.audio('horseSound', ['assets/audio/horse.ogg', 'assets/audio/horse.mp3']);
this.load.audio('pigSound', ['assets/audio/pig.ogg', 'assets/audio/pig.mp3']);
this.load.audio('sheepSound', ['assets/audio/sheep.ogg', 'assets/audio/sheep.mp3']);
Здесь загружаются файлы в формате mp3 и файлы формата ogg. На момент написания этих слов звукового формата, который подходил бы для всех платформ, поддерживающих HTML5, не было. Форматы MP3 и OGG позволяют организовать работу приложения на всех основных платформах. Для того, чтобы конвертировать звуковые файлы в различные форматы, можно воспользоваться приложением Audacity. Существуют его версии для Linux, Windows и Mac.
Добавим ключи звуковых ресурсов в массив animalData и создадим звуковые объекты для каждого существа. Добавим эти объекты в свойство customParams каждого спрайта.
//данные для обитателей фермы
var animalData = [
{key: 'chicken', text: 'CHICKEN', audio: 'chickenSound'},
{key: 'horse', text: 'HORSE', audio: 'horseSound'},
{key: 'pig', text: 'PIG', audio: 'pigSound'},
{key: 'sheep', text: 'SHEEP', audio: 'sheepSound'}
];
// создадим группу для хранения всех игровых объектов
this.animals = this.game.add.group();
var self = this;
var animal;
animalData.forEach(function(element){
//
Cоздаём животное или птицу, сохраняем свойства:
animal = self.animals.create(-1000, self.game.world.centerY, element.key, 0);
//Я сохраняю всё, что не относится к Phaser, в отдельном объекте
animal.customParams = {text: element.text, sound: self.game.add.audio(element.audio)};
//установим опорную точку по центру спрайта
animal.anchor.setTo(0.5);
// создадим анимацию
animal.animations.add('animate', [0, 1, 2, 1, 0, 1], 3, false);
// включим ввод, объекты будут реагировать на касания и щелчки мышью
animal.inputEnabled = true;
animal.input.pixelPerfectClick = true;
animal.events.onInputDown.add(self.animateAnimal, this);
});
И, наконец, будем проигрывать звуки там же, где включаем анимацию, в методе animateAnimal.
// воспроизводим анимацию и проигрываем звук
animateAnimal: function(sprite, event) {
sprite.play('animate');
sprite.customParams.sound.play();
},
Теперь звуковая часть игры заработала!
Где брать звуки для игр?
Звуки для этой игры мы отыскали на Freesound.org, они находятся в открытом доступе. Потом к ним добавили записанные фрагменты речи с использованием Audacity. Freesound.org – это отличное место, где можно найти звуковые эффекты для игр. Если вы решите этим ресурсом воспользоваться, обращайте внимание на лицензии к файлам. Некоторые из них требуют указания сведений об авторских правах, а некоторые нельзя использовать в коммерческих проектах.
Работа с текстом
Остались последние штрихи: надписи с названиями животных и птиц, которые будут появляться на экране под ними.
Если вы взглянете на код, который уже есть, то обнаружите, что текстовое название для каждого обитателя фермы уже присутствует в объекте со свойствами animalData, оно передано спрайту при создании.
Напишем метод, который выводит на экран названия животных и птиц. Этот метод будет принимать соответствующий спрайт.
showText: function(animal) {
//создать объект для вывода текста если он пока не существует
if(!this.animalText) {
var style = {font: "bold 30pt Arial", fill: "#D0171B", align: "center"};
this.animalText = this.game.add.text(this.game.width/2, this.game.height * 0.85, 'asdfasfd' , style);
this.animalText.anchor.setTo(0.5);
}
this.animalText.setText(animal.customParams.text);
this.animalText.visible = true;
}
Метод выводит название обитателя фермы, спрайт которого передан ему в качестве параметра. Для того, чтобы больше узнать о том, как работать с объектом Text, какие параметры ему можно передавать, обратитесь к документации.
Метод create – это место, где существо впервые выводится на экран. Вместе с изображением нужно вывести и надпись. Поэтому теперь та часть кода, где мы задаём переменную currentAnimal, должна выглядеть так, как показано ниже.
//поместим первое существо по центру экрана
this.currentAnimal = this.animals.next();
this.currentAnimal.position.set(this.game.world.centerX, this.game.world.centerY);
this.showText(this.currentAnimal);
При переключении между животными и птицами нужно, во время перехода, скрывать текст. Для этого можно воспользоваться свойством спрайта visibility. После завершения анимации надо показать название нового животного или птицы.
// переключение между обитателями фермы
switchAnimal: function(sprite, event) {
// если анимация происходит, не делаем ничего
if(this.isMoving) {
return false;
}
this.isMoving = true;
//скрыть текст
this.animalText.visible = false;
var newAnimal, endX;
// определяем, в зависимости от нажатой кнопки, кто должен появиться
if(sprite.customParams.direction > 0) {
newAnimal = this.animals.next();
newAnimal.x = -newAnimal.width/2;
endX = 640 + this.currentAnimal.width/2;
}
else {
newAnimal = this.animals.previous();
newAnimal.x = 640 + newAnimal.width/2;
endX = -this.currentAnimal.width/2;
}
// настраиваем и запускаем анимацию, перемещение по оси x
var newAnimalMovement = game.add.tween(newAnimal);
newAnimalMovement.to({ x: this.game.world.centerX }, 1000);
newAnimalMovement.onComplete.add(function()
{
this.isMoving = false;
//покажем текст
this.showText(newAnimal);
}, this);
newAnimalMovement.start();
var currentAnimalMovement = game.add.tween(this.currentAnimal);
currentAnimalMovement.to({ x: endX }, 1000);
currentAnimalMovement.start();
this.currentAnimal = newAnimal;
},
Готовая игра
Примите поздравления! Игра для малышей готова. Можете с ней поэкспериментировать, например – добавить больше обитателей фермы и эффектов. Если хотите, можете взять готовый проект здесь.
Экспорт игры на различные платформы
У HTML5-игр есть одна замечательная особенность: они без проблем работают в браузерах, при этом неважно, на какой платформе. Игры, в которые можно играть, открывая их страницы в браузере, готовы к распространению среди широчайшей аудитории. Однако, если ваша цель – мобильные пользователи, лучше будет оформить игру в виде приложения, рассчитанного на конкретную платформу и опубликовать в соответствующем магазине приложений.
Сейчас я расскажу, как превратить нашу игру в полноценное Android-приложение с использованием Cordova и Intel XDK. Подробности о Cordova и о гибридных приложениях вы можете найти в моей статье «How to Build a Virtual Pet Game with HTML5 and Cordova». В ней так же используется API Cordova.
Если вы пользовались Intel XDK, читая это руководство, то у вас уже должен быть готовый проект игры. В противном случае можете импортировать демонстрационный проект, перейдя на закладку Projects (Проекты) и выполнив там команду Start a New Project > Import Your HTML5 Code Base (Создать новый проект > Импортировать ваш HTML5-код).
После того, как импорт завершён, игру можно будет запустить в эмуляторе. Кроме того, можно запустить игру и на физическом устройстве, воспользовавшись возможностями закладки Test (Тестирование) (предварительно на устройство нужно установить специальное приложение от Intel, App Preview, и синхронизировать учётные записи).
Для того, чтобы собрать приложение для Android, равно как и для других платформ, нужно воспользоваться вкладкой Build (Сборка). Что касается Android, то здесь есть два варианта. Первый называется просто «Android», второй – «Crosswalk for Android». Разница между ними заключается в том, что при выборе второго варианта в пакет приложения будет входить не только игра, но и компоненты проекта Crosswalk, в частности, элемент управления webview, основанный на Chrome. В результате игра будет одинаково выглядеть и одинаково работать на любом Android-смартфоне, независимо от версии ОС (надо отметить, что поддерживаются только Android 4+). Это – главное преимущество Crosswalk. Но, за всё надо платить. В данном случае минус – в размере APK файла. Например, простое Android-приложение, сгенерированное Intel XDK, может уместиться в APK-файле размером 1 Мб, а то же самое приложение, при сборке которого использовался Crosswalk, уже займёт 20 Мб.
Вне зависимости от выбранного варианта, сборка приложения для Android произойдёт в облаке, а готовый APK-файл можно будет установить на устройстве или опубликовать в магазинах приложений Google и Amazon.
Заключение
Вот, что вы узнали, изучив это руководство.
- Как работать со спрайтами. Что такое опорная точка, как вращать, отражать, масштабировать, перемещать спрайты.
- Как обрабатывать ввод данных.
- Как масштабировать игровой экран для правильной поддержки дисплеев различных устройств.
- Как работать с группами в Phaser.
- Что такое интерполяционная и покадровая анимация.
- Как добавить в игру текстовые надписи.
- Как озвучить игру.
- Как экспортировать HTML5-игру в виде обычного Android-приложения.
Всё это вместе позволило вам создать собственную игру для самых маленьких. Если хотите продолжить изучать Phaser, вот несколько моих статей и материалов, подготовленных мною в соавторстве с другими людьми, посвящённых этому фреймворку. How to Make a Virtual Pet Game with HTML5 and Cordova, How to Make a Sidescroller Game with HTML5, HTML5 Phaser Tutorial – SpaceHipster, HTML5 Phaser Tutorial – Top-down Games. Если вы предпочитаете видеоуроки – вот коллекция HTML5-курсов в Zenva Academy.
Кстати, если вы сделали что-нибудь интересное на базе кода рассмотренной здесь игры, или создали и опубликовали собственную игру – расскажите нам! Кроме того, если вы опубликовали игру после чтения одного из моих руководств – свяжитесь со мной и мы напишем о вашей игре в GameDev Academy. Это прибавит ей популярности и загрузок.
Комментарии (15)
ACCNCC
28.04.2016 18:25В чем плюсы в Intel XDK по сравнению с cordova?
adasoft
30.04.2016 08:49Если не брать «сборку билда в облаке», который предоставляет XDК, то можно использовать фичу быстрого переключения «устройств», с разными разрешениями и разными «ОС» (но по капотом останется десктопный хромиум), а также довольно удобная для тестирования связкая XDK + Intel App Preview под все основные платформы (в том числе Windows 10), не без косяком конечно, но для быстрого теста подходит.
Zhbert
28.04.2016 22:12+2Черт… Я хоть и вырос уже, но теперь тоже хочу себе горшок со встроенным планшетом!
BubaVV
28.04.2016 22:41По превью подумал, что это тренажер «снайпера» и уже начал возмущаться про плохую водостойкость и сложность мытья после использования
GeMir
29.04.2016 14:10Есть весьма любопытный доклад профессора Манфреда Шпитцера (Prof. Dr. Dr. Manfred Spitzer) о том, насколько «полезны» для развивающегося мозга ребёнка планшеты, смартфоны и всевозможные «игры для самых маленьких». К сожалению, только на немецком: www.youtube.com/watch?v=FnDEF7Aw9HI Точка зрения весьма радикальная и потому не слишком популярная, однако, повод для размышлений даёт.
Zhbert
30.04.2016 21:55Там и без точки зрения профессора все понятно, стоит только посмотреть на лица детей, взрощенных на плашметах, когда они в процессе пальцетыка… Довольно часто наблюдаю такую картину — ужас охватывает, представляется такое поколение зомби, неспособных даже штаны на себя натянуть.
Sirion
28.04.2016 22:59Игры, созданные на базе Phaser, нельзя запустить, просто открыв файл Index.html в браузере. Для их нормальной работы нужен веб-сервер.
Это, собственно, почему?ashalaenko
29.04.2016 17:52Такая же ситуация и с cocos2d-js/html5. Там идет ajax запрос при старте для подгрузки ресурсов, если не ошибаюсь, а когда у вас строка урл вида file:///home/.../index.html ни о каком ajax речи быть не может. В phaser, скорее всего, такая же штука.
adasoft
29.04.2016 22:05Можно использовать XDK, можно NetBeans, можно JetBrains, можно внешний вебсервер.
1nt3g3r
А какая скорость работы на слабых Android-устройствах? Меня смущает, что это javascript. Есть примеры игр на phaser, которые можно «пощупать»?
ACCNCC
Телефон 1gb ram = 40-50fps в 3д игре на webgl (three.js)
javascript быстрый язык если не использовать dom