Когда я впервые услышал о Polymer, я подумал о моих старых днях Silverlight. Silverlight использовал XHTML для разметки и C # для кода. Polymer похож, но Polymer использует HTML и Javascript. Смотрите эту прекрасную статью для введения в Polymer. В этом руководстве мы создадим классическую игру Sokoban, используя веб-компоненты и отличный генератор Yeoman, генератор-полимер , и опубликуем ее с помощью Bower.
Настройка Полимер
Настройка проекта Polymer так же проста, как следующие две команды:
$ npm install generator-polymer -g $ yo polymer
Он попросит вас включить некоторые стандартные компоненты. Поскольку нам это не нужно, вы можете сказать «нет» всем.
Это сгенерированная структура папок. Все пользовательские элементы находятся в папке app/elements .
. |-- Gruntfile.js |-- app | |-- elements | | |-- elements.html | | |-- soko-ban | | | |-- soko-ban.html | | | `-- soko-ban.scss | |-- index.html | |-- scripts | | |-- app.js |-- bower.json `-- package.json
Чтобы начать разработку, запустите grunt serve . Он будет обслуживать index.html и будет следить за тем, чтобы файлы не перезагружались при их изменении Это index.html , я включил только основные части для использования Polymer.
<html> <head> <script src="bower_components/platform/platform.js"></script> <!-- build:vulcanized elements/elements.vulcanized.html --> <link rel="import" href="elements/elements.html"> <!-- endbuild --> </head> <body unresolved> <div class="game-container"> <!-- insert your elements here --> <soko-ban></soko-ban> </div> <script src="scripts/app.js"></script> </body> </html>
Мы включаем platform.js для включения Polymer и импортируем elements.html который далее импортирует все наши элементы. Обратите внимание, что он обернут в build:vulcanized блок сборки, который объединит все наши импортированные элементы в один файл. Наконец, в body мы добавляем наши пользовательские элементы Polymer. Я включил последний элемент, который мы будем создавать, sokoban-ban , вы можете заменить его другими подэлементами, чтобы проверить их при сборке.
Пользовательский элемент: sprite-el
Первый пользовательский элемент, который мы создадим, — это элемент спрайта, который будет служить основой для всех спрайтов, таких как ящики и наш игрок. Чтобы добавить пользовательский элемент, выполните одну команду.
$ yo polymer:el sprite-el
Это создаст подпапку elements/sprite-el и добавит два файла, sprite-el.html и sprite-el.scss . Он также sprite-el.html в elements.html , в основном выполняя шаблон для вас.
Смотрите sprite-el.html в elements.html Yeoman.
File: elements/elements.html
<link rel="import" href="sprite-el/sprite-el.html">
Элемент Декларации
Давайте определим наш пользовательский элемент sprite-el .
<link rel="import" href="../../bower_components/polymer/polymer.html"> <polymer-element name="sprite-el"> <template> <link rel="stylesheet" href="sprite-el.css"> <div class="sprite" style="top: {{posY}}px; left: {{posX}}px; height: {{frame.height}}px; width: {{frame.width}}px; background: url({{spriteUrl}}) {{frame.x}}px {{frame.y}}px"> </div> </template> <script> (function () { 'use strict'; Polymer({ publish: { spriteUrl: 'images/sprites.png', frame: { x: 0, y: 0 }, position: { x: 0, y: 0 }, computed: { posX: 'position.x * 64', posY: 'position.y * 64' } } }); })(); </script> </polymer-element>
Сначала мы включаем polymer.html и открываем тег polymer-element с атрибутом имени sprite-el , который является обязательным и должен включать - . Далее у нас есть два вложенных тега, template и script . template содержит разметку для нашего пользовательского элемента. В script мы вызываем функцию Polymer для запуска пользовательского элемента. Для получения дополнительной информации см. Документацию .
Шаблон элемента
В шаблон мы включили стиль sprite-el.css , скомпилированный Grunt из sprite-el.scss .
Далее у нас есть div с классом sprite и атрибутом style . Атрибут style определяет top , left , height , width и background , стиль для определения положения и границ спрайта и его изображения. Мы включили эти стили встроенными, потому что мы должны использовать привязку данных для этих атрибутов стиля.
Связывание данных, опубликованные и вычисленные свойства
Свойства элемента могут быть связаны непосредственно с представлением с помощью выражений Polymer , таких как {{posY}} , {{frame.height}} , {{spriteUrl}} .
posX и posY определены в computed свойстве, которое указывает, что это вычисляемые свойства . Это динамические свойства, которые вычисляются на основе других значений свойств. В нашем случае они зависят от position.x и position.y поэтому всякий раз, когда изменяется свойство position они также пересчитываются и обновляются в представлении.
spriteUrl и frame являются опубликованными свойствами . Это означает, что вы делаете это свойство частью «открытого API» элемента. Таким образом, пользователи элемента могут изменить их. Опубликованные свойства также привязаны к данным и доступны через {{}} .
Таможенный элемент: box-el
Следующий пользовательский элемент — это элемент box, он будет состоять из нашего sprite-el и будет представлять коробки, стены и землю. Давай побеспокоим Йомана еще раз.
$ yo polymer:el box-el
Искусство игры и рамки спрайтов
Все игровое искусство взято с сайта 1001.com и лицензировано CC-BY-SA 4.0. Вы можете найти все спрайты и полный исходный код на GitHub .
У нас есть пять спрайтов — B для ящиков, BD для темных ящиков, T для мишени, W для стен и G для земли. На самом деле лучше определять движущиеся блоки и фоновые спрайты в отдельных слоях, но для простоты мы включаем их все в один элемент. Каждый кадр определяет положение кадра в спрайт-листе, а также его высоту и ширину.
Давайте определим наш пользовательский элемент box-el :
<polymer-element name="box-el"> <template> <link rel="stylesheet" href="box-el.css"> <sprite-el frame="{{frame}}" position="{{model.position}}" style="height: {{frame.height}}px; width: {{frame.width}}px;"></sprite-el> </template> <script> (function () { 'use strict'; Polymer({ publish: { model: { position: { x: 0, y: 0 }, type: 'W' } }, computed: { frame: 'boxCoords[model.type]' }, ready: function() { this.boxCoords = { "B": { x:"-192", y:"0", width:"64", height:"64" }, "BD": { x:"-128", y:"-256", width:"64", height:"64" }, "T": { x:"-64", y:"-384", width:"32", height:"32" }, "W": { x:"0", y:"-320", width:"64", height:"64" }, "G": { x:"-64", y:"-256", width:"64", height:"64" } }; } }); })(); </script> </polymer-element>
Наследование и состав
Элементы box и player будут использовать базовый элемент sprite. Есть два способа сделать это, используя наследование или композицию. Мы не будем расширять sprite-el , а будем использовать композицию. Для получения дополнительной информации о наследовании см. Этот пост в блоге и эту ссылку .
Мы включаем sprite-el в наш шаблон и присваиваем его атрибуты. Помните опубликованные свойства frame и position ? Здесь мы назначаем их через атрибуты.
Методы жизненного цикла
Одно дополнительное свойство box-el имеет не только опубликованные и вычисляемые свойства, но и метод ready жизненного цикла . Метод ready жизненного цикла вызывается, когда элемент полностью подготовлен, мы можем назначить дополнительные свойства в этом boxCoords , в нашем случае это boxCoords который используется в вычисляемом frame свойстве.
Таможенный элемент: sokoban-el
Наш последний элемент — сама игра Sokoban. Он будет состоять из элементов нашего player-el , элементов бокса, стены и земли.
Модель игры, игровой контроллер и менеджер ввода
Вся логика игры находится внутри типа GameController . Он генерирует игровую карту и напрямую манипулирует игровой моделью. Модель игры — это данные, ограниченные на наш взгляд, то есть полимерный элемент. Таким образом, все изменения в модели, сделанные GameController , автоматически обновляются в представлении. Я не буду вдаваться в подробности об игровой логике в этой статье, вы можете проверить полный исходный код для более подробной информации.
Обработка пользовательского ввода может быть выполнена с использованием декларативного отображения событий . Но все же есть некоторые предостережения. Смотрите этот вопрос на переполнение стека . Поэтому я использовал пользовательский тип для обработки ввода, KeyboardInputManager .
Давайте определим наш пользовательский элемент soko-ban :
<polymer-element name="soko-ban"> <template> <link rel="stylesheet" href="soko-ban.css"> <template repeat="{{box in boxes}}"> <box-el model="{{box}}"></box-el> </template> <player-el model="{{player}}" id="character"></player-el> </template> <script> (function () { 'use strict'; Polymer({ ready: function() { var controller = new GameController(); var model = controller.getModel(); /** Sample Model **/ /** this.player = { position: { x: 0, y: 0 } }; this.boxes = [ { type: 'W', position: { x: 10, y: 10 } }, { type: 'WD', position: { x: 10, y: 100 } } ]; */ this.player = model.player; this.boxes = model.boxes; var inputManager = new KeyboardInputManager(); var char = this.$.character; inputManager.on('move', function(val) { switch (val) { case KeyboardInputManager.Direction.UP: controller.move(GameController.Direction.UP); break; case KeyboardInputManager.Direction.RIGHT: controller.move(GameController.Direction.RIGHT); break; case KeyboardInputManager.Direction.DOWN: controller.move(GameController.Direction.DOWN); break; case KeyboardInputManager.Direction.LEFT: controller.move(GameController.Direction.LEFT); break; } if (controller.isGameOver()) { this.fire('finished', { target: model.target }); } }.bind(this)); } }); })(); </script> </polymer-element>
Обратите внимание на два свойства нашего player и boxes Polymer, мы установили их для нашей модели. Вы можете вручную установить для них жестко закодированные значения, как вы можете видеть в прокомментированном коде, для целей тестирования.
Итерационные шаблоны
Свойство boxes является массивом значений. Мы можем сгенерировать один экземпляр шаблона для каждого элемента в массиве. Обратите внимание на использование тега template и атрибута repeat для перебора массива блоков. Смотрите документацию для получения дополнительной информации.
Увольнение пользовательских событий
Вы также можете запускать пользовательские события в вашем элементе Polymer, используя метод fire . В нашем случае мы запускаем finished событие, когда игра заканчивается. Вы можете прослушивать события, как показано ниже.
document.querySelector('soko-ban') .addEventListener('finished', function(e) { alert('Congratz you have pushed all ' + e.detail.target + ' boxes!'); });
Опубликуйте это
Мы использовали generator-polymer для построения нашего приложения. Существует также еще один генератор, генератор-элемент и шаблонный шаблон Polymer для создания и публикации пользовательских элементов. После того, как вы создали свой пользовательский элемент с генератором, вы можете опубликовать его с помощью Bower. Для получения дополнительной информации о публикации см. Эти прекрасные статьи, здесь и здесь .
Не забудьте добавить тег web-component в ваш bower.json . После того, как вы опубликовали его в Bower, ваш элемент должен быть доступен в реестре Bower . Также обязательно отправьте его на customelements.io .
Узнайте больше и Live Demo
В этом уроке мы увидели Polymer в действии, создав Sokoban. Как правило, вам не нужно создавать свои собственные элементы, вы можете использовать существующие, составляя их для создания более привлекательных элементов. Посетите галерею веб-компонентов по адресу customelements.io .
Вы можете сделать больше с Polymer, который мы не рассмотрели, например, элементы стилей, свойства наблюдения и т. Д. Для получения дополнительной информации см. Руководство разработчика API . Вы можете найти полный исходный код этого проекта на GitHub и посмотреть живую демонстрацию на моем сайте .