Phaser - это фантастическая игровая среда HTML5, которую можно легко запустить на любом устройстве. Недостатком является то, что ее основа - HTML5, поэтому не существует готового решения для создания мобильной версии вашей игры. Вы можете использовать другие инструменты, такие как PhoneGap, CocoonJS, Ionic и т. д., чтобы создать гибридное мобильное приложение, но вы будите зависеть от сторонних инструментов. Тем не менее, если вы оптимизируете свою игру должным образом, любой может играть в нее на мобильном устройстве, но это требует, чтобы пользователь обязательно посещал ваш сайт, и при этом у него не будет некоторых приятных фишек приложения, таких как значок на домашнем экране или возможность играть в игру без сети.
Что если бы у вас было гибридное приложение, представляющее собой смесь собственного мобильного приложения и веб-приложения? Прогрессивные веб-приложения или PWA, напоминают мобильное приложение, могут работать в автономном режиме, быстро реагируют и очень просты в установке. Кроме того, мы можем сделать нашу игру PWA без каких-либо сторонних инструментов. Нам просто нужно добавить несколько файлов в наш проект, и нам нужно добавить дополнительный код на нашу главную html-страницу.
Вы можете скачать все файлы, связанные с исходным кодом, здесь .
Для этого урока вам понадобится следующее:
В этом руководстве мы собираемся использовать часть кода из шаблона проекта веб-пакета Phaser 3, который доступен на GitHub. Мы не будем использовать данный веб-пакет для этого урока, однако мы будем использовать базовый шаблон и изображения логотипа Phaser. Вы можете скачать базовый код проекта здесь.
В архиве вы увидите три папки (css
, js
и img
) и файл index.html
. Если вы откроете index.html
в своем редакторе кода, вы должны увидеть следующий код:
<!DOCTYPE html>
<html>
<head>
<link rel="stylesheet" href="css/style.css" />
<title>PWA-game</title>
</head>
<body>
<script src="//cdn.jsdelivr.net/npm/phaser@3.20.1/dist/phaser.js"></script>
<script src="js/game.js"></script>
</body>
</html>
В папке js
есть файл с именем game.js
, котором есть вся логика для нашей игры Phaser. Если вы откроете game.js
, то вы увидите следующий код:
var config = {
type: Phaser.AUTO,
parent: 'phaser-example',
width: window.innerWidth,
height: window.innerHeight,
scene: {
preload: preload,
create: create
}
};
var game = new Phaser.Game(config);
function preload() {
this.load.image('logo', 'img/logo.png');
}
function create() {
this.logo = this.add.image(0, 0, 'logo');
this.logo.setScale(0.5);
Phaser.Display.Align.In.Center(
this.logo,
this.add.zone(window.innerWidth/2, window.innerHeight/2, window.innerWidth, window.innerHeight)
);
this.tweens.add({
targets: this.logo,
y: 450,
duration: 2000,
ease: 'Power2',
yoyo: true,
loop: -1
});
}
Наконец, если вы запустите свой сервер и попробуете запустить игру, вы должны увидеть черный экран с логотипом Phaser.
Теперь, когда наш проект настроен, мы добавим в нашу игру Service worker. Прежде чем мы начнем писать код, давайте рассмотрим, что такое Service worker. Service worker - это служба JavaScript, являющаяся по сути файлом со сценарием JavaScript, который запускается в фоновом потоке браузера и может использоваться для перехвата запросов, кеширования и извлечения кешированных ресурсов, а также для доставки push-уведомлений. Что это значит для нас? Мы можем использовать Service worker для кеширования ресурсов нашей игры в браузере пользователя, что позволит нам использовать кешированные ресурсы для ускорения загрузки и автономной работы.
Service Worker имеет жизненный цикл, полностью отделенный от веб-страницы, поэтому он не может получить доступ к DOM напрямую. Вместо этого, Service Worker может обмениваться данными со страницами, которые он контролирует, реагируя на сообщения, отправленные через интерфейс postMessage
и эти страницы могут манипулировать DOM, если это необходимо. Он прерывается, когда не используется, и перезапускается заново, если необходим. Поэтому нельзя полагаться на какое-то глобальное состояние в обработчиках onfetch и onmessage. Если необходимо сохранить какую-либо информацию между перезапусками, Service Worker’ы имеют доступ к API IndexedDB.
Чтобы использовать Service Worker’ы, ваша игра должна быть доступна по протоколу HTTPS. В процессе разработки вы можете использовать Service Worker на localhost. Наконец, большинство браузеров поддерживают Service Worker’ы, но есть и исключения. Вы можете узнать какие браузеры поддерживаются их на странице: Service Worker Ready.
Итак, давайте, наконец, добавим нашего Service Worker. Чтобы его установить, первое, что нам нужно сделать - это его зарегистрировать. В папке вашего проекта создайте новый файл с именем sw.js
. Пока мы оставим этот файл пустым. Затем откройте index.html
и добавьте следующий код перед добавлением других файлов JavaScript:
<script>
if ('serviceWorker' in navigator) {
window.addEventListener('load', function() {
navigator.serviceWorker.register('/sw.js', {scope: '/'}).then(function(registration) {
console.log('ServiceWorker зарегистрирован с областью видимости в пределах: ', registration.scope);
}, function(err) {
console.log('Регистрация ServiceWorker не удалась: ', err);
});
});
}
</script>
Давайте рассмотрим код, который мы только что добавили:
/sw.js
и устанавливаем область видимости (scope
) для нашего Service Worker в корень сайта: /
. В принципе данное значение является значением по умолчанию и его можно не указывать. Параметр scope
используется для управления тем, какие части вашего приложения могут использоваться Service Worker. Например, если вы установите scope значение /games/
, то Service Worker сможет получить доступ только к файлам расположенным в /games/
.Теперь, если вы сохраните и перезагрузите свою игру в браузере, откроете Сonsole в инструментах разработчика Chrome, вы должны увидеть сообщение об успешной регистрации Service Worker.
Вы также можете проверить, что Service Worker был установлен, щелкнув на вкладку Application в инструментах разработчика Chrome. Там, если вы выберите пункт Service Workers , вы увидите зарегистрированный нами Service Worker.
Хоть наш Service Worker установлен, но он ничего не делает. Чтобы исправить это, мы его обновим, чтобы он кешировал все ресурсы, используемые нашей игрой. В sw.js добавьте следующий код:
var cacheName = 'phaser-v1';
var filesToCache = [
'/',
'/index.html',
'/img/logo.png',
'/img/icon-192.png',
'/img/icon-256.png',
'/img/icon-512.png',
'/js/game.js',
'/css/style.css',
'https://cdn.jsdelivr.net/npm/phaser@3.20.1/dist/phaser.min.js'
];
self.addEventListener('install', function(event) {
console.log('установка sw');
event.waitUntil(
caches.open(cacheName).then(function(cache) {
console.log('sw кеширует файлы');
return cache.addAll(filesToCache);
}).catch(function(err) {
console.log(err);
})
);
});
Давайте рассмотрим код, который мы только что добавили:
cacheName
и filesToCache
. Переменная cacheName
используется для хранения имени кеша, который мы будем использовать для хранения кешированных версий наших файлов. Переменная filesToCache
является массивом имен файлов, которые мы хотим кешировать для нашей игры.event.waitUntil()
, который принимает промис в качестве аргумента и использует его для определения успешности установки.event.waitUntil
мы сначала вызываем метод caches.open()
, который используется для открытия кеша в браузере пользователя. Этот метод берет имя кеша, который вы хотите открыть (cacheName) и возвращает промис, который будет преобразован в объект кеша, хранящийся в браузере пользователя.addAll()
возвращаемого объекта cache
. Этот метод берет массив строк filesToCache
с URL-адресами файлов, которые мы хотим кешировать, и возвращает промис, который ничего не вернет, если все файлы будут кешированы. Важно отметить, что если какой-либо из файлов не загружается в кеш, весь этап установки завершится неудачно.Это может быть сложновато для понимания, проще говоря мы сделали следующее:
Теперь, с учетом нашей логики кеширования, мы должны добавить функционал, который позволит нам использовать кешированные ресурсы. Чтобы использовать кешированные ресурсы, нам нужно добавить новый прослушиватель для события fetch
, который запускается каждый раз, когда пользователь посещает страницу после установки Service Worker. В sw.js
добавте следующий код в конец файла:
self.addEventListener('fetch', (event) => {
console.log('sw fetch');
console.log(event.request.url);
event.respondWith(
caches.match(event.request).then(function(response) {
return response || fetch(event.request);
}).catch(function (error) {
console.log(error);
})
);
});
В приведенном выше коде мы делаем следующее:
fetch
для нашего Service Worker. Это событие вызывается каждый раз, когда делается запрос, который входит в область видимости нашего Service Worker.event.respondWith()
. Этот метод перехватывает обращения к сети браузера и использует наш промис.event.respondWith()
методе мы сначала вызвали метод caches.match()
и передали ему объект event.request
. Затем, если запрошенный ресурс был найден в кеше, мы возвращаем кешированный ресурс. Если запрошенный ресурс не был найден в кеше, мы получаем этот запрос и возвращаем ответ.Теперь, когда у нас есть код для загрузки наших кешированных ресурсов, мы можем протестировать нашу игру в автономном режиме, чтобы убедиться, что кешированные ресурсы загружаются правильно. Если вы сохраните и перезагрузите свою игру, она, вероятно, будет выглядеть так, как будто ничего не изменилось, и если вы загляните в Консоль в инструментах разработчика, вы можете не увидеть никаких журналов событий fetch
. Причина этого заключается в том, как Service Worker на самом деле обновляется.
Когда браузер обнаруживает изменение в файле Service Worker, он устанавливает новый Service Worker в фоновом режиме. Когда это происходит, ваш старый Service Worker все еще контролирует текущую страницу, на которой вы находитесь, и он перейдет в состояние waiting
. Как только вы закроете текущую страницу или перейдете на другой сайт, старый сервисный работник будет уничтожен, а управление перейдет к новому.
Чтобы увидеть это в Chrome, если вы вернетесь на вкладку Application
и щелкните на Service Workers
, вы увидите текущий активный Service Worker и обновленный.
Если вы закроете вкладку, в которой запущена ваша игра, и перезагрузите игру в другой вкладке, Service Worker должен показать, что он обновлен.
!
Чтобы обойти это, вы можете установить флажок
Update on reload
в верхней части вкладки Service Workers
. В этом случае Chrome автоматически установит обновленный Service Worke.
Теперь, если вы посмотрите на вкладку Console
, вы должны увидеть некоторые события fetch.
Чтобы увидеть, будет ли наша игра работать, когда мы не в сети, мы можем либо выключить сервер, на котором расположена наша игра, либо вернуться на вкладку
Service Workers
и установить флажок Offline
. Теперь, если вы попытаетесь перезагрузить свою игру, вы увидите, что Service Worker загружает ресурсы для нашей игры из кеша, и что наша игра доступна для игры в автономном режиме.
Управление кешем
Если вы когда-нибудь захотите заставить Service Worker использовать новую версию кеша или решите использовать несколько кешей, то вам потребуется способ очистки старых кешей на пользовательском устройстве. Для этого мы можем добавить слушатель события для события activate
. Это событие вызывается каждый раз, когда Service Worker получает контроль, и мы будем использовать это событие для очистки нашего кеша.
Добавьте в конец файла sw.js
:
self.addEventListener('activate', function(event) {
console.log('событие activate sw');
event.waitUntil(
caches.keys().then(function(keyList) {
return Promise.all(keyList.map(function(key) {
if (key !== cacheName) {
console.log('удаление старого кеша sw', key);
return caches.delete(key);
}
}));
})
);
});
Давайте рассмотрим код, который мы только что добавили:
activate
и передали ему функцию обратного вызова, которая будет запускаться при срабатывании этого события.caches.key()
, чтобы получить все текущие кеши для нашего Service Worker.cacheName
- имя текущего кеша.Теперь, когда наша игра работает в автономном режиме, мы добавим ей возможность добавлять нашу игру на домашний экран пользователя. Чтобы включить эту функцию, нам нужно будет прослушать событие beforeinstallprompt
, которое срабатывает в Chrome при соблюдении определенных условий. Когда это событие запускается, мы можем показать кнопку, чтобы пользователь установил нашу игру, и когда он щелкнет по этой кнопке, появится приглашение Chrome добавить приложение на домашний экран.
Chrome используется для автоматического отображения запроса на добавление PWA на домашний экран пользователя, однако, начиная с Chrome 68, Chrome больше не делает это автоматически, и вы должны прослушивать beforeinstallprompt
.
Для запуска Chrome beforeinstallprompt
ваш PWA должен соответствовать следующим критериям:
fetch
.Давайте начнем для этого добавлять код в нашу игру. Первое, что мы сделаем, это создадим файл манифеста. Файл манифеста веб-приложения представляет собой простой JSON-файл, который сообщает браузеру о вашем веб-приложении и о том, как оно должно вести себя при установке на устройстве пользователя. В своем проекте создайте новый файл с именем manifest.json
и добавьте в него следующий код:
{
"name": "Приложение Phaser PWA",
"short_name": "PWA_Phaser",
"description": "Шаблон оффлайн веб-приложения.",
"start_url": "/",
"background_color": "#000000",
"theme_color": "#0f4a73",
"display": "standalone",
"icons": [{
"src": "img/icon-192.png",
"sizes": "192x192",
"type": "image/png"
},{
"src": "img/icon-256.png",
"sizes": "256x256",
"type": "image/png"
},{
"src": "img/icon-512.png",
"sizes": "512x512",
"type": "image/png"
}]
}
Давайте рассмотрим код, который мы только что добавили:
name
и short_name
- названия, которые показываются пользователю на домашнем экране и в строке, которая отображается пользователю при установке приложения.icons
представляет собой массив иконок , которые будут использоваться для значка приложения на главном экране и при запуске приложения.start_url
сообщает браузеру начальный адрес при запуске.display
используется для настройки пользовательского интерфейса браузера, который отображается, когда приложение запускается. Вы можете прочитать больше о различных вариантах здесь.theme_color
используется для управления цветом на панели задач.background
- цвет, который используется на заставке веб-приложения.Имея код для файла манифеста, нам просто нужно обновить наш index.html файл. Откройте index.html и замените весь код в файле следующим кодом:
<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta charset="utf-8">
<meta name="theme-color" content="black" />
<link rel="manifest" href="manifest.json" />
<link rel="icon" href="img/icon-192.png" sizes="192x192" />
<link rel="icon" href="img/icon-256.png" sizes="256x256" />
<link rel="icon" href="img/icon-512.png" sizes="512x512" />
<link rel="stylesheet" href="css/style.css" />
</head>
<body>
<!-- Модальное окно -->
<div id="myModal" class="modal">
<!-- Контент в модальном контенте -->
<div class="modal-content">
<span class="close">×</span>
<p>Добавить на домашний экран?</p>
<button onclick="offlinePrompt()">Установить</button>
</div>
</div>
<script>
if ('serviceWorker' in navigator) {
window.addEventListener('load', function() {
navigator.serviceWorker.register('/sw.js', {scope: '/'}).then(function(registration) {
console.log('ServiceWorker зарегистрирован с областью видимости в пределах: ', registration.scope);
}, function(err) {
console.log('Регистрация ServiceWorker не удалась: ', err);
});
});
}
let deferredPrompt;
window.addEventListener('beforeinstallprompt', function (e) {
console.log('beforeinstallprompt запущен');
e.preventDefault();
deferredPrompt = e;
modal.style.display = 'block';
});
// Получаем модальное окно
var modal = document.getElementById('myModal');
// Получаем элемент <span> закрывающее модальное окно
var span = document.getElementsByClassName('close')[0];
// Если пользователь щелкает за пределами модального окна - оно закрывается
window.onclick = function(event) {
if (event.target == modal) {
modal.style.display = 'none';
}
}
// Если пользователь щелкает <span> (x), модальное окно закрывается
span.onclick = function() {
modal.style.display = 'none';
}
function offlinePrompt() {
deferredPrompt.prompt();
}
</script>
<script src="https://cdn.jsdelivr.net/npm/phaser@3.20.1/dist/phaser.min.js"></script>
<script src="js/game.js"></script>
</body>
</html>
В приведенном выше коде мы сделали следующее:
<head>
нашего кода мы добавили метатег, чтобы сделать нашу игру отзывчивой. Мы также добавили ссылку на наш файл манифеста и добавили ссылки на наши иконки.<body>
нашего кода мы добавили модальный окно в нашу игру. Оно содержит кнопку установки, и при нажатии на нее будет отображаться приглашение Chrome для установки PWA. По умолчанию мы скрываем это окно и показываем его только при запуске beforeinstallprompt
.<script>
мы добавили прослушиватель для события beforeinstallprompt
, а в функции обратного вызова мы предотвращаем событие по умолчанию, мы сохраняем это событие для последующего доступа и, наконец, отображаем наше модальное окно. Когда нажата кнопка установки в модальном окне, мы вызываем метод prompt()
для этого события.Теперь, если вы сохраните и перезагрузите свою игру, вы можете проверить функциональность добавления на домашний экран. Предварительно нужно удалить старую версию приложения на странице chrome://apps.
Когда вы разрабатываете свой PWA, вы можете включить локальное обновление. Чтобы сделать это, вам нужно включить флаг
#enable-desktop-pwas-local-updating
в Chrome, начиная с версии Chrome 67. Чтобы включить этот флаг, вы можете посетить chrome://flags/#enable-desktop-pwas-local-updating, а затем выбрать из раскрывающего списка значение Enabled.
Как только вы это сделаете, то локальные изменения в манифесте будут сразу синхронизироваться с приложением. Например, измените в файле manifest.json
"name": "Измененное приложение Phaser PWA". Это сразу отобразится на приложении.
Теперь у вас есть первая автономная игра Phaser, которая представляет собой PWA.